Loading services/core/java/com/android/server/input/KeyboardLayoutManager.java +154 −70 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.input; import android.annotation.AnyThread; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; Loading Loading @@ -99,6 +101,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2; private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3; private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4; private static final int MSG_CURRENT_IME_INFO_CHANGED = 5; private final Context mContext; private final NativeInputManagerService mNative; Loading @@ -108,16 +111,17 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { private final Handler mHandler; // Connected keyboards with associated keyboard layouts (either auto-detected or manually // selected layout). If the mapped value is null/empty, it means that no layout has been // configured for the keyboard and user might need to manually configure it from the Settings. private final SparseArray<Set<String>> mConfiguredKeyboards = new SparseArray<>(); // selected layout). private final SparseArray<KeyboardConfiguration> mConfiguredKeyboards = new SparseArray<>(); private Toast mSwitchedKeyboardLayoutToast; // This cache stores "best-matched" layouts so that we don't need to run the matching // algorithm repeatedly. @GuardedBy("mKeyboardLayoutCache") private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>(); private final Object mImeInfoLock = new Object(); @Nullable @GuardedBy("mImeInfoLock") private ImeInfo mCurrentImeInfo; KeyboardLayoutManager(Context context, NativeInputManagerService nativeService, Loading Loading @@ -155,26 +159,32 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } @Override @MainThread public void onInputDeviceAdded(int deviceId) { onInputDeviceChanged(deviceId); if (useNewSettingsUi()) { // Force native callback to set up keyboard layout overlay for newly added keyboards reloadKeyboardLayouts(); } } @Override @MainThread public void onInputDeviceRemoved(int deviceId) { mConfiguredKeyboards.remove(deviceId); maybeUpdateNotification(); } @Override @MainThread public void onInputDeviceChanged(int deviceId) { final InputDevice inputDevice = getInputDevice(deviceId); if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { return; } KeyboardConfiguration config = mConfiguredKeyboards.get(deviceId); if (config == null) { config = new KeyboardConfiguration(); mConfiguredKeyboards.put(deviceId, config); } boolean needToShowNotification = false; if (!useNewSettingsUi()) { synchronized (mDataStore) { String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier()); Loading @@ -182,55 +192,67 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { layout = getDefaultKeyboardLayout(inputDevice); if (layout != null) { setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout); } else { mConfiguredKeyboards.put(inputDevice.getId(), new HashSet<>()); } } config.setCurrentLayout(layout); if (layout == null) { // In old settings show notification always until user manually selects a // layout in the settings. needToShowNotification = true; } } } else { final InputDeviceIdentifier identifier = inputDevice.getIdentifier(); final String key = getLayoutDescriptor(identifier); Set<String> selectedLayouts = new HashSet<>(); boolean needToShowMissingLayoutNotification = false; for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) { // Check if the layout has been previously configured String layout = getKeyboardLayoutForInputDeviceInternal(identifier, new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle, imeInfo.mImeSubtype)); if (layout == null) { needToShowMissingLayoutNotification = true; continue; // If even one layout not configured properly, we need to ask user to configure // the keyboard properly from the Settings. selectedLayouts.clear(); break; } selectedLayouts.add(layout); } if (needToShowMissingLayoutNotification) { // If even one layout not configured properly we will show configuration // notification allowing user to set the keyboard layout. selectedLayouts.clear(); } if (DEBUG) { Slog.d(TAG, "Layouts selected for input device: " + identifier + " -> selectedLayouts: " + selectedLayouts); } mConfiguredKeyboards.set(inputDevice.getId(), selectedLayouts); config.setConfiguredLayouts(selectedLayouts); // Update current layout: If there is a change then need to reload. synchronized (mImeInfoLock) { String layout = getKeyboardLayoutForInputDeviceInternal( inputDevice.getIdentifier(), mCurrentImeInfo); if (!Objects.equals(layout, config.getCurrentLayout())) { config.setCurrentLayout(layout); mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS); } } synchronized (mDataStore) { try { if (!mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) { // No need to show the notification only if layout selection didn't change if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) { // Need to show the notification only if layout selection changed // from the previous configuration return; needToShowNotification = true; } } finally { mDataStore.saveIfNeeded(); } } } if (needToShowNotification) { maybeUpdateNotification(); } } private String getDefaultKeyboardLayout(final InputDevice inputDevice) { final Locale systemLocale = mContext.getResources().getConfiguration().locale; Loading Loading @@ -323,12 +345,14 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { reloadKeyboardLayouts(); } @AnyThread public KeyboardLayout[] getKeyboardLayouts() { final ArrayList<KeyboardLayout> list = new ArrayList<>(); visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> list.add(layout)); return list.toArray(new KeyboardLayout[0]); } @AnyThread public KeyboardLayout[] getKeyboardLayoutsForInputDevice( final InputDeviceIdentifier identifier) { if (useNewSettingsUi()) { Loading Loading @@ -375,6 +399,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { KeyboardLayout[]::new); } @AnyThread @Nullable public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) { Objects.requireNonNull(keyboardLayoutDescriptor, Loading Loading @@ -543,6 +568,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { return key.toString(); } @AnyThread @Nullable public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) { if (useNewSettingsUi()) { Loading @@ -566,6 +592,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { if (useNewSettingsUi()) { Loading @@ -592,6 +619,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) { if (useNewSettingsUi()) { Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported"); Loading @@ -608,6 +636,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { if (useNewSettingsUi()) { Loading Loading @@ -635,6 +664,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { if (useNewSettingsUi()) { Loading Loading @@ -667,6 +697,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public void switchKeyboardLayout(int deviceId, int direction) { if (useNewSettingsUi()) { Slog.e(TAG, "switchKeyboardLayout API not supported"); Loading @@ -675,7 +706,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget(); } // Must be called on handler. @MainThread private void handleSwitchKeyboardLayout(int deviceId, int direction) { final InputDevice device = getInputDevice(deviceId); if (device != null) { Loading Loading @@ -713,23 +744,14 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } @Nullable @AnyThread public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) { String keyboardLayoutDescriptor; if (useNewSettingsUi()) { InputDevice inputDevice = getInputDevice(identifier); if (inputDevice == null) { // getKeyboardLayoutOverlay() called before input device added completely. Need // to wait till the device is added which will call reloadKeyboardLayouts() return null; } if (mCurrentImeInfo == null) { // Haven't received onInputMethodSubtypeChanged() callback from IMMS. Will reload // keyboard layouts once we receive the callback. return null; } synchronized (mImeInfoLock) { keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier, mCurrentImeInfo); } } else { keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier); } Loading @@ -755,6 +777,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { return result; } @AnyThread @Nullable public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, Loading @@ -773,6 +796,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { return layout; } @AnyThread public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype, Loading @@ -783,8 +807,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } Objects.requireNonNull(keyboardLayoutDescriptor, "keyboardLayoutDescriptor must not be null"); String key = createLayoutKey(identifier, userId, InputMethodSubtypeHandle.of(imeInfo, imeSubtype)); String key = createLayoutKey(identifier, new ImeInfo(userId, InputMethodSubtypeHandle.of(imeInfo, imeSubtype), imeSubtype)); synchronized (mDataStore) { try { // Key for storing into data store = <device descriptor>,<userId>,<subtypeHandle> Loading @@ -803,6 +827,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier, @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) { Loading @@ -815,8 +840,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } private KeyboardLayout[] getKeyboardLayoutListForInputDeviceInternal( InputDeviceIdentifier identifier, ImeInfo imeInfo) { String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle); InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) { String key = createLayoutKey(identifier, imeInfo); // Fetch user selected layout and always include it in layout list. String userSelectedLayout; Loading @@ -826,7 +851,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>(); String imeLanguageTag; if (imeInfo.mImeSubtype == null) { if (imeInfo == null || imeInfo.mImeSubtype == null) { imeLanguageTag = ""; } else { ULocale imeLocale = imeInfo.mImeSubtype.getPhysicalKeyboardHintLanguageTag(); Loading Loading @@ -866,6 +891,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { return potentialLayouts.toArray(new KeyboardLayout[0]); } @AnyThread public void onInputMethodSubtypeChanged(@UserIdInt int userId, @Nullable InputMethodSubtypeHandle subtypeHandle, @Nullable InputMethodSubtype subtype) { Loading @@ -879,25 +905,45 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } return; } synchronized (mImeInfoLock) { if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle) || mCurrentImeInfo.mUserId != userId) { mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype); mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS); mHandler.sendEmptyMessage(MSG_CURRENT_IME_INFO_CHANGED); if (DEBUG) { Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId + " subtypeHandle=" + subtypeHandle); } } } } @MainThread private void onCurrentImeInfoChanged() { synchronized (mImeInfoLock) { for (int i = 0; i < mConfiguredKeyboards.size(); i++) { InputDevice inputDevice = Objects.requireNonNull( getInputDevice(mConfiguredKeyboards.keyAt(i))); String layout = getKeyboardLayoutForInputDeviceInternal(inputDevice.getIdentifier(), mCurrentImeInfo); KeyboardConfiguration config = mConfiguredKeyboards.valueAt(i); if (!Objects.equals(layout, config.getCurrentLayout())) { config.setCurrentLayout(layout); mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS); return; } } } } @Nullable private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier, ImeInfo imeInfo) { @Nullable ImeInfo imeInfo) { InputDevice inputDevice = getInputDevice(identifier); if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { return null; } String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle); String key = createLayoutKey(identifier, imeInfo); String layout; synchronized (mDataStore) { layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key); Loading @@ -923,11 +969,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { @Nullable private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice, ImeInfo imeInfo, KeyboardLayout[] layoutList) { if (imeInfo.mImeSubtypeHandle == null) { return null; } @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) { Arrays.sort(layoutList); // Check <VendorID, ProductID> matching for explicitly declared custom KCM files. Loading Loading @@ -961,12 +1003,12 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } InputMethodSubtype subtype = imeInfo.mImeSubtype; // Can't auto select layout based on IME if subtype or language tag is null if (subtype == null) { if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) { // Can't auto select layout based on IME info is null return null; } InputMethodSubtype subtype = imeInfo.mImeSubtype; // Check layout type, language tag information from IME for matching ULocale pkLocale = subtype.getPhysicalKeyboardHintLanguageTag(); String pkLanguageTag = Loading Loading @@ -1043,6 +1085,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { mNative.reloadKeyboardLayouts(); } @MainThread private void maybeUpdateNotification() { if (mConfiguredKeyboards.size() == 0) { hideKeyboardLayoutNotification(); Loading @@ -1051,7 +1094,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { for (int i = 0; i < mConfiguredKeyboards.size(); i++) { // If we have a keyboard with no selected layouts, we should always show missing // layout notification even if there are other keyboards that are configured properly. if (mConfiguredKeyboards.valueAt(i).isEmpty()) { if (!mConfiguredKeyboards.valueAt(i).hasConfiguredLayouts()) { showMissingKeyboardLayoutNotification(); return; } Loading @@ -1059,7 +1102,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { showConfiguredKeyboardLayoutNotification(); } // Must be called on handler. @MainThread private void showMissingKeyboardLayoutNotification() { final Resources r = mContext.getResources(); final String missingKeyboardLayoutNotificationContent = r.getString( Loading @@ -1084,6 +1127,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @MainThread private void showKeyboardLayoutNotification(@NonNull String intentTitle, @NonNull String intentContent, @Nullable InputDevice targetDevice) { final NotificationManager notificationManager = mContext.getSystemService( Loading Loading @@ -1119,7 +1163,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { notification, UserHandle.ALL); } // Must be called on handler. @MainThread private void hideKeyboardLayoutNotification() { NotificationManager notificationManager = mContext.getSystemService( NotificationManager.class); Loading @@ -1132,6 +1176,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { UserHandle.ALL); } @MainThread private void showConfiguredKeyboardLayoutNotification() { final Resources r = mContext.getResources(); Loading @@ -1144,8 +1189,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0)); final Set<String> selectedLayouts = mConfiguredKeyboards.valueAt(0); if (inputDevice == null || selectedLayouts == null || selectedLayouts.isEmpty()) { final KeyboardConfiguration config = mConfiguredKeyboards.valueAt(0); if (inputDevice == null || !config.hasConfiguredLayouts()) { return; } Loading @@ -1153,10 +1198,11 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { r.getString( R.string.keyboard_layout_notification_selected_title, inputDevice.getName()), createConfiguredNotificationText(mContext, selectedLayouts), createConfiguredNotificationText(mContext, config.getConfiguredLayouts()), inputDevice); } @MainThread private String createConfiguredNotificationText(@NonNull Context context, @NonNull Set<String> selectedLayouts) { final Resources r = context.getResources(); Loading Loading @@ -1199,6 +1245,9 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { case MSG_UPDATE_KEYBOARD_LAYOUTS: updateKeyboardLayouts(); return true; case MSG_CURRENT_IME_INFO_CHANGED: onCurrentImeInfoChanged(); return true; default: return false; } Loading Loading @@ -1252,11 +1301,13 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { return imeInfoList; } private String createLayoutKey(InputDeviceIdentifier identifier, int userId, @NonNull InputMethodSubtypeHandle subtypeHandle) { Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null"); return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + userId + ",subtypeHandle:" + subtypeHandle.toStringHandle(); private String createLayoutKey(InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) { if (imeInfo == null) { return getLayoutDescriptor(identifier); } Objects.requireNonNull(imeInfo.mImeSubtypeHandle, "subtypeHandle must not be null"); return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + imeInfo.mUserId + ",subtypeHandle:" + imeInfo.mImeSubtypeHandle.toStringHandle(); } private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout, Loading Loading @@ -1350,6 +1401,39 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } private static class KeyboardConfiguration { // If null or empty, it means no layout is configured for the device. And user needs to // manually set up the device. @Nullable private Set<String> mConfiguredLayouts; // If null, it means no layout is selected for the device. @Nullable private String mCurrentLayout; private boolean hasConfiguredLayouts() { return mConfiguredLayouts != null && !mConfiguredLayouts.isEmpty(); } @Nullable private Set<String> getConfiguredLayouts() { return mConfiguredLayouts; } private void setConfiguredLayouts(Set<String> configuredLayouts) { mConfiguredLayouts = configuredLayouts; } @Nullable private String getCurrentLayout() { return mCurrentLayout; } private void setCurrentLayout(String currentLayout) { mCurrentLayout = currentLayout; } } private interface KeyboardLayoutVisitor { void visitKeyboardLayout(Resources resources, int keyboardLayoutResId, KeyboardLayout layout); Loading Loading
services/core/java/com/android/server/input/KeyboardLayoutManager.java +154 −70 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server.input; import android.annotation.AnyThread; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; Loading Loading @@ -99,6 +101,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2; private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3; private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4; private static final int MSG_CURRENT_IME_INFO_CHANGED = 5; private final Context mContext; private final NativeInputManagerService mNative; Loading @@ -108,16 +111,17 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { private final Handler mHandler; // Connected keyboards with associated keyboard layouts (either auto-detected or manually // selected layout). If the mapped value is null/empty, it means that no layout has been // configured for the keyboard and user might need to manually configure it from the Settings. private final SparseArray<Set<String>> mConfiguredKeyboards = new SparseArray<>(); // selected layout). private final SparseArray<KeyboardConfiguration> mConfiguredKeyboards = new SparseArray<>(); private Toast mSwitchedKeyboardLayoutToast; // This cache stores "best-matched" layouts so that we don't need to run the matching // algorithm repeatedly. @GuardedBy("mKeyboardLayoutCache") private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>(); private final Object mImeInfoLock = new Object(); @Nullable @GuardedBy("mImeInfoLock") private ImeInfo mCurrentImeInfo; KeyboardLayoutManager(Context context, NativeInputManagerService nativeService, Loading Loading @@ -155,26 +159,32 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } @Override @MainThread public void onInputDeviceAdded(int deviceId) { onInputDeviceChanged(deviceId); if (useNewSettingsUi()) { // Force native callback to set up keyboard layout overlay for newly added keyboards reloadKeyboardLayouts(); } } @Override @MainThread public void onInputDeviceRemoved(int deviceId) { mConfiguredKeyboards.remove(deviceId); maybeUpdateNotification(); } @Override @MainThread public void onInputDeviceChanged(int deviceId) { final InputDevice inputDevice = getInputDevice(deviceId); if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { return; } KeyboardConfiguration config = mConfiguredKeyboards.get(deviceId); if (config == null) { config = new KeyboardConfiguration(); mConfiguredKeyboards.put(deviceId, config); } boolean needToShowNotification = false; if (!useNewSettingsUi()) { synchronized (mDataStore) { String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier()); Loading @@ -182,55 +192,67 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { layout = getDefaultKeyboardLayout(inputDevice); if (layout != null) { setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout); } else { mConfiguredKeyboards.put(inputDevice.getId(), new HashSet<>()); } } config.setCurrentLayout(layout); if (layout == null) { // In old settings show notification always until user manually selects a // layout in the settings. needToShowNotification = true; } } } else { final InputDeviceIdentifier identifier = inputDevice.getIdentifier(); final String key = getLayoutDescriptor(identifier); Set<String> selectedLayouts = new HashSet<>(); boolean needToShowMissingLayoutNotification = false; for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) { // Check if the layout has been previously configured String layout = getKeyboardLayoutForInputDeviceInternal(identifier, new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle, imeInfo.mImeSubtype)); if (layout == null) { needToShowMissingLayoutNotification = true; continue; // If even one layout not configured properly, we need to ask user to configure // the keyboard properly from the Settings. selectedLayouts.clear(); break; } selectedLayouts.add(layout); } if (needToShowMissingLayoutNotification) { // If even one layout not configured properly we will show configuration // notification allowing user to set the keyboard layout. selectedLayouts.clear(); } if (DEBUG) { Slog.d(TAG, "Layouts selected for input device: " + identifier + " -> selectedLayouts: " + selectedLayouts); } mConfiguredKeyboards.set(inputDevice.getId(), selectedLayouts); config.setConfiguredLayouts(selectedLayouts); // Update current layout: If there is a change then need to reload. synchronized (mImeInfoLock) { String layout = getKeyboardLayoutForInputDeviceInternal( inputDevice.getIdentifier(), mCurrentImeInfo); if (!Objects.equals(layout, config.getCurrentLayout())) { config.setCurrentLayout(layout); mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS); } } synchronized (mDataStore) { try { if (!mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) { // No need to show the notification only if layout selection didn't change if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) { // Need to show the notification only if layout selection changed // from the previous configuration return; needToShowNotification = true; } } finally { mDataStore.saveIfNeeded(); } } } if (needToShowNotification) { maybeUpdateNotification(); } } private String getDefaultKeyboardLayout(final InputDevice inputDevice) { final Locale systemLocale = mContext.getResources().getConfiguration().locale; Loading Loading @@ -323,12 +345,14 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { reloadKeyboardLayouts(); } @AnyThread public KeyboardLayout[] getKeyboardLayouts() { final ArrayList<KeyboardLayout> list = new ArrayList<>(); visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> list.add(layout)); return list.toArray(new KeyboardLayout[0]); } @AnyThread public KeyboardLayout[] getKeyboardLayoutsForInputDevice( final InputDeviceIdentifier identifier) { if (useNewSettingsUi()) { Loading Loading @@ -375,6 +399,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { KeyboardLayout[]::new); } @AnyThread @Nullable public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) { Objects.requireNonNull(keyboardLayoutDescriptor, Loading Loading @@ -543,6 +568,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { return key.toString(); } @AnyThread @Nullable public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) { if (useNewSettingsUi()) { Loading @@ -566,6 +592,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { if (useNewSettingsUi()) { Loading @@ -592,6 +619,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) { if (useNewSettingsUi()) { Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported"); Loading @@ -608,6 +636,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { if (useNewSettingsUi()) { Loading Loading @@ -635,6 +664,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, String keyboardLayoutDescriptor) { if (useNewSettingsUi()) { Loading Loading @@ -667,6 +697,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public void switchKeyboardLayout(int deviceId, int direction) { if (useNewSettingsUi()) { Slog.e(TAG, "switchKeyboardLayout API not supported"); Loading @@ -675,7 +706,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget(); } // Must be called on handler. @MainThread private void handleSwitchKeyboardLayout(int deviceId, int direction) { final InputDevice device = getInputDevice(deviceId); if (device != null) { Loading Loading @@ -713,23 +744,14 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } @Nullable @AnyThread public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) { String keyboardLayoutDescriptor; if (useNewSettingsUi()) { InputDevice inputDevice = getInputDevice(identifier); if (inputDevice == null) { // getKeyboardLayoutOverlay() called before input device added completely. Need // to wait till the device is added which will call reloadKeyboardLayouts() return null; } if (mCurrentImeInfo == null) { // Haven't received onInputMethodSubtypeChanged() callback from IMMS. Will reload // keyboard layouts once we receive the callback. return null; } synchronized (mImeInfoLock) { keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier, mCurrentImeInfo); } } else { keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier); } Loading @@ -755,6 +777,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { return result; } @AnyThread @Nullable public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, Loading @@ -773,6 +796,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { return layout; } @AnyThread public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier, @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype, Loading @@ -783,8 +807,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } Objects.requireNonNull(keyboardLayoutDescriptor, "keyboardLayoutDescriptor must not be null"); String key = createLayoutKey(identifier, userId, InputMethodSubtypeHandle.of(imeInfo, imeSubtype)); String key = createLayoutKey(identifier, new ImeInfo(userId, InputMethodSubtypeHandle.of(imeInfo, imeSubtype), imeSubtype)); synchronized (mDataStore) { try { // Key for storing into data store = <device descriptor>,<userId>,<subtypeHandle> Loading @@ -803,6 +827,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @AnyThread public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier, @UserIdInt int userId, @NonNull InputMethodInfo imeInfo, @Nullable InputMethodSubtype imeSubtype) { Loading @@ -815,8 +840,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } private KeyboardLayout[] getKeyboardLayoutListForInputDeviceInternal( InputDeviceIdentifier identifier, ImeInfo imeInfo) { String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle); InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) { String key = createLayoutKey(identifier, imeInfo); // Fetch user selected layout and always include it in layout list. String userSelectedLayout; Loading @@ -826,7 +851,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>(); String imeLanguageTag; if (imeInfo.mImeSubtype == null) { if (imeInfo == null || imeInfo.mImeSubtype == null) { imeLanguageTag = ""; } else { ULocale imeLocale = imeInfo.mImeSubtype.getPhysicalKeyboardHintLanguageTag(); Loading Loading @@ -866,6 +891,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { return potentialLayouts.toArray(new KeyboardLayout[0]); } @AnyThread public void onInputMethodSubtypeChanged(@UserIdInt int userId, @Nullable InputMethodSubtypeHandle subtypeHandle, @Nullable InputMethodSubtype subtype) { Loading @@ -879,25 +905,45 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } return; } synchronized (mImeInfoLock) { if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle) || mCurrentImeInfo.mUserId != userId) { mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype); mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS); mHandler.sendEmptyMessage(MSG_CURRENT_IME_INFO_CHANGED); if (DEBUG) { Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId + " subtypeHandle=" + subtypeHandle); } } } } @MainThread private void onCurrentImeInfoChanged() { synchronized (mImeInfoLock) { for (int i = 0; i < mConfiguredKeyboards.size(); i++) { InputDevice inputDevice = Objects.requireNonNull( getInputDevice(mConfiguredKeyboards.keyAt(i))); String layout = getKeyboardLayoutForInputDeviceInternal(inputDevice.getIdentifier(), mCurrentImeInfo); KeyboardConfiguration config = mConfiguredKeyboards.valueAt(i); if (!Objects.equals(layout, config.getCurrentLayout())) { config.setCurrentLayout(layout); mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS); return; } } } } @Nullable private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier, ImeInfo imeInfo) { @Nullable ImeInfo imeInfo) { InputDevice inputDevice = getInputDevice(identifier); if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) { return null; } String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle); String key = createLayoutKey(identifier, imeInfo); String layout; synchronized (mDataStore) { layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key); Loading @@ -923,11 +969,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { @Nullable private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice, ImeInfo imeInfo, KeyboardLayout[] layoutList) { if (imeInfo.mImeSubtypeHandle == null) { return null; } @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) { Arrays.sort(layoutList); // Check <VendorID, ProductID> matching for explicitly declared custom KCM files. Loading Loading @@ -961,12 +1003,12 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } InputMethodSubtype subtype = imeInfo.mImeSubtype; // Can't auto select layout based on IME if subtype or language tag is null if (subtype == null) { if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) { // Can't auto select layout based on IME info is null return null; } InputMethodSubtype subtype = imeInfo.mImeSubtype; // Check layout type, language tag information from IME for matching ULocale pkLocale = subtype.getPhysicalKeyboardHintLanguageTag(); String pkLanguageTag = Loading Loading @@ -1043,6 +1085,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { mNative.reloadKeyboardLayouts(); } @MainThread private void maybeUpdateNotification() { if (mConfiguredKeyboards.size() == 0) { hideKeyboardLayoutNotification(); Loading @@ -1051,7 +1094,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { for (int i = 0; i < mConfiguredKeyboards.size(); i++) { // If we have a keyboard with no selected layouts, we should always show missing // layout notification even if there are other keyboards that are configured properly. if (mConfiguredKeyboards.valueAt(i).isEmpty()) { if (!mConfiguredKeyboards.valueAt(i).hasConfiguredLayouts()) { showMissingKeyboardLayoutNotification(); return; } Loading @@ -1059,7 +1102,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { showConfiguredKeyboardLayoutNotification(); } // Must be called on handler. @MainThread private void showMissingKeyboardLayoutNotification() { final Resources r = mContext.getResources(); final String missingKeyboardLayoutNotificationContent = r.getString( Loading @@ -1084,6 +1127,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } @MainThread private void showKeyboardLayoutNotification(@NonNull String intentTitle, @NonNull String intentContent, @Nullable InputDevice targetDevice) { final NotificationManager notificationManager = mContext.getSystemService( Loading Loading @@ -1119,7 +1163,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { notification, UserHandle.ALL); } // Must be called on handler. @MainThread private void hideKeyboardLayoutNotification() { NotificationManager notificationManager = mContext.getSystemService( NotificationManager.class); Loading @@ -1132,6 +1176,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { UserHandle.ALL); } @MainThread private void showConfiguredKeyboardLayoutNotification() { final Resources r = mContext.getResources(); Loading @@ -1144,8 +1189,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0)); final Set<String> selectedLayouts = mConfiguredKeyboards.valueAt(0); if (inputDevice == null || selectedLayouts == null || selectedLayouts.isEmpty()) { final KeyboardConfiguration config = mConfiguredKeyboards.valueAt(0); if (inputDevice == null || !config.hasConfiguredLayouts()) { return; } Loading @@ -1153,10 +1198,11 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { r.getString( R.string.keyboard_layout_notification_selected_title, inputDevice.getName()), createConfiguredNotificationText(mContext, selectedLayouts), createConfiguredNotificationText(mContext, config.getConfiguredLayouts()), inputDevice); } @MainThread private String createConfiguredNotificationText(@NonNull Context context, @NonNull Set<String> selectedLayouts) { final Resources r = context.getResources(); Loading Loading @@ -1199,6 +1245,9 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { case MSG_UPDATE_KEYBOARD_LAYOUTS: updateKeyboardLayouts(); return true; case MSG_CURRENT_IME_INFO_CHANGED: onCurrentImeInfoChanged(); return true; default: return false; } Loading Loading @@ -1252,11 +1301,13 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { return imeInfoList; } private String createLayoutKey(InputDeviceIdentifier identifier, int userId, @NonNull InputMethodSubtypeHandle subtypeHandle) { Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null"); return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + userId + ",subtypeHandle:" + subtypeHandle.toStringHandle(); private String createLayoutKey(InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) { if (imeInfo == null) { return getLayoutDescriptor(identifier); } Objects.requireNonNull(imeInfo.mImeSubtypeHandle, "subtypeHandle must not be null"); return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + imeInfo.mUserId + ",subtypeHandle:" + imeInfo.mImeSubtypeHandle.toStringHandle(); } private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout, Loading Loading @@ -1350,6 +1401,39 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { } } private static class KeyboardConfiguration { // If null or empty, it means no layout is configured for the device. And user needs to // manually set up the device. @Nullable private Set<String> mConfiguredLayouts; // If null, it means no layout is selected for the device. @Nullable private String mCurrentLayout; private boolean hasConfiguredLayouts() { return mConfiguredLayouts != null && !mConfiguredLayouts.isEmpty(); } @Nullable private Set<String> getConfiguredLayouts() { return mConfiguredLayouts; } private void setConfiguredLayouts(Set<String> configuredLayouts) { mConfiguredLayouts = configuredLayouts; } @Nullable private String getCurrentLayout() { return mCurrentLayout; } private void setCurrentLayout(String currentLayout) { mCurrentLayout = currentLayout; } } private interface KeyboardLayoutVisitor { void visitKeyboardLayout(Resources resources, int keyboardLayoutResId, KeyboardLayout layout); Loading