| /* | |
| * Copyright (C) 2022 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| package com.android.settings.inputmethod; | |
| import android.app.Activity; | |
| import android.app.settings.SettingsEnums; | |
| import android.content.Context; | |
| import android.hardware.input.InputDeviceIdentifier; | |
| import android.hardware.input.InputManager; | |
| import android.hardware.input.KeyboardLayout; | |
| import android.hardware.input.KeyboardLayoutSelectionResult; | |
| import android.os.Bundle; | |
| import android.os.UserHandle; | |
| import android.os.UserManager; | |
| import android.util.Log; | |
| import android.view.InputDevice; | |
| import android.view.inputmethod.InputMethodInfo; | |
| import android.view.inputmethod.InputMethodManager; | |
| import android.view.inputmethod.InputMethodSubtype; | |
| import androidx.preference.Preference; | |
| import androidx.preference.PreferenceCategory; | |
| import androidx.preference.PreferenceScreen; | |
| import com.android.internal.util.Preconditions; | |
| import com.android.settings.R; | |
| import com.android.settings.Utils; | |
| import com.android.settings.core.SubSettingLauncher; | |
| import com.android.settings.dashboard.DashboardFragment; | |
| import com.android.settings.dashboard.profileselector.ProfileSelectFragment; | |
| import com.android.settings.inputmethod.InputPeripheralsSettingsUtils.KeyboardInfo; | |
| import java.util.ArrayList; | |
| import java.util.Collections; | |
| import java.util.Comparator; | |
| import java.util.List; | |
| public class NewKeyboardLayoutEnabledLocalesFragment extends DashboardFragment | |
| implements InputManager.InputDeviceListener { | |
| private static final String TAG = "NewKeyboardLayoutEnabledLocalesFragment"; | |
| private InputManager mIm; | |
| private InputMethodManager mImm; | |
| private InputDeviceIdentifier mInputDeviceIdentifier; | |
| private int mUserId; | |
| private int mInputDeviceId; | |
| private Context mContext; | |
| private ArrayList<KeyboardInfo> mKeyboardInfoList = new ArrayList<>(); | |
| @Override | |
| public void onAttach(Context context) { | |
| super.onAttach(context); | |
| mContext = context; | |
| final int profileType = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE); | |
| final int currentUserId = UserHandle.myUserId(); | |
| final int newUserId; | |
| final UserManager userManager = mContext.getSystemService(UserManager.class); | |
| switch (profileType) { | |
| case ProfileSelectFragment.ProfileType.WORK: { | |
| // If the user is a managed profile user, use currentUserId directly. Or get the | |
| // managed profile userId instead. | |
| newUserId = userManager.isManagedProfile() | |
| ? currentUserId : Utils.getManagedProfileId(userManager, currentUserId); | |
| break; | |
| } | |
| case ProfileSelectFragment.ProfileType.PRIVATE: { | |
| // If the user is a private profile user, use currentUserId directly. Or get the | |
| // private profile userId instead. | |
| newUserId = userManager.isPrivateProfile() | |
| ? currentUserId | |
| : Utils.getCurrentUserIdOfType( | |
| userManager, ProfileSelectFragment.ProfileType.PRIVATE); | |
| break; | |
| } | |
| case ProfileSelectFragment.ProfileType.PERSONAL: { | |
| // Use the parent user of the current user if the current user is profile. | |
| final UserHandle currentUser = UserHandle.of(currentUserId); | |
| final UserHandle userProfileParent = userManager.getProfileParent(currentUser); | |
| if (userProfileParent != null) { | |
| newUserId = userProfileParent.getIdentifier(); | |
| } else { | |
| newUserId = currentUserId; | |
| } | |
| break; | |
| } | |
| default: | |
| newUserId = currentUserId; | |
| } | |
| mUserId = newUserId; | |
| mIm = mContext.getSystemService(InputManager.class); | |
| mImm = mContext.getSystemService(InputMethodManager.class); | |
| mInputDeviceId = -1; | |
| Activity activity = Preconditions.checkNotNull(getActivity()); | |
| InputDevice inputDeviceFromIntent = | |
| activity.getIntent().getParcelableExtra( | |
| InputPeripheralsSettingsUtils.EXTRA_INPUT_DEVICE, | |
| InputDevice.class); | |
| if (inputDeviceFromIntent != null) { | |
| launchLayoutPickerWithIdentifier(inputDeviceFromIntent.getIdentifier()); | |
| } | |
| } | |
| @Override | |
| public void onActivityCreated(final Bundle icicle) { | |
| super.onActivityCreated(icicle); | |
| Bundle arguments = getArguments(); | |
| if (arguments == null) { | |
| Log.e(TAG, "Arguments should not be null"); | |
| return; | |
| } | |
| mInputDeviceIdentifier = | |
| arguments.getParcelable( | |
| InputPeripheralsSettingsUtils.EXTRA_INPUT_DEVICE_IDENTIFIER, | |
| InputDeviceIdentifier.class); | |
| if (mInputDeviceIdentifier == null) { | |
| Log.e(TAG, "The inputDeviceIdentifier should not be null"); | |
| return; | |
| } | |
| InputDevice inputDevice = | |
| InputPeripheralsSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier); | |
| if (inputDevice == null) { | |
| Log.e(TAG, "inputDevice is null"); | |
| return; | |
| } | |
| final String title = inputDevice.getName(); | |
| getActivity().setTitle(title); | |
| } | |
| @Override | |
| public void onStart() { | |
| super.onStart(); | |
| mIm.registerInputDeviceListener(this, null); | |
| InputDevice inputDevice = | |
| InputPeripheralsSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier); | |
| if (inputDevice == null) { | |
| Log.e(TAG, "Unable to start: input device is null"); | |
| getActivity().finish(); | |
| return; | |
| } | |
| mInputDeviceId = inputDevice.getId(); | |
| } | |
| @Override | |
| public void onResume() { | |
| super.onResume(); | |
| updateCheckedState(); | |
| } | |
| @Override | |
| public void onStop() { | |
| super.onStop(); | |
| mIm.unregisterInputDeviceListener(this); | |
| mInputDeviceId = -1; | |
| } | |
| private void launchLayoutPickerWithIdentifier( | |
| InputDeviceIdentifier inputDeviceIdentifier) { | |
| if (InputPeripheralsSettingsUtils.getInputDevice(mIm, inputDeviceIdentifier) == null) { | |
| return; | |
| } | |
| InputMethodInfo info = mImm.getCurrentInputMethodInfoAsUser(UserHandle.of(mUserId)); | |
| InputMethodSubtype subtype = mImm.getCurrentInputMethodSubtype(); | |
| CharSequence subtypeLabel = getSubtypeLabel(mContext, info, subtype); | |
| showKeyboardLayoutPicker( | |
| subtypeLabel, | |
| inputDeviceIdentifier, | |
| mUserId, | |
| info, | |
| subtype); | |
| } | |
| private void updateCheckedState() { | |
| if (InputPeripheralsSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier) == null) { | |
| return; | |
| } | |
| PreferenceScreen preferenceScreen = getPreferenceScreen(); | |
| preferenceScreen.removeAll(); | |
| List<InputMethodInfo> infoList = | |
| mImm.getEnabledInputMethodListAsUser(UserHandle.of(mUserId)); | |
| // Remove IMEs with no suitable ime subtypes | |
| infoList.removeIf(imeInfo -> { | |
| List<InputMethodSubtype> subtypes = | |
| mImm.getEnabledInputMethodSubtypeListAsUser(imeInfo.getId(), true, | |
| UserHandle.of(mUserId)); | |
| for (InputMethodSubtype subtype : subtypes) { | |
| if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) { | |
| return false; | |
| } | |
| } | |
| return true; | |
| }); | |
| Collections.sort(infoList, new Comparator<InputMethodInfo>() { | |
| public int compare(InputMethodInfo o1, InputMethodInfo o2) { | |
| String s1 = o1.loadLabel(mContext.getPackageManager()).toString(); | |
| String s2 = o2.loadLabel(mContext.getPackageManager()).toString(); | |
| return s1.compareTo(s2); | |
| } | |
| }); | |
| for (InputMethodInfo info : infoList) { | |
| mKeyboardInfoList.clear(); | |
| List<InputMethodSubtype> subtypes = | |
| mImm.getEnabledInputMethodSubtypeListAsUser(info.getId(), true, | |
| UserHandle.of(mUserId)); | |
| for (InputMethodSubtype subtype : subtypes) { | |
| if (subtype.isSuitableForPhysicalKeyboardLayoutMapping()) { | |
| mapLanguageWithLayout(info, subtype); | |
| } | |
| } | |
| updatePreferenceLayout(preferenceScreen, info, infoList.size() > 1); | |
| } | |
| } | |
| private void mapLanguageWithLayout(InputMethodInfo info, InputMethodSubtype subtype) { | |
| CharSequence subtypeLabel = getSubtypeLabel(mContext, info, subtype); | |
| KeyboardLayout[] keyboardLayouts = | |
| InputPeripheralsSettingsUtils.getKeyboardLayouts( | |
| mIm, mUserId, mInputDeviceIdentifier, info, subtype); | |
| KeyboardLayoutSelectionResult result = InputPeripheralsSettingsUtils.getKeyboardLayout( | |
| mIm, mUserId, mInputDeviceIdentifier, info, subtype); | |
| if (result.getLayoutDescriptor() != null) { | |
| for (int i = 0; i < keyboardLayouts.length; i++) { | |
| if (keyboardLayouts[i].getDescriptor().equals(result.getLayoutDescriptor())) { | |
| KeyboardInfo keyboardInfo = new KeyboardInfo( | |
| subtypeLabel, | |
| keyboardLayouts[i].getLabel(), | |
| result.getSelectionCriteria(), | |
| info, | |
| subtype); | |
| mKeyboardInfoList.add(keyboardInfo); | |
| break; | |
| } | |
| } | |
| } else { | |
| // if there is no auto-selected layout, we should show "Default" | |
| KeyboardInfo keyboardInfo = new KeyboardInfo( | |
| subtypeLabel, | |
| mContext.getString(R.string.keyboard_default_layout), | |
| KeyboardLayoutSelectionResult.LAYOUT_SELECTION_CRITERIA_UNSPECIFIED, | |
| info, | |
| subtype); | |
| mKeyboardInfoList.add(keyboardInfo); | |
| } | |
| } | |
| private void updatePreferenceLayout(PreferenceScreen preferenceScreen, InputMethodInfo info, | |
| boolean hasMultipleImes) { | |
| if (mKeyboardInfoList.isEmpty()) { | |
| return; | |
| } | |
| PreferenceCategory preferenceCategory = new PreferenceCategory(mContext); | |
| preferenceCategory.setTitle(hasMultipleImes ? mContext.getString(R.string.ime_label_title, | |
| info.loadLabel(mContext.getPackageManager())) | |
| : mContext.getString(R.string.enabled_locales_keyboard_layout)); | |
| preferenceCategory.setKey(info.getPackageName()); | |
| preferenceScreen.addPreference(preferenceCategory); | |
| Collections.sort(mKeyboardInfoList, new Comparator<KeyboardInfo>() { | |
| public int compare(KeyboardInfo o1, KeyboardInfo o2) { | |
| String s1 = o1.getSubtypeLabel().toString(); | |
| String s2 = o2.getSubtypeLabel().toString(); | |
| return s1.compareTo(s2); | |
| } | |
| }); | |
| for (KeyboardInfo keyboardInfo : mKeyboardInfoList) { | |
| final Preference pref = new Preference(mContext); | |
| pref.setKey(keyboardInfo.getPrefId()); | |
| pref.setTitle(keyboardInfo.getSubtypeLabel()); | |
| pref.setSummary(keyboardInfo.getLayoutSummaryText(mContext)); | |
| pref.setOnPreferenceClickListener( | |
| preference -> { | |
| showKeyboardLayoutPicker( | |
| keyboardInfo.getSubtypeLabel(), | |
| mInputDeviceIdentifier, | |
| mUserId, | |
| keyboardInfo.getInputMethodInfo(), | |
| keyboardInfo.getInputMethodSubtype()); | |
| return true; | |
| }); | |
| preferenceCategory.addPreference(pref); | |
| } | |
| } | |
| @Override | |
| public void onInputDeviceAdded(int deviceId) { | |
| // Do nothing. | |
| } | |
| @Override | |
| public void onInputDeviceRemoved(int deviceId) { | |
| if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { | |
| getActivity().finish(); | |
| } | |
| } | |
| @Override | |
| public void onInputDeviceChanged(int deviceId) { | |
| if (mInputDeviceId >= 0 && deviceId == mInputDeviceId) { | |
| updateCheckedState(); | |
| } | |
| } | |
| @Override | |
| protected String getLogTag() { | |
| return TAG; | |
| } | |
| @Override | |
| public int getMetricsCategory() { | |
| return SettingsEnums.SETTINGS_KEYBOARDS_ENABLED_LOCALES; | |
| } | |
| @Override | |
| protected int getPreferenceScreenResId() { | |
| return R.xml.keyboard_settings_enabled_locales_list; | |
| } | |
| private void showKeyboardLayoutPicker( | |
| CharSequence subtypeLabel, | |
| InputDeviceIdentifier inputDeviceIdentifier, | |
| int userId, | |
| InputMethodInfo inputMethodInfo, | |
| InputMethodSubtype inputMethodSubtype) { | |
| Bundle arguments = new Bundle(); | |
| arguments.putParcelable( | |
| InputPeripheralsSettingsUtils.EXTRA_INPUT_DEVICE_IDENTIFIER, | |
| inputDeviceIdentifier); | |
| arguments.putParcelable( | |
| InputPeripheralsSettingsUtils.EXTRA_INPUT_METHOD_INFO, inputMethodInfo); | |
| arguments.putParcelable( | |
| InputPeripheralsSettingsUtils.EXTRA_INPUT_METHOD_SUBTYPE, inputMethodSubtype); | |
| arguments.putInt(InputPeripheralsSettingsUtils.EXTRA_USER_ID, userId); | |
| arguments.putCharSequence(InputPeripheralsSettingsUtils.EXTRA_TITLE, subtypeLabel); | |
| new SubSettingLauncher(mContext) | |
| .setSourceMetricsCategory(getMetricsCategory()) | |
| .setDestination(NewKeyboardLayoutPickerFragment.class.getName()) | |
| .setArguments(arguments) | |
| .launch(); | |
| } | |
| private CharSequence getSubtypeLabel( | |
| Context context, InputMethodInfo info, InputMethodSubtype subtype) { | |
| return subtype.getDisplayName( | |
| context, info.getPackageName(), info.getServiceInfo().applicationInfo); | |
| } | |
| } |