Loading core/java/android/view/KeyboardShortcutInfo.java +12 −3 Original line number Diff line number Diff line Loading @@ -28,8 +28,8 @@ import android.os.Parcelable; * Information about a Keyboard Shortcut. */ public final class KeyboardShortcutInfo implements Parcelable { private final CharSequence mLabel; private final Icon mIcon; @Nullable private final CharSequence mLabel; @Nullable private Icon mIcon; private final char mBaseCharacter; private final int mKeycode; private final int mModifiers; Loading Loading @@ -115,6 +115,15 @@ public final class KeyboardShortcutInfo implements Parcelable { return mIcon; } /** * Removes an icon that was previously set. * * @hide */ public void clearIcon() { mIcon = null; } /** * Returns the base keycode that, combined with the modifiers, triggers this shortcut. If the * base character was set instead, returns {@link KeyEvent#KEYCODE_UNKNOWN}. Valid keycodes are Loading packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +82 −20 Original line number Diff line number Diff line Loading @@ -19,9 +19,14 @@ package com.android.systemui.statusbar; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.SynchronousUserSwitchObserver; import android.app.UserSwitchObserver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; Loading @@ -37,6 +42,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.input.InputManagerGlobal; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.text.Editable; Loading Loading @@ -136,6 +142,8 @@ public final class KeyboardShortcutListSearch { }; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final HandlerThread mHandlerThread = new HandlerThread("KeyboardShortcutHelper"); @VisibleForTesting Handler mBackgroundHandler; @VisibleForTesting public Context mContext; private final IPackageManager mPackageManager; Loading @@ -143,6 +151,13 @@ public final class KeyboardShortcutListSearch { private KeyCharacterMap mKeyCharacterMap; private KeyCharacterMap mBackupKeyCharacterMap; private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { @Override public void onUserSwitching(int newUserId) throws RemoteException { dismiss(); } }; @VisibleForTesting KeyboardShortcutListSearch(Context context, WindowManager windowManager) { this.mContext = new ContextThemeWrapper( Loading Loading @@ -413,35 +428,74 @@ public final class KeyboardShortcutListSearch { private boolean mAppShortcutsReceived; private boolean mImeShortcutsReceived; @VisibleForTesting public void showKeyboardShortcuts(int deviceId) { retrieveKeyCharacterMap(deviceId); mAppShortcutsReceived = false; mImeShortcutsReceived = false; mWindowManager.requestAppKeyboardShortcuts(result -> { private void onAppSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { // Add specific app shortcuts if (result != null) { if (result.isEmpty()) { mCurrentAppPackageName = null; mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false); } else { mCurrentAppPackageName = result.get(0).getPackageName(); mSpecificAppGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result)); if (validateKeyboardShortcutHelperIconUri()) { KeyboardShortcuts.sanitiseShortcuts(result); } mSpecificAppGroup.addAll( reMapToKeyboardShortcutMultiMappingGroup(result)); mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true); } } mAppShortcutsReceived = true; if (mImeShortcutsReceived) { mergeAndShowKeyboardShortcutsGroups(); } }, deviceId); mWindowManager.requestImeKeyboardShortcuts(result -> { } private void onImeSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { // Add specific Ime shortcuts if (result != null) { if (!result.isEmpty()) { mInputGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result)); if (validateKeyboardShortcutHelperIconUri()) { KeyboardShortcuts.sanitiseShortcuts(result); } mInputGroup.addAll( reMapToKeyboardShortcutMultiMappingGroup(result)); } } mImeShortcutsReceived = true; if (mAppShortcutsReceived) { mergeAndShowKeyboardShortcutsGroups(); } } @VisibleForTesting public void showKeyboardShortcuts(int deviceId) { if (mBackgroundHandler == null) { mHandlerThread.start(); mBackgroundHandler = new Handler(mHandlerThread.getLooper()); } if (validateKeyboardShortcutHelperIconUri()) { try { ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); } catch (RemoteException e) { Log.e(TAG, "could not register user switch observer", e); } } retrieveKeyCharacterMap(deviceId); mAppShortcutsReceived = false; mImeShortcutsReceived = false; mWindowManager.requestAppKeyboardShortcuts( result -> { mBackgroundHandler.post(() -> { onAppSpecificShortcutsReceived(result); }); }, deviceId); mWindowManager.requestImeKeyboardShortcuts( result -> { mBackgroundHandler.post(() -> { onImeSpecificShortcutsReceived(result); }); }, deviceId); } Loading Loading @@ -508,6 +562,14 @@ public final class KeyboardShortcutListSearch { mKeyboardShortcutsBottomSheetDialog.dismiss(); mKeyboardShortcutsBottomSheetDialog = null; } mHandlerThread.quit(); if (validateKeyboardShortcutHelperIconUri()) { try { ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); } catch (RemoteException e) { Log.e(TAG, "Could not unregister user switch observer", e); } } } private KeyboardShortcutMultiMappingGroup getMultiMappingSystemShortcuts(Context context) { Loading packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +74 −4 Original line number Diff line number Diff line Loading @@ -20,11 +20,16 @@ import static android.content.Context.LAYOUT_INFLATER_SERVICE; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.AppGlobals; import android.app.Dialog; import android.app.SynchronousUserSwitchObserver; import android.app.UserSwitchObserver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; Loading @@ -39,6 +44,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.input.InputManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.util.Log; Loading Loading @@ -93,6 +99,8 @@ public final class KeyboardShortcuts { }; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final HandlerThread mHandlerThread = new HandlerThread("KeyboardShortcutHelper"); @VisibleForTesting Handler mBackgroundHandler; @VisibleForTesting public Context mContext; private final IPackageManager mPackageManager; private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() { Loading Loading @@ -129,6 +137,13 @@ public final class KeyboardShortcuts { @Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null; @Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null; private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { @Override public void onUserSwitching(int newUserId) throws RemoteException { dismiss(); } }; @VisibleForTesting KeyboardShortcuts(Context context, WindowManager windowManager) { this.mContext = new ContextThemeWrapper( Loading Loading @@ -374,21 +389,68 @@ public final class KeyboardShortcuts { @VisibleForTesting public void showKeyboardShortcuts(int deviceId) { if (mBackgroundHandler == null) { mHandlerThread.start(); mBackgroundHandler = new Handler(mHandlerThread.getLooper()); } if (validateKeyboardShortcutHelperIconUri()) { try { ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); } catch (RemoteException e) { Log.e(TAG, "could not register user switch observer", e); } } retrieveKeyCharacterMap(deviceId); mReceivedAppShortcutGroups = null; mReceivedImeShortcutGroups = null; mWindowManager.requestAppKeyboardShortcuts( result -> { mReceivedAppShortcutGroups = result; maybeMergeAndShowKeyboardShortcuts(); mBackgroundHandler.post(() -> { onAppSpecificShortcutsReceived(result); }); }, deviceId); mWindowManager.requestImeKeyboardShortcuts( result -> { mReceivedImeShortcutGroups = result; maybeMergeAndShowKeyboardShortcuts(); mBackgroundHandler.post(() -> { onImeSpecificShortcutsReceived(result); }); }, deviceId); } private void onAppSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { mReceivedAppShortcutGroups = result == null ? Collections.emptyList() : result; if (validateKeyboardShortcutHelperIconUri()) { sanitiseShortcuts(mReceivedAppShortcutGroups); } maybeMergeAndShowKeyboardShortcuts(); } private void onImeSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { mReceivedImeShortcutGroups = result == null ? Collections.emptyList() : result; if (validateKeyboardShortcutHelperIconUri()) { sanitiseShortcuts(mReceivedImeShortcutGroups); } maybeMergeAndShowKeyboardShortcuts(); } static void sanitiseShortcuts(List<KeyboardShortcutGroup> shortcutGroups) { for (KeyboardShortcutGroup group : shortcutGroups) { for (KeyboardShortcutInfo info : group.getItems()) { info.clearIcon(); } } } private void maybeMergeAndShowKeyboardShortcuts() { if (mReceivedAppShortcutGroups == null || mReceivedImeShortcutGroups == null) { return; Loading @@ -413,6 +475,14 @@ public final class KeyboardShortcuts { mKeyboardShortcutsDialog.dismiss(); mKeyboardShortcutsDialog = null; } mHandlerThread.quit(); if (validateKeyboardShortcutHelperIconUri()) { try { ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); } catch (RemoteException e) { Log.e(TAG, "Could not unregister user switch observer", e); } } } private KeyboardShortcutGroup getSystemShortcuts() { Loading packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java +68 −0 Original line number Diff line number Diff line Loading @@ -20,14 +20,21 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.graphics.drawable.Icon; import android.os.Handler; import android.platform.test.annotations.EnableFlags; import android.view.KeyboardShortcutGroup; import android.view.KeyboardShortcutInfo; import android.view.WindowManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.google.android.material.bottomsheet.BottomSheetDialog; Loading @@ -36,10 +43,14 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.Arrays; import java.util.Collections; @SmallTest @RunWith(AndroidJUnit4.class) public class KeyboardShortcutListSearchTest extends SysuiTestCase { Loading @@ -51,6 +62,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { @Mock private BottomSheetDialog mBottomSheetDialog; @Mock WindowManager mWindowManager; @Mock Handler mHandler; @Before public void setUp() { Loading @@ -58,6 +70,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { mKeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch; mKeyboardShortcutListSearch.mKeyboardShortcutsBottomSheetDialog = mBottomSheetDialog; mKeyboardShortcutListSearch.mContext = mContext; mKeyboardShortcutListSearch.mBackgroundHandler = mHandler; } @Test Loading @@ -78,4 +91,59 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt()); verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt()); } @Test @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) public void requestAppKeyboardShortcuts_callback_sanitisesIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID); ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mWindowManager).requestAppKeyboardShortcuts(callbackCaptor.capture(), anyInt()); callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); verify(mHandler).post(handlerRunnableCaptor.capture()); handlerRunnableCaptor.getValue().run(); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } @Test @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) public void requestImeKeyboardShortcuts_callback_sanitisesIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID); ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mWindowManager).requestImeKeyboardShortcuts(callbackCaptor.capture(), anyInt()); callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); verify(mHandler).post(handlerRunnableCaptor.capture()); handlerRunnableCaptor.getValue().run(); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } private KeyboardShortcutGroup createKeyboardShortcutGroupForIconTests() { Icon icon = mock(Icon.class); KeyboardShortcutInfo info1 = mock(KeyboardShortcutInfo.class); KeyboardShortcutInfo info2 = mock(KeyboardShortcutInfo.class); when(info1.getIcon()).thenReturn(icon); when(info2.getIcon()).thenReturn(icon); when(info1.getLabel()).thenReturn("label"); when(info2.getLabel()).thenReturn("label"); KeyboardShortcutGroup group = new KeyboardShortcutGroup("label", Arrays.asList(new KeyboardShortcutInfo[]{ info1, info2})); group.setPackageName("com.example"); return group; } } packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java +87 −0 Original line number Diff line number Diff line Loading @@ -20,25 +20,36 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Dialog; import android.graphics.drawable.Icon; import android.os.Handler; import android.platform.test.annotations.EnableFlags; import android.view.KeyboardShortcutGroup; import android.view.KeyboardShortcutInfo; import android.view.WindowManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.Arrays; import java.util.Collections; @SmallTest @RunWith(AndroidJUnit4.class) public class KeyboardShortcutsTest extends SysuiTestCase { Loading @@ -50,6 +61,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase { @Mock private Dialog mDialog; @Mock WindowManager mWindowManager; @Mock Handler mHandler; @Before public void setUp() { Loading @@ -57,6 +69,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase { mKeyboardShortcuts.sInstance = mKeyboardShortcuts; mKeyboardShortcuts.mKeyboardShortcutsDialog = mDialog; mKeyboardShortcuts.mContext = mContext; mKeyboardShortcuts.mBackgroundHandler = mHandler; } @Test Loading @@ -77,4 +90,78 @@ public class KeyboardShortcutsTest extends SysuiTestCase { verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt()); verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt()); } @Test public void sanitiseShortcuts_clearsIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); KeyboardShortcuts.sanitiseShortcuts(Collections.singletonList(group)); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } @Test public void sanitiseShortcuts_nullPackage_clearsIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); group.setPackageName(null); KeyboardShortcuts.sanitiseShortcuts(Collections.singletonList(group)); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } @Test @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) public void requestAppKeyboardShortcuts_callback_sanitisesIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); mKeyboardShortcuts.toggle(mContext, DEVICE_ID); ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mWindowManager).requestAppKeyboardShortcuts(callbackCaptor.capture(), anyInt()); callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); verify(mHandler).post(handlerRunnableCaptor.capture()); handlerRunnableCaptor.getValue().run(); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } @Test @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) public void requestImeKeyboardShortcuts_callback_sanitisesIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); mKeyboardShortcuts.toggle(mContext, DEVICE_ID); ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mWindowManager).requestImeKeyboardShortcuts(callbackCaptor.capture(), anyInt()); callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); verify(mHandler).post(handlerRunnableCaptor.capture()); handlerRunnableCaptor.getValue().run(); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } private KeyboardShortcutGroup createKeyboardShortcutGroupForIconTests() { Icon icon = mock(Icon.class); KeyboardShortcutInfo info1 = mock(KeyboardShortcutInfo.class); KeyboardShortcutInfo info2 = mock(KeyboardShortcutInfo.class); when(info1.getIcon()).thenReturn(icon); when(info2.getIcon()).thenReturn(icon); KeyboardShortcutGroup group = new KeyboardShortcutGroup("label", Arrays.asList(new KeyboardShortcutInfo[]{ info1, info2})); group.setPackageName("com.example"); return group; } } Loading
core/java/android/view/KeyboardShortcutInfo.java +12 −3 Original line number Diff line number Diff line Loading @@ -28,8 +28,8 @@ import android.os.Parcelable; * Information about a Keyboard Shortcut. */ public final class KeyboardShortcutInfo implements Parcelable { private final CharSequence mLabel; private final Icon mIcon; @Nullable private final CharSequence mLabel; @Nullable private Icon mIcon; private final char mBaseCharacter; private final int mKeycode; private final int mModifiers; Loading Loading @@ -115,6 +115,15 @@ public final class KeyboardShortcutInfo implements Parcelable { return mIcon; } /** * Removes an icon that was previously set. * * @hide */ public void clearIcon() { mIcon = null; } /** * Returns the base keycode that, combined with the modifiers, triggers this shortcut. If the * base character was set instead, returns {@link KeyEvent#KEYCODE_UNKNOWN}. Valid keycodes are Loading
packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcutListSearch.java +82 −20 Original line number Diff line number Diff line Loading @@ -19,9 +19,14 @@ package com.android.systemui.statusbar; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AppGlobals; import android.app.SynchronousUserSwitchObserver; import android.app.UserSwitchObserver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; Loading @@ -37,6 +42,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.input.InputManagerGlobal; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.text.Editable; Loading Loading @@ -136,6 +142,8 @@ public final class KeyboardShortcutListSearch { }; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final HandlerThread mHandlerThread = new HandlerThread("KeyboardShortcutHelper"); @VisibleForTesting Handler mBackgroundHandler; @VisibleForTesting public Context mContext; private final IPackageManager mPackageManager; Loading @@ -143,6 +151,13 @@ public final class KeyboardShortcutListSearch { private KeyCharacterMap mKeyCharacterMap; private KeyCharacterMap mBackupKeyCharacterMap; private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { @Override public void onUserSwitching(int newUserId) throws RemoteException { dismiss(); } }; @VisibleForTesting KeyboardShortcutListSearch(Context context, WindowManager windowManager) { this.mContext = new ContextThemeWrapper( Loading Loading @@ -413,35 +428,74 @@ public final class KeyboardShortcutListSearch { private boolean mAppShortcutsReceived; private boolean mImeShortcutsReceived; @VisibleForTesting public void showKeyboardShortcuts(int deviceId) { retrieveKeyCharacterMap(deviceId); mAppShortcutsReceived = false; mImeShortcutsReceived = false; mWindowManager.requestAppKeyboardShortcuts(result -> { private void onAppSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { // Add specific app shortcuts if (result != null) { if (result.isEmpty()) { mCurrentAppPackageName = null; mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, false); } else { mCurrentAppPackageName = result.get(0).getPackageName(); mSpecificAppGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result)); if (validateKeyboardShortcutHelperIconUri()) { KeyboardShortcuts.sanitiseShortcuts(result); } mSpecificAppGroup.addAll( reMapToKeyboardShortcutMultiMappingGroup(result)); mKeySearchResultMap.put(SHORTCUT_SPECIFICAPP_INDEX, true); } } mAppShortcutsReceived = true; if (mImeShortcutsReceived) { mergeAndShowKeyboardShortcutsGroups(); } }, deviceId); mWindowManager.requestImeKeyboardShortcuts(result -> { } private void onImeSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { // Add specific Ime shortcuts if (result != null) { if (!result.isEmpty()) { mInputGroup.addAll(reMapToKeyboardShortcutMultiMappingGroup(result)); if (validateKeyboardShortcutHelperIconUri()) { KeyboardShortcuts.sanitiseShortcuts(result); } mInputGroup.addAll( reMapToKeyboardShortcutMultiMappingGroup(result)); } } mImeShortcutsReceived = true; if (mAppShortcutsReceived) { mergeAndShowKeyboardShortcutsGroups(); } } @VisibleForTesting public void showKeyboardShortcuts(int deviceId) { if (mBackgroundHandler == null) { mHandlerThread.start(); mBackgroundHandler = new Handler(mHandlerThread.getLooper()); } if (validateKeyboardShortcutHelperIconUri()) { try { ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); } catch (RemoteException e) { Log.e(TAG, "could not register user switch observer", e); } } retrieveKeyCharacterMap(deviceId); mAppShortcutsReceived = false; mImeShortcutsReceived = false; mWindowManager.requestAppKeyboardShortcuts( result -> { mBackgroundHandler.post(() -> { onAppSpecificShortcutsReceived(result); }); }, deviceId); mWindowManager.requestImeKeyboardShortcuts( result -> { mBackgroundHandler.post(() -> { onImeSpecificShortcutsReceived(result); }); }, deviceId); } Loading Loading @@ -508,6 +562,14 @@ public final class KeyboardShortcutListSearch { mKeyboardShortcutsBottomSheetDialog.dismiss(); mKeyboardShortcutsBottomSheetDialog = null; } mHandlerThread.quit(); if (validateKeyboardShortcutHelperIconUri()) { try { ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); } catch (RemoteException e) { Log.e(TAG, "Could not unregister user switch observer", e); } } } private KeyboardShortcutMultiMappingGroup getMultiMappingSystemShortcuts(Context context) { Loading
packages/SystemUI/src/com/android/systemui/statusbar/KeyboardShortcuts.java +74 −4 Original line number Diff line number Diff line Loading @@ -20,11 +20,16 @@ import static android.content.Context.LAYOUT_INFLATER_SERVICE; import static android.view.View.IMPORTANT_FOR_ACCESSIBILITY_YES; import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; import static com.android.systemui.Flags.validateKeyboardShortcutHelperIconUri; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.AlertDialog; import android.app.AppGlobals; import android.app.Dialog; import android.app.SynchronousUserSwitchObserver; import android.app.UserSwitchObserver; import android.content.ComponentName; import android.content.Context; import android.content.DialogInterface; Loading @@ -39,6 +44,7 @@ import android.graphics.drawable.Drawable; import android.graphics.drawable.Icon; import android.hardware.input.InputManager; import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.RemoteException; import android.util.Log; Loading Loading @@ -93,6 +99,8 @@ public final class KeyboardShortcuts { }; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final HandlerThread mHandlerThread = new HandlerThread("KeyboardShortcutHelper"); @VisibleForTesting Handler mBackgroundHandler; @VisibleForTesting public Context mContext; private final IPackageManager mPackageManager; private final OnClickListener mDialogCloseListener = new DialogInterface.OnClickListener() { Loading Loading @@ -129,6 +137,13 @@ public final class KeyboardShortcuts { @Nullable private List<KeyboardShortcutGroup> mReceivedAppShortcutGroups = null; @Nullable private List<KeyboardShortcutGroup> mReceivedImeShortcutGroups = null; private final UserSwitchObserver mUserSwitchObserver = new SynchronousUserSwitchObserver() { @Override public void onUserSwitching(int newUserId) throws RemoteException { dismiss(); } }; @VisibleForTesting KeyboardShortcuts(Context context, WindowManager windowManager) { this.mContext = new ContextThemeWrapper( Loading Loading @@ -374,21 +389,68 @@ public final class KeyboardShortcuts { @VisibleForTesting public void showKeyboardShortcuts(int deviceId) { if (mBackgroundHandler == null) { mHandlerThread.start(); mBackgroundHandler = new Handler(mHandlerThread.getLooper()); } if (validateKeyboardShortcutHelperIconUri()) { try { ActivityManager.getService().registerUserSwitchObserver(mUserSwitchObserver, TAG); } catch (RemoteException e) { Log.e(TAG, "could not register user switch observer", e); } } retrieveKeyCharacterMap(deviceId); mReceivedAppShortcutGroups = null; mReceivedImeShortcutGroups = null; mWindowManager.requestAppKeyboardShortcuts( result -> { mReceivedAppShortcutGroups = result; maybeMergeAndShowKeyboardShortcuts(); mBackgroundHandler.post(() -> { onAppSpecificShortcutsReceived(result); }); }, deviceId); mWindowManager.requestImeKeyboardShortcuts( result -> { mReceivedImeShortcutGroups = result; maybeMergeAndShowKeyboardShortcuts(); mBackgroundHandler.post(() -> { onImeSpecificShortcutsReceived(result); }); }, deviceId); } private void onAppSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { mReceivedAppShortcutGroups = result == null ? Collections.emptyList() : result; if (validateKeyboardShortcutHelperIconUri()) { sanitiseShortcuts(mReceivedAppShortcutGroups); } maybeMergeAndShowKeyboardShortcuts(); } private void onImeSpecificShortcutsReceived(List<KeyboardShortcutGroup> result) { mReceivedImeShortcutGroups = result == null ? Collections.emptyList() : result; if (validateKeyboardShortcutHelperIconUri()) { sanitiseShortcuts(mReceivedImeShortcutGroups); } maybeMergeAndShowKeyboardShortcuts(); } static void sanitiseShortcuts(List<KeyboardShortcutGroup> shortcutGroups) { for (KeyboardShortcutGroup group : shortcutGroups) { for (KeyboardShortcutInfo info : group.getItems()) { info.clearIcon(); } } } private void maybeMergeAndShowKeyboardShortcuts() { if (mReceivedAppShortcutGroups == null || mReceivedImeShortcutGroups == null) { return; Loading @@ -413,6 +475,14 @@ public final class KeyboardShortcuts { mKeyboardShortcutsDialog.dismiss(); mKeyboardShortcutsDialog = null; } mHandlerThread.quit(); if (validateKeyboardShortcutHelperIconUri()) { try { ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver); } catch (RemoteException e) { Log.e(TAG, "Could not unregister user switch observer", e); } } } private KeyboardShortcutGroup getSystemShortcuts() { Loading
packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutListSearchTest.java +68 −0 Original line number Diff line number Diff line Loading @@ -20,14 +20,21 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.graphics.drawable.Icon; import android.os.Handler; import android.platform.test.annotations.EnableFlags; import android.view.KeyboardShortcutGroup; import android.view.KeyboardShortcutInfo; import android.view.WindowManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import com.google.android.material.bottomsheet.BottomSheetDialog; Loading @@ -36,10 +43,14 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.Arrays; import java.util.Collections; @SmallTest @RunWith(AndroidJUnit4.class) public class KeyboardShortcutListSearchTest extends SysuiTestCase { Loading @@ -51,6 +62,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { @Mock private BottomSheetDialog mBottomSheetDialog; @Mock WindowManager mWindowManager; @Mock Handler mHandler; @Before public void setUp() { Loading @@ -58,6 +70,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { mKeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch; mKeyboardShortcutListSearch.mKeyboardShortcutsBottomSheetDialog = mBottomSheetDialog; mKeyboardShortcutListSearch.mContext = mContext; mKeyboardShortcutListSearch.mBackgroundHandler = mHandler; } @Test Loading @@ -78,4 +91,59 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase { verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt()); verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt()); } @Test @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) public void requestAppKeyboardShortcuts_callback_sanitisesIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID); ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mWindowManager).requestAppKeyboardShortcuts(callbackCaptor.capture(), anyInt()); callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); verify(mHandler).post(handlerRunnableCaptor.capture()); handlerRunnableCaptor.getValue().run(); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } @Test @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) public void requestImeKeyboardShortcuts_callback_sanitisesIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); mKeyboardShortcutListSearch.toggle(mContext, DEVICE_ID); ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mWindowManager).requestImeKeyboardShortcuts(callbackCaptor.capture(), anyInt()); callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); verify(mHandler).post(handlerRunnableCaptor.capture()); handlerRunnableCaptor.getValue().run(); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } private KeyboardShortcutGroup createKeyboardShortcutGroupForIconTests() { Icon icon = mock(Icon.class); KeyboardShortcutInfo info1 = mock(KeyboardShortcutInfo.class); KeyboardShortcutInfo info2 = mock(KeyboardShortcutInfo.class); when(info1.getIcon()).thenReturn(icon); when(info2.getIcon()).thenReturn(icon); when(info1.getLabel()).thenReturn("label"); when(info2.getLabel()).thenReturn("label"); KeyboardShortcutGroup group = new KeyboardShortcutGroup("label", Arrays.asList(new KeyboardShortcutInfo[]{ info1, info2})); group.setPackageName("com.example"); return group; } }
packages/SystemUI/tests/src/com/android/systemui/statusbar/KeyboardShortcutsTest.java +87 −0 Original line number Diff line number Diff line Loading @@ -20,25 +20,36 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.app.Dialog; import android.graphics.drawable.Icon; import android.os.Handler; import android.platform.test.annotations.EnableFlags; import android.view.KeyboardShortcutGroup; import android.view.KeyboardShortcutInfo; import android.view.WindowManager; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; import com.android.systemui.Flags; import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.Arrays; import java.util.Collections; @SmallTest @RunWith(AndroidJUnit4.class) public class KeyboardShortcutsTest extends SysuiTestCase { Loading @@ -50,6 +61,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase { @Mock private Dialog mDialog; @Mock WindowManager mWindowManager; @Mock Handler mHandler; @Before public void setUp() { Loading @@ -57,6 +69,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase { mKeyboardShortcuts.sInstance = mKeyboardShortcuts; mKeyboardShortcuts.mKeyboardShortcutsDialog = mDialog; mKeyboardShortcuts.mContext = mContext; mKeyboardShortcuts.mBackgroundHandler = mHandler; } @Test Loading @@ -77,4 +90,78 @@ public class KeyboardShortcutsTest extends SysuiTestCase { verify(mWindowManager).requestAppKeyboardShortcuts(any(), anyInt()); verify(mWindowManager).requestImeKeyboardShortcuts(any(), anyInt()); } @Test public void sanitiseShortcuts_clearsIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); KeyboardShortcuts.sanitiseShortcuts(Collections.singletonList(group)); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } @Test public void sanitiseShortcuts_nullPackage_clearsIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); group.setPackageName(null); KeyboardShortcuts.sanitiseShortcuts(Collections.singletonList(group)); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } @Test @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) public void requestAppKeyboardShortcuts_callback_sanitisesIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); mKeyboardShortcuts.toggle(mContext, DEVICE_ID); ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mWindowManager).requestAppKeyboardShortcuts(callbackCaptor.capture(), anyInt()); callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); verify(mHandler).post(handlerRunnableCaptor.capture()); handlerRunnableCaptor.getValue().run(); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } @Test @EnableFlags(Flags.FLAG_VALIDATE_KEYBOARD_SHORTCUT_HELPER_ICON_URI) public void requestImeKeyboardShortcuts_callback_sanitisesIcons() { KeyboardShortcutGroup group = createKeyboardShortcutGroupForIconTests(); mKeyboardShortcuts.toggle(mContext, DEVICE_ID); ArgumentCaptor<WindowManager.KeyboardShortcutsReceiver> callbackCaptor = ArgumentCaptor.forClass(WindowManager.KeyboardShortcutsReceiver.class); ArgumentCaptor<Runnable> handlerRunnableCaptor = ArgumentCaptor.forClass(Runnable.class); verify(mWindowManager).requestImeKeyboardShortcuts(callbackCaptor.capture(), anyInt()); callbackCaptor.getValue().onKeyboardShortcutsReceived(Collections.singletonList(group)); verify(mHandler).post(handlerRunnableCaptor.capture()); handlerRunnableCaptor.getValue().run(); verify(group.getItems().get(0)).clearIcon(); verify(group.getItems().get(1)).clearIcon(); } private KeyboardShortcutGroup createKeyboardShortcutGroupForIconTests() { Icon icon = mock(Icon.class); KeyboardShortcutInfo info1 = mock(KeyboardShortcutInfo.class); KeyboardShortcutInfo info2 = mock(KeyboardShortcutInfo.class); when(info1.getIcon()).thenReturn(icon); when(info2.getIcon()).thenReturn(icon); KeyboardShortcutGroup group = new KeyboardShortcutGroup("label", Arrays.asList(new KeyboardShortcutInfo[]{ info1, info2})); group.setPackageName("com.example"); return group; } }