Loading packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +86 −10 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ */ package com.android.systemui.bubbles; import static android.app.Notification.FLAG_BUBBLE; import static android.os.AsyncTask.Status.FINISHED; import static android.view.Display.INVALID_DISPLAY; Loading @@ -27,6 +27,7 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; Loading Loading @@ -55,6 +56,11 @@ import java.util.Objects; class Bubble implements BubbleViewProvider { private static final String TAG = "Bubble"; /** * NotificationEntry associated with the bubble. A null value implies this bubble is loaded * from disk. */ @Nullable private NotificationEntry mEntry; private final String mKey; Loading Loading @@ -96,11 +102,18 @@ class Bubble implements BubbleViewProvider { private Bitmap mBadgedImage; private int mDotColor; private Path mDotPath; private int mFlags; // TODO: Decouple Bubble from NotificationEntry and transform ShortcutInfo into Bubble Bubble(ShortcutInfo shortcutInfo) { /** * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); mShortcutInfo = shortcutInfo; mKey = shortcutInfo.getId(); mKey = key; mFlags = 0; } /** Used in tests when no UI is required. */ Loading @@ -111,6 +124,7 @@ class Bubble implements BubbleViewProvider { mKey = e.getKey(); mLastUpdated = e.getSbn().getPostTime(); mSuppressionListener = listener; mFlags = e.getSbn().getNotification().flags; } @Override Loading @@ -118,12 +132,22 @@ class Bubble implements BubbleViewProvider { return mKey; } @Nullable public NotificationEntry getEntry() { return mEntry; } @Nullable public UserHandle getUser() { if (mEntry != null) return mEntry.getSbn().getUser(); if (mShortcutInfo != null) return mShortcutInfo.getUserHandle(); return null; } public String getPackageName() { return mEntry.getSbn().getPackageName(); return mEntry == null ? mShortcutInfo == null ? null : mShortcutInfo.getPackage() : mEntry.getSbn().getPackageName(); } @Override Loading Loading @@ -167,6 +191,18 @@ class Bubble implements BubbleViewProvider { return mExpandedView; } @Nullable public String getTitle() { final CharSequence titleCharSeq; if (mEntry == null) { titleCharSeq = null; } else { titleCharSeq = mEntry.getSbn().getNotification().extras.getCharSequence( Notification.EXTRA_TITLE); } return titleCharSeq != null ? titleCharSeq.toString() : null; } /** * Call when the views should be removed, ensure this is called to clean up ActivityView * content. Loading Loading @@ -207,7 +243,8 @@ class Bubble implements BubbleViewProvider { void inflate(BubbleViewInfoTask.Callback callback, Context context, BubbleStackView stackView, BubbleIconFactory iconFactory) { BubbleIconFactory iconFactory, boolean skipInflation) { if (isBubbleLoading()) { mInflationTask.cancel(true /* mayInterruptIfRunning */); } Loading @@ -215,6 +252,7 @@ class Bubble implements BubbleViewProvider { context, stackView, iconFactory, skipInflation, callback); if (mInflateSynchronously) { mInflationTask.onPostExecute(mInflationTask.doInBackground()); Loading Loading @@ -327,6 +365,7 @@ class Bubble implements BubbleViewProvider { * Whether this notification should be shown in the shade. */ boolean showInShade() { if (mEntry == null) return false; return !shouldSuppressNotification() || !mEntry.isClearable(); } Loading @@ -334,8 +373,8 @@ class Bubble implements BubbleViewProvider { * Sets whether this notification should be suppressed in the shade. */ void setSuppressNotification(boolean suppressNotification) { if (mEntry == null) return; boolean prevShowInShade = showInShade(); Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); int flags = data.getFlags(); if (suppressNotification) { Loading Loading @@ -366,6 +405,7 @@ class Bubble implements BubbleViewProvider { */ @Override public boolean showDot() { if (mEntry == null) return false; return mShowBubbleUpdateDot && !mEntry.shouldSuppressNotificationDot() && !shouldSuppressNotification(); Loading @@ -375,6 +415,7 @@ class Bubble implements BubbleViewProvider { * Whether the flyout for the bubble should be shown. */ boolean showFlyout() { if (mEntry == null) return false; return !mSuppressFlyout && !mEntry.shouldSuppressPeek() && !shouldSuppressNotification() && !mEntry.shouldSuppressNotificationList(); Loading @@ -394,6 +435,7 @@ class Bubble implements BubbleViewProvider { } float getDesiredHeight(Context context) { if (mEntry == null) return 0; Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); boolean useRes = data.getDesiredHeightResId() != 0; if (useRes) { Loading @@ -407,6 +449,7 @@ class Bubble implements BubbleViewProvider { } String getDesiredHeightString() { if (mEntry == null) return String.valueOf(0); Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); boolean useRes = data.getDesiredHeightResId() != 0; if (useRes) { Loading @@ -423,11 +466,13 @@ class Bubble implements BubbleViewProvider { * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}. */ boolean usingShortcutInfo() { return mEntry.getBubbleMetadata().getShortcutId() != null; return mEntry != null && mEntry.getBubbleMetadata().getShortcutId() != null || mShortcutInfo != null; } @Nullable PendingIntent getBubbleIntent() { if (mEntry == null) return null; Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); if (data != null) { return data.getIntent(); Loading @@ -435,16 +480,32 @@ class Bubble implements BubbleViewProvider { return null; } Intent getSettingsIntent() { Intent getSettingsIntent(final Context context) { final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); intent.putExtra(Settings.EXTRA_APP_UID, mEntry.getSbn().getUid()); final int uid = getUid(context); if (uid != -1) { intent.putExtra(Settings.EXTRA_APP_UID, uid); } intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); return intent; } private int getUid(final Context context) { if (mEntry != null) return mEntry.getSbn().getUid(); final PackageManager pm = context.getPackageManager(); if (pm == null) return -1; try { final ApplicationInfo info = pm.getApplicationInfo(mShortcutInfo.getPackage(), 0); return info.uid; } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "cannot find uid", e); } return -1; } private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) { PackageManager pm = context.getPackageManager(); Resources r; Loading @@ -466,11 +527,13 @@ class Bubble implements BubbleViewProvider { } private boolean shouldSuppressNotification() { if (mEntry == null) return false; return mEntry.getBubbleMetadata() != null && mEntry.getBubbleMetadata().isNotificationSuppressed(); } boolean shouldAutoExpand() { if (mEntry == null) return false; Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata(); return (metadata != null && metadata.getAutoExpandBubble()) || mShouldAutoExpand; } Loading @@ -479,6 +542,19 @@ class Bubble implements BubbleViewProvider { mShouldAutoExpand = shouldAutoExpand; } public boolean isBubble() { if (mEntry == null) return (mFlags & FLAG_BUBBLE) != 0; return (mEntry.getSbn().getNotification().flags & FLAG_BUBBLE) != 0; } public void enable(int option) { mFlags |= option; } public void disable(int option) { mFlags &= ~option; } @Override public String toString() { return "Bubble{" + mKey + '}'; Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +82 −29 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager.RunningTaskInfo; import android.app.INotificationManager; Loading Loading @@ -113,6 +114,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * Bubbles are a special type of content that can "float" on top of other apps or System UI. Loading Loading @@ -243,13 +245,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * This can happen when an app cancels a bubbled notification or when the user dismisses a * bubble. */ void removeNotification(NotificationEntry entry, int reason); void removeNotification(@NonNull NotificationEntry entry, int reason); /** * Called when a bubbled notification has changed whether it should be * filtered from the shade. */ void invalidateNotifications(String reason); void invalidateNotifications(@NonNull String reason); /** * Called on a bubbled entry that has been removed when there are no longer Loading @@ -259,7 +261,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * removes all remnants of the group's summary from the notification pipeline. * TODO: (b/145659174) Only old pipeline needs this - delete post-migration. */ void maybeCancelSummary(NotificationEntry entry); void maybeCancelSummary(@NonNull NotificationEntry entry); } /** Loading Loading @@ -756,10 +758,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mBubbleIconFactory = new BubbleIconFactory(mContext); // Reload each bubble for (Bubble b: mBubbleData.getBubbles()) { b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory); b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory, false /* skipInflation */); } for (Bubble b: mBubbleData.getOverflowBubbles()) { b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory); b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory, false /* skipInflation */); } } Loading Loading @@ -846,7 +850,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi void promoteBubbleFromOverflow(Bubble bubble) { bubble.setInflateSynchronously(mInflateSynchronously); setIsBubble(bubble.getEntry(), /* isBubble */ true); setIsBubble(bubble, /* isBubble */ true); mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory); } Loading Loading @@ -896,10 +900,30 @@ public class BubbleController implements ConfigurationController.ConfigurationLi updateBubble(notif, false /* suppressFlyout */, true /* showInShade */); } /** * Fills the overflow bubbles by loading them from disk. */ void loadOverflowBubblesFromDisk() { if (!mBubbleData.getOverflowBubbles().isEmpty()) { // we don't need to load overflow bubbles from disk if it is already in memory return; } mDataRepository.loadBubbles((bubbles) -> { bubbles.forEach(bubble -> { if (mBubbleData.getBubbles().contains(bubble)) { // if the bubble is already active, there's no need to push it to overflow return; } bubble.inflate((b) -> mBubbleData.overflowBubble(DISMISS_AGED, bubble), mContext, mStackView, mBubbleIconFactory, true /* skipInflation */); }); return null; }); } void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) { // Lazy init stack view when a bubble is created ensureStackViewCreated(); // If this is an interruptive notif, mark that it's interrupted if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) { notif.setInterruption(); Loading @@ -919,11 +943,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi return; } mHandler.post( () -> removeBubble(bubble.getEntry(), () -> removeBubble(bubble.getKey(), BubbleController.DISMISS_INVALID_INTENT)); }); }, mContext, mStackView, mBubbleIconFactory); mContext, mStackView, mBubbleIconFactory, false /* skipInflation */); } /** Loading @@ -935,7 +959,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @param entry the notification to change bubble state for. * @param shouldBubble whether the notification should show as a bubble or not. */ public void onUserChangedBubble(NotificationEntry entry, boolean shouldBubble) { public void onUserChangedBubble(@Nullable final NotificationEntry entry, boolean shouldBubble) { if (entry == null) { return; } NotificationChannel channel = entry.getChannel(); final String appPkg = entry.getSbn().getPackageName(); final int appUid = entry.getSbn().getUid(); Loading Loading @@ -974,14 +1001,14 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** * Removes the bubble with the given NotificationEntry. * Removes the bubble with the given key. * <p> * Must be called from the main thread. */ @MainThread void removeBubble(NotificationEntry entry, int reason) { if (mBubbleData.hasAnyBubbleWithKey(entry.getKey())) { mBubbleData.notificationEntryRemoved(entry, reason); void removeBubble(String key, int reason) { if (mBubbleData.hasAnyBubbleWithKey(key)) { mBubbleData.notificationEntryRemoved(key, reason); } } Loading @@ -999,7 +1026,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi && canLaunchInActivityView(mContext, entry); if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) { // It was previously a bubble but no longer a bubble -- lets remove it removeBubble(entry, DISMISS_NO_LONGER_BUBBLE); removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE); } else if (shouldBubble && entry.isBubble()) { updateBubble(entry); } Loading @@ -1013,10 +1040,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Remove any associated bubble children with the summary final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); for (int i = 0; i < bubbleChildren.size(); i++) { removeBubble(bubbleChildren.get(i).getEntry(), DISMISS_GROUP_CANCELLED); removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED); } } else { removeBubble(entry, DISMISS_NOTIF_CANCEL); removeBubble(entry.getKey(), DISMISS_NOTIF_CANCEL); } } Loading @@ -1038,7 +1065,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi rankingMap.getRanking(key, mTmpRanking); boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key); if (isActiveBubble && !mTmpRanking.canBubble()) { mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED); mBubbleData.notificationEntryRemoved(entry.getKey(), BubbleController.DISMISS_BLOCKED); } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) { entry.setFlagBubble(true); onEntryUpdated(entry); Loading @@ -1046,7 +1074,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } private void setIsBubble(NotificationEntry entry, boolean isBubble) { private void setIsBubble(@NonNull final NotificationEntry entry, final boolean isBubble) { Objects.requireNonNull(entry); if (isBubble) { entry.getSbn().getNotification().flags |= FLAG_BUBBLE; } else { Loading @@ -1059,11 +1088,31 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) { Objects.requireNonNull(b); if (isBubble) { b.enable(FLAG_BUBBLE); } else { b.disable(FLAG_BUBBLE); } if (b.getEntry() != null) { setIsBubble(b.getEntry(), isBubble); } else { try { mBarService.onNotificationBubbleChanged(b.getKey(), isBubble, 0); } catch (RemoteException e) { // Bad things have happened } } } @SuppressWarnings("FieldCanBeLocal") private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() { @Override public void applyUpdate(BubbleData.Update update) { // Lazy load overflow bubbles from disk loadOverflowBubblesFromDisk(); // Update bubbles in overflow. if (mOverflowCallback != null) { mOverflowCallback.run(); Loading Loading @@ -1098,18 +1147,21 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // The bubble is now gone & the notification is hidden from the shade, so // time to actually remove it for (NotifCallback cb : mCallbacks) { if (bubble.getEntry() != null) { cb.removeNotification(bubble.getEntry(), REASON_CANCEL); } } } else { if (bubble.getEntry().isBubble() && bubble.showInShade()) { setIsBubble(bubble.getEntry(), false /* isBubble */); if (bubble.isBubble() && bubble.showInShade()) { setIsBubble(bubble, false /* isBubble */); } if (bubble.getEntry().getRow() != null) { if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) { bubble.getEntry().getRow().updateBubbleButton(); } } } if (bubble.getEntry() != null) { final String groupKey = bubble.getEntry().getSbn().getGroupKey(); if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { // Time to potentially remove the summary Loading @@ -1118,6 +1170,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } } } mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository); if (update.addedBubble != null) { Loading @@ -1139,7 +1192,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (update.selectionChanged) { mStackView.setSelectedBubble(update.selectedBubble); if (update.selectedBubble != null) { if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) { mNotificationGroupManager.updateSuppression( update.selectedBubble.getEntry()); } Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +16 −8 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.annotation.NonNull; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; Loading Loading @@ -214,7 +215,7 @@ public class BubbleData { notificationEntryUpdated(bubble, false /* suppressFlyout */, true /* showInShade */); }, mContext, stack, factory); mContext, stack, factory, false /* skipInflation */); } void setShowingOverflow(boolean showingOverflow) { Loading Loading @@ -268,7 +269,8 @@ public class BubbleData { } mPendingBubbles.remove(bubble); // No longer pending once we're here Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey()); suppressFlyout |= !bubble.getEntry().getRanking().visuallyInterruptive(); suppressFlyout |= bubble.getEntry() == null || !bubble.getEntry().getRanking().visuallyInterruptive(); if (prevBubble == null) { // Create a new bubble Loading Loading @@ -297,11 +299,14 @@ public class BubbleData { dispatchPendingChanges(); } public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) { /** * Called when a notification associated with a bubble is removed. */ public void notificationEntryRemoved(String key, @DismissReason int reason) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason); Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason); } doRemove(entry.getKey(), reason); doRemove(key, reason); dispatchPendingChanges(); } Loading Loading @@ -349,7 +354,7 @@ public class BubbleData { return bubbleChildren; } for (Bubble b : mBubbles) { if (groupKey.equals(b.getEntry().getSbn().getGroupKey())) { if (b.getEntry() != null && groupKey.equals(b.getEntry().getSbn().getGroupKey())) { bubbleChildren.add(b); } } Loading Loading @@ -447,8 +452,10 @@ public class BubbleData { Bubble newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } if (bubbleToRemove.getEntry() != null) { maybeSendDeleteIntent(reason, bubbleToRemove.getEntry()); } } void overflowBubble(@DismissReason int reason, Bubble bubble) { if (bubble.getPendingIntentCanceled() Loading Loading @@ -615,7 +622,8 @@ public class BubbleData { return true; } private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) { private void maybeSendDeleteIntent(@DismissReason int reason, @NonNull final NotificationEntry entry) { if (reason == BubbleController.DISMISS_USER_GESTURE) { Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata(); PendingIntent deleteIntent = bubbleMetadata != null Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +17 −12 Original line number Diff line number Diff line Loading @@ -74,8 +74,10 @@ internal class BubbleDataRepository @Inject constructor( private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> val shortcutId = b.shortcutInfo?.id ?: return@mapNotNull null BubbleEntity(userId, b.packageName, shortcutId) var shortcutId = b.shortcutInfo?.id if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId if (shortcutId == null) return@mapNotNull null BubbleEntity(userId, b.packageName, shortcutId, b.key) } } Loading Loading @@ -108,7 +110,6 @@ internal class BubbleDataRepository @Inject constructor( /** * Load bubbles from disk. */ // TODO: call this method from BubbleController and update UI @SuppressLint("WrongConstant") fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch { /** Loading @@ -132,17 +133,17 @@ internal class BubbleDataRepository @Inject constructor( val shortcutKeys = entities.map { ShortcutKey(it.userId, it.packageName) }.toSet() /** * Retrieve shortcuts with given userId/packageName combination, then construct a mapping * between BubbleEntity and ShortcutInfo. * from the userId/packageName pair to a list of associated ShortcutInfo. * e.g. * { * BubbleEntity(0, "com.example.messenger", "id-0") -> * ShortcutKey(0, "com.example.messenger") -> [ * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-0"), * BubbleEntity(0, "com.example.messenger", "id-2") -> * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-2"), * BubbleEntity(10, "com.example.chat", "id-1") -> * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-2") * ] * ShortcutKey(10, "com.example.chat") -> [ * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-1"), * BubbleEntity(10, "com.example.chat", "id-3") -> * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-3") * ] * } */ val shortcutMap = shortcutKeys.flatMap { key -> Loading @@ -150,11 +151,15 @@ internal class BubbleDataRepository @Inject constructor( LauncherApps.ShortcutQuery() .setPackage(key.pkg) .setQueryFlags(SHORTCUT_QUERY_FLAG), UserHandle.of(key.userId)) ?.map { BubbleEntity(key.userId, key.pkg, it.id) to it } ?: emptyList() }.toMap() ?: emptyList() }.groupBy { ShortcutKey(it.userId, it.`package`) } // For each entity loaded from xml, find the corresponding ShortcutInfo then convert them // into Bubble. val bubbles = entities.mapNotNull { entity -> shortcutMap[entity]?.let { Bubble(it) } } val bubbles = entities.mapNotNull { entity -> shortcutMap[ShortcutKey(entity.userId, entity.packageName)] ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id } ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo) } } uiScope.launch { cb(bubbles) } } Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +2 −7 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +86 −10 Original line number Diff line number Diff line Loading @@ -15,7 +15,7 @@ */ package com.android.systemui.bubbles; import static android.app.Notification.FLAG_BUBBLE; import static android.os.AsyncTask.Status.FINISHED; import static android.view.Display.INVALID_DISPLAY; Loading @@ -27,6 +27,7 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.LauncherApps; import android.content.pm.PackageManager; import android.content.pm.ShortcutInfo; Loading Loading @@ -55,6 +56,11 @@ import java.util.Objects; class Bubble implements BubbleViewProvider { private static final String TAG = "Bubble"; /** * NotificationEntry associated with the bubble. A null value implies this bubble is loaded * from disk. */ @Nullable private NotificationEntry mEntry; private final String mKey; Loading Loading @@ -96,11 +102,18 @@ class Bubble implements BubbleViewProvider { private Bitmap mBadgedImage; private int mDotColor; private Path mDotPath; private int mFlags; // TODO: Decouple Bubble from NotificationEntry and transform ShortcutInfo into Bubble Bubble(ShortcutInfo shortcutInfo) { /** * Create a bubble with limited information based on given {@link ShortcutInfo}. * Note: Currently this is only being used when the bubble is persisted to disk. */ Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo) { Objects.requireNonNull(key); Objects.requireNonNull(shortcutInfo); mShortcutInfo = shortcutInfo; mKey = shortcutInfo.getId(); mKey = key; mFlags = 0; } /** Used in tests when no UI is required. */ Loading @@ -111,6 +124,7 @@ class Bubble implements BubbleViewProvider { mKey = e.getKey(); mLastUpdated = e.getSbn().getPostTime(); mSuppressionListener = listener; mFlags = e.getSbn().getNotification().flags; } @Override Loading @@ -118,12 +132,22 @@ class Bubble implements BubbleViewProvider { return mKey; } @Nullable public NotificationEntry getEntry() { return mEntry; } @Nullable public UserHandle getUser() { if (mEntry != null) return mEntry.getSbn().getUser(); if (mShortcutInfo != null) return mShortcutInfo.getUserHandle(); return null; } public String getPackageName() { return mEntry.getSbn().getPackageName(); return mEntry == null ? mShortcutInfo == null ? null : mShortcutInfo.getPackage() : mEntry.getSbn().getPackageName(); } @Override Loading Loading @@ -167,6 +191,18 @@ class Bubble implements BubbleViewProvider { return mExpandedView; } @Nullable public String getTitle() { final CharSequence titleCharSeq; if (mEntry == null) { titleCharSeq = null; } else { titleCharSeq = mEntry.getSbn().getNotification().extras.getCharSequence( Notification.EXTRA_TITLE); } return titleCharSeq != null ? titleCharSeq.toString() : null; } /** * Call when the views should be removed, ensure this is called to clean up ActivityView * content. Loading Loading @@ -207,7 +243,8 @@ class Bubble implements BubbleViewProvider { void inflate(BubbleViewInfoTask.Callback callback, Context context, BubbleStackView stackView, BubbleIconFactory iconFactory) { BubbleIconFactory iconFactory, boolean skipInflation) { if (isBubbleLoading()) { mInflationTask.cancel(true /* mayInterruptIfRunning */); } Loading @@ -215,6 +252,7 @@ class Bubble implements BubbleViewProvider { context, stackView, iconFactory, skipInflation, callback); if (mInflateSynchronously) { mInflationTask.onPostExecute(mInflationTask.doInBackground()); Loading Loading @@ -327,6 +365,7 @@ class Bubble implements BubbleViewProvider { * Whether this notification should be shown in the shade. */ boolean showInShade() { if (mEntry == null) return false; return !shouldSuppressNotification() || !mEntry.isClearable(); } Loading @@ -334,8 +373,8 @@ class Bubble implements BubbleViewProvider { * Sets whether this notification should be suppressed in the shade. */ void setSuppressNotification(boolean suppressNotification) { if (mEntry == null) return; boolean prevShowInShade = showInShade(); Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); int flags = data.getFlags(); if (suppressNotification) { Loading Loading @@ -366,6 +405,7 @@ class Bubble implements BubbleViewProvider { */ @Override public boolean showDot() { if (mEntry == null) return false; return mShowBubbleUpdateDot && !mEntry.shouldSuppressNotificationDot() && !shouldSuppressNotification(); Loading @@ -375,6 +415,7 @@ class Bubble implements BubbleViewProvider { * Whether the flyout for the bubble should be shown. */ boolean showFlyout() { if (mEntry == null) return false; return !mSuppressFlyout && !mEntry.shouldSuppressPeek() && !shouldSuppressNotification() && !mEntry.shouldSuppressNotificationList(); Loading @@ -394,6 +435,7 @@ class Bubble implements BubbleViewProvider { } float getDesiredHeight(Context context) { if (mEntry == null) return 0; Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); boolean useRes = data.getDesiredHeightResId() != 0; if (useRes) { Loading @@ -407,6 +449,7 @@ class Bubble implements BubbleViewProvider { } String getDesiredHeightString() { if (mEntry == null) return String.valueOf(0); Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); boolean useRes = data.getDesiredHeightResId() != 0; if (useRes) { Loading @@ -423,11 +466,13 @@ class Bubble implements BubbleViewProvider { * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}. */ boolean usingShortcutInfo() { return mEntry.getBubbleMetadata().getShortcutId() != null; return mEntry != null && mEntry.getBubbleMetadata().getShortcutId() != null || mShortcutInfo != null; } @Nullable PendingIntent getBubbleIntent() { if (mEntry == null) return null; Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); if (data != null) { return data.getIntent(); Loading @@ -435,16 +480,32 @@ class Bubble implements BubbleViewProvider { return null; } Intent getSettingsIntent() { Intent getSettingsIntent(final Context context) { final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); intent.putExtra(Settings.EXTRA_APP_UID, mEntry.getSbn().getUid()); final int uid = getUid(context); if (uid != -1) { intent.putExtra(Settings.EXTRA_APP_UID, uid); } intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); return intent; } private int getUid(final Context context) { if (mEntry != null) return mEntry.getSbn().getUid(); final PackageManager pm = context.getPackageManager(); if (pm == null) return -1; try { final ApplicationInfo info = pm.getApplicationInfo(mShortcutInfo.getPackage(), 0); return info.uid; } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "cannot find uid", e); } return -1; } private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) { PackageManager pm = context.getPackageManager(); Resources r; Loading @@ -466,11 +527,13 @@ class Bubble implements BubbleViewProvider { } private boolean shouldSuppressNotification() { if (mEntry == null) return false; return mEntry.getBubbleMetadata() != null && mEntry.getBubbleMetadata().isNotificationSuppressed(); } boolean shouldAutoExpand() { if (mEntry == null) return false; Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata(); return (metadata != null && metadata.getAutoExpandBubble()) || mShouldAutoExpand; } Loading @@ -479,6 +542,19 @@ class Bubble implements BubbleViewProvider { mShouldAutoExpand = shouldAutoExpand; } public boolean isBubble() { if (mEntry == null) return (mFlags & FLAG_BUBBLE) != 0; return (mEntry.getSbn().getNotification().flags & FLAG_BUBBLE) != 0; } public void enable(int option) { mFlags |= option; } public void disable(int option) { mFlags &= ~option; } @Override public String toString() { return "Bubble{" + mKey + '}'; Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +82 −29 Original line number Diff line number Diff line Loading @@ -42,6 +42,7 @@ import static java.lang.annotation.ElementType.LOCAL_VARIABLE; import static java.lang.annotation.ElementType.PARAMETER; import static java.lang.annotation.RetentionPolicy.SOURCE; import android.annotation.NonNull; import android.annotation.UserIdInt; import android.app.ActivityManager.RunningTaskInfo; import android.app.INotificationManager; Loading Loading @@ -113,6 +114,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.Target; import java.util.ArrayList; import java.util.List; import java.util.Objects; /** * Bubbles are a special type of content that can "float" on top of other apps or System UI. Loading Loading @@ -243,13 +245,13 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * This can happen when an app cancels a bubbled notification or when the user dismisses a * bubble. */ void removeNotification(NotificationEntry entry, int reason); void removeNotification(@NonNull NotificationEntry entry, int reason); /** * Called when a bubbled notification has changed whether it should be * filtered from the shade. */ void invalidateNotifications(String reason); void invalidateNotifications(@NonNull String reason); /** * Called on a bubbled entry that has been removed when there are no longer Loading @@ -259,7 +261,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * removes all remnants of the group's summary from the notification pipeline. * TODO: (b/145659174) Only old pipeline needs this - delete post-migration. */ void maybeCancelSummary(NotificationEntry entry); void maybeCancelSummary(@NonNull NotificationEntry entry); } /** Loading Loading @@ -756,10 +758,12 @@ public class BubbleController implements ConfigurationController.ConfigurationLi mBubbleIconFactory = new BubbleIconFactory(mContext); // Reload each bubble for (Bubble b: mBubbleData.getBubbles()) { b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory); b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory, false /* skipInflation */); } for (Bubble b: mBubbleData.getOverflowBubbles()) { b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory); b.inflate(null /* callback */, mContext, mStackView, mBubbleIconFactory, false /* skipInflation */); } } Loading Loading @@ -846,7 +850,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi void promoteBubbleFromOverflow(Bubble bubble) { bubble.setInflateSynchronously(mInflateSynchronously); setIsBubble(bubble.getEntry(), /* isBubble */ true); setIsBubble(bubble, /* isBubble */ true); mBubbleData.promoteBubbleFromOverflow(bubble, mStackView, mBubbleIconFactory); } Loading Loading @@ -896,10 +900,30 @@ public class BubbleController implements ConfigurationController.ConfigurationLi updateBubble(notif, false /* suppressFlyout */, true /* showInShade */); } /** * Fills the overflow bubbles by loading them from disk. */ void loadOverflowBubblesFromDisk() { if (!mBubbleData.getOverflowBubbles().isEmpty()) { // we don't need to load overflow bubbles from disk if it is already in memory return; } mDataRepository.loadBubbles((bubbles) -> { bubbles.forEach(bubble -> { if (mBubbleData.getBubbles().contains(bubble)) { // if the bubble is already active, there's no need to push it to overflow return; } bubble.inflate((b) -> mBubbleData.overflowBubble(DISMISS_AGED, bubble), mContext, mStackView, mBubbleIconFactory, true /* skipInflation */); }); return null; }); } void updateBubble(NotificationEntry notif, boolean suppressFlyout, boolean showInShade) { // Lazy init stack view when a bubble is created ensureStackViewCreated(); // If this is an interruptive notif, mark that it's interrupted if (notif.getImportance() >= NotificationManager.IMPORTANCE_HIGH) { notif.setInterruption(); Loading @@ -919,11 +943,11 @@ public class BubbleController implements ConfigurationController.ConfigurationLi return; } mHandler.post( () -> removeBubble(bubble.getEntry(), () -> removeBubble(bubble.getKey(), BubbleController.DISMISS_INVALID_INTENT)); }); }, mContext, mStackView, mBubbleIconFactory); mContext, mStackView, mBubbleIconFactory, false /* skipInflation */); } /** Loading @@ -935,7 +959,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi * @param entry the notification to change bubble state for. * @param shouldBubble whether the notification should show as a bubble or not. */ public void onUserChangedBubble(NotificationEntry entry, boolean shouldBubble) { public void onUserChangedBubble(@Nullable final NotificationEntry entry, boolean shouldBubble) { if (entry == null) { return; } NotificationChannel channel = entry.getChannel(); final String appPkg = entry.getSbn().getPackageName(); final int appUid = entry.getSbn().getUid(); Loading Loading @@ -974,14 +1001,14 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } /** * Removes the bubble with the given NotificationEntry. * Removes the bubble with the given key. * <p> * Must be called from the main thread. */ @MainThread void removeBubble(NotificationEntry entry, int reason) { if (mBubbleData.hasAnyBubbleWithKey(entry.getKey())) { mBubbleData.notificationEntryRemoved(entry, reason); void removeBubble(String key, int reason) { if (mBubbleData.hasAnyBubbleWithKey(key)) { mBubbleData.notificationEntryRemoved(key, reason); } } Loading @@ -999,7 +1026,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi && canLaunchInActivityView(mContext, entry); if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) { // It was previously a bubble but no longer a bubble -- lets remove it removeBubble(entry, DISMISS_NO_LONGER_BUBBLE); removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE); } else if (shouldBubble && entry.isBubble()) { updateBubble(entry); } Loading @@ -1013,10 +1040,10 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // Remove any associated bubble children with the summary final List<Bubble> bubbleChildren = mBubbleData.getBubblesInGroup(groupKey); for (int i = 0; i < bubbleChildren.size(); i++) { removeBubble(bubbleChildren.get(i).getEntry(), DISMISS_GROUP_CANCELLED); removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED); } } else { removeBubble(entry, DISMISS_NOTIF_CANCEL); removeBubble(entry.getKey(), DISMISS_NOTIF_CANCEL); } } Loading @@ -1038,7 +1065,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi rankingMap.getRanking(key, mTmpRanking); boolean isActiveBubble = mBubbleData.hasAnyBubbleWithKey(key); if (isActiveBubble && !mTmpRanking.canBubble()) { mBubbleData.notificationEntryRemoved(entry, BubbleController.DISMISS_BLOCKED); mBubbleData.notificationEntryRemoved(entry.getKey(), BubbleController.DISMISS_BLOCKED); } else if (entry != null && mTmpRanking.isBubble() && !isActiveBubble) { entry.setFlagBubble(true); onEntryUpdated(entry); Loading @@ -1046,7 +1074,8 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } private void setIsBubble(NotificationEntry entry, boolean isBubble) { private void setIsBubble(@NonNull final NotificationEntry entry, final boolean isBubble) { Objects.requireNonNull(entry); if (isBubble) { entry.getSbn().getNotification().flags |= FLAG_BUBBLE; } else { Loading @@ -1059,11 +1088,31 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) { Objects.requireNonNull(b); if (isBubble) { b.enable(FLAG_BUBBLE); } else { b.disable(FLAG_BUBBLE); } if (b.getEntry() != null) { setIsBubble(b.getEntry(), isBubble); } else { try { mBarService.onNotificationBubbleChanged(b.getKey(), isBubble, 0); } catch (RemoteException e) { // Bad things have happened } } } @SuppressWarnings("FieldCanBeLocal") private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() { @Override public void applyUpdate(BubbleData.Update update) { // Lazy load overflow bubbles from disk loadOverflowBubblesFromDisk(); // Update bubbles in overflow. if (mOverflowCallback != null) { mOverflowCallback.run(); Loading Loading @@ -1098,18 +1147,21 @@ public class BubbleController implements ConfigurationController.ConfigurationLi // The bubble is now gone & the notification is hidden from the shade, so // time to actually remove it for (NotifCallback cb : mCallbacks) { if (bubble.getEntry() != null) { cb.removeNotification(bubble.getEntry(), REASON_CANCEL); } } } else { if (bubble.getEntry().isBubble() && bubble.showInShade()) { setIsBubble(bubble.getEntry(), false /* isBubble */); if (bubble.isBubble() && bubble.showInShade()) { setIsBubble(bubble, false /* isBubble */); } if (bubble.getEntry().getRow() != null) { if (bubble.getEntry() != null && bubble.getEntry().getRow() != null) { bubble.getEntry().getRow().updateBubbleButton(); } } } if (bubble.getEntry() != null) { final String groupKey = bubble.getEntry().getSbn().getGroupKey(); if (mBubbleData.getBubblesInGroup(groupKey).isEmpty()) { // Time to potentially remove the summary Loading @@ -1118,6 +1170,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi } } } } mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository); if (update.addedBubble != null) { Loading @@ -1139,7 +1192,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi if (update.selectionChanged) { mStackView.setSelectedBubble(update.selectedBubble); if (update.selectedBubble != null) { if (update.selectedBubble != null && update.selectedBubble.getEntry() != null) { mNotificationGroupManager.updateSuppression( update.selectedBubble.getEntry()); } Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +16 −8 Original line number Diff line number Diff line Loading @@ -21,6 +21,7 @@ import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_DATA; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; import android.annotation.NonNull; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; Loading Loading @@ -214,7 +215,7 @@ public class BubbleData { notificationEntryUpdated(bubble, false /* suppressFlyout */, true /* showInShade */); }, mContext, stack, factory); mContext, stack, factory, false /* skipInflation */); } void setShowingOverflow(boolean showingOverflow) { Loading Loading @@ -268,7 +269,8 @@ public class BubbleData { } mPendingBubbles.remove(bubble); // No longer pending once we're here Bubble prevBubble = getBubbleInStackWithKey(bubble.getKey()); suppressFlyout |= !bubble.getEntry().getRanking().visuallyInterruptive(); suppressFlyout |= bubble.getEntry() == null || !bubble.getEntry().getRanking().visuallyInterruptive(); if (prevBubble == null) { // Create a new bubble Loading Loading @@ -297,11 +299,14 @@ public class BubbleData { dispatchPendingChanges(); } public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) { /** * Called when a notification associated with a bubble is removed. */ public void notificationEntryRemoved(String key, @DismissReason int reason) { if (DEBUG_BUBBLE_DATA) { Log.d(TAG, "notificationEntryRemoved: entry=" + entry + " reason=" + reason); Log.d(TAG, "notificationEntryRemoved: key=" + key + " reason=" + reason); } doRemove(entry.getKey(), reason); doRemove(key, reason); dispatchPendingChanges(); } Loading Loading @@ -349,7 +354,7 @@ public class BubbleData { return bubbleChildren; } for (Bubble b : mBubbles) { if (groupKey.equals(b.getEntry().getSbn().getGroupKey())) { if (b.getEntry() != null && groupKey.equals(b.getEntry().getSbn().getGroupKey())) { bubbleChildren.add(b); } } Loading Loading @@ -447,8 +452,10 @@ public class BubbleData { Bubble newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } if (bubbleToRemove.getEntry() != null) { maybeSendDeleteIntent(reason, bubbleToRemove.getEntry()); } } void overflowBubble(@DismissReason int reason, Bubble bubble) { if (bubble.getPendingIntentCanceled() Loading Loading @@ -615,7 +622,8 @@ public class BubbleData { return true; } private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) { private void maybeSendDeleteIntent(@DismissReason int reason, @NonNull final NotificationEntry entry) { if (reason == BubbleController.DISMISS_USER_GESTURE) { Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata(); PendingIntent deleteIntent = bubbleMetadata != null Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleDataRepository.kt +17 −12 Original line number Diff line number Diff line Loading @@ -74,8 +74,10 @@ internal class BubbleDataRepository @Inject constructor( private fun transform(userId: Int, bubbles: List<Bubble>): List<BubbleEntity> { return bubbles.mapNotNull { b -> val shortcutId = b.shortcutInfo?.id ?: return@mapNotNull null BubbleEntity(userId, b.packageName, shortcutId) var shortcutId = b.shortcutInfo?.id if (shortcutId == null) shortcutId = b.entry?.bubbleMetadata?.shortcutId if (shortcutId == null) return@mapNotNull null BubbleEntity(userId, b.packageName, shortcutId, b.key) } } Loading Loading @@ -108,7 +110,6 @@ internal class BubbleDataRepository @Inject constructor( /** * Load bubbles from disk. */ // TODO: call this method from BubbleController and update UI @SuppressLint("WrongConstant") fun loadBubbles(cb: (List<Bubble>) -> Unit) = ioScope.launch { /** Loading @@ -132,17 +133,17 @@ internal class BubbleDataRepository @Inject constructor( val shortcutKeys = entities.map { ShortcutKey(it.userId, it.packageName) }.toSet() /** * Retrieve shortcuts with given userId/packageName combination, then construct a mapping * between BubbleEntity and ShortcutInfo. * from the userId/packageName pair to a list of associated ShortcutInfo. * e.g. * { * BubbleEntity(0, "com.example.messenger", "id-0") -> * ShortcutKey(0, "com.example.messenger") -> [ * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-0"), * BubbleEntity(0, "com.example.messenger", "id-2") -> * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-2"), * BubbleEntity(10, "com.example.chat", "id-1") -> * ShortcutInfo(userId=0, pkg="com.example.messenger", id="id-2") * ] * ShortcutKey(10, "com.example.chat") -> [ * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-1"), * BubbleEntity(10, "com.example.chat", "id-3") -> * ShortcutInfo(userId=10, pkg="com.example.chat", id="id-3") * ] * } */ val shortcutMap = shortcutKeys.flatMap { key -> Loading @@ -150,11 +151,15 @@ internal class BubbleDataRepository @Inject constructor( LauncherApps.ShortcutQuery() .setPackage(key.pkg) .setQueryFlags(SHORTCUT_QUERY_FLAG), UserHandle.of(key.userId)) ?.map { BubbleEntity(key.userId, key.pkg, it.id) to it } ?: emptyList() }.toMap() ?: emptyList() }.groupBy { ShortcutKey(it.userId, it.`package`) } // For each entity loaded from xml, find the corresponding ShortcutInfo then convert them // into Bubble. val bubbles = entities.mapNotNull { entity -> shortcutMap[entity]?.let { Bubble(it) } } val bubbles = entities.mapNotNull { entity -> shortcutMap[ShortcutKey(entity.userId, entity.packageName)] ?.first { shortcutInfo -> entity.shortcutId == shortcutInfo.id } ?.let { shortcutInfo -> Bubble(entity.key, shortcutInfo) } } uiScope.launch { cb(bubbles) } } Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleExpandedView.java +2 −7 File changed.Preview size limit exceeded, changes collapsed. Show changes