diff options
| author | 2024-02-05 09:13:52 -0800 | |
|---|---|---|
| committer | 2024-08-08 10:29:34 -0700 | |
| commit | b1894725dad97e913ec10c9de2630fcfb308e62d (patch) | |
| tree | 0c9f564992f51a181c0fcdcf20c918d94fbb07a8 | |
| parent | 0348bcc27ed0c8e6dabc324888044e475ecc955f (diff) | |
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
8 files changed, 175 insertions, 19 deletions
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java index f9a1d940c734..dc511be59764 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java @@ -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; } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java index 7dbbb04e4406..5cd2cb7d51d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java @@ -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; diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java index 949a7236434a..29520efd70b0 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java @@ -1335,6 +1335,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; @@ -2439,6 +2474,16 @@ public class BubbleController implements ConfigurationChangeListener, } @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( () -> mController.expandStackAndSelectBubbleFromLauncher(key, topOnScreen)); @@ -2634,6 +2679,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(() -> { BubbleController.this.expandStackAndSelectBubble(bubble); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java index b6da761b0f9c..3c6c6fa0d8d5 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java @@ -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,23 +423,19 @@ 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; + // 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 { + // If there's no entry it must be a persisted bubble + bubbleToReturn = persistedBubble; } - } 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, - mMainExecutor); - } else { - // Persisted bubble being promoted - bubbleToReturn = persistedBubble; } } @@ -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, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java index fdb45239fa63..a0c0a25d97a2 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java @@ -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); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java index 5e2141aa639e..5f8f0fd0c54c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleTaskViewHelper.java @@ -36,6 +36,7 @@ import android.view.ViewGroup; import androidx.annotation.Nullable; import com.android.internal.protolog.ProtoLog; +import com.android.wm.shell.Flags; import com.android.wm.shell.taskview.TaskView; /** @@ -110,6 +111,8 @@ public class BubbleTaskViewHelper { 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( @@ -124,7 +127,7 @@ public class BubbleTaskViewHelper { /* options= */ null); mTaskView.startActivity(pi, /* fillInIntent= */ null, options, launchBounds); - } else if (mBubble.hasMetadataShortcutId()) { + } else if (isShortcutBubble) { options.setApplyActivityFlagsForBubbles(true); mTaskView.startShortcutActivity(mBubble.getShortcutInfo(), options, launchBounds); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java index 589dfd24624e..9a27fb65ac2c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubbles.java @@ -23,6 +23,7 @@ import static java.lang.annotation.RetentionPolicy.SOURCE; import android.app.NotificationChannel; import android.content.Intent; +import android.content.pm.ShortcutInfo; import android.content.pm.UserInfo; import android.graphics.drawable.Icon; import android.hardware.HardwareBuffer; @@ -118,6 +119,14 @@ public interface Bubbles { /** * Request the stack expand if needed, then select the specified Bubble as current. + * If no bubble exists for this entry, one is created. + * + * @param info the shortcut info to use to create the bubble. + */ + void expandStackAndSelectBubble(ShortcutInfo info); + + /** + * Request the stack expand if needed, then select the specified Bubble as current. * * @param bubble the bubble to be selected */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl index 0907ddd1de83..5c789749412c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl @@ -18,6 +18,7 @@ package com.android.wm.shell.bubbles; import android.content.Intent; import android.graphics.Rect; +import android.content.pm.ShortcutInfo; import com.android.wm.shell.bubbles.IBubblesListener; import com.android.wm.shell.common.bubbles.BubbleBarLocation; @@ -48,4 +49,8 @@ interface IBubbles { oneway void updateBubbleBarTopOnScreen(in int topOnScreen) = 10; oneway void stopBubbleDrag(in BubbleBarLocation location, in int topOnScreen) = 11; + + oneway void showShortcutBubble(in ShortcutInfo info) = 12; + + oneway void showAppBubble(in Intent intent) = 13; }
\ No newline at end of file |