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

Commit 6daab713 authored by Ben Murdoch's avatar Ben Murdoch Committed by Android (Google) Code Review
Browse files

Merge "Clear icons for app/ime keyboard shortcuts in shortcut helper." into main

parents b2a73ecc 81077e77
Loading
Loading
Loading
Loading
+12 −3
Original line number Diff line number Diff line
@@ -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;
@@ -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
+82 −20
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;

@@ -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(
@@ -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);
    }

@@ -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) {
+74 −4
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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() {
@@ -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(
@@ -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;
@@ -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() {
+68 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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 {
@@ -51,6 +62,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase {

    @Mock private BottomSheetDialog mBottomSheetDialog;
    @Mock WindowManager mWindowManager;
    @Mock Handler mHandler;

    @Before
    public void setUp() {
@@ -58,6 +70,7 @@ public class KeyboardShortcutListSearchTest extends SysuiTestCase {
        mKeyboardShortcutListSearch.sInstance = mKeyboardShortcutListSearch;
        mKeyboardShortcutListSearch.mKeyboardShortcutsBottomSheetDialog = mBottomSheetDialog;
        mKeyboardShortcutListSearch.mContext = mContext;
        mKeyboardShortcutListSearch.mBackgroundHandler = mHandler;
    }

    @Test
@@ -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;
    }
}
+87 −0
Original line number Diff line number Diff line
@@ -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 {
@@ -50,6 +61,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase {

    @Mock private Dialog mDialog;
    @Mock WindowManager mWindowManager;
    @Mock Handler mHandler;

    @Before
    public void setUp() {
@@ -57,6 +69,7 @@ public class KeyboardShortcutsTest extends SysuiTestCase {
        mKeyboardShortcuts.sInstance = mKeyboardShortcuts;
        mKeyboardShortcuts.mKeyboardShortcutsDialog = mDialog;
        mKeyboardShortcuts.mContext = mContext;
        mKeyboardShortcuts.mBackgroundHandler = mHandler;
    }

    @Test
@@ -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;
    }
}