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