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

Commit 94c865ce authored by Ben Murdoch's avatar Ben Murdoch
Browse files

Refactor ModifierShortcutManager.

Instead of arrays of intents to represent shortcuts, now use
data objects to make the code simpler and easier to follow.

Bug: 358603902
Flag: com.android.hardware.input.modifier_shortcut_manager_refactor
Test: atest ModifierShortcutManagerTests ModifierShortcutTests

Change-Id: Ibc368bc804d17ede6c83fbf77070e85776738e77
parent 5e3c0c44
Loading
Loading
Loading
Loading
+410 −175
Original line number Diff line number Diff line
@@ -17,7 +17,9 @@
package com.android.server.policy;

import static com.android.server.flags.Flags.modifierShortcutManagerMultiuser;
import static com.android.hardware.input.Flags.modifierShortcutManagerRefactor;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SuppressLint;
import android.app.role.RoleManager;
@@ -37,6 +39,7 @@ import android.text.TextUtils;
import android.util.IndentingPrintWriter;
import android.util.Log;
import android.util.LongSparseArray;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;
import android.view.KeyCharacterMap;
@@ -81,8 +84,8 @@ public class ModifierShortcutManager {
    private static final String ATTRIBUTE_SHIFT = "shift";
    private static final String ATTRIBUTE_ROLE = "role";

    private final SparseArray<Intent> mIntentShortcuts = new SparseArray<>();
    private final SparseArray<Intent> mShiftShortcuts = new SparseArray<>();
    private final SparseArray<Intent> mCategoryShortcuts = new SparseArray<>();
    private final SparseArray<Intent> mShiftCategoryShortcuts = new SparseArray<>();
    private final SparseArray<String> mRoleShortcuts = new SparseArray<String>();
    private final SparseArray<String> mShiftRoleShortcuts = new SparseArray<String>();
    private final Map<String, Intent> mRoleIntents = new HashMap<String, Intent>();
@@ -127,6 +130,7 @@ public class ModifierShortcutManager {
    private boolean mSearchKeyShortcutPending = false;
    private boolean mConsumeSearchKeyUp = true;
    private UserHandle mCurrentUser;
    private final Map<Pair<Character, Boolean>, Bookmark> mBookmarks = new HashMap<>();

    ModifierShortcutManager(Context context, Handler handler, UserHandle currentUser) {
        mContext = context;
@@ -134,7 +138,14 @@ public class ModifierShortcutManager {
        RoleManager rm = mContext.getSystemService(RoleManager.class);
        rm.addOnRoleHoldersChangedListenerAsUser(mContext.getMainExecutor(),
                (String roleName, UserHandle user) -> {
                    if (modifierShortcutManagerRefactor()) {
                        mBookmarks.values().stream().filter(b ->
                                b instanceof RoleBookmark
                                && ((RoleBookmark) b).getRole().equals(roleName))
                                .forEach(Bookmark::clearIntent);
                    } else {
                        mRoleIntents.remove(roleName);
                    }
                }, UserHandle.ALL);
        mCurrentUser = currentUser;
        mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
@@ -146,9 +157,27 @@ public class ModifierShortcutManager {

        // Role based shortcuts may resolve to different apps for different users
        // so clear the cache.
        clearRoleIntents();
        clearComponentIntents();
    }

    void clearRoleIntents() {
        if (modifierShortcutManagerRefactor()) {
            mBookmarks.values().stream().filter(b ->
                    b instanceof RoleBookmark).forEach(Bookmark::clearIntent);
        } else {
            mRoleIntents.clear();
        }
    }

    void clearComponentIntents() {
        if (modifierShortcutManagerRefactor()) {
            mBookmarks.values().stream().filter(b ->
                    b instanceof ComponentBookmark).forEach(Bookmark::clearIntent);
        } else {
            mComponentIntents.clear();
        }
    }

    /**
     * Gets the shortcut intent for a given keycode+modifier. Make sure you
@@ -176,14 +205,35 @@ public class ModifierShortcutManager {

        Intent shortcutIntent = null;

        // If the Shift key is pressed, then search for the shift shortcuts.
        SparseArray<Intent> shortcutMap = isShiftOn ? mShiftShortcuts : mIntentShortcuts;

        // First try the exact keycode (with modifiers).
        int shortcutChar = kcm.get(keyCode, metaState);
        if (shortcutChar == 0) {
            return null;
        }

        if (modifierShortcutManagerRefactor()) {
            Bookmark bookmark = mBookmarks.get(new Pair<>((char) shortcutChar, isShiftOn));
            if (bookmark == null) {
                // Next try the primary character on that key.
                shortcutChar = Character.toLowerCase(kcm.getDisplayLabel(keyCode));
                if (shortcutChar == 0) {
                    return null;
                }
                bookmark = mBookmarks.get(new Pair<>((char) shortcutChar, isShiftOn));
            }

            if (bookmark != null) {
                Context context = modifierShortcutManagerMultiuser()
                        ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
                shortcutIntent = bookmark.getIntent(context);
            } else {
                Log.d(TAG, "No bookmark found for "
                        + (isShiftOn ? "SHIFT+" : "") + (char) shortcutChar);
            }
        } else {
            // If the Shift key is pressed, then search for the shift shortcuts.
            SparseArray<Intent> shortcutMap = isShiftOn
                    ? mShiftCategoryShortcuts : mCategoryShortcuts;
            shortcutIntent = shortcutMap.get(shortcutChar);

            if (shortcutIntent == null) {
@@ -215,14 +265,13 @@ public class ModifierShortcutManager {
                    }
                }
            }
        }
        return shortcutIntent;
    }

    private Intent getRoleLaunchIntent(String role) {
        Intent intent = mRoleIntents.get(role);
        if (intent == null) {
            Context context = modifierShortcutManagerMultiuser()
                    ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
    @Nullable
    private static Intent getRoleLaunchIntent(Context context, String role) {
        Intent intent = null;
        RoleManager rm = context.getSystemService(RoleManager.class);
        PackageManager pm = context.getPackageManager();
        if (rm.isRoleAvailable(role)) {
@@ -231,22 +280,36 @@ public class ModifierShortcutManager {
                intent = pm.getLaunchIntentForPackage(rolePackage);
                if (intent != null) {
                    intent.putExtra(EXTRA_ROLE, role);
                        mRoleIntents.put(role, intent);

                } else {
                    Log.w(TAG, "No launch intent for role " + role);
                }
            } else {
                    Log.w(TAG, "No default application for role " + role);
                Log.w(TAG, "No default application for role "
                        + role + " user=" + context.getUser());
            }
        } else {
            Log.w(TAG, "Role " + role + " is not available.");
        }
        return intent;
    }

    @Nullable
    private Intent getRoleLaunchIntent(String role) {
        Intent intent = mRoleIntents.get(role);
        if (intent == null) {
            Context context = modifierShortcutManagerMultiuser()
                    ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
            intent = getRoleLaunchIntent(context, role);
            if (intent != null) {
                mRoleIntents.put(role, intent);
            }
        }

        return intent;
    }

    private void loadShortcuts() {

        try {
            XmlResourceParser parser = mContext.getResources().getXml(R.xml.bookmarks);
            XmlUtils.beginDocument(parser, TAG_BOOKMARKS);
@@ -276,9 +339,27 @@ public class ModifierShortcutManager {
                    continue;
                }

                final int shortcutChar = shortcutName.charAt(0);
                final boolean isShiftShortcut = (shiftName != null && shiftName.equals("true"));
                final Intent intent;

                if (modifierShortcutManagerRefactor()) {
                    final char shortcutChar = shortcutName.charAt(0);
                    Bookmark bookmark = null;
                    if (packageName != null && className != null) {
                        bookmark = new ComponentBookmark(
                                shortcutChar, isShiftShortcut, packageName, className);
                    } else if (categoryName != null) {
                        bookmark = new CategoryBookmark(
                                shortcutChar, isShiftShortcut, categoryName);
                    } else if (roleName != null) {
                        bookmark = new RoleBookmark(shortcutChar, isShiftShortcut, roleName);
                    }
                    if (bookmark != null) {
                        Log.d(TAG, "adding shortcut " + bookmark + "shift="
                                + isShiftShortcut + " char=" + shortcutChar);
                        mBookmarks.put(new Pair<>(shortcutChar, isShiftShortcut), bookmark);
                    }
                } else {
                    final int shortcutChar = shortcutName.charAt(0);
                    if (packageName != null && className != null) {
                        if (roleName != null || categoryName != null) {
                            Log.w(TAG, "Cannot specify role or category when package and class"
@@ -287,16 +368,22 @@ public class ModifierShortcutManager {
                            continue;
                        }
                        if (modifierShortcutManagerMultiuser()) {
                        ComponentName componentName = new ComponentName(packageName, className);
                            ComponentName componentName =
                                    new ComponentName(packageName, className);
                            if (isShiftShortcut) {
                                mShiftComponentShortcuts.put(shortcutChar, componentName);
                            } else {
                                mComponentShortcuts.put(shortcutChar, componentName);
                            }
                        continue;
                        } else {
                        intent = resolveComponentNameIntent(packageName, className);
                            Intent intent = resolveComponentNameIntent(packageName, className);
                            if (isShiftShortcut) {
                                mShiftCategoryShortcuts.put(shortcutChar, intent);
                            } else {
                                mCategoryShortcuts.put(shortcutChar, intent);
                            }
                        }
                        continue;
                    } else if (categoryName != null) {
                        if (roleName != null) {
                            Log.w(TAG, "Cannot specify role bookmark when category is present for"
@@ -304,10 +391,18 @@ public class ModifierShortcutManager {
                                    + " category= " + categoryName);
                            continue;
                        }
                    intent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, categoryName);
                        Intent intent = Intent.makeMainSelectorActivity(
                                Intent.ACTION_MAIN, categoryName);
                        if (intent == null) {
                            Log.w(TAG, "Null selector intent for " + categoryName);
                        } else {
                            if (isShiftShortcut) {
                                mShiftCategoryShortcuts.put(shortcutChar, intent);
                            } else {
                                mCategoryShortcuts.put(shortcutChar, intent);
                            }
                        }
                        continue;
                    } else if (roleName != null) {
                        // We can't resolve the role at the time of this file being parsed as the
                        // device hasn't finished booting, so we will look it up lazily.
@@ -322,11 +417,6 @@ public class ModifierShortcutManager {
                                + ": missing package/class, category or role attributes");
                        continue;
                    }

                if (isShiftShortcut) {
                    mShiftShortcuts.put(shortcutChar, intent);
                } else {
                    mIntentShortcuts.put(shortcutChar, intent);
                }
            }
        } catch (XmlPullParserException | IOException e) {
@@ -336,6 +426,9 @@ public class ModifierShortcutManager {

    @Nullable
    private Intent resolveComponentNameIntent(ComponentName componentName) {
        if (modifierShortcutManagerRefactor()) {
            return null;
        } else {
            Intent intent = mComponentIntents.get(componentName);
            if (intent == null) {
                intent = resolveComponentNameIntent(
@@ -346,11 +439,22 @@ public class ModifierShortcutManager {
            }
            return intent;
        }
    }

    @Nullable
    private Intent resolveComponentNameIntent(String packageName, String className) {
        if (modifierShortcutManagerRefactor()) {
            return null;
        } else {
            Context context = modifierShortcutManagerMultiuser()
                        ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;
            return resolveComponentNameIntent(context, packageName, className);
        }
    }

    @Nullable
    private static Intent resolveComponentNameIntent(
            Context context, String packageName, String className) {
        PackageManager pm = context.getPackageManager();
        int flags = PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
        if (!modifierShortcutManagerMultiuser()) {
@@ -562,17 +666,30 @@ public class ModifierShortcutManager {
     */
    public KeyboardShortcutGroup getApplicationLaunchKeyboardShortcuts(int deviceId) {
        List<KeyboardShortcutInfo> shortcuts = new ArrayList();
        for (int i = 0; i <  mIntentShortcuts.size(); i++) {
        if (modifierShortcutManagerRefactor()) {
            for (Bookmark b : mBookmarks.values()) {
                KeyboardShortcutInfo info = shortcutInfoFromIntent(
                    (char) (mIntentShortcuts.keyAt(i)), mIntentShortcuts.valueAt(i), false);
                        b.getShortcutChar(), b.getIntent(mContext), b.isShift());
                if (info != null) {
                    shortcuts.add(info);
                }
            }
        } else {
            for (int i = 0; i <  mCategoryShortcuts.size(); i++) {
                KeyboardShortcutInfo info = shortcutInfoFromIntent(
                        (char) (mCategoryShortcuts.keyAt(i)),
                        mCategoryShortcuts.valueAt(i),
                        false);
                if (info != null) {
                    shortcuts.add(info);
                }
            }

        for (int i = 0; i <  mShiftShortcuts.size(); i++) {
            for (int i = 0; i <  mShiftCategoryShortcuts.size(); i++) {
                KeyboardShortcutInfo info = shortcutInfoFromIntent(
                    (char) (mShiftShortcuts.keyAt(i)), mShiftShortcuts.valueAt(i), true);
                        (char) (mShiftCategoryShortcuts.keyAt(i)),
                        mShiftCategoryShortcuts.valueAt(i),
                        true);
                if (info != null) {
                    shortcuts.add(info);
                }
@@ -581,7 +698,9 @@ public class ModifierShortcutManager {
            for (int i = 0; i <  mRoleShortcuts.size(); i++) {
                String role = mRoleShortcuts.valueAt(i);
                KeyboardShortcutInfo info = shortcutInfoFromIntent(
                    (char) (mRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), false);
                        (char) (mRoleShortcuts.keyAt(i)),
                        getRoleLaunchIntent(role),
                        false);
                if (info != null) {
                    shortcuts.add(info);
                }
@@ -590,7 +709,9 @@ public class ModifierShortcutManager {
            for (int i = 0; i <  mShiftRoleShortcuts.size(); i++) {
                String role = mShiftRoleShortcuts.valueAt(i);
                KeyboardShortcutInfo info = shortcutInfoFromIntent(
                    (char) (mShiftRoleShortcuts.keyAt(i)), getRoleLaunchIntent(role), true);
                        (char) (mShiftRoleShortcuts.keyAt(i)),
                        getRoleLaunchIntent(role),
                        true);
                if (info != null) {
                    shortcuts.add(info);
                }
@@ -619,7 +740,7 @@ public class ModifierShortcutManager {
                    }
                }
            }

        }
        return new KeyboardShortcutGroup(
                mContext.getString(R.string.keyboard_shortcut_group_applications),
                shortcuts);
@@ -800,7 +921,29 @@ public class ModifierShortcutManager {
    void dump(String prefix, PrintWriter pw) {
        IndentingPrintWriter ipw = new IndentingPrintWriter(pw,  "  ", prefix);
        ipw.println("ModifierShortcutManager shortcuts:");
        if (modifierShortcutManagerRefactor()) {
            ipw.increaseIndent();
            for (Bookmark b : mBookmarks.values()) {
                boolean isShift = b.isShift();
                char shortcutChar = b.getShortcutChar();
                Context context = modifierShortcutManagerMultiuser()
                        ? mContext.createContextAsUser(mCurrentUser, 0) : mContext;

                Intent intent = b.getIntent(context);
                ipw.print(isShift ? "SHIFT+" : "");
                ipw.println(shortcutChar + " " + intent);
                ipw.increaseIndent();
                ipw.increaseIndent();
                KeyboardShortcutInfo info = shortcutInfoFromIntent(shortcutChar, intent, isShift);
                if (info != null) {
                    ipw.println("Resolves to: " + info.getLabel());
                } else {
                    ipw.println("<No KeyboardShortcutInfo available for this shortcut>");
                }
                ipw.decreaseIndent();
                ipw.decreaseIndent();
            }
        } else {
            ipw.increaseIndent();
            ipw.println("Roles");
            ipw.increaseIndent();
@@ -821,15 +964,15 @@ public class ModifierShortcutManager {
            ipw.decreaseIndent();
            ipw.println("Selectors");
            ipw.increaseIndent();
        for (int i = 0; i <  mIntentShortcuts.size(); i++) {
            char shortcutChar = (char) mIntentShortcuts.keyAt(i);
            Intent intent = mIntentShortcuts.valueAt(i);
            for (int i = 0; i < mCategoryShortcuts.size(); i++) {
                char shortcutChar = (char) mCategoryShortcuts.keyAt(i);
                Intent intent = mCategoryShortcuts.valueAt(i);
                ipw.println(shortcutChar + " " + intent);
            }

        for (int i = 0; i <  mShiftShortcuts.size(); i++) {
            char shortcutChar = (char) mShiftShortcuts.keyAt(i);
            Intent intent = mShiftShortcuts.valueAt(i);
            for (int i = 0; i < mShiftCategoryShortcuts.size(); i++) {
                char shortcutChar = (char) mShiftCategoryShortcuts.keyAt(i);
                Intent intent = mShiftCategoryShortcuts.valueAt(i);
                ipw.println("SHIFT+" + shortcutChar + " " + intent);

            }
@@ -854,3 +997,95 @@ public class ModifierShortcutManager {
            }
        }
    }

    private abstract static  class Bookmark {
        private final char mShortcutChar;
        private final boolean mShift;
        protected Intent mIntent;

        Bookmark(char shortcutChar, boolean shift) {
            mShortcutChar = shortcutChar;
            mShift = shift;
        }

        public char getShortcutChar() {
            return mShortcutChar;
        }

        public boolean isShift() {
            return mShift;
        }

        public abstract Intent getIntent(Context context);

        public void clearIntent() {
            mIntent = null;
        }

    }

    private static final class RoleBookmark extends Bookmark {
        private final String mRole;

        RoleBookmark(char shortcutChar, boolean shift, String role) {
            super(shortcutChar, shift);
            mRole = role;
        }

        public String getRole() {
            return mRole;
        }

        @Nullable
        @Override
        public Intent getIntent(Context context) {
            if (mIntent != null) {
                return mIntent;
            }
            mIntent = getRoleLaunchIntent(context, mRole);
            return mIntent;
        }
    }

    private static final class CategoryBookmark extends Bookmark {
        private final String mCategory;

        CategoryBookmark(char shortcutChar, boolean shift, String category) {
            super(shortcutChar, shift);
            mCategory = category;
        }

        @NonNull
        @Override
        public Intent getIntent(Context context) {
            if (mIntent != null) {
                return mIntent;
            }

            mIntent = Intent.makeMainSelectorActivity(Intent.ACTION_MAIN, mCategory);
            return mIntent;
        }
    }

    private static final class ComponentBookmark extends Bookmark {
        private final String mPackageName;
        private final String mClassName;

        ComponentBookmark(
                char shortcutChar, boolean shift, String packageName, String className) {
            super(shortcutChar, shift);
            mPackageName = packageName;
            mClassName = className;
        }

        @Nullable
        @Override
        public Intent getIntent(Context context) {
            if (mIntent != null) {
                return mIntent;
            }
            mIntent = resolveComponentNameIntent(context, mPackageName, mClassName);
            return mIntent;
        }
    }
}
+10 −0
Original line number Diff line number Diff line
@@ -41,6 +41,8 @@ import android.content.res.XmlResourceParser;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.view.KeyEvent;
import android.view.KeyboardShortcutGroup;
import android.view.KeyboardShortcutInfo;
@@ -50,6 +52,8 @@ import androidx.test.filters.SmallTest;
import com.android.internal.R;

import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;

import java.util.Collections;
@@ -62,7 +66,13 @@ import java.util.Collections;
 */

@SmallTest
@EnableFlags(com.android.hardware.input.Flags.FLAG_MODIFIER_SHORTCUT_MANAGER_REFACTOR)
public class ModifierShortcutManagerTests {

    @ClassRule public static final SetFlagsRule.ClassRule SET_FLAGS_CLASS_RULE =
            new SetFlagsRule.ClassRule();
    @Rule public final SetFlagsRule mSetFlagsRule = SET_FLAGS_CLASS_RULE.createSetFlagsRule();

    private ModifierShortcutManager mModifierShortcutManager;
    private Handler mHandler;
    private Context mContext;
+5 −10
Original line number Diff line number Diff line
@@ -44,26 +44,21 @@ import android.app.role.RoleManager;
import android.content.ComponentName;
import android.content.Intent;
import android.os.RemoteException;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.annotations.Presubmit;
import android.platform.test.annotations.RequiresFlagsDisabled;
import android.platform.test.annotations.RequiresFlagsEnabled;
import android.platform.test.flag.junit.CheckFlagsRule;
import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.util.SparseArray;

import androidx.test.filters.SmallTest;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;

@Presubmit
@SmallTest
@EnableFlags(com.android.hardware.input.Flags.FLAG_MODIFIER_SHORTCUT_MANAGER_REFACTOR)
public class ModifierShortcutTests extends ShortcutKeyTestBase {

    @Rule
    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();

    private static final SparseArray<String> INTENT_SHORTCUTS =  new SparseArray<>();
    private static final SparseArray<String> ROLE_SHORTCUTS =  new SparseArray<>();
    static {
@@ -258,7 +253,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
     * META+CTRL+BACKSPACE for taking a bugreport when the flag is enabled.
     */
    @Test
    @RequiresFlagsEnabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
    @EnableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
    public void testTakeBugReport_flagEnabled() throws RemoteException {
        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0);
        mPhoneWindowManager.assertTakeBugreport(true);
@@ -268,7 +263,7 @@ public class ModifierShortcutTests extends ShortcutKeyTestBase {
     * META+CTRL+BACKSPACE for taking a bugreport does nothing when the flag is disabledd.
     */
    @Test
    @RequiresFlagsDisabled(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
    @DisableFlags(com.android.server.flags.Flags.FLAG_NEW_BUGREPORT_KEYBOARD_SHORTCUT)
    public void testTakeBugReport_flagDisabled() throws RemoteException {
        sendKeyCombination(new int[]{KEYCODE_META_LEFT, KEYCODE_CTRL_LEFT, KEYCODE_DEL}, 0);
        mPhoneWindowManager.assertTakeBugreport(false);