• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.settings.inputmethod;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.app.Activity;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.database.ContentObserver;
25 import android.hardware.input.InputDeviceIdentifier;
26 import android.hardware.input.InputManager;
27 import android.hardware.input.KeyboardLayout;
28 import android.os.Bundle;
29 import android.os.Handler;
30 import android.os.UserHandle;
31 import android.provider.SearchIndexableResource;
32 import android.provider.Settings.Secure;
33 import android.support.v14.preference.SwitchPreference;
34 import android.support.v7.preference.Preference;
35 import android.support.v7.preference.Preference.OnPreferenceChangeListener;
36 import android.support.v7.preference.PreferenceCategory;
37 import android.support.v7.preference.PreferenceScreen;
38 import android.text.TextUtils;
39 import android.view.InputDevice;
40 
41 import com.android.internal.inputmethod.InputMethodUtils;
42 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
43 import com.android.internal.util.Preconditions;
44 import com.android.settings.R;
45 import com.android.settings.Settings;
46 import com.android.settings.SettingsPreferenceFragment;
47 import com.android.settings.search.BaseSearchIndexProvider;
48 import com.android.settings.search.Indexable;
49 import com.android.settingslib.utils.ThreadUtils;
50 
51 import java.text.Collator;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.HashMap;
55 import java.util.List;
56 import java.util.Objects;
57 
58 public final class PhysicalKeyboardFragment extends SettingsPreferenceFragment
59         implements InputManager.InputDeviceListener,
60         KeyboardLayoutDialogFragment.OnSetupKeyboardLayoutsListener, Indexable {
61 
62     private static final String KEYBOARD_ASSISTANCE_CATEGORY = "keyboard_assistance_category";
63     private static final String SHOW_VIRTUAL_KEYBOARD_SWITCH = "show_virtual_keyboard_switch";
64     private static final String KEYBOARD_SHORTCUTS_HELPER = "keyboard_shortcuts_helper";
65 
66     @NonNull
67     private final ArrayList<HardKeyboardDeviceInfo> mLastHardKeyboards = new ArrayList<>();
68 
69     private InputManager mIm;
70     @NonNull
71     private PreferenceCategory mKeyboardAssistanceCategory;
72     @NonNull
73     private SwitchPreference mShowVirtualKeyboardSwitch;
74     @NonNull
75     private InputMethodUtils.InputMethodSettings mSettings;
76 
77     private Intent mIntentWaitingForResult;
78 
79     @Override
onCreatePreferences(Bundle bundle, String s)80     public void onCreatePreferences(Bundle bundle, String s) {
81         Activity activity = Preconditions.checkNotNull(getActivity());
82         addPreferencesFromResource(R.xml.physical_keyboard_settings);
83         mIm = Preconditions.checkNotNull(activity.getSystemService(InputManager.class));
84         mSettings = new InputMethodUtils.InputMethodSettings(
85                 activity.getResources(),
86                 getContentResolver(),
87                 new HashMap<>(),
88                 new ArrayList<>(),
89                 UserHandle.myUserId(),
90                 false /* copyOnWrite */);
91         mKeyboardAssistanceCategory = Preconditions.checkNotNull(
92                 (PreferenceCategory) findPreference(KEYBOARD_ASSISTANCE_CATEGORY));
93         mShowVirtualKeyboardSwitch = Preconditions.checkNotNull(
94                 (SwitchPreference) mKeyboardAssistanceCategory.findPreference(
95                         SHOW_VIRTUAL_KEYBOARD_SWITCH));
96         findPreference(KEYBOARD_SHORTCUTS_HELPER).setOnPreferenceClickListener(
97                 new Preference.OnPreferenceClickListener() {
98                     @Override
99                     public boolean onPreferenceClick(Preference preference) {
100                         toggleKeyboardShortcutsMenu();
101                         return true;
102                     }
103                 });
104     }
105 
106     @Override
onResume()107     public void onResume() {
108         super.onResume();
109         mLastHardKeyboards.clear();
110         scheduleUpdateHardKeyboards();
111         mIm.registerInputDeviceListener(this, null);
112         mShowVirtualKeyboardSwitch.setOnPreferenceChangeListener(
113                 mShowVirtualKeyboardSwitchPreferenceChangeListener);
114         registerShowVirtualKeyboardSettingsObserver();
115     }
116 
117     @Override
onPause()118     public void onPause() {
119         super.onPause();
120         mLastHardKeyboards.clear();
121         mIm.unregisterInputDeviceListener(this);
122         mShowVirtualKeyboardSwitch.setOnPreferenceChangeListener(null);
123         unregisterShowVirtualKeyboardSettingsObserver();
124     }
125 
126     @Override
onInputDeviceAdded(int deviceId)127     public void onInputDeviceAdded(int deviceId) {
128         scheduleUpdateHardKeyboards();
129     }
130 
131     @Override
onInputDeviceRemoved(int deviceId)132     public void onInputDeviceRemoved(int deviceId) {
133         scheduleUpdateHardKeyboards();
134     }
135 
136     @Override
onInputDeviceChanged(int deviceId)137     public void onInputDeviceChanged(int deviceId) {
138         scheduleUpdateHardKeyboards();
139     }
140 
141     @Override
getMetricsCategory()142     public int getMetricsCategory() {
143         return MetricsEvent.PHYSICAL_KEYBOARDS;
144     }
145 
scheduleUpdateHardKeyboards()146     private void scheduleUpdateHardKeyboards() {
147         final Context context = getContext();
148         ThreadUtils.postOnBackgroundThread(() -> {
149             final List<HardKeyboardDeviceInfo> newHardKeyboards = getHardKeyboards(context);
150             ThreadUtils.postOnMainThread(() -> updateHardKeyboards(newHardKeyboards));
151         });
152     }
153 
updateHardKeyboards(@onNull List<HardKeyboardDeviceInfo> newHardKeyboards)154     private void updateHardKeyboards(@NonNull List<HardKeyboardDeviceInfo> newHardKeyboards) {
155         if (Objects.equals(mLastHardKeyboards, newHardKeyboards)) {
156             // Nothing has changed.  Ignore.
157             return;
158         }
159 
160         // TODO(yukawa): Maybe we should follow the style used in ConnectedDeviceDashboardFragment.
161 
162         mLastHardKeyboards.clear();
163         mLastHardKeyboards.addAll(newHardKeyboards);
164 
165         final PreferenceScreen preferenceScreen = getPreferenceScreen();
166         preferenceScreen.removeAll();
167         final PreferenceCategory category = new PreferenceCategory(getPrefContext());
168         category.setTitle(R.string.builtin_keyboard_settings_title);
169         category.setOrder(0);
170         preferenceScreen.addPreference(category);
171 
172         for (HardKeyboardDeviceInfo hardKeyboardDeviceInfo : newHardKeyboards) {
173             // TODO(yukawa): Consider using com.android.settings.widget.GearPreference
174             final Preference pref = new Preference(getPrefContext());
175             pref.setTitle(hardKeyboardDeviceInfo.mDeviceName);
176             pref.setSummary(hardKeyboardDeviceInfo.mLayoutLabel);
177             pref.setOnPreferenceClickListener(preference -> {
178                 showKeyboardLayoutDialog(hardKeyboardDeviceInfo.mDeviceIdentifier);
179                 return true;
180             });
181             category.addPreference(pref);
182         }
183 
184         mKeyboardAssistanceCategory.setOrder(1);
185         preferenceScreen.addPreference(mKeyboardAssistanceCategory);
186         updateShowVirtualKeyboardSwitch();
187     }
188 
showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier)189     private void showKeyboardLayoutDialog(InputDeviceIdentifier inputDeviceIdentifier) {
190         KeyboardLayoutDialogFragment fragment = new KeyboardLayoutDialogFragment(
191                 inputDeviceIdentifier);
192         fragment.setTargetFragment(this, 0);
193         fragment.show(getActivity().getFragmentManager(), "keyboardLayout");
194     }
195 
registerShowVirtualKeyboardSettingsObserver()196     private void registerShowVirtualKeyboardSettingsObserver() {
197         unregisterShowVirtualKeyboardSettingsObserver();
198         getActivity().getContentResolver().registerContentObserver(
199                 Secure.getUriFor(Secure.SHOW_IME_WITH_HARD_KEYBOARD),
200                 false,
201                 mContentObserver,
202                 UserHandle.myUserId());
203         updateShowVirtualKeyboardSwitch();
204     }
205 
unregisterShowVirtualKeyboardSettingsObserver()206     private void unregisterShowVirtualKeyboardSettingsObserver() {
207         getActivity().getContentResolver().unregisterContentObserver(mContentObserver);
208     }
209 
updateShowVirtualKeyboardSwitch()210     private void updateShowVirtualKeyboardSwitch() {
211         mShowVirtualKeyboardSwitch.setChecked(mSettings.isShowImeWithHardKeyboardEnabled());
212     }
213 
toggleKeyboardShortcutsMenu()214     private void toggleKeyboardShortcutsMenu() {
215         getActivity().requestShowKeyboardShortcuts();
216     }
217 
218     private final OnPreferenceChangeListener mShowVirtualKeyboardSwitchPreferenceChangeListener =
219             new OnPreferenceChangeListener() {
220                 @Override
221                 public boolean onPreferenceChange(Preference preference, Object newValue) {
222                     mSettings.setShowImeWithHardKeyboard((Boolean) newValue);
223                     return true;
224                 }
225             };
226 
227     private final ContentObserver mContentObserver = new ContentObserver(new Handler(true)) {
228         @Override
229         public void onChange(boolean selfChange) {
230             updateShowVirtualKeyboardSwitch();
231         }
232     };
233 
234     @Override
onSetupKeyboardLayouts(InputDeviceIdentifier inputDeviceIdentifier)235     public void onSetupKeyboardLayouts(InputDeviceIdentifier inputDeviceIdentifier) {
236         final Intent intent = new Intent(Intent.ACTION_MAIN);
237         intent.setClass(getActivity(), Settings.KeyboardLayoutPickerActivity.class);
238         intent.putExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER,
239                 inputDeviceIdentifier);
240         mIntentWaitingForResult = intent;
241         startActivityForResult(intent, 0);
242     }
243 
244     @Override
onActivityResult(int requestCode, int resultCode, Intent data)245     public void onActivityResult(int requestCode, int resultCode, Intent data) {
246         super.onActivityResult(requestCode, resultCode, data);
247 
248         if (mIntentWaitingForResult != null) {
249             InputDeviceIdentifier inputDeviceIdentifier = mIntentWaitingForResult
250                     .getParcelableExtra(KeyboardLayoutPickerFragment.EXTRA_INPUT_DEVICE_IDENTIFIER);
251             mIntentWaitingForResult = null;
252             showKeyboardLayoutDialog(inputDeviceIdentifier);
253         }
254     }
255 
getLayoutLabel(@onNull InputDevice device, @NonNull Context context, @NonNull InputManager im)256     private static String getLayoutLabel(@NonNull InputDevice device,
257             @NonNull Context context, @NonNull InputManager im) {
258         final String currentLayoutDesc =
259                 im.getCurrentKeyboardLayoutForInputDevice(device.getIdentifier());
260         if (currentLayoutDesc == null) {
261             return context.getString(R.string.keyboard_layout_default_label);
262         }
263         final KeyboardLayout currentLayout = im.getKeyboardLayout(currentLayoutDesc);
264         if (currentLayout == null) {
265             return context.getString(R.string.keyboard_layout_default_label);
266         }
267         // If current layout is specified but the layout is null, just return an empty string
268         // instead of falling back to R.string.keyboard_layout_default_label.
269         return TextUtils.emptyIfNull(currentLayout.getLabel());
270     }
271 
272     @NonNull
getHardKeyboards(@onNull Context context)273     static List<HardKeyboardDeviceInfo> getHardKeyboards(@NonNull Context context) {
274         final List<HardKeyboardDeviceInfo> keyboards = new ArrayList<>();
275         final InputManager im = context.getSystemService(InputManager.class);
276         if (im == null) {
277             return new ArrayList<>();
278         }
279         for (int deviceId : InputDevice.getDeviceIds()) {
280             final InputDevice device = InputDevice.getDevice(deviceId);
281             if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
282                 continue;
283             }
284             keyboards.add(new HardKeyboardDeviceInfo(
285                     device.getName(), device.getIdentifier(), getLayoutLabel(device, context, im)));
286         }
287 
288         // We intentionally don't reuse Comparator because Collator may not be thread-safe.
289         final Collator collator = Collator.getInstance();
290         keyboards.sort((a, b) -> {
291             int result = collator.compare(a.mDeviceName, b.mDeviceName);
292             if (result != 0) {
293                 return result;
294             }
295             result = a.mDeviceIdentifier.getDescriptor().compareTo(
296                     b.mDeviceIdentifier.getDescriptor());
297             if (result != 0) {
298                 return result;
299             }
300             return collator.compare(a.mLayoutLabel, b.mLayoutLabel);
301         });
302         return keyboards;
303     }
304 
305     public static final class HardKeyboardDeviceInfo {
306         @NonNull
307         public final String mDeviceName;
308         @NonNull
309         public final InputDeviceIdentifier mDeviceIdentifier;
310         @NonNull
311         public final String mLayoutLabel;
312 
HardKeyboardDeviceInfo( @ullable String deviceName, @NonNull InputDeviceIdentifier deviceIdentifier, @NonNull String layoutLabel)313         public HardKeyboardDeviceInfo(
314                 @Nullable String deviceName,
315                 @NonNull InputDeviceIdentifier deviceIdentifier,
316                 @NonNull String layoutLabel) {
317             mDeviceName = TextUtils.emptyIfNull(deviceName);
318             mDeviceIdentifier = deviceIdentifier;
319             mLayoutLabel = layoutLabel;
320         }
321 
322         @Override
equals(Object o)323         public boolean equals(Object o) {
324             if (o == this) return true;
325             if (o == null) return false;
326 
327             if (!(o instanceof HardKeyboardDeviceInfo)) return false;
328 
329             final HardKeyboardDeviceInfo that = (HardKeyboardDeviceInfo) o;
330             if (!TextUtils.equals(mDeviceName, that.mDeviceName)) {
331                 return false;
332             }
333             if (!Objects.equals(mDeviceIdentifier, that.mDeviceIdentifier)) {
334                 return false;
335             }
336             if (!TextUtils.equals(mLayoutLabel, that.mLayoutLabel)) {
337                 return false;
338             }
339 
340             return true;
341         }
342     }
343 
344     public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
345             new BaseSearchIndexProvider() {
346                 @Override
347                 public List<SearchIndexableResource> getXmlResourcesToIndex(
348                         Context context, boolean enabled) {
349                     final SearchIndexableResource sir = new SearchIndexableResource(context);
350                     sir.xmlResId = R.xml.physical_keyboard_settings;
351                     return Arrays.asList(sir);
352                 }
353             };
354 }
355