diff options
5 files changed, 271 insertions, 65 deletions
diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index 0489dc812ab6..fef5e83cecca 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -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); diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 07f353025f87..307490665080 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -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> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 92dc5694ff2c..4c03e6a33fda 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -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,10 +4965,19 @@ <!-- 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" /> - + <java-symbol name="materialColorOnSecondaryFixedVariant" type="attr"/> <java-symbol name="materialColorOnTertiaryFixedVariant" type="attr"/> <java-symbol name="materialColorSurfaceContainerLowest" type="attr"/> diff --git a/services/core/java/com/android/server/input/KeyboardLayoutManager.java b/services/core/java/com/android/server/input/KeyboardLayoutManager.java index 289079c809c3..f873a1b867d2 100644 --- a/services/core/java/com/android/server/input/KeyboardLayoutManager.java +++ b/services/core/java/com/android/server/input/KeyboardLayoutManager.java @@ -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,10 +164,8 @@ final class KeyboardLayoutManager implements InputManager.InputDeviceListener { @Override public void onInputDeviceRemoved(int deviceId) { - if (!useNewSettingsUi()) { - mKeyboardsWithMissingLayouts.removeIf(device -> device.getId() == deviceId); - maybeUpdateNotification(); - } + mConfiguredKeyboards.remove(deviceId); + maybeUpdateNotification(); } @Override @@ -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,66 +1043,140 @@ 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) { - final Intent intent = new Intent(Settings.ACTION_HARD_KEYBOARD_SETTINGS); - if (device != null) { - intent.putExtra(Settings.EXTRA_INPUT_DEVICE_IDENTIFIER, device.getIdentifier()); + 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; } - 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)) - .setContentIntent(keyboardLayoutIntent) - .setSmallIcon(R.drawable.ic_settings_language) - .setColor(mContext.getColor( - com.android.internal.R.color.system_notification_accent_color)) - .build(); - notificationManager.notifyAsUser(null, - SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT, - notification, UserHandle.ALL); - mKeyboardLayoutNotificationShown = true; + 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 (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); + + Notification notification = + new Notification.Builder(mContext, SystemNotificationChannels.PHYSICAL_KEYBOARD) + .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); } // Must be called on handler. - private void hideMissingKeyboardLayoutNotification(NotificationManager notificationManager) { - if (mKeyboardLayoutNotificationShown) { - mKeyboardLayoutNotificationShown = false; - notificationManager.cancelAsUser(null, - SystemMessageProto.SystemMessage.NOTE_SELECT_KEYBOARD_LAYOUT, - UserHandle.ALL); + 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)); } } @@ -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"); diff --git a/services/core/java/com/android/server/input/PersistentDataStore.java b/services/core/java/com/android/server/input/PersistentDataStore.java index a2b183628686..bce210d0a4a4 100644 --- a/services/core/java/com/android/server/input/PersistentDataStore.java +++ b/services/core/java/com/android/server/input/PersistentDataStore.java @@ -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)); |