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

Commit b1894725 authored by Mady Mellor's avatar Mady Mellor
Browse files

Add a way to bubble shortcuts not from notifications

- Bubble creation method via shortcut info
- Method on BubbleController to add a shortcut bubble, exposed this
  to launcher
- Separate path to create a shortcut in BubbleExpandedView
- Hide badges for app intent bubbles

Flag: com.android.wm.shell.enable_bubble_anything
Bug: 342245211
Test: manual - enable the flag and try to bubble a shortcut from
               launcher via the longpress menu
             - try to bubble an app via the longpress menu
Change-Id: I79a1a2d6f215dddb878993e3703c9b22329685ed
parent 0348bcc2
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -357,7 +357,9 @@ public class BadgedImageView extends ConstraintLayout {

    void showBadge() {
        Bitmap appBadgeBitmap = mBubble.getAppBadge();
        if (appBadgeBitmap == null) {
        final boolean isAppLaunchIntent = (mBubble instanceof Bubble)
                && ((Bubble) mBubble).isAppLaunchIntent();
        if (appBadgeBitmap == null || isAppLaunchIntent) {
            mAppIcon.setVisibility(GONE);
            return;
        }
+43 −0
Original line number Diff line number Diff line
@@ -50,6 +50,7 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.logging.InstanceId;
import com.android.internal.protolog.ProtoLog;
import com.android.launcher3.icons.BubbleIconFactory;
import com.android.wm.shell.Flags;
import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
import com.android.wm.shell.common.bubbles.BubbleInfo;
@@ -246,7 +247,23 @@ public class Bubble implements BubbleViewProvider {
        mAppIntent = intent;
        mDesiredHeight = Integer.MAX_VALUE;
        mPackageName = intent.getPackage();
    }

    private Bubble(ShortcutInfo info, Executor mainExecutor) {
        mGroupKey = null;
        mLocusId = null;
        mFlags = 0;
        mUser = info.getUserHandle();
        mIcon = info.getIcon();
        mIsAppBubble = false;
        mKey = getBubbleKeyForShortcut(info);
        mShowBubbleUpdateDot = false;
        mMainExecutor = mainExecutor;
        mTaskId = INVALID_TASK_ID;
        mAppIntent = null;
        mDesiredHeight = Integer.MAX_VALUE;
        mPackageName = info.getPackage();
        mShortcutInfo = info;
    }

    /** Creates an app bubble. */
@@ -263,6 +280,13 @@ public class Bubble implements BubbleViewProvider {
                mainExecutor);
    }

    /** Creates a shortcut bubble. */
    public static Bubble createShortcutBubble(
            ShortcutInfo info,
            Executor mainExecutor) {
        return new Bubble(info, mainExecutor);
    }

    /**
     * Returns the key for an app bubble from an app with package name, {@code packageName} on an
     * Android user, {@code user}.
@@ -273,6 +297,14 @@ public class Bubble implements BubbleViewProvider {
        return KEY_APP_BUBBLE + ":" + user.getIdentifier()  + ":" + packageName;
    }

    /**
     * Returns the key for a shortcut bubble using {@code packageName}, {@code user}, and the
     * {@code shortcutInfo} id.
     */
    public static String getBubbleKeyForShortcut(ShortcutInfo info) {
        return info.getPackage() + ":" + info.getUserId() + ":" + info.getId();
    }

    @VisibleForTesting(visibility = PRIVATE)
    public Bubble(@NonNull final BubbleEntry entry,
            final Bubbles.BubbleMetadataFlagListener listener,
@@ -888,6 +920,17 @@ public class Bubble implements BubbleViewProvider {
        return mIntent;
    }

    /**
     * Whether this bubble represents the full app, i.e. the intent used is the launch
     * intent for an app. In this case we don't show a badge on the icon.
     */
    public boolean isAppLaunchIntent() {
        if (Flags.enableBubbleAnything() && mAppIntent != null) {
            return mAppIntent.hasCategory("android.intent.category.LAUNCHER");
        }
        return false;
    }

    @Nullable
    PendingIntent getDeleteIntent() {
        return mDeleteIntent;
+52 −0
Original line number Diff line number Diff line
@@ -1334,6 +1334,40 @@ public class BubbleController implements ConfigurationChangeListener,
        }
    }

    /**
     * Expands and selects a bubble created or found via the provided shortcut info.
     *
     * @param info the shortcut info for the bubble.
     */
    public void expandStackAndSelectBubble(ShortcutInfo info) {
        if (!Flags.enableBubbleAnything()) return;
        Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow
        ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info);
        if (b.isInflated()) {
            mBubbleData.setSelectedBubbleAndExpandStack(b);
        } else {
            b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
            inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
        }
    }

    /**
     * Expands and selects a bubble created or found for this app.
     *
     * @param intent the intent for the bubble.
     */
    public void expandStackAndSelectBubble(Intent intent) {
        if (!Flags.enableBubbleAnything()) return;
        Bubble b = mBubbleData.getOrCreateBubble(intent); // Removes from overflow
        ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent);
        if (b.isInflated()) {
            mBubbleData.setSelectedBubbleAndExpandStack(b);
        } else {
            b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE);
            inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false);
        }
    }

    /**
     * Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble
     * exists for this entry, and it is able to bubble, a new bubble will be created.
@@ -2323,6 +2357,7 @@ public class BubbleController implements ConfigurationChangeListener,
     * @param entry   the entry to bubble.
     */
    static boolean canLaunchInTaskView(Context context, BubbleEntry entry) {
        if (Flags.enableBubbleAnything()) return true;
        PendingIntent intent = entry.getBubbleMetadata() != null
                ? entry.getBubbleMetadata().getIntent()
                : null;
@@ -2438,6 +2473,16 @@ public class BubbleController implements ConfigurationChangeListener,
            mMainExecutor.execute(mListener::unregister);
        }

        @Override
        public void showShortcutBubble(ShortcutInfo info) {
            mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(info));
        }

        @Override
        public void showAppBubble(Intent intent) {
            mMainExecutor.execute(() -> mController.expandStackAndSelectBubble(intent));
        }

        @Override
        public void showBubble(String key, int topOnScreen) {
            mMainExecutor.execute(
@@ -2633,6 +2678,13 @@ public class BubbleController implements ConfigurationChangeListener,
            });
        }

        @Override
        public void expandStackAndSelectBubble(ShortcutInfo info) {
            mMainExecutor.execute(() -> {
                BubbleController.this.expandStackAndSelectBubble(info);
            });
        }

        @Override
        public void expandStackAndSelectBubble(Bubble bubble) {
            mMainExecutor.execute(() -> {
+54 −16
Original line number Diff line number Diff line
@@ -23,8 +23,10 @@ import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
import android.annotation.NonNull;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.LocusId;
import android.content.pm.ShortcutInfo;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
@@ -421,25 +423,21 @@ public class BubbleData {
        Bubble bubbleToReturn = getBubbleInStackWithKey(key);

        if (bubbleToReturn == null) {
            bubbleToReturn = getOverflowBubbleWithKey(key);
            if (bubbleToReturn != null) {
                // Promoting from overflow
                mOverflowBubbles.remove(bubbleToReturn);
                if (mOverflowBubbles.isEmpty()) {
                    mStateChange.showOverflowChanged = true;
                }
            } else if (mPendingBubbles.containsKey(key)) {
                // Update while it was pending
                bubbleToReturn = mPendingBubbles.get(key);
            } else if (entry != null) {
                // New bubble
                bubbleToReturn = new Bubble(entry, mBubbleMetadataFlagListener, mCancelledListener,
            // Check if it's in the overflow
            bubbleToReturn = findAndRemoveBubbleFromOverflow(key);
            if (bubbleToReturn == null) {
                if (entry != null) {
                    // Not in the overflow, have an entry, so it's a new bubble
                    bubbleToReturn = new Bubble(entry,
                            mBubbleMetadataFlagListener,
                            mCancelledListener,
                            mMainExecutor);
                } else {
                // Persisted bubble being promoted
                    // If there's no entry it must be a persisted bubble
                    bubbleToReturn = persistedBubble;
                }
            }
        }

        if (entry != null) {
            bubbleToReturn.setEntry(entry);
@@ -448,6 +446,46 @@ public class BubbleData {
        return bubbleToReturn;
    }

    Bubble getOrCreateBubble(ShortcutInfo info) {
        String bubbleKey = Bubble.getBubbleKeyForShortcut(info);
        Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
        if (bubbleToReturn == null) {
            bubbleToReturn = Bubble.createShortcutBubble(info, mMainExecutor);
        }
        return bubbleToReturn;
    }

    Bubble getOrCreateBubble(Intent intent) {
        UserHandle user = UserHandle.of(mCurrentUserId);
        String bubbleKey = Bubble.getAppBubbleKeyForApp(intent.getPackage(),
                user);
        Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
        if (bubbleToReturn == null) {
            bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor);
        }
        return bubbleToReturn;
    }

    @Nullable
    private Bubble findAndRemoveBubbleFromOverflow(String key) {
        Bubble bubbleToReturn = getBubbleInStackWithKey(key);
        if (bubbleToReturn != null) {
            return bubbleToReturn;
        }
        bubbleToReturn = getOverflowBubbleWithKey(key);
        if (bubbleToReturn != null) {
            mOverflowBubbles.remove(bubbleToReturn);
            // Promoting from overflow
            mOverflowBubbles.remove(bubbleToReturn);
            if (mOverflowBubbles.isEmpty()) {
                mStateChange.showOverflowChanged = true;
            }
        } else if (mPendingBubbles.containsKey(key)) {
            bubbleToReturn = mPendingBubbles.get(key);
        }
        return bubbleToReturn;
    }

    /**
     * When this method is called it is expected that all info in the bubble has completed loading.
     * @see Bubble#inflate(BubbleViewInfoTask.Callback, Context, BubbleExpandedViewManager,
+5 −1
Original line number Diff line number Diff line
@@ -232,6 +232,9 @@ public class BubbleExpandedView extends LinearLayout {
                    fillInIntent.addFlags(FLAG_ACTIVITY_NEW_DOCUMENT);
                    fillInIntent.addFlags(FLAG_ACTIVITY_MULTIPLE_TASK);

                    final boolean isShortcutBubble = (mBubble.hasMetadataShortcutId()
                            || (mBubble.getShortcutInfo() != null && Flags.enableBubbleAnything()));

                    if (mBubble.isAppBubble()) {
                        Context context =
                                mContext.createContextAsUser(
@@ -246,7 +249,8 @@ public class BubbleExpandedView extends LinearLayout {
                                /* options= */ null);
                        mTaskView.startActivity(pi, /* fillInIntent= */ null, options,
                                launchBounds);
                    } else if (!mIsOverflow && mBubble.hasMetadataShortcutId()) {
                    } else if (!mIsOverflow && isShortcutBubble) {
                        ProtoLog.v(WM_SHELL_BUBBLES, "startingShortcutBubble=%s", getBubbleKey());
                        options.setApplyActivityFlagsForBubbles(true);
                        mTaskView.startShortcutActivity(mBubble.getShortcutInfo(),
                                options, launchBounds);
Loading