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