/* * 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.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.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 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; } @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 updateCheckedState() { if (InputPeripheralsSettingsUtils.getInputDevice(mIm, mInputDeviceIdentifier) == null) { return; } PreferenceScreen preferenceScreen = getPreferenceScreen(); preferenceScreen.removeAll(); List infoList = mImm.getEnabledInputMethodListAsUser(UserHandle.of(mUserId)); // Remove IMEs with no suitable ime subtypes infoList.removeIf(imeInfo -> { List 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() { 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 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() { 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); } }