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