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

Commit 9ebfecd1 authored by Vaibhav Devmurari's avatar Vaibhav Devmurari Committed by Android (Google) Code Review
Browse files

Merge "Show notification about auto-selected layout when PK connected" into udc-dev

parents 78ef24c2 25dcf55d
Loading
Loading
Loading
Loading
+1 −5
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@ package com.android.internal.notification;
import static android.app.admin.DevicePolicyResources.Strings.Core.NOTIFICATION_CHANNEL_DEVICE_ADMIN;

import android.app.INotificationManager;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.admin.DevicePolicyManager;
@@ -25,7 +24,6 @@ import android.content.Context;
import android.content.pm.ParceledListSlice;
import android.media.AudioAttributes;
import android.os.RemoteException;
import android.provider.Settings;

import com.android.internal.R;

@@ -78,9 +76,7 @@ public class SystemNotificationChannels {
        final NotificationChannel physicalKeyboardChannel = new NotificationChannel(
                PHYSICAL_KEYBOARD,
                context.getString(R.string.notification_channel_physical_keyboard),
                NotificationManager.IMPORTANCE_DEFAULT);
        physicalKeyboardChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI,
                Notification.AUDIO_ATTRIBUTES_DEFAULT);
                NotificationManager.IMPORTANCE_LOW);
        physicalKeyboardChannel.setBlockable(true);
        channelsList.add(physicalKeyboardChannel);

+19 −2
Original line number Diff line number Diff line
@@ -3787,8 +3787,10 @@
    <!-- Title of the physical keyboard category in the input method selector [CHAR LIMIT=30] -->
    <string name="hardware">Show virtual keyboard</string>

    <!-- Title of the notification to prompt the user to configure physical keyboard settings. -->
    <string name="select_keyboard_layout_notification_title">Configure physical keyboard</string>
    <!-- Title of the notification to prompt the user to configure physical keyboard settings. [CHAR LIMIT=NOTIF_TITLE] -->
    <string name="select_keyboard_layout_notification_title">Configure <xliff:g id="device_name" example="Foobar USB Keyboard">%s</xliff:g></string>
    <!-- Title of the notification to prompt the user to configure physical keyboard settings when multiple keyboards connected. [CHAR LIMIT=NOTIF_TITLE] -->
    <string name="select_multiple_keyboards_layout_notification_title">Configure physical keyboards</string>
    <!-- Message of the notification to prompt the user to configure physical keyboard settings
         where the user can associate language with physical keyboard layout. -->
    <string name="select_keyboard_layout_notification_message">Tap to select language and layout</string>
@@ -6264,4 +6266,19 @@ ul.</string>
    <string name="concurrent_display_notification_thermal_content">Dual Screen is unavailable because your phone is getting too warm</string>
    <!-- Text of device state notification turn off button. [CHAR LIMIT=NONE] -->
    <string name="device_state_notification_turn_off_button">Turn off</string>

    <!-- Notification title when a keyboard has been configured [CHAR LIMIT=NOTIF_TITLE] -->
    <string name="keyboard_layout_notification_selected_title"><xliff:g id="device_name" example="Foobar USB Keyboard">%s</xliff:g> configured</string>
    <!-- Notification message shown when one layout was configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
    <string name="keyboard_layout_notification_one_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%s</xliff:g>. Tap to change.</string>
    <!-- Notification message shown when two layout were configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
    <string name="keyboard_layout_notification_two_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%1$s</xliff:g>, <xliff:g id="layout_2" example="German">%2$s</xliff:g>. Tap to change.</string>
    <!-- Notification message shown when three layout were configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
    <string name="keyboard_layout_notification_three_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%1$s</xliff:g>, <xliff:g id="layout_2" example="German">%2$s</xliff:g>, <xliff:g id="layout_3" example="French">%3$s</xliff:g>. Tap to change.</string>
    <!-- Notification message shown when more than three layout were configured for a physical keyboard [CHAR LIMIT=NOTIF_BODY] -->
    <string name="keyboard_layout_notification_more_than_three_selected_message">Keyboard layout set to <xliff:g id="layout_1" example="English (US)">%1$s</xliff:g>, <xliff:g id="layout_2" example="English (US)">%2$s</xliff:g>, <xliff:g id="layout_3" example="French">%3$s</xliff:g>\u2026 Tap to change.</string>
    <!-- Notification title when multiple keyboards with selected layouts have been connected the first time simultaneously [CHAR LIMIT=NOTIF_TITLE] -->
    <string name="keyboard_layout_notification_multiple_selected_title">Physical keyboards configured</string>
    <!-- Notification message when multiple keyboards with selected layouts have been connected the first time simultaneously [CHAR LIMIT=NOTIF_BODY] -->
    <string name="keyboard_layout_notification_multiple_selected_message">Tap to view keyboards</string>
</resources>
+11 −1
Original line number Diff line number Diff line
@@ -2138,6 +2138,7 @@
  <java-symbol type="string" name="report" />
  <java-symbol type="string" name="select_input_method" />
  <java-symbol type="string" name="select_keyboard_layout_notification_title" />
  <java-symbol type="string" name="select_multiple_keyboards_layout_notification_title" />
  <java-symbol type="string" name="select_keyboard_layout_notification_message" />
  <java-symbol type="string" name="smv_application" />
  <java-symbol type="string" name="smv_process" />
@@ -4964,6 +4965,15 @@
  <!-- Whether to show weather on the lockscreen by default. -->
  <java-symbol type="bool" name="config_lockscreenWeatherEnabledByDefault" />

  <!-- For keyboard notification -->
  <java-symbol type="string" name="keyboard_layout_notification_selected_title"/>
  <java-symbol type="string" name="keyboard_layout_notification_one_selected_message"/>
  <java-symbol type="string" name="keyboard_layout_notification_two_selected_message"/>
  <java-symbol type="string" name="keyboard_layout_notification_three_selected_message"/>
  <java-symbol type="string" name="keyboard_layout_notification_more_than_three_selected_message"/>
  <java-symbol type="string" name="keyboard_layout_notification_multiple_selected_title"/>
  <java-symbol type="string" name="keyboard_layout_notification_multiple_selected_message"/>

  <java-symbol type="bool" name="config_batteryStatsResetOnUnplugHighBatteryLevel" />
  <java-symbol type="bool" name="config_batteryStatsResetOnUnplugAfterSignificantCharge" />

+200 −57
Original line number Diff line number Diff line
@@ -45,14 +45,17 @@ import android.os.LocaleList;
import android.os.Looper;
import android.os.Message;
import android.os.UserHandle;
import android.os.UserManager;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.FeatureFlagUtils;
import android.util.Log;
import android.util.Slog;
import android.util.SparseArray;
import android.view.InputDevice;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import android.widget.Toast;

@@ -75,6 +78,7 @@ import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Stream;

/**
@@ -102,8 +106,10 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
    private final PersistentDataStore mDataStore;
    private final Handler mHandler;

    private final List<InputDevice> mKeyboardsWithMissingLayouts = new ArrayList<>();
    private boolean mKeyboardLayoutNotificationShown = false;
    // 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<>();
    private Toast mSwitchedKeyboardLayoutToast;

    // This cache stores "best-matched" layouts so that we don't need to run the matching
@@ -158,11 +164,9 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {

    @Override
    public void onInputDeviceRemoved(int deviceId) {
        if (!useNewSettingsUi()) {
            mKeyboardsWithMissingLayouts.removeIf(device -> device.getId() == deviceId);
        mConfiguredKeyboards.remove(deviceId);
        maybeUpdateNotification();
    }
    }

    @Override
    public void onInputDeviceChanged(int deviceId) {
@@ -178,13 +182,53 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                    if (layout != null) {
                        setCurrentKeyboardLayoutForInputDevice(inputDevice.getIdentifier(), layout);
                    } else {
                        mKeyboardsWithMissingLayouts.add(inputDevice);
                        mConfiguredKeyboards.put(inputDevice.getId(), new HashSet<>());
                    }
                }
                maybeUpdateNotification();
            }
        } 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;
                }
                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);

            synchronized (mDataStore) {
                try {
                    if (!mDataStore.setSelectedKeyboardLayouts(key, selectedLayouts)) {
                        // No need to show the notification only if layout selection didn't change
                        // from the previous configuration
                        return;
                    }
                } finally {
                    mDataStore.saveIfNeeded();
                }
        // TODO(b/259530132): Show notification for new Settings UI
            }
        }
        maybeUpdateNotification();
    }

    private String getDefaultKeyboardLayout(final InputDevice inputDevice) {
@@ -999,67 +1043,141 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
    }

    private void maybeUpdateNotification() {
        NotificationManager notificationManager = mContext.getSystemService(
                NotificationManager.class);
        if (notificationManager == null) {
        if (mConfiguredKeyboards.size() == 0) {
            hideKeyboardLayoutNotification();
            return;
        }
        if (!mKeyboardsWithMissingLayouts.isEmpty()) {
            if (mKeyboardsWithMissingLayouts.size() > 1) {
                // We have more than one keyboard missing a layout, so drop the
                // user at the generic input methods page, so they can pick which
                // one to set.
                showMissingKeyboardLayoutNotification(notificationManager, null);
            } else {
                showMissingKeyboardLayoutNotification(notificationManager,
                        mKeyboardsWithMissingLayouts.get(0));
        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()) {
                showMissingKeyboardLayoutNotification();
                return;
            }
        } else if (mKeyboardLayoutNotificationShown) {
            hideMissingKeyboardLayoutNotification(notificationManager);
        }
        showConfiguredKeyboardLayoutNotification();
    }

    // Must be called on handler.
    private void showMissingKeyboardLayoutNotification(NotificationManager notificationManager,
            InputDevice device) {
        if (!mKeyboardLayoutNotificationShown) {
    private void showMissingKeyboardLayoutNotification() {
        final Resources r = mContext.getResources();
        final String missingKeyboardLayoutNotificationContent = r.getString(
                R.string.select_keyboard_layout_notification_message);

        if (mConfiguredKeyboards.size() == 1) {
            final InputDevice device = getInputDevice(mConfiguredKeyboards.keyAt(0));
            if (device == null) {
                return;
            }
            showKeyboardLayoutNotification(
                    r.getString(
                            R.string.select_keyboard_layout_notification_title,
                            device.getName()),
                    missingKeyboardLayoutNotificationContent,
                    device);
        } else {
            showKeyboardLayoutNotification(
                    r.getString(R.string.select_multiple_keyboards_layout_notification_title),
                    missingKeyboardLayoutNotificationContent,
                    null);
        }
    }

    private void showKeyboardLayoutNotification(@NonNull String intentTitle,
            @NonNull String intentContent, @Nullable InputDevice targetDevice) {
        final NotificationManager notificationManager = mContext.getSystemService(
                NotificationManager.class);
        if (notificationManager == null) {
            return;
        }

        final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS);
            if (device != null) {
                intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, device.getIdentifier());

        if (targetDevice != null) {
            intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, targetDevice.getIdentifier());
        }

        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        final PendingIntent keyboardLayoutIntent = PendingIntent.getActivityAsUser(mContext, 0,
                intent, PendingIntent.FLAG_IMMUTABLE, null, UserHandle.CURRENT);

            Resources r = mContext.getResources();
        Notification notification =
                new Notification.Builder(mContext, SystemNotificationChannels.PHYSICAL_KEYBOARD)
                            .setContentTitle(r.getString(
                                    R.string.select_keyboard_layout_notification_title))
                            .setContentText(r.getString(
                                    R.string.select_keyboard_layout_notification_message))
                        .setContentTitle(intentTitle)
                        .setContentText(intentContent)
                        .setContentIntent(keyboardLayoutIntent)
                        .setSmallIcon(R.drawable.ic_settings_language)
                        .setColor(mContext.getColor(
                                com.android.internal.R.color.system_notification_accent_color))
                        .setAutoCancel(true)
                        .build();
        notificationManager.notifyAsUser(null,
                SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
                notification, UserHandle.ALL);
            mKeyboardLayoutNotificationShown = true;
        }
    }

    // Must be called on handler.
    private void hideMissingKeyboardLayoutNotification(NotificationManager notificationManager) {
        if (mKeyboardLayoutNotificationShown) {
            mKeyboardLayoutNotificationShown = false;
    private void hideKeyboardLayoutNotification() {
        NotificationManager notificationManager = mContext.getSystemService(
                NotificationManager.class);
        if (notificationManager == null) {
            return;
        }

        notificationManager.cancelAsUser(null,
                SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT,
                UserHandle.ALL);
    }

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

        if (mConfiguredKeyboards.size() != 1) {
            showKeyboardLayoutNotification(
                    r.getString(R.string.keyboard_layout_notification_multiple_selected_title),
                    r.getString(R.string.keyboard_layout_notification_multiple_selected_message),
                    null);
            return;
        }

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

        showKeyboardLayoutNotification(
                r.getString(
                        R.string.keyboard_layout_notification_selected_title,
                        inputDevice.getName()),
                createConfiguredNotificationText(mContext, selectedLayouts),
                inputDevice);
    }

    private String createConfiguredNotificationText(@NonNull Context context,
            @NonNull Set<String> selectedLayouts) {
        final Resources r = context.getResources();
        List<String> layoutNames = new ArrayList<>();
        selectedLayouts.forEach(
                (layoutDesc) -> layoutNames.add(getKeyboardLayout(layoutDesc).getLabel()));
        Collections.sort(layoutNames);
        switch (layoutNames.size()) {
            case 1:
                return r.getString(R.string.keyboard_layout_notification_one_selected_message,
                        layoutNames.get(0));
            case 2:
                return r.getString(R.string.keyboard_layout_notification_two_selected_message,
                        layoutNames.get(0), layoutNames.get(1));
            case 3:
                return r.getString(R.string.keyboard_layout_notification_three_selected_message,
                        layoutNames.get(0), layoutNames.get(1), layoutNames.get(2));
            default:
                return r.getString(
                        R.string.keyboard_layout_notification_more_than_three_selected_message,
                        layoutNames.get(0), layoutNames.get(1), layoutNames.get(2));
        }
    }

    private boolean handleMessage(Message msg) {
@@ -1102,6 +1220,31 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener {
                identifier.getDescriptor()) : null;
    }

    private List<ImeInfo> getImeInfoListForLayoutMapping() {
        List<ImeInfo> imeInfoList = new ArrayList<>();
        UserManager userManager = Objects.requireNonNull(
                mContext.getSystemService(UserManager.class));
        InputMethodManager inputMethodManager = Objects.requireNonNull(
                mContext.getSystemService(InputMethodManager.class));
        for (UserHandle userHandle : userManager.getUserHandles(true /* excludeDying */)) {
            int userId = userHandle.getIdentifier();
            for (InputMethodInfo imeInfo : inputMethodManager.getEnabledInputMethodListAsUser(
                    userId)) {
                for (InputMethodSubtype imeSubtype :
                        inputMethodManager.getEnabledInputMethodSubtypeList(
                                imeInfo, true /* allowsImplicitlyEnabledSubtypes */)) {
                    if (!imeSubtype.isSuitableForPhysicalKeyboardLayoutMapping()) {
                        continue;
                    }
                    imeInfoList.add(
                            new ImeInfo(userId, InputMethodSubtypeHandle.of(imeInfo, imeSubtype),
                                    imeSubtype));
                }
            }
        }
        return imeInfoList;
    }

    private String createLayoutKey(InputDeviceIdentifier identifier, int userId,
            @NonNull InputMethodSubtypeHandle subtypeHandle) {
        Objects.requireNonNull(subtypeHandle, "subtypeHandle must not be null");
+40 −0
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.server.input;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.hardware.input.TouchCalibration;
import android.util.ArrayMap;
@@ -43,6 +44,7 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -155,6 +157,16 @@ final class PersistentDataStore {
        return false;
    }

    public boolean setSelectedKeyboardLayouts(String inputDeviceDescriptor,
            @NonNull Set<String> selectedLayouts) {
        InputDeviceState state = getOrCreateInputDeviceState(inputDeviceDescriptor);
        if (state.setSelectedKeyboardLayouts(selectedLayouts)) {
            setDirty();
            return true;
        }
        return false;
    }

    public String[] getKeyboardLayouts(String inputDeviceDescriptor) {
        InputDeviceState state = getInputDeviceState(inputDeviceDescriptor);
        if (state == null) {
@@ -408,6 +420,8 @@ final class PersistentDataStore {

        private final Map<String, String> mKeyboardLayoutMap = new ArrayMap<>();

        private Set<String> mSelectedKeyboardLayouts;

        public TouchCalibration getTouchCalibration(int surfaceRotation) {
            try {
                return mTouchCalibration[surfaceRotation];
@@ -439,6 +453,14 @@ final class PersistentDataStore {
            return !Objects.equals(mKeyboardLayoutMap.put(key, keyboardLayout), keyboardLayout);
        }

        public boolean setSelectedKeyboardLayouts(@NonNull Set<String> selectedLayouts) {
            if (Objects.equals(mSelectedKeyboardLayouts, selectedLayouts)) {
                return false;
            }
            mSelectedKeyboardLayouts = new HashSet<>(selectedLayouts);
            return true;
        }

        @Nullable
        public String getCurrentKeyboardLayout() {
            return mCurrentKeyboardLayout;
@@ -588,6 +610,16 @@ final class PersistentDataStore {
                                "Missing layout attribute on keyed-keyboard-layout.");
                    }
                    mKeyboardLayoutMap.put(key, layout);
                } else if (parser.getName().equals("selected-keyboard-layout")) {
                    String layout = parser.getAttributeValue(null, "layout");
                    if (layout == null) {
                        throw new XmlPullParserException(
                                "Missing layout attribute on selected-keyboard-layout.");
                    }
                    if (mSelectedKeyboardLayouts == null) {
                        mSelectedKeyboardLayouts = new HashSet<>();
                    }
                    mSelectedKeyboardLayouts.add(layout);
                } else if (parser.getName().equals("light-info")) {
                    int lightId = parser.getAttributeInt(null, "light-id");
                    int lightBrightness = parser.getAttributeInt(null, "light-brightness");
@@ -668,6 +700,14 @@ final class PersistentDataStore {
                serializer.endTag(null, "keyed-keyboard-layout");
            }

            if (mSelectedKeyboardLayouts != null) {
                for (String layout : mSelectedKeyboardLayouts) {
                    serializer.startTag(null, "selected-keyboard-layout");
                    serializer.attribute(null, "layout", layout);
                    serializer.endTag(null, "selected-keyboard-layout");
                }
            }

            for (int i = 0; i < mKeyboardBacklightBrightnessMap.size(); i++) {
                serializer.startTag(null, "light-info");
                serializer.attributeInt(null, "light-id", mKeyboardBacklightBrightnessMap.keyAt(i));