blob: c154f6136f9c5e09516247b781ce8ad07463133f [file] [log] [blame]
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +00001/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings.inputmethod;
18
Yohei Yukawa1dff3852016-04-01 03:53:34 -070019import android.annotation.NonNull;
20import android.annotation.Nullable;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000021import android.app.Activity;
Fan Zhang31b21002019-01-16 13:49:47 -080022import android.app.settings.SettingsEnums;
Abodunrinwa Toki5f0b59b2016-01-25 23:02:23 +000023import android.content.Context;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000024import android.content.Intent;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000025import android.database.ContentObserver;
26import android.hardware.input.InputDeviceIdentifier;
27import android.hardware.input.InputManager;
28import android.hardware.input.KeyboardLayout;
29import android.os.Bundle;
30import android.os.Handler;
Yohei Yukawa966476b2016-06-16 00:02:01 -070031import android.os.UserHandle;
Fan Zhange3535d92017-08-02 17:32:43 -070032import android.provider.SearchIndexableResource;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000033import android.provider.Settings.Secure;
Yohei Yukawa1dff3852016-04-01 03:53:34 -070034import android.text.TextUtils;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000035import android.view.InputDevice;
Yohei Yukawa1dff3852016-04-01 03:53:34 -070036
Fan Zhang23f8d592018-08-28 15:11:40 -070037import androidx.preference.Preference;
38import androidx.preference.Preference.OnPreferenceChangeListener;
39import androidx.preference.PreferenceCategory;
40import androidx.preference.PreferenceScreen;
41import androidx.preference.SwitchPreference;
42
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000043import com.android.internal.util.Preconditions;
44import com.android.settings.R;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000045import com.android.settings.Settings;
46import com.android.settings.SettingsPreferenceFragment;
Tadashi G. Takaoka7bbc6322017-01-10 15:02:22 +090047import com.android.settings.search.BaseSearchIndexProvider;
48import com.android.settings.search.Indexable;
Tony Mantler0fcd6cb2018-03-26 15:17:25 -070049import com.android.settingslib.search.SearchIndexable;
Yohei Yukawa7129b372018-03-08 13:47:56 -080050import com.android.settingslib.utils.ThreadUtils;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000051
Abodunrinwa Toki226e4522016-04-22 17:45:03 +010052import java.text.Collator;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000053import java.util.ArrayList;
Fan Zhange3535d92017-08-02 17:32:43 -070054import java.util.Arrays;
Abodunrinwa Toki61184692016-04-11 11:30:30 +010055import java.util.List;
Abodunrinwa Toki226e4522016-04-22 17:45:03 +010056import java.util.Objects;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000057
Tony Mantler0fcd6cb2018-03-26 15:17:25 -070058@SearchIndexable
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000059public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment
Yohei Yukawa7129b372018-03-08 13:47:56 -080060 implements InputManager.InputDeviceListener,
Fan Zhang78ea7da2018-07-02 13:44:57 -070061 KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener {
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000062
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000063 private static final String KEYBOARD_ASSISTANCE_CATEGORY = "keyboard_assistance_category";
64 private static final String SHOW_VIRTUAL_KEYBOARD_SWITCH = "show_virtual_keyboard_switch";
Clara Bayarriea8772a2016-04-01 14:36:35 +010065 private static final String KEYBOARD_SHORTCUTS_HELPER = "keyboard_shortcuts_helper";
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000066
Yohei Yukawa1dff3852016-04-01 03:53:34 -070067 @NonNull
Yohei Yukawa7129b372018-03-08 13:47:56 -080068 private final ArrayList<HardKeyboardDeviceInfo> mLastHardKeyboards = new ArrayList<>();
Yohei Yukawa1dff3852016-04-01 03:53:34 -070069
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000070 private InputManager mIm;
Yohei Yukawa1dff3852016-04-01 03:53:34 -070071 @NonNull
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000072 private PreferenceCategory mKeyboardAssistanceCategory;
Yohei Yukawa1dff3852016-04-01 03:53:34 -070073 @NonNull
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000074 private SwitchPreference mShowVirtualKeyboardSwitch;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000075
Yohei Yukawa7129b372018-03-08 13:47:56 -080076 private Intent mIntentWaitingForResult;
77
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000078 @Override
79 public void onCreatePreferences(Bundle bundle, String s) {
80 Activity activity = Preconditions.checkNotNull(getActivity());
81 addPreferencesFromResource(R.xml.physical_keyboard_settings);
82 mIm = Preconditions.checkNotNull(activity.getSystemService(InputManager.class));
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000083 mKeyboardAssistanceCategory = Preconditions.checkNotNull(
84 (PreferenceCategory) findPreference(KEYBOARD_ASSISTANCE_CATEGORY));
85 mShowVirtualKeyboardSwitch = Preconditions.checkNotNull(
86 (SwitchPreference) mKeyboardAssistanceCategory.findPreference(
87 SHOW_VIRTUAL_KEYBOARD_SWITCH));
Clara Bayarriea8772a2016-04-01 14:36:35 +010088 findPreference(KEYBOARD_SHORTCUTS_HELPER).setOnPreferenceClickListener(
89 new Preference.OnPreferenceClickListener() {
90 @Override
91 public boolean onPreferenceClick(Preference preference) {
92 toggleKeyboardShortcutsMenu();
93 return true;
94 }
95 });
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +000096 }
97
98 @Override
99 public void onResume() {
100 super.onResume();
Yohei Yukawa1dff3852016-04-01 03:53:34 -0700101 mLastHardKeyboards.clear();
Yohei Yukawa7129b372018-03-08 13:47:56 -0800102 scheduleUpdateHardKeyboards();
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000103 mIm.registerInputDeviceListener(this, null);
104 mShowVirtualKeyboardSwitch.setOnPreferenceChangeListener(
105 mShowVirtualKeyboardSwitchPreferenceChangeListener);
106 registerShowVirtualKeyboardSettingsObserver();
107 }
108
109 @Override
110 public void onPause() {
111 super.onPause();
Yohei Yukawa1dff3852016-04-01 03:53:34 -0700112 mLastHardKeyboards.clear();
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000113 mIm.unregisterInputDeviceListener(this);
114 mShowVirtualKeyboardSwitch.setOnPreferenceChangeListener(null);
115 unregisterShowVirtualKeyboardSettingsObserver();
116 }
117
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000118 @Override
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000119 public void onInputDeviceAdded(int deviceId) {
Yohei Yukawa7129b372018-03-08 13:47:56 -0800120 scheduleUpdateHardKeyboards();
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000121 }
122
123 @Override
124 public void onInputDeviceRemoved(int deviceId) {
Yohei Yukawa7129b372018-03-08 13:47:56 -0800125 scheduleUpdateHardKeyboards();
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000126 }
127
128 @Override
129 public void onInputDeviceChanged(int deviceId) {
Yohei Yukawa7129b372018-03-08 13:47:56 -0800130 scheduleUpdateHardKeyboards();
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000131 }
132
133 @Override
Fan Zhang65076132016-08-08 10:25:13 -0700134 public int getMetricsCategory() {
Fan Zhang31b21002019-01-16 13:49:47 -0800135 return SettingsEnums.PHYSICAL_KEYBOARDS;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000136 }
137
Yohei Yukawa7129b372018-03-08 13:47:56 -0800138 private void scheduleUpdateHardKeyboards() {
139 final Context context = getContext();
140 ThreadUtils.postOnBackgroundThread(() -> {
141 final List<HardKeyboardDeviceInfo> newHardKeyboards = getHardKeyboards(context);
142 ThreadUtils.postOnMainThread(() -> updateHardKeyboards(newHardKeyboards));
143 });
Yohei Yukawa1dff3852016-04-01 03:53:34 -0700144 }
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000145
Yohei Yukawa7129b372018-03-08 13:47:56 -0800146 private void updateHardKeyboards(@NonNull List<HardKeyboardDeviceInfo> newHardKeyboards) {
147 if (Objects.equals(mLastHardKeyboards, newHardKeyboards)) {
148 // Nothing has changed. Ignore.
149 return;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000150 }
Yohei Yukawa7129b372018-03-08 13:47:56 -0800151
152 // TODO(yukawa): Maybe we should follow the style used in ConnectedDeviceDashboardFragment.
153
154 mLastHardKeyboards.clear();
155 mLastHardKeyboards.addAll(newHardKeyboards);
156
157 final PreferenceScreen preferenceScreen = getPreferenceScreen();
158 preferenceScreen.removeAll();
159 final PreferenceCategory category = new PreferenceCategory(getPrefContext());
160 category.setTitle(R.string.builtin_keyboard_settings_title);
161 category.setOrder(0);
162 preferenceScreen.addPreference(category);
163
164 for (HardKeyboardDeviceInfo hardKeyboardDeviceInfo : newHardKeyboards) {
165 // TODO(yukawa): Consider using com.android.settings.widget.GearPreference
166 final Preference pref = new Preference(getPrefContext());
167 pref.setTitle(hardKeyboardDeviceInfo.mDeviceName);
168 pref.setSummary(hardKeyboardDeviceInfo.mLayoutLabel);
169 pref.setOnPreferenceClickListener(preference -> {
170 showKeyboardLayoutDialog(hardKeyboardDeviceInfo.mDeviceIdentifier);
171 return true;
172 });
173 category.addPreference(pref);
174 }
175
176 mKeyboardAssistanceCategory.setOrder(1);
177 preferenceScreen.addPreference(mKeyboardAssistanceCategory);
178 updateShowVirtualKeyboardSwitch();
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000179 }
180
Yohei Yukawa7129b372018-03-08 13:47:56 -0800181 private void showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier) {
Yohei Yukawa690f1032018-03-19 19:20:29 -0700182 KeyboardLayoutDialogFragment fragment = new KeyboardLayoutDialogFragment(
183 inputDeviceIdentifier);
184 fragment.setTargetFragment(this, 0);
tmfang27c84de2018-06-28 11:39:05 +0800185 fragment.show(getActivity().getSupportFragmentManager(), "keyboardLayout");
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000186 }
187
188 private void registerShowVirtualKeyboardSettingsObserver() {
189 unregisterShowVirtualKeyboardSettingsObserver();
190 getActivity().getContentResolver().registerContentObserver(
191 Secure.getUriFor(Secure.SHOW_IME_WITH_HARD_KEYBOARD),
192 false,
193 mContentObserver,
Yohei Yukawa966476b2016-06-16 00:02:01 -0700194 UserHandle.myUserId());
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000195 updateShowVirtualKeyboardSwitch();
196 }
197
198 private void unregisterShowVirtualKeyboardSettingsObserver() {
199 getActivity().getContentResolver().unregisterContentObserver(mContentObserver);
200 }
201
202 private void updateShowVirtualKeyboardSwitch() {
Yohei Yukawa9f172542018-04-13 18:13:09 -0700203 mShowVirtualKeyboardSwitch.setChecked(
204 Secure.getInt(getContentResolver(), Secure.SHOW_IME_WITH_HARD_KEYBOARD, 0) != 0);
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000205 }
206
Clara Bayarriea8772a2016-04-01 14:36:35 +0100207 private void toggleKeyboardShortcutsMenu() {
Clara Bayarrifcc411c2016-05-16 14:16:46 +0100208 getActivity().requestShowKeyboardShortcuts();
Clara Bayarriea8772a2016-04-01 14:36:35 +0100209 }
210
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000211 private final OnPreferenceChangeListener mShowVirtualKeyboardSwitchPreferenceChangeListener =
Yohei Yukawa9f172542018-04-13 18:13:09 -0700212 (preference, newValue) -> {
213 Secure.putInt(getContentResolver(), Secure.SHOW_IME_WITH_HARD_KEYBOARD,
214 ((Boolean) newValue) ? 1 : 0);
215 return true;
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000216 };
217
218 private final ContentObserver mContentObserver = new ContentObserver(new Handler(true)) {
219 @Override
220 public void onChange(boolean selfChange) {
221 updateShowVirtualKeyboardSwitch();
222 }
223 };
Abodunrinwa Toki5f0b59b2016-01-25 23:02:23 +0000224
Yohei Yukawa7129b372018-03-08 13:47:56 -0800225 @Override
226 public void onSetupKeyboardLayouts(InputDeviceIdentifier inputDeviceIdentifier) {
227 final Intent intent = new Intent(Intent.ACTION_MAIN);
228 intent.setClass(getActivity(), Settings.KeyboardLayoutPickerActivity.class);
229 intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER,
230 inputDeviceIdentifier);
231 mIntentWaitingForResult = intent;
232 startActivityForResult(intent, 0);
233 }
Fan Zhange3535d92017-08-02 17:32:43 -0700234
Yohei Yukawa7129b372018-03-08 13:47:56 -0800235 @Override
236 public void onActivityResult(int requestCode, int resultCode, Intent data) {
237 super.onActivityResult(requestCode, resultCode, data);
Abodunrinwa Toki5f0b59b2016-01-25 23:02:23 +0000238
Yohei Yukawa7129b372018-03-08 13:47:56 -0800239 if (mIntentWaitingForResult != null) {
240 InputDeviceIdentifier inputDeviceIdentifier = mIntentWaitingForResult
241 .getParcelableExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER);
242 mIntentWaitingForResult = null;
243 showKeyboardLayoutDialog(inputDeviceIdentifier);
Yohei Yukawa1dff3852016-04-01 03:53:34 -0700244 }
245 }
246
Yohei Yukawa7129b372018-03-08 13:47:56 -0800247 private static String getLayoutLabel(@NonNull InputDevice device,
248 @NonNull Context context, @NonNull InputManager im) {
249 final String currentLayoutDesc =
250 im.getCurrentKeyboardLayoutForInputDevice(device.getIdentifier());
251 if (currentLayoutDesc == null) {
252 return context.getString(R.string.keyboard_layout_default_label);
Abodunrinwa Toki5f0b59b2016-01-25 23:02:23 +0000253 }
Yohei Yukawa7129b372018-03-08 13:47:56 -0800254 final KeyboardLayout currentLayout = im.getKeyboardLayout(currentLayoutDesc);
255 if (currentLayout == null) {
256 return context.getString(R.string.keyboard_layout_default_label);
257 }
258 // If current layout is specified but the layout is null, just return an empty string
259 // instead of falling back to R.string.keyboard_layout_default_label.
260 return TextUtils.emptyIfNull(currentLayout.getLabel());
261 }
Abodunrinwa Toki5f0b59b2016-01-25 23:02:23 +0000262
Yohei Yukawa7129b372018-03-08 13:47:56 -0800263 @NonNull
264 static List<HardKeyboardDeviceInfo> getHardKeyboards(@NonNull Context context) {
265 final List<HardKeyboardDeviceInfo> keyboards = new ArrayList<>();
266 final InputManager im = context.getSystemService(InputManager.class);
267 if (im == null) {
268 return new ArrayList<>();
269 }
270 for (int deviceId : InputDevice.getDeviceIds()) {
271 final InputDevice device = InputDevice.getDevice(deviceId);
272 if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
273 continue;
Abodunrinwa Toki5f0b59b2016-01-25 23:02:23 +0000274 }
Yohei Yukawa7129b372018-03-08 13:47:56 -0800275 keyboards.add(new HardKeyboardDeviceInfo(
276 device.getName(), device.getIdentifier(), getLayoutLabel(device, context, im)));
Abodunrinwa Toki61184692016-04-11 11:30:30 +0100277 }
278
Yohei Yukawa7129b372018-03-08 13:47:56 -0800279 // We intentionally don't reuse Comparator because Collator may not be thread-safe.
280 final Collator collator = Collator.getInstance();
281 keyboards.sort((a, b) -> {
282 int result = collator.compare(a.mDeviceName, b.mDeviceName);
283 if (result != 0) {
284 return result;
Abodunrinwa Toki61184692016-04-11 11:30:30 +0100285 }
Yohei Yukawa7129b372018-03-08 13:47:56 -0800286 result = a.mDeviceIdentifier.getDescriptor().compareTo(
287 b.mDeviceIdentifier.getDescriptor());
288 if (result != 0) {
289 return result;
290 }
291 return collator.compare(a.mLayoutLabel, b.mLayoutLabel);
292 });
293 return keyboards;
Abodunrinwa Toki5f0b59b2016-01-25 23:02:23 +0000294 }
295
Yohei Yukawa1dff3852016-04-01 03:53:34 -0700296 public static final class HardKeyboardDeviceInfo {
297 @NonNull
298 public final String mDeviceName;
299 @NonNull
300 public final InputDeviceIdentifier mDeviceIdentifier;
Yohei Yukawa7129b372018-03-08 13:47:56 -0800301 @NonNull
302 public final String mLayoutLabel;
Abodunrinwa Toki5f0b59b2016-01-25 23:02:23 +0000303
Yohei Yukawa1dff3852016-04-01 03:53:34 -0700304 public HardKeyboardDeviceInfo(
Yohei Yukawa7129b372018-03-08 13:47:56 -0800305 @Nullable String deviceName,
306 @NonNull InputDeviceIdentifier deviceIdentifier,
307 @NonNull String layoutLabel) {
308 mDeviceName = TextUtils.emptyIfNull(deviceName);
Yohei Yukawa1dff3852016-04-01 03:53:34 -0700309 mDeviceIdentifier = deviceIdentifier;
Yohei Yukawa7129b372018-03-08 13:47:56 -0800310 mLayoutLabel = layoutLabel;
Yohei Yukawa1dff3852016-04-01 03:53:34 -0700311 }
312
313 @Override
314 public boolean equals(Object o) {
315 if (o == this) return true;
316 if (o == null) return false;
317
318 if (!(o instanceof HardKeyboardDeviceInfo)) return false;
319
320 final HardKeyboardDeviceInfo that = (HardKeyboardDeviceInfo) o;
321 if (!TextUtils.equals(mDeviceName, that.mDeviceName)) {
322 return false;
323 }
Yohei Yukawa7129b372018-03-08 13:47:56 -0800324 if (!Objects.equals(mDeviceIdentifier, that.mDeviceIdentifier)) {
Yohei Yukawa1dff3852016-04-01 03:53:34 -0700325 return false;
326 }
Yohei Yukawa7129b372018-03-08 13:47:56 -0800327 if (!TextUtils.equals(mLayoutLabel, that.mLayoutLabel)) {
Yohei Yukawa1dff3852016-04-01 03:53:34 -0700328 return false;
329 }
330
331 return true;
332 }
333 }
334
Raff Tsaiac3e0d02019-09-19 17:06:45 +0800335 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
Tadashi G. Takaoka7bbc6322017-01-10 15:02:22 +0900336 new BaseSearchIndexProvider() {
Fan Zhange3535d92017-08-02 17:32:43 -0700337 @Override
338 public List<SearchIndexableResource> getXmlResourcesToIndex(
339 Context context, boolean enabled) {
340 final SearchIndexableResource sir = new SearchIndexableResource(context);
341 sir.xmlResId = R.xml.physical_keyboard_settings;
342 return Arrays.asList(sir);
343 }
344 };
Abodunrinwa Toki976bb3f2016-01-20 18:43:20 +0000345}