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

Commit 3331b99f authored by Steven Ng's avatar Steven Ng
Browse files

Add multi-users support to app bubbles

Test: atest SystemUITests:com.android.systemui.wmshell.BubblesTest
atest SystemUITests:com.android.systemui.notetask.NoteTaskControllerTest
atest SystemUITests:com.android.systemui.notetask.NoteTaskEventLoggerTest
atest SystemUITests:com.android.systemui.notetask.NoteTaskInfoResolverTest
atest SystemUITests:com.android.systemui.notetask.NoteTaskInfoTest
Manual: open personal notes app shortcut & then open work note app shortcut.
Verify both notes apps are shown in a separate bubble.
Bug: 273533235

Change-Id: I1a2ee7824b3495966ba20e181b6f6d6cf8a14605
parent 73f41157
Loading
Loading
Loading
Loading
+40 −5
Original line number Diff line number Diff line
@@ -65,7 +65,11 @@ import java.util.concurrent.Executor;
public class Bubble implements BubbleViewProvider {
    private static final String TAG = "Bubble";

    public static final String KEY_APP_BUBBLE = "key_app_bubble";
    /** A string suffix used in app bubbles' {@link #mKey}. */
    private static final String KEY_APP_BUBBLE = "key_app_bubble";

    /** Whether the bubble is an app bubble. */
    private final boolean mIsAppBubble;

    private final String mKey;
    @Nullable
@@ -182,7 +186,7 @@ public class Bubble implements BubbleViewProvider {
    private PendingIntent mDeleteIntent;

    /**
     * Used only for a special bubble in the stack that has the key {@link #KEY_APP_BUBBLE}.
     * Used only for a special bubble in the stack that has {@link #mIsAppBubble} set to true.
     * There can only be one of these bubbles in the stack and this intent will be populated for
     * that bubble.
     */
@@ -217,24 +221,54 @@ public class Bubble implements BubbleViewProvider {
        mMainExecutor = mainExecutor;
        mTaskId = taskId;
        mBubbleMetadataFlagListener = listener;
        mIsAppBubble = false;
    }

    public Bubble(Intent intent,
    private Bubble(
            Intent intent,
            UserHandle user,
            @Nullable Icon icon,
            boolean isAppBubble,
            String key,
            Executor mainExecutor) {
        mKey = KEY_APP_BUBBLE;
        mGroupKey = null;
        mLocusId = null;
        mFlags = 0;
        mUser = user;
        mIcon = icon;
        mIsAppBubble = isAppBubble;
        mKey = key;
        mShowBubbleUpdateDot = false;
        mMainExecutor = mainExecutor;
        mTaskId = INVALID_TASK_ID;
        mAppIntent = intent;
        mDesiredHeight = Integer.MAX_VALUE;
        mPackageName = intent.getPackage();

    }

    /** Creates an app bubble. */
    public static Bubble createAppBubble(
            Intent intent,
            UserHandle user,
            @Nullable Icon icon,
            Executor mainExecutor) {
        return new Bubble(intent,
                user,
                icon,
                /* isAppBubble= */ true,
                /* key= */ getAppBubbleKeyForApp(intent.getPackage(), user),
                mainExecutor);
    }

    /**
     * Returns the key for an app bubble from an app with package name, {@code packageName} on an
     * Android user, {@code user}.
     */
    public static String getAppBubbleKeyForApp(String packageName, UserHandle user) {
        Objects.requireNonNull(packageName);
        Objects.requireNonNull(user);
        return KEY_APP_BUBBLE + ":" + user.getIdentifier()  + ":" + packageName;
    }

    @VisibleForTesting(visibility = PRIVATE)
@@ -242,6 +276,7 @@ public class Bubble implements BubbleViewProvider {
            final Bubbles.BubbleMetadataFlagListener listener,
            final Bubbles.PendingIntentCanceledListener intentCancelListener,
            Executor mainExecutor) {
        mIsAppBubble = false;
        mKey = entry.getKey();
        mGroupKey = entry.getGroupKey();
        mLocusId = entry.getLocusId();
@@ -819,7 +854,7 @@ public class Bubble implements BubbleViewProvider {
    }

    boolean isAppBubble() {
        return KEY_APP_BUBBLE.equals(mKey);
        return mIsAppBubble;
    }

    Intent getSettingsIntent(final Context context) {
+16 −15
Original line number Diff line number Diff line
@@ -24,7 +24,6 @@ import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;

import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_CONTROLLER;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_GESTURE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
@@ -1196,14 +1195,15 @@ public class BubbleController implements ConfigurationChangeListener,
            return;
        }

        String appBubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(), user);
        PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier());
        if (!isResizableActivity(intent, packageManager, KEY_APP_BUBBLE)) return;
        if (!isResizableActivity(intent, packageManager, appBubbleKey)) return;

        Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE);
        Bubble existingAppBubble = mBubbleData.getBubbleInStackWithKey(appBubbleKey);
        if (existingAppBubble != null) {
            BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
            if (isStackExpanded()) {
                if (selectedBubble != null && KEY_APP_BUBBLE.equals(selectedBubble.getKey())) {
                if (selectedBubble != null && appBubbleKey.equals(selectedBubble.getKey())) {
                    // App bubble is expanded, lets collapse
                    collapseStack();
                } else {
@@ -1217,7 +1217,7 @@ public class BubbleController implements ConfigurationChangeListener,
            }
        } else {
            // App bubble does not exist, lets add and expand it
            Bubble b = new Bubble(intent, user, icon, mMainExecutor);
            Bubble b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
            b.setShouldAutoExpand(true);
            inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
        }
@@ -1250,8 +1250,8 @@ public class BubbleController implements ConfigurationChangeListener,
    }

    /** Sets the app bubble's taskId which is cached for SysUI. */
    public void setAppBubbleTaskId(int taskId) {
        mImpl.mCachedState.setAppBubbleTaskId(taskId);
    public void setAppBubbleTaskId(String key, int taskId) {
        mImpl.mCachedState.setAppBubbleTaskId(key, taskId);
    }

    /**
@@ -2049,7 +2049,8 @@ public class BubbleController implements ConfigurationChangeListener,
            private HashSet<String> mSuppressedBubbleKeys = new HashSet<>();
            private HashMap<String, String> mSuppressedGroupToNotifKeys = new HashMap<>();
            private HashMap<String, Bubble> mShortcutIdToBubble = new HashMap<>();
            private int mAppBubbleTaskId = INVALID_TASK_ID;

            private HashMap<String, Integer> mAppBubbleTaskIds = new HashMap();

            private ArrayList<Bubble> mTmpBubbles = new ArrayList<>();

@@ -2081,20 +2082,20 @@ public class BubbleController implements ConfigurationChangeListener,

                mSuppressedBubbleKeys.clear();
                mShortcutIdToBubble.clear();
                mAppBubbleTaskId = INVALID_TASK_ID;
                mAppBubbleTaskIds.clear();
                for (Bubble b : mTmpBubbles) {
                    mShortcutIdToBubble.put(b.getShortcutId(), b);
                    updateBubbleSuppressedState(b);

                    if (KEY_APP_BUBBLE.equals(b.getKey())) {
                        mAppBubbleTaskId = b.getTaskId();
                    if (b.isAppBubble()) {
                        mAppBubbleTaskIds.put(b.getKey(), b.getTaskId());
                    }
                }
            }

            /** Sets the app bubble's taskId which is cached for SysUI. */
            synchronized void setAppBubbleTaskId(int taskId) {
                mAppBubbleTaskId = taskId;
            synchronized void setAppBubbleTaskId(String key, int taskId) {
                mAppBubbleTaskIds.put(key, taskId);
            }

            /**
@@ -2147,7 +2148,7 @@ public class BubbleController implements ConfigurationChangeListener,
                    pw.println("   suppressing: " + key);
                }

                pw.print("mAppBubbleTaskId: " + mAppBubbleTaskId);
                pw.print("mAppBubbleTaskIds: " + mAppBubbleTaskIds.values());
            }
        }

@@ -2209,7 +2210,7 @@ public class BubbleController implements ConfigurationChangeListener,

        @Override
        public boolean isAppBubbleTaskId(int taskId) {
            return mCachedState.mAppBubbleTaskId == taskId;
            return mCachedState.mAppBubbleTaskIds.values().contains(taskId);
        }

        @Override
+1 −2
Original line number Diff line number Diff line
@@ -17,7 +17,6 @@ package com.android.wm.shell.bubbles;

import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
@@ -779,7 +778,7 @@ public class BubbleData {
                || !(reason == Bubbles.DISMISS_AGED
                || reason == Bubbles.DISMISS_USER_GESTURE
                || reason == Bubbles.DISMISS_RELOAD_FROM_DISK)
                || KEY_APP_BUBBLE.equals(bubble.getKey())) {
                || bubble.isAppBubble()) {
            return;
        }
        if (DEBUG_BUBBLE_DATA) {
+2 −2
Original line number Diff line number Diff line
@@ -287,9 +287,9 @@ public class BubbleExpandedView extends LinearLayout {
            // The taskId is saved to use for removeTask, preventing appearance in recent tasks.
            mTaskId = taskId;

            if (Bubble.KEY_APP_BUBBLE.equals(getBubbleKey())) {
            if (mBubble != null && mBubble.isAppBubble()) {
                // Let the controller know sooner what the taskId is.
                mController.setAppBubbleTaskId(mTaskId);
                mController.setAppBubbleTaskId(mBubble.getKey(), mTaskId);
            }

            // With the task org, the taskAppeared callback will only happen once the task has
+9 −7
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package com.android.wm.shell.bubbles;

import static com.android.wm.shell.bubbles.Bubble.KEY_APP_BUBBLE;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

@@ -185,7 +183,10 @@ public class BubbleDataTest extends ShellTestCase {

        Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
        appBubbleIntent.setPackage(mContext.getPackageName());
        mAppBubble = new Bubble(appBubbleIntent, new UserHandle(1), mock(Icon.class),
        mAppBubble = Bubble.createAppBubble(
                appBubbleIntent,
                new UserHandle(1),
                mock(Icon.class),
                mMainExecutor);

        mPositioner = new TestableBubblePositioner(mContext,
@@ -1101,14 +1102,15 @@ public class BubbleDataTest extends ShellTestCase {

    @Test
    public void test_removeAppBubble_skipsOverflow() {
        String appBubbleKey = mAppBubble.getKey();
        mBubbleData.notificationEntryUpdated(mAppBubble, true /* suppressFlyout*/,
                false /* showInShade */);
        assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isEqualTo(mAppBubble);
        assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isEqualTo(mAppBubble);

        mBubbleData.dismissBubbleWithKey(KEY_APP_BUBBLE, Bubbles.DISMISS_USER_GESTURE);
        mBubbleData.dismissBubbleWithKey(appBubbleKey, Bubbles.DISMISS_USER_GESTURE);

        assertThat(mBubbleData.getOverflowBubbleWithKey(KEY_APP_BUBBLE)).isNull();
        assertThat(mBubbleData.getBubbleInStackWithKey(KEY_APP_BUBBLE)).isNull();
        assertThat(mBubbleData.getOverflowBubbleWithKey(appBubbleKey)).isNull();
        assertThat(mBubbleData.getBubbleInStackWithKey(appBubbleKey)).isNull();
    }

    private void verifyUpdateReceived() {
Loading