• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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.app.Activity;
20 import android.app.settings.SettingsEnums;
21 import android.content.Context;
22 import android.hardware.input.InputDeviceIdentifier;
23 import android.hardware.input.InputManager;
24 import android.hardware.input.KeyboardLayout;
25 import android.hardware.input.KeyboardLayoutSelectionResult;
26 import android.os.Bundle;
27 import android.os.UserHandle;
28 import android.os.UserManager;
29 import android.util.Log;
30 import android.view.InputDevice;
31 import android.view.inputmethod.InputMethodInfo;
32 import android.view.inputmethod.InputMethodManager;
33 import android.view.inputmethod.InputMethodSubtype;
34 
35 import androidx.preference.Preference;
36 import androidx.preference.PreferenceCategory;
37 import androidx.preference.PreferenceScreen;
38 
39 import com.android.internal.util.Preconditions;
40 import com.android.settings.R;
41 import com.android.settings.Utils;
42 import com.android.settings.core.SubSettingLauncher;
43 import com.android.settings.dashboard.DashboardFragment;
44 import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
45 import com.android.settings.inputmethod.InputPeripheralsSettingsUtils.KeyboardInfo;
46 
47 import java.util.ArrayList;
48 import java.util.Collections;
49 import java.util.Comparator;
50 import java.util.List;
51 
52 public class NewKeyboardLayoutEnabledLocalesFragment extends DashboardFragment
53         implements InputManager.InputDeviceListener {
54 
55     private static final String TAG = "NewKeyboardLayoutEnabledLocalesFragment";
56 
57     private InputManager mIm;
58     private InputMethodManager mImm;
59     private InputDeviceIdentifier mInputDeviceIdentifier;
60     private int mUserId;
61     private int mInputDeviceId;
62     private Context mContext;
63     private ArrayList<KeyboardInfo> mKeyboardInfoList = new ArrayList<>();
64 
65     @Override
onAttach(Context context)66     public void onAttach(Context context) {
67         super.onAttach(context);
68 
69         mContext = context;
70         final int profileType = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE);
71         final int currentUserId = UserHandle.myUserId();
72         final int newUserId;
73         final UserManager userManager = mContext.getSystemService(UserManager.class);
74 
75         switch (profileType) {
76             case ProfileSelectFragment.ProfileType.WORK: {
77                 // If the user is a managed profile user, use currentUserId directly. Or get the
78                 // managed profile userId instead.
79                 newUserId = userManager.isManagedProfile()
80                         ? currentUserId : Utils.getManagedProfileId(userManager, currentUserId);
81                 break;
82             }
83             case ProfileSelectFragment.ProfileType.PRIVATE: {
84                 // If the user is a private profile user, use currentUserId directly. Or get the
85                 // private profile userId instead.
86                 newUserId = userManager.isPrivateProfile()
87                         ? currentUserId
88                         : Utils.getCurrentUserIdOfType(
89                                 userManager, ProfileSelectFragment.ProfileType.PRIVATE);
90                 break;
91             }
92             case ProfileSelectFragment.ProfileType.PERSONAL: {
93                 // Use the parent user of the current user if the current user is profile.
94                 final UserHandle currentUser = UserHandle.of(currentUserId);
95                 final UserHandle userProfileParent = userManager.getProfileParent(currentUser);
96                 if (userProfileParent != null) {
97                     newUserId = userProfileParent.getIdentifier();
98                 } else {
99                     newUserId = currentUserId;
100                 }
101                 break;
102             }
103             default:
104                 newUserId = currentUserId;
105         }
106 
107         mUserId = newUserId;
108         mIm = mContext.getSystemService(InputManager.class);
109         mImm = mContext.getSystemService(InputMethodManager.class);
110         mInputDeviceId = -1;
111 
112         Activity activity = Preconditions.checkNotNull(getActivity());
113         InputDevice inputDeviceFromIntent =
114                 activity.getIntent().getParcelableExtra(
115                         InputPeripheralsSettingsUtils.EXTRA_INPUT_DEVICE,
116                         InputDevice.class);
117 
118         if (inputDeviceFromIntent != null) {
119             launchLayoutPickerWithIdentifier(inputDeviceFromIntent.getIdentifier());
120         }
121     }
122 
123     @Override
onActivityCreated(final Bundle icicle)124     public void onActivityCreated(final Bundle icicle) {
125         super.onActivityCreated(icicle);
126         Bundle arguments = getArguments();
127         if (arguments == null) {
128             Log.e(TAG, "Arguments should not be null");
129             return;
130         }
131         mInputDeviceIdentifier =
132                 arguments.getParcelable(
133                         InputPeripheralsSettingsUtils.EXTRA_INPUT_DEVICE_IDENTIFIER,
134                         InputDeviceIdentifier.class);
135         if (mInputDeviceIdentifier == null) {
136             Log.e(TAG, "The inputDeviceIdentifier should not be null");
137             return;
138         }
139         InputDevice inputDevice =
140                 InputPeripheralsSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier);
141         if (inputDevice == null) {
142             Log.e(TAG, "inputDevice is null");
143             return;
144         }
145         final String title = inputDevice.getName();
146         getActivity().setTitle(title);
147     }
148 
149     @Override
onStart()150     public void onStart() {
151         super.onStart();
152         mIm.registerInputDeviceListener(this, null);
153         InputDevice inputDevice =
154                 InputPeripheralsSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier);
155         if (inputDevice == null) {
156             Log.e(TAG, "Unable to start: input device is null");
157             getActivity().finish();
158             return;
159         }
160         mInputDeviceId = inputDevice.getId();
161     }
162 
163     @Override
onResume()164     public void onResume() {
165         super.onResume();
166         updateCheckedState();
167     }
168 
169     @Override
onStop()170     public void onStop() {
171         super.onStop();
172         mIm.unregisterInputDeviceListener(this);
173         mInputDeviceId = -1;
174     }
175 
launchLayoutPickerWithIdentifier( InputDeviceIdentifier inputDeviceIdentifier)176     private void launchLayoutPickerWithIdentifier(
177             InputDeviceIdentifier inputDeviceIdentifier) {
178         if (InputPeripheralsSettingsUtils.getInputDevice(mIm, inputDeviceIdentifier) == null) {
179             return;
180         }
181         InputMethodInfo info = mImm.getCurrentInputMethodInfoAsUser(UserHandle.of(mUserId));
182         InputMethodSubtype subtype = mImm.getCurrentInputMethodSubtype();
183         CharSequence subtypeLabel = getSubtypeLabel(mContext, info, subtype);
184 
185         showKeyboardLayoutPicker(
186                 subtypeLabel,
187                 inputDeviceIdentifier,
188                 mUserId,
189                 info,
190                 subtype);
191     }
192 
updateCheckedState()193     private void updateCheckedState() {
194         if (InputPeripheralsSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier) == null) {
195             return;
196         }
197 
198         PreferenceScreen preferenceScreen = getPreferenceScreen();
199         preferenceScreen.removeAll();
200         List<InputMethodInfo> infoList =
201                 mImm.getEnabledInputMethodListAsUser(UserHandle.of(mUserId));
202 
203         // Remove IMEs with no suitable ime subtypes
204         infoList.removeIf(imeInfo -> {
205             List<InputMethodSubtype> subtypes =
206                     mImm.getEnabledInputMethodSubtypeListAsUser(imeInfo.getId(), true,
207                             UserHandle.of(mUserId));
208             for (InputMethodSubtype subtype : subtypes) {
209                 if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
210                     return false;
211                 }
212             }
213             return true;
214         });
215         Collections.sort(infoList, new Comparator<InputMethodInfo>() {
216             public int compare(InputMethodInfo o1, InputMethodInfo o2) {
217                 String s1 = o1.loadLabel(mContext.getPackageManager()).toString();
218                 String s2 = o2.loadLabel(mContext.getPackageManager()).toString();
219                 return s1.compareTo(s2);
220             }
221         });
222 
223         for (InputMethodInfo info : infoList) {
224             mKeyboardInfoList.clear();
225             List<InputMethodSubtype> subtypes =
226                     mImm.getEnabledInputMethodSubtypeListAsUser(info.getId(), true,
227                             UserHandle.of(mUserId));
228             for (InputMethodSubtype subtype : subtypes) {
229                 if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
230                     mapLanguageWithLayout(info, subtype);
231                 }
232             }
233             updatePreferenceLayout(preferenceScreen, info, infoList.size() > 1);
234         }
235     }
236 
mapLanguageWithLayout(InputMethodInfo info, InputMethodSubtype subtype)237     private void mapLanguageWithLayout(InputMethodInfo info, InputMethodSubtype subtype) {
238         CharSequence subtypeLabel = getSubtypeLabel(mContext, info, subtype);
239         KeyboardLayout[] keyboardLayouts =
240                 InputPeripheralsSettingsUtils.getKeyboardLayouts(
241                         mIm, mUserId, mInputDeviceIdentifier, info, subtype);
242         KeyboardLayoutSelectionResult result = InputPeripheralsSettingsUtils.getKeyboardLayout(
243                 mIm, mUserId, mInputDeviceIdentifier, info, subtype);
244         if (result.getLayoutDescriptor() != null) {
245             for (int i = 0; i < keyboardLayouts.length; i++) {
246                 if (keyboardLayouts[i].getDescriptor().equals(result.getLayoutDescriptor())) {
247                     KeyboardInfo keyboardInfo = new KeyboardInfo(
248                             subtypeLabel,
249                             keyboardLayouts[i].getLabel(),
250                             result.getSelectionCriteria(),
251                             info,
252                             subtype);
253                     mKeyboardInfoList.add(keyboardInfo);
254                     break;
255                 }
256             }
257         } else {
258             // if there is no auto-selected layout, we should show "Default"
259             KeyboardInfo keyboardInfo = new KeyboardInfo(
260                     subtypeLabel,
261                     mContext.getString(R.string.keyboard_default_layout),
262                     KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_UNSPECIFIED,
263                     info,
264                     subtype);
265             mKeyboardInfoList.add(keyboardInfo);
266         }
267     }
268 
updatePreferenceLayout(PreferenceScreen preferenceScreen, InputMethodInfo info, boolean hasMultipleImes)269     private void updatePreferenceLayout(PreferenceScreen preferenceScreen, InputMethodInfo info,
270             boolean hasMultipleImes) {
271         if (mKeyboardInfoList.isEmpty()) {
272             return;
273         }
274         PreferenceCategory preferenceCategory = new PreferenceCategory(mContext);
275         preferenceCategory.setTitle(hasMultipleImes ? mContext.getString(R.string.ime_label_title,
276                 info.loadLabel(mContext.getPackageManager()))
277                 : mContext.getString(R.string.enabled_locales_keyboard_layout));
278         preferenceCategory.setKey(info.getPackageName());
279         preferenceScreen.addPreference(preferenceCategory);
280         Collections.sort(mKeyboardInfoList, new Comparator<KeyboardInfo>() {
281             public int compare(KeyboardInfo o1, KeyboardInfo o2) {
282                 String s1 = o1.getSubtypeLabel().toString();
283                 String s2 = o2.getSubtypeLabel().toString();
284                 return s1.compareTo(s2);
285             }
286         });
287 
288         for (KeyboardInfo keyboardInfo : mKeyboardInfoList) {
289             final Preference pref = new Preference(mContext);
290             pref.setKey(keyboardInfo.getPrefId());
291             pref.setTitle(keyboardInfo.getSubtypeLabel());
292             pref.setSummary(keyboardInfo.getLayoutSummaryText(mContext));
293             pref.setOnPreferenceClickListener(
294                     preference -> {
295                         showKeyboardLayoutPicker(
296                                 keyboardInfo.getSubtypeLabel(),
297                                 mInputDeviceIdentifier,
298                                 mUserId,
299                                 keyboardInfo.getInputMethodInfo(),
300                                 keyboardInfo.getInputMethodSubtype());
301                         return true;
302                     });
303             preferenceCategory.addPreference(pref);
304         }
305     }
306 
307     @Override
onInputDeviceAdded(int deviceId)308     public void onInputDeviceAdded(int deviceId) {
309         // Do nothing.
310     }
311 
312     @Override
onInputDeviceRemoved(int deviceId)313     public void onInputDeviceRemoved(int deviceId) {
314         if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) {
315             getActivity().finish();
316         }
317     }
318 
319     @Override
onInputDeviceChanged(int deviceId)320     public void onInputDeviceChanged(int deviceId) {
321         if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) {
322             updateCheckedState();
323         }
324     }
325 
326     @Override
getLogTag()327     protected String getLogTag() {
328         return TAG;
329     }
330 
331     @Override
getMetricsCategory()332     public int getMetricsCategory() {
333         return SettingsEnums.SETTINGS_KEYBOARDS_ENABLED_LOCALES;
334     }
335 
336     @Override
getPreferenceScreenResId()337     protected int getPreferenceScreenResId() {
338         return R.xml.keyboard_settings_enabled_locales_list;
339     }
340 
showKeyboardLayoutPicker( CharSequence subtypeLabel, InputDeviceIdentifier inputDeviceIdentifier, int userId, InputMethodInfo inputMethodInfo, InputMethodSubtype inputMethodSubtype)341     private void showKeyboardLayoutPicker(
342             CharSequence subtypeLabel,
343             InputDeviceIdentifier inputDeviceIdentifier,
344             int userId,
345             InputMethodInfo inputMethodInfo,
346             InputMethodSubtype inputMethodSubtype) {
347         Bundle arguments = new Bundle();
348         arguments.putParcelable(
349                 InputPeripheralsSettingsUtils.EXTRA_INPUT_DEVICE_IDENTIFIER,
350                 inputDeviceIdentifier);
351         arguments.putParcelable(
352                 InputPeripheralsSettingsUtils.EXTRA_INPUT_METHOD_INFO, inputMethodInfo);
353         arguments.putParcelable(
354                 InputPeripheralsSettingsUtils.EXTRA_INPUT_METHOD_SUBTYPE, inputMethodSubtype);
355         arguments.putInt(InputPeripheralsSettingsUtils.EXTRA_USER_ID, userId);
356         arguments.putCharSequence(InputPeripheralsSettingsUtils.EXTRA_TITLE, subtypeLabel);
357         new SubSettingLauncher(mContext)
358                 .setSourceMetricsCategory(getMetricsCategory())
359                 .setDestination(NewKeyboardLayoutPickerFragment.class.getName())
360                 .setArguments(arguments)
361                 .launch();
362     }
363 
getSubtypeLabel( Context context, InputMethodInfo info, InputMethodSubtype subtype)364     private CharSequence getSubtypeLabel(
365             Context context, InputMethodInfo info, InputMethodSubtype subtype) {
366         return subtype.getDisplayName(
367                 context, info.getPackageName(), info.getServiceInfo().applicationInfo);
368     }
369 }
370