Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ec0e5675 authored by Vaibhav Devmurari's avatar Vaibhav Devmurari Committed by Automerger Merge Worker
Browse files

Merge "Maintain keyboard configuration and reload layouts if changed" into udc-dev am: ec725e14

parents 715ad39d ec725e14
Loading
Loading
Loading
Loading
+154 −70
Original line number Original line Diff line number Diff line
@@ -16,6 +16,8 @@


package com.android.server.input;
package com.android.server.input;


import android.annotation.AnyThread;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.annotation.UserIdInt;
@@ -99,6 +101,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
    private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
    private static final int MSG_SWITCH_KEYBOARD_LAYOUT = 2;
    private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
    private static final int MSG_RELOAD_KEYBOARD_LAYOUTS = 3;
    private static final int MSG_UPDATE_KEYBOARD_LAYOUTS = 4;
    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 Context mContext;
    private final NativeInputManagerService mNative;
    private final NativeInputManagerService mNative;
@@ -108,16 +111,17 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
    private final Handler mHandler;
    private final Handler mHandler;


    // Connected keyboards with associated keyboard layouts (either auto-detected or manually
    // 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
    // selected layout).
    // configured for the keyboard and user might need to manually configure it from the Settings.
    private final SparseArray<KeyboardConfiguration> mConfiguredKeyboards = new SparseArray<>();
    private final SparseArray<Set<String>> mConfiguredKeyboards = new SparseArray<>();
    private Toast mSwitchedKeyboardLayoutToast;
    private Toast mSwitchedKeyboardLayoutToast;


    // This cache stores "best-matched" layouts so that we don't need to run the matching
    // This cache stores "best-matched" layouts so that we don't need to run the matching
    // algorithm repeatedly.
    // algorithm repeatedly.
    @GuardedBy("mKeyboardLayoutCache")
    @GuardedBy("mKeyboardLayoutCache")
    private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>();
    private final Map<String, String> mKeyboardLayoutCache = new ArrayMap<>();
    private final Object mImeInfoLock = new Object();
    @Nullable
    @Nullable
    @GuardedBy("mImeInfoLock")
    private ImeInfo mCurrentImeInfo;
    private ImeInfo mCurrentImeInfo;


    KeyboardLayoutManager(Context context, NativeInputManagerService nativeService,
    KeyboardLayoutManager(Context context, NativeInputManagerService nativeService,
@@ -155,26 +159,32 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
    }
    }


    @Override
    @Override
    @MainThread
    public void onInputDeviceAdded(int deviceId) {
    public void onInputDeviceAdded(int deviceId) {
        onInputDeviceChanged(deviceId);
        onInputDeviceChanged(deviceId);
        if (useNewSettingsUi()) {
            // Force native callback to set up keyboard layout overlay for newly added keyboards
            reloadKeyboardLayouts();
        }
    }
    }


    @Override
    @Override
    @MainThread
    public void onInputDeviceRemoved(int deviceId) {
    public void onInputDeviceRemoved(int deviceId) {
        mConfiguredKeyboards.remove(deviceId);
        mConfiguredKeyboards.remove(deviceId);
        maybeUpdateNotification();
        maybeUpdateNotification();
    }
    }


    @Override
    @Override
    @MainThread
    public void onInputDeviceChanged(int deviceId) {
    public void onInputDeviceChanged(int deviceId) {
        final InputDevice inputDevice = getInputDevice(deviceId);
        final InputDevice inputDevice = getInputDevice(deviceId);
        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
            return;
            return;
        }
        }
        KeyboardConfiguration config = mConfiguredKeyboards.get(deviceId);
        if (config == null) {
            config = new KeyboardConfiguration();
            mConfiguredKeyboards.put(deviceId, config);
        }

        boolean needToShowNotification = false;
        if (!useNewSettingsUi()) {
        if (!useNewSettingsUi()) {
            synchronized (mDataStore) {
            synchronized (mDataStore) {
                String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
                String layout = getCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier());
@@ -182,55 +192,67 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                    layout = getDefaultKeyboardLayout(inputDevice);
                    layout = getDefaultKeyboardLayout(inputDevice);
                    if (layout != null) {
                    if (layout != null) {
                        setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
                        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 {
        } else {
            final InputDeviceIdentifier identifier = inputDevice.getIdentifier();
            final InputDeviceIdentifier identifier = inputDevice.getIdentifier();
            final String key = getLayoutDescriptor(identifier);
            final String key = getLayoutDescriptor(identifier);
            Set<String> selectedLayouts = new HashSet<>();
            Set<String> selectedLayouts = new HashSet<>();
            boolean needToShowMissingLayoutNotification = false;
            for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) {
            for (ImeInfo imeInfo : getImeInfoListForLayoutMapping()) {
                // Check if the layout has been previously configured
                // Check if the layout has been previously configured
                String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
                String layout = getKeyboardLayoutForInputDeviceInternal(identifier,
                        new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle,
                        new ImeInfo(imeInfo.mUserId, imeInfo.mImeSubtypeHandle,
                                imeInfo.mImeSubtype));
                                imeInfo.mImeSubtype));
                if (layout == null) {
                if (layout == null) {
                    needToShowMissingLayoutNotification = true;
                    // If even one layout not configured properly, we need to ask user to configure
                    continue;
                    // the keyboard properly from the Settings.
                    selectedLayouts.clear();
                    break;
                }
                }
                selectedLayouts.add(layout);
                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) {
            if (DEBUG) {
                Slog.d(TAG,
                Slog.d(TAG,
                        "Layouts selected for input device: " + identifier + " -> selectedLayouts: "
                        "Layouts selected for input device: " + identifier + " -> selectedLayouts: "
                                + 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) {
            synchronized (mDataStore) {
                try {
                try {
                    if (!mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
                    if (mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
                        // No need to show the notification only if layout selection didn't change
                        // Need to show the notification only if layout selection changed
                        // from the previous configuration
                        // from the previous configuration
                        return;
                        needToShowNotification = true;
                    }
                    }
                } finally {
                } finally {
                    mDataStore.saveIfNeeded();
                    mDataStore.saveIfNeeded();
                }
                }
            }
            }
        }
        }
        if (needToShowNotification) {
            maybeUpdateNotification();
            maybeUpdateNotification();
        }
        }
    }


    private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
    private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
        final Locale systemLocale = mContext.getResources().getConfiguration().locale;
        final Locale systemLocale = mContext.getResources().getConfiguration().locale;
@@ -323,12 +345,14 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        reloadKeyboardLayouts();
        reloadKeyboardLayouts();
    }
    }


    @AnyThread
    public KeyboardLayout[] getKeyboardLayouts() {
    public KeyboardLayout[] getKeyboardLayouts() {
        final ArrayList<KeyboardLayout> list = new ArrayList<>();
        final ArrayList<KeyboardLayout> list = new ArrayList<>();
        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> list.add(layout));
        visitAllKeyboardLayouts((resources, keyboardLayoutResId, layout) -> list.add(layout));
        return list.toArray(new KeyboardLayout[0]);
        return list.toArray(new KeyboardLayout[0]);
    }
    }


    @AnyThread
    public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
    public KeyboardLayout[] getKeyboardLayoutsForInputDevice(
            final InputDeviceIdentifier identifier) {
            final InputDeviceIdentifier identifier) {
        if (useNewSettingsUi()) {
        if (useNewSettingsUi()) {
@@ -375,6 +399,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                KeyboardLayout[]::new);
                KeyboardLayout[]::new);
    }
    }


    @AnyThread
    @Nullable
    @Nullable
    public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
    public KeyboardLayout getKeyboardLayout(String keyboardLayoutDescriptor) {
        Objects.requireNonNull(keyboardLayoutDescriptor,
        Objects.requireNonNull(keyboardLayoutDescriptor,
@@ -543,6 +568,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        return key.toString();
        return key.toString();
    }
    }


    @AnyThread
    @Nullable
    @Nullable
    public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
    public String getCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier) {
        if (useNewSettingsUi()) {
        if (useNewSettingsUi()) {
@@ -566,6 +592,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }
        }
    }
    }


    @AnyThread
    public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
    public void setCurrentKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
            String keyboardLayoutDescriptor) {
            String keyboardLayoutDescriptor) {
        if (useNewSettingsUi()) {
        if (useNewSettingsUi()) {
@@ -592,6 +619,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }
        }
    }
    }


    @AnyThread
    public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
    public String[] getEnabledKeyboardLayoutsForInputDevice(InputDeviceIdentifier identifier) {
        if (useNewSettingsUi()) {
        if (useNewSettingsUi()) {
            Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported");
            Slog.e(TAG, "getEnabledKeyboardLayoutsForInputDevice API not supported");
@@ -608,6 +636,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }
        }
    }
    }


    @AnyThread
    public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
    public void addKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
            String keyboardLayoutDescriptor) {
            String keyboardLayoutDescriptor) {
        if (useNewSettingsUi()) {
        if (useNewSettingsUi()) {
@@ -635,6 +664,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }
        }
    }
    }


    @AnyThread
    public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
    public void removeKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
            String keyboardLayoutDescriptor) {
            String keyboardLayoutDescriptor) {
        if (useNewSettingsUi()) {
        if (useNewSettingsUi()) {
@@ -667,6 +697,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }
        }
    }
    }


    @AnyThread
    public void switchKeyboardLayout(int deviceId, int direction) {
    public void switchKeyboardLayout(int deviceId, int direction) {
        if (useNewSettingsUi()) {
        if (useNewSettingsUi()) {
            Slog.e(TAG, "switchKeyboardLayout API not supported");
            Slog.e(TAG, "switchKeyboardLayout API not supported");
@@ -675,7 +706,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, deviceId, direction).sendToTarget();
    }
    }


    // Must be called on handler.
    @MainThread
    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
        final InputDevice device = getInputDevice(deviceId);
        final InputDevice device = getInputDevice(deviceId);
        if (device != null) {
        if (device != null) {
@@ -713,23 +744,14 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
    }
    }


    @Nullable
    @Nullable
    @AnyThread
    public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
    public String[] getKeyboardLayoutOverlay(InputDeviceIdentifier identifier) {
        String keyboardLayoutDescriptor;
        String keyboardLayoutDescriptor;
        if (useNewSettingsUi()) {
        if (useNewSettingsUi()) {
            InputDevice inputDevice = getInputDevice(identifier);
            synchronized (mImeInfoLock) {
            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;
            }

                keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
                keyboardLayoutDescriptor = getKeyboardLayoutForInputDeviceInternal(identifier,
                        mCurrentImeInfo);
                        mCurrentImeInfo);
            }
        } else {
        } else {
            keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
            keyboardLayoutDescriptor = getCurrentKeyboardLayoutForInputDevice(identifier);
        }
        }
@@ -755,6 +777,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        return result;
        return result;
    }
    }


    @AnyThread
    @Nullable
    @Nullable
    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
    public String getKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
@@ -773,6 +796,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        return layout;
        return layout;
    }
    }


    @AnyThread
    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
    public void setKeyboardLayoutForInputDevice(InputDeviceIdentifier identifier,
            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
            @Nullable InputMethodSubtype imeSubtype,
            @Nullable InputMethodSubtype imeSubtype,
@@ -783,8 +807,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }
        }
        Objects.requireNonNull(keyboardLayoutDescriptor,
        Objects.requireNonNull(keyboardLayoutDescriptor,
                "keyboardLayoutDescriptor must not be null");
                "keyboardLayoutDescriptor must not be null");
        String key = createLayoutKey(identifier, userId,
        String key = createLayoutKey(identifier,
                InputMethodSubtypeHandle.of(imeInfo, imeSubtype));
                new ImeInfo(userId, InputMethodSubtypeHandle.of(imeInfo, imeSubtype), imeSubtype));
        synchronized (mDataStore) {
        synchronized (mDataStore) {
            try {
            try {
                // Key for storing into data store = <device descriptor>,<userId>,<subtypeHandle>
                // Key for storing into data store = <device descriptor>,<userId>,<subtypeHandle>
@@ -803,6 +827,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }
        }
    }
    }


    @AnyThread
    public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
    public KeyboardLayout[] getKeyboardLayoutListForInputDevice(InputDeviceIdentifier identifier,
            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
            @UserIdInt int userId, @NonNull InputMethodInfo imeInfo,
            @Nullable InputMethodSubtype imeSubtype) {
            @Nullable InputMethodSubtype imeSubtype) {
@@ -815,8 +840,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
    }
    }


    private KeyboardLayout[] getKeyboardLayoutListForInputDeviceInternal(
    private KeyboardLayout[] getKeyboardLayoutListForInputDeviceInternal(
            InputDeviceIdentifier identifier, ImeInfo imeInfo) {
            InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) {
        String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
        String key = createLayoutKey(identifier, imeInfo);


        // Fetch user selected layout and always include it in layout list.
        // Fetch user selected layout and always include it in layout list.
        String userSelectedLayout;
        String userSelectedLayout;
@@ -826,7 +851,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {


        final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
        final ArrayList<KeyboardLayout> potentialLayouts = new ArrayList<>();
        String imeLanguageTag;
        String imeLanguageTag;
        if (imeInfo.mImeSubtype == null) {
        if (imeInfo == null || imeInfo.mImeSubtype == null) {
            imeLanguageTag = "";
            imeLanguageTag = "";
        } else {
        } else {
            ULocale imeLocale = imeInfo.mImeSubtype.getPhysicalKeyboardHintLanguageTag();
            ULocale imeLocale = imeInfo.mImeSubtype.getPhysicalKeyboardHintLanguageTag();
@@ -866,6 +891,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        return potentialLayouts.toArray(new KeyboardLayout[0]);
        return potentialLayouts.toArray(new KeyboardLayout[0]);
    }
    }


    @AnyThread
    public void onInputMethodSubtypeChanged(@UserIdInt int userId,
    public void onInputMethodSubtypeChanged(@UserIdInt int userId,
            @Nullable InputMethodSubtypeHandle subtypeHandle,
            @Nullable InputMethodSubtypeHandle subtypeHandle,
            @Nullable InputMethodSubtype subtype) {
            @Nullable InputMethodSubtype subtype) {
@@ -879,25 +905,45 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
            }
            }
            return;
            return;
        }
        }
        synchronized (mImeInfoLock) {
            if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle)
            if (mCurrentImeInfo == null || !subtypeHandle.equals(mCurrentImeInfo.mImeSubtypeHandle)
                    || mCurrentImeInfo.mUserId != userId) {
                    || mCurrentImeInfo.mUserId != userId) {
                mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype);
                mCurrentImeInfo = new ImeInfo(userId, subtypeHandle, subtype);
            mHandler.sendEmptyMessage(MSG_RELOAD_KEYBOARD_LAYOUTS);
                mHandler.sendEmptyMessage(MSG_CURRENT_IME_INFO_CHANGED);
                if (DEBUG) {
                if (DEBUG) {
                    Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId
                    Slog.d(TAG, "InputMethodSubtype changed: userId=" + userId
                            + " subtypeHandle=" + subtypeHandle);
                            + " 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
    @Nullable
    private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier,
    private String getKeyboardLayoutForInputDeviceInternal(InputDeviceIdentifier identifier,
            ImeInfo imeInfo) {
            @Nullable ImeInfo imeInfo) {
        InputDevice inputDevice = getInputDevice(identifier);
        InputDevice inputDevice = getInputDevice(identifier);
        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
        if (inputDevice == null || inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
            return null;
            return null;
        }
        }
        String key = createLayoutKey(identifier, imeInfo.mUserId, imeInfo.mImeSubtypeHandle);
        String key = createLayoutKey(identifier, imeInfo);
        String layout;
        String layout;
        synchronized (mDataStore) {
        synchronized (mDataStore) {
            layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
            layout = mDataStore.getKeyboardLayout(getLayoutDescriptor(identifier), key);
@@ -923,11 +969,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {


    @Nullable
    @Nullable
    private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice,
    private static String getDefaultKeyboardLayoutBasedOnImeInfo(InputDevice inputDevice,
            ImeInfo imeInfo, KeyboardLayout[] layoutList) {
            @Nullable ImeInfo imeInfo, KeyboardLayout[] layoutList) {
        if (imeInfo.mImeSubtypeHandle == null) {
            return null;
        }

        Arrays.sort(layoutList);
        Arrays.sort(layoutList);


        // Check <VendorID, ProductID> matching for explicitly declared custom KCM files.
        // Check <VendorID, ProductID> matching for explicitly declared custom KCM files.
@@ -961,12 +1003,12 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
            }
            }
        }
        }


        InputMethodSubtype subtype = imeInfo.mImeSubtype;
        if (imeInfo == null || imeInfo.mImeSubtypeHandle == null || imeInfo.mImeSubtype == null) {
        // Can't auto select layout based on IME if subtype or language tag is null
            // Can't auto select layout based on IME info is null
        if (subtype == null) {
            return null;
            return null;
        }
        }


        InputMethodSubtype subtype = imeInfo.mImeSubtype;
        // Check layout type, language tag information from IME for matching
        // Check layout type, language tag information from IME for matching
        ULocale pkLocale = subtype.getPhysicalKeyboardHintLanguageTag();
        ULocale pkLocale = subtype.getPhysicalKeyboardHintLanguageTag();
        String pkLanguageTag =
        String pkLanguageTag =
@@ -1043,6 +1085,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        mNative.reloadKeyboardLayouts();
        mNative.reloadKeyboardLayouts();
    }
    }


    @MainThread
    private void maybeUpdateNotification() {
    private void maybeUpdateNotification() {
        if (mConfiguredKeyboards.size() == 0) {
        if (mConfiguredKeyboards.size() == 0) {
            hideKeyboardLayoutNotification();
            hideKeyboardLayoutNotification();
@@ -1051,7 +1094,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
        for (int i = 0; i < mConfiguredKeyboards.size(); i++) {
            // If we have a keyboard with no selected layouts, we should always show missing
            // 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.
            // layout notification even if there are other keyboards that are configured properly.
            if (mConfiguredKeyboards.valueAt(i).isEmpty()) {
            if (!mConfiguredKeyboards.valueAt(i).hasConfiguredLayouts()) {
                showMissingKeyboardLayoutNotification();
                showMissingKeyboardLayoutNotification();
                return;
                return;
            }
            }
@@ -1059,7 +1102,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        showConfiguredKeyboardLayoutNotification();
        showConfiguredKeyboardLayoutNotification();
    }
    }


    // Must be called on handler.
    @MainThread
    private void showMissingKeyboardLayoutNotification() {
    private void showMissingKeyboardLayoutNotification() {
        final Resources r = mContext.getResources();
        final Resources r = mContext.getResources();
        final String missingKeyboardLayoutNotificationContent = r.getString(
        final String missingKeyboardLayoutNotificationContent = r.getString(
@@ -1084,6 +1127,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }
        }
    }
    }


    @MainThread
    private void showKeyboardLayoutNotification(@NonNull String intentTitle,
    private void showKeyboardLayoutNotification(@NonNull String intentTitle,
            @NonNull String intentContent, @Nullable InputDevice targetDevice) {
            @NonNull String intentContent, @Nullable InputDevice targetDevice) {
        final NotificationManager notificationManager = mContext.getSystemService(
        final NotificationManager notificationManager = mContext.getSystemService(
@@ -1119,7 +1163,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                notification, UserHandle.ALL);
                notification, UserHandle.ALL);
    }
    }


    // Must be called on handler.
    @MainThread
    private void hideKeyboardLayoutNotification() {
    private void hideKeyboardLayoutNotification() {
        NotificationManager notificationManager = mContext.getSystemService(
        NotificationManager notificationManager = mContext.getSystemService(
                NotificationManager.class);
                NotificationManager.class);
@@ -1132,6 +1176,7 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                UserHandle.ALL);
                UserHandle.ALL);
    }
    }


    @MainThread
    private void showConfiguredKeyboardLayoutNotification() {
    private void showConfiguredKeyboardLayoutNotification() {
        final Resources r = mContext.getResources();
        final Resources r = mContext.getResources();


@@ -1144,8 +1189,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        }
        }


        final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0));
        final InputDevice inputDevice = getInputDevice(mConfiguredKeyboards.keyAt(0));
        final Set<String> selectedLayouts = mConfiguredKeyboards.valueAt(0);
        final KeyboardConfiguration config = mConfiguredKeyboards.valueAt(0);
        if (inputDevice == null || selectedLayouts == null || selectedLayouts.isEmpty()) {
        if (inputDevice == null || !config.hasConfiguredLayouts()) {
            return;
            return;
        }
        }


@@ -1153,10 +1198,11 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                r.getString(
                r.getString(
                        R.string.keyboard_layout_notification_selected_title,
                        R.string.keyboard_layout_notification_selected_title,
                        inputDevice.getName()),
                        inputDevice.getName()),
                createConfiguredNotificationText(mContext, selectedLayouts),
                createConfiguredNotificationText(mContext, config.getConfiguredLayouts()),
                inputDevice);
                inputDevice);
    }
    }


    @MainThread
    private String createConfiguredNotificationText(@NonNull Context context,
    private String createConfiguredNotificationText(@NonNull Context context,
            @NonNull Set<String> selectedLayouts) {
            @NonNull Set<String> selectedLayouts) {
        final Resources r = context.getResources();
        final Resources r = context.getResources();
@@ -1199,6 +1245,9 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
            case MSG_UPDATE_KEYBOARD_LAYOUTS:
            case MSG_UPDATE_KEYBOARD_LAYOUTS:
                updateKeyboardLayouts();
                updateKeyboardLayouts();
                return true;
                return true;
            case MSG_CURRENT_IME_INFO_CHANGED:
                onCurrentImeInfoChanged();
                return true;
            default:
            default:
                return false;
                return false;
        }
        }
@@ -1252,11 +1301,13 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
        return imeInfoList;
        return imeInfoList;
    }
    }


    private String createLayoutKey(InputDeviceIdentifier identifier, int userId,
    private String createLayoutKey(InputDeviceIdentifier identifier, @Nullable ImeInfo imeInfo) {
            @NonNull InputMethodSubtypeHandle subtypeHandle) {
        if (imeInfo == null) {
        Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null");
            return getLayoutDescriptor(identifier);
        return "layoutDescriptor:" + getLayoutDescriptor(identifier) + ",userId:" + userId
        }
                + ",subtypeHandle:" + subtypeHandle.toStringHandle();
        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,
    private static boolean isLayoutCompatibleWithLanguageTag(KeyboardLayout layout,
@@ -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 {
    private interface KeyboardLayoutVisitor {
        void visitKeyboardLayout(Resources resources,
        void visitKeyboardLayout(Resources resources,
                int keyboardLayoutResId, KeyboardLayout layout);
                int keyboardLayoutResId, KeyboardLayout layout);