Loading packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +51 −11 Original line number Diff line number Diff line Loading @@ -16,47 +16,65 @@ package com.android.systemui.bubbles; import android.os.UserHandle; import android.view.LayoutInflater; import com.android.systemui.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.Objects; /** * Encapsulates the data and UI elements of a bubble. */ class Bubble { private static final boolean DEBUG = false; private static final String TAG = "Bubble"; private final String mKey; private final String mGroupId; private final BubbleExpandedView.OnBubbleBlockedListener mListener; private boolean mInflated; public BubbleView iconView; public BubbleExpandedView expandedView; public NotificationEntry entry; BubbleView iconView; BubbleExpandedView expandedView; private static String groupId(NotificationEntry entry) { UserHandle user = entry.notification.getUser(); return user.getIdentifier() + '|' + entry.notification.getPackageName(); } Bubble(NotificationEntry e, BubbleExpandedView.OnBubbleBlockedListener listener) { entry = e; mKey = e.key; mGroupId = groupId(e); mListener = listener; } /** @deprecated use the other constructor to defer View creation. */ @Deprecated Bubble(NotificationEntry e, LayoutInflater inflater, BubbleStackView stackView, BubbleExpandedView.OnBubbleBlockedListener listener) { this(e, listener); inflate(inflater, stackView); } public String getKey() { return mKey; } public String getGroupId() { return mGroupId; } public String getPackageName() { return entry.notification.getPackageName(); } boolean isInflated() { return mInflated; } public void updateDotVisibility() { if (iconView != null) { iconView.updateDotVisibility(); } } void inflate(LayoutInflater inflater, BubbleStackView stackView) { if (mInflated) { return; Loading @@ -73,10 +91,32 @@ class Bubble { mInflated = true; } void setDismissed() { entry.setBubbleDismissed(true); // TODO: move this somewhere where it can be guaranteed not to run until safe from flicker if (expandedView != null) { expandedView.cleanUpExpandedState(); } } void setEntry(NotificationEntry entry) { this.entry = entry; if (mInflated) { iconView.update(entry); expandedView.update(entry); } } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Bubble)) return false; Bubble bubble = (Bubble) o; return Objects.equals(mKey, bubble.mKey); } @Override public int hashCode() { return Objects.hash(mKey); } } packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +58 −65 Original line number Diff line number Diff line Loading @@ -77,8 +77,7 @@ import javax.inject.Singleton; * The controller manages addition, removal, and visible state of bubbles on screen. */ @Singleton public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListener, ConfigurationController.ConfigurationListener { public class BubbleController implements ConfigurationController.ConfigurationListener { private static final String TAG = "BubbleController"; Loading Loading @@ -174,6 +173,10 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe @Override public void onStateChanged(int newState) { mState = newState; boolean shouldCollapse = (mState != SHADE); if (shouldCollapse) { collapseStack(); } updateVisibility(); } } Loading Loading @@ -236,7 +239,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } mStackView.setOnBlockedListener(this); } } Loading Loading @@ -284,28 +286,38 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe if (mStackView == null) { return false; } for (Bubble bubble : mBubbleData.getBubbles()) { if (!bubble.entry.isBubbleDismissed()) { return true; } } return false; return mBubbleData.hasBubbles(); } /** * Whether the stack of bubbles is expanded or not. */ public boolean isStackExpanded() { return mStackView != null && mStackView.isExpanded(); return mBubbleData.isExpanded(); } /** * Tell the stack of bubbles to expand. */ public void expandStack() { mBubbleData.setExpanded(true); } /** * Tell the stack of bubbles to collapse. */ public void collapseStack() { if (mStackView != null) { mStackView.collapseStack(); mBubbleData.setExpanded(false /* expanded */); } void selectBubble(Bubble bubble) { mBubbleData.setSelectedBubble(bubble); } @VisibleForTesting void selectBubble(String key) { Bubble bubble = mBubbleData.getBubbleWithKey(key); selectBubble(bubble); } /** Loading @@ -314,8 +326,10 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe * @param notificationKey the notification key for the bubble to be selected */ public void expandStackAndSelectBubble(String notificationKey) { if (mStackView != null && mBubbleData.getBubble(notificationKey) != null) { mStackView.setExpandedBubble(notificationKey); Bubble bubble = mBubbleData.getBubbleWithKey(notificationKey); if (bubble != null) { mBubbleData.setSelectedBubble(bubble); mBubbleData.setExpanded(true); } } Loading @@ -323,13 +337,7 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack. */ void dismissStack(@DismissReason int reason) { if (mStackView == null) { return; } mStackView.stackDismissed(reason); updateVisibility(); mNotificationEntryManager.updateNotifications(); mBubbleData.dismissAll(reason); } /** Loading @@ -348,20 +356,7 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe * @param notif the notification associated with this bubble. */ void updateBubble(NotificationEntry notif) { if (mStackView != null && mBubbleData.getBubble(notif.key) != null) { // It's an update mStackView.updateBubble(notif); } else { // It's new ensureStackViewCreated(); mStackView.addBubble(notif); } Bubble bubble = mBubbleData.getBubble(notif.key); if (shouldAutoExpand(notif)) { mStackView.setSelectedBubble(bubble); mStackView.setExpanded(true); } updateVisibility(); mBubbleData.notificationEntryUpdated(notif); } /** Loading @@ -371,23 +366,10 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe */ @MainThread void removeBubble(String key, int reason) { if (mStackView != null) { mStackView.removeBubble(key, reason); } mNotificationEntryManager.updateNotifications(); updateVisibility(); } @Override public void onBubbleBlocked(NotificationEntry entry) { Object[] bubbles = mBubbleData.getBubbles().toArray(); for (int i = 0; i < bubbles.length; i++) { NotificationEntry e = ((Bubble) bubbles[i]).entry; boolean samePackage = entry.notification.getPackageName().equals( e.notification.getPackageName()); if (samePackage) { removeBubble(entry.key, DISMISS_BLOCKED); } // TEMP: refactor to change this to pass entry Bubble bubble = mBubbleData.getBubbleWithKey(key); if (bubble != null) { mBubbleData.notificationEntryRemoved(bubble.entry, reason); } } Loading Loading @@ -424,7 +406,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe updateShowInShadeForSuppressNotification(entry); entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed updateBubble(entry); mStackView.updateDotVisibility(entry.key); } } Loading @@ -446,44 +427,57 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe } }; @SuppressWarnings("FieldCanBeLocal") private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() { @Override public void onBubbleAdded(Bubble bubble) { ensureStackViewCreated(); mStackView.addBubble(bubble); } @Override public void onBubbleRemoved(Bubble bubble, int reason) { if (mStackView != null) { mStackView.removeBubble(bubble); } } public void onBubbleUpdated(Bubble bubble) { if (mStackView != null) { mStackView.updateBubble(bubble); } } @Override public void onOrderChanged(List<Bubble> bubbles) { } @Override public void onSelectionChanged(Bubble selectedBubble) { if (mStackView != null) { mStackView.setSelectedBubble(selectedBubble); } } @Override public void onExpandedChanged(boolean expanded) { if (mStackView != null) { mStackView.setExpanded(expanded); } } @Override public void showFlyoutText(Bubble bubble, String text) { if (mStackView != null) { mStackView.animateInFlyoutForBubble(bubble); } } @Override public void apply() { mNotificationEntryManager.updateNotifications(); updateVisibility(); } }; Loading Loading @@ -514,7 +508,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE); } else if (mStackView != null) { mStackView.setVisibility(INVISIBLE); collapseStack(); } updateBubblesShowing(); } Loading Loading @@ -621,14 +614,14 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe @Override public void onTaskMovedToFront(RunningTaskInfo taskInfo) { if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) { mStackView.collapseStack(); mBubbleData.setExpanded(false); } } @Override public void onActivityLaunchOnSecondaryDisplayRerouted() { if (mStackView != null) { mStackView.collapseStack(); mBubbleData.setExpanded(false); } } } Loading packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +242 −23 Original line number Diff line number Diff line Loading @@ -15,14 +15,24 @@ */ package com.android.systemui.bubbles; import androidx.annotation.Nullable; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; import android.app.ActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.bubbles.BubbleController.DismissReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; import javax.inject.Inject; import javax.inject.Singleton; Loading @@ -33,6 +43,8 @@ import javax.inject.Singleton; @Singleton public class BubbleData { private static final String TAG = "BubbleData"; /** * This interface reports changes to the state and appearance of bubbles which should be applied * as necessary to the UI. Loading @@ -53,7 +65,7 @@ public class BubbleData { * A Bubble has been removed. A call to {@link #onOrderChanged(List)} will * follow. */ void onBubbleRemoved(Bubble bubble, @BubbleController.DismissReason int reason); void onBubbleRemoved(Bubble bubble, @DismissReason int reason); /** * An existing bubble has been updated. Loading Loading @@ -86,46 +98,253 @@ public class BubbleData { void apply(); } private HashMap<String, Bubble> mBubbles = new HashMap<>(); private final Context mContext; private final List<Bubble> mBubbles = new ArrayList<>(); private Bubble mSelectedBubble; private boolean mExpanded; private Listener mListener; @VisibleForTesting @Inject public BubbleData() {} public BubbleData(Context context) { mContext = context; } public boolean hasBubbles() { return !mBubbles.isEmpty(); } public boolean isExpanded() { return mExpanded; } public boolean hasBubbleWithKey(String key) { return getBubbleWithKey(key) != null; } public void setExpanded(boolean expanded) { if (setExpandedInternal(expanded)) { mListener.apply(); } } public void setSelectedBubble(Bubble bubble) { if (setSelectedBubbleInternal(bubble)) { mListener.apply(); } } public void notificationEntryUpdated(NotificationEntry entry) { Bubble bubble = getBubbleWithKey(entry.key); if (bubble == null) { // Create a new bubble bubble = new Bubble(entry, this::onBubbleBlocked); mBubbles.add(0, bubble); // TODO: reorder/group mListener.onBubbleAdded(bubble); } else { // Updates an existing bubble bubble.setEntry(entry); mListener.onBubbleUpdated(bubble); } if (shouldAutoExpand(entry)) { setSelectedBubbleInternal(bubble); if (!mExpanded) { setExpandedInternal(true); } } else if (mSelectedBubble == null) { setSelectedBubbleInternal(bubble); } // TODO: reorder/group mListener.apply(); } public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) { int indexToRemove = indexForKey(entry.key); if (indexToRemove >= 0) { Bubble removed = mBubbles.remove(indexToRemove); removed.setDismissed(); mListener.onBubbleRemoved(removed, reason); maybeSendDeleteIntent(reason, removed.entry); if (mBubbles.isEmpty()) { setExpandedInternal(false); setSelectedBubbleInternal(null); } else if (removed == mSelectedBubble) { int newIndex = Math.min(indexToRemove, mBubbles.size() - 1); Bubble newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } // TODO: reorder/group mListener.apply(); } } public void dismissAll(@DismissReason int reason) { boolean changed = setExpandedInternal(false); while (!mBubbles.isEmpty()) { Bubble bubble = mBubbles.remove(0); bubble.setDismissed(); maybeSendDeleteIntent(reason, bubble.entry); mListener.onBubbleRemoved(bubble, reason); changed = true; } if (setSelectedBubbleInternal(null)) { changed = true; } if (changed) { // TODO: reorder/group mListener.apply(); } } /** * The set of bubbles. * Requests a change to the selected bubble. Calls {@link Listener#onSelectionChanged} if * the value changes. * * @param bubble the new selected bubble * @return true if the state changed as a result */ public Collection<Bubble> getBubbles() { return mBubbles.values(); private boolean setSelectedBubbleInternal(Bubble bubble) { if (Objects.equals(bubble, mSelectedBubble)) { return false; } if (bubble != null && !mBubbles.contains(bubble)) { Log.e(TAG, "Cannot select bubble which doesn't exist!" + " (" + bubble + ") bubbles=" + mBubbles); return false; } if (mExpanded) { // TODO: bubble.markAsActive() ? bubble.entry.setShowInShadeWhenBubble(false); } mSelectedBubble = bubble; mListener.onSelectionChanged(mSelectedBubble); return true; } @Nullable public Bubble getBubble(String key) { return mBubbles.get(key); /** * Requests a change to the expanded state. Calls {@link Listener#onExpandedChanged} if * the value changes. * * @param shouldExpand the new requested state * @return true if the state changed as a result */ private boolean setExpandedInternal(boolean shouldExpand) { if (mExpanded == shouldExpand) { return false; } if (shouldExpand) { if (mBubbles.isEmpty()) { Log.e(TAG, "Attempt to expand stack when empty!"); return false; } if (mSelectedBubble == null) { Log.e(TAG, "Attempt to expand stack without selected bubble!"); return false; } // TODO: bubble.markAsActive() ? mSelectedBubble.entry.setShowInShadeWhenBubble(false); } // TODO: reorder/regroup mExpanded = shouldExpand; mListener.onExpandedChanged(mExpanded); return true; } public void addBubble(Bubble b) { mBubbles.put(b.getKey(), b); private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) { if (reason == BubbleController.DISMISS_USER_GESTURE) { Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata(); PendingIntent deleteIntent = bubbleMetadata != null ? bubbleMetadata.getDeleteIntent() : null; if (deleteIntent != null) { try { deleteIntent.send(); } catch (PendingIntent.CanceledException e) { Log.w(TAG, "Failed to send delete intent for bubble with key: " + entry.key); } } } } @Nullable public Bubble removeBubble(String key) { return mBubbles.remove(key); private void onBubbleBlocked(NotificationEntry entry) { boolean changed = false; final String blockedPackage = entry.notification.getPackageName(); for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) { Bubble bubble = i.next(); if (bubble.getPackageName().equals(blockedPackage)) { i.remove(); mListener.onBubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED); changed = true; } } if (changed) { // TODO: reorder/group mListener.apply(); } } public void updateBubble(String key, NotificationEntry newEntry) { Bubble oldBubble = mBubbles.get(key); if (oldBubble != null) { oldBubble.setEntry(newEntry); private int indexForKey(String key) { for (int i = 0; i < mBubbles.size(); i++) { Bubble bubble = mBubbles.get(i); if (bubble.getKey().equals(key)) { return i; } } return -1; } public void clear() { mBubbles.clear(); private Bubble removeBubbleWithKey(String key) { for (int i = 0; i < mBubbles.size(); i++) { Bubble bubble = mBubbles.get(i); if (bubble.getKey().equals(key)) { mBubbles.remove(i); return bubble; } } return null; } /** * The set of bubbles. * * @deprecated */ @Deprecated public Collection<Bubble> getBubbles() { return Collections.unmodifiableList(mBubbles); } @VisibleForTesting(visibility = PRIVATE) Bubble getBubbleWithKey(String key) { for (int i = 0; i < mBubbles.size(); i++) { Bubble bubble = mBubbles.get(i); if (bubble.getKey().equals(key)) { return bubble; } } return null; } public void setListener(Listener listener) { mListener = listener; } boolean shouldAutoExpand(NotificationEntry entry) { Notification.BubbleMetadata metadata = entry.getBubbleMetadata(); return metadata != null && metadata.getAutoExpandBubble() && isForegroundApp(entry.notification.getPackageName()); } /** * Return true if the applications with the package name is running in foreground. * * @param pkgName application package name. */ boolean isForegroundApp(String pkgName) { ActivityManager am = mContext.getSystemService(ActivityManager.class); List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1 /* maxNum */); return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName()); } } No newline at end of file packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +47 −157 File changed.Preview size limit exceeded, changes collapsed. Show changes packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +1 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import android.os.Trace; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleData; Loading Loading @@ -120,7 +119,7 @@ public class NotificationViewHierarchyManager { for (int i = 0; i < N; i++) { NotificationEntry ent = activeNotifications.get(i); if (ent.isRowDismissed() || ent.isRowRemoved() || (mBubbleData.getBubble(ent.key) != null && !ent.showInShadeWhenBubble())) { || (mBubbleData.hasBubbleWithKey(ent.key) && !ent.showInShadeWhenBubble())) { // we don't want to update removed notifications because they could // temporarily become children if they were isolated before. continue; Loading Loading
packages/SystemUI/src/com/android/systemui/bubbles/Bubble.java +51 −11 Original line number Diff line number Diff line Loading @@ -16,47 +16,65 @@ package com.android.systemui.bubbles; import android.os.UserHandle; import android.view.LayoutInflater; import com.android.systemui.R; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.Objects; /** * Encapsulates the data and UI elements of a bubble. */ class Bubble { private static final boolean DEBUG = false; private static final String TAG = "Bubble"; private final String mKey; private final String mGroupId; private final BubbleExpandedView.OnBubbleBlockedListener mListener; private boolean mInflated; public BubbleView iconView; public BubbleExpandedView expandedView; public NotificationEntry entry; BubbleView iconView; BubbleExpandedView expandedView; private static String groupId(NotificationEntry entry) { UserHandle user = entry.notification.getUser(); return user.getIdentifier() + '|' + entry.notification.getPackageName(); } Bubble(NotificationEntry e, BubbleExpandedView.OnBubbleBlockedListener listener) { entry = e; mKey = e.key; mGroupId = groupId(e); mListener = listener; } /** @deprecated use the other constructor to defer View creation. */ @Deprecated Bubble(NotificationEntry e, LayoutInflater inflater, BubbleStackView stackView, BubbleExpandedView.OnBubbleBlockedListener listener) { this(e, listener); inflate(inflater, stackView); } public String getKey() { return mKey; } public String getGroupId() { return mGroupId; } public String getPackageName() { return entry.notification.getPackageName(); } boolean isInflated() { return mInflated; } public void updateDotVisibility() { if (iconView != null) { iconView.updateDotVisibility(); } } void inflate(LayoutInflater inflater, BubbleStackView stackView) { if (mInflated) { return; Loading @@ -73,10 +91,32 @@ class Bubble { mInflated = true; } void setDismissed() { entry.setBubbleDismissed(true); // TODO: move this somewhere where it can be guaranteed not to run until safe from flicker if (expandedView != null) { expandedView.cleanUpExpandedState(); } } void setEntry(NotificationEntry entry) { this.entry = entry; if (mInflated) { iconView.update(entry); expandedView.update(entry); } } @Override public boolean equals(Object o) { if (this == o) return true; if (!(o instanceof Bubble)) return false; Bubble bubble = (Bubble) o; return Objects.equals(mKey, bubble.mKey); } @Override public int hashCode() { return Objects.hash(mKey); } }
packages/SystemUI/src/com/android/systemui/bubbles/BubbleController.java +58 −65 Original line number Diff line number Diff line Loading @@ -77,8 +77,7 @@ import javax.inject.Singleton; * The controller manages addition, removal, and visible state of bubbles on screen. */ @Singleton public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListener, ConfigurationController.ConfigurationListener { public class BubbleController implements ConfigurationController.ConfigurationListener { private static final String TAG = "BubbleController"; Loading Loading @@ -174,6 +173,10 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe @Override public void onStateChanged(int newState) { mState = newState; boolean shouldCollapse = (mState != SHADE); if (shouldCollapse) { collapseStack(); } updateVisibility(); } } Loading Loading @@ -236,7 +239,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe if (mExpandListener != null) { mStackView.setExpandListener(mExpandListener); } mStackView.setOnBlockedListener(this); } } Loading Loading @@ -284,28 +286,38 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe if (mStackView == null) { return false; } for (Bubble bubble : mBubbleData.getBubbles()) { if (!bubble.entry.isBubbleDismissed()) { return true; } } return false; return mBubbleData.hasBubbles(); } /** * Whether the stack of bubbles is expanded or not. */ public boolean isStackExpanded() { return mStackView != null && mStackView.isExpanded(); return mBubbleData.isExpanded(); } /** * Tell the stack of bubbles to expand. */ public void expandStack() { mBubbleData.setExpanded(true); } /** * Tell the stack of bubbles to collapse. */ public void collapseStack() { if (mStackView != null) { mStackView.collapseStack(); mBubbleData.setExpanded(false /* expanded */); } void selectBubble(Bubble bubble) { mBubbleData.setSelectedBubble(bubble); } @VisibleForTesting void selectBubble(String key) { Bubble bubble = mBubbleData.getBubbleWithKey(key); selectBubble(bubble); } /** Loading @@ -314,8 +326,10 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe * @param notificationKey the notification key for the bubble to be selected */ public void expandStackAndSelectBubble(String notificationKey) { if (mStackView != null && mBubbleData.getBubble(notificationKey) != null) { mStackView.setExpandedBubble(notificationKey); Bubble bubble = mBubbleData.getBubbleWithKey(notificationKey); if (bubble != null) { mBubbleData.setSelectedBubble(bubble); mBubbleData.setExpanded(true); } } Loading @@ -323,13 +337,7 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe * Tell the stack of bubbles to be dismissed, this will remove all of the bubbles in the stack. */ void dismissStack(@DismissReason int reason) { if (mStackView == null) { return; } mStackView.stackDismissed(reason); updateVisibility(); mNotificationEntryManager.updateNotifications(); mBubbleData.dismissAll(reason); } /** Loading @@ -348,20 +356,7 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe * @param notif the notification associated with this bubble. */ void updateBubble(NotificationEntry notif) { if (mStackView != null && mBubbleData.getBubble(notif.key) != null) { // It's an update mStackView.updateBubble(notif); } else { // It's new ensureStackViewCreated(); mStackView.addBubble(notif); } Bubble bubble = mBubbleData.getBubble(notif.key); if (shouldAutoExpand(notif)) { mStackView.setSelectedBubble(bubble); mStackView.setExpanded(true); } updateVisibility(); mBubbleData.notificationEntryUpdated(notif); } /** Loading @@ -371,23 +366,10 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe */ @MainThread void removeBubble(String key, int reason) { if (mStackView != null) { mStackView.removeBubble(key, reason); } mNotificationEntryManager.updateNotifications(); updateVisibility(); } @Override public void onBubbleBlocked(NotificationEntry entry) { Object[] bubbles = mBubbleData.getBubbles().toArray(); for (int i = 0; i < bubbles.length; i++) { NotificationEntry e = ((Bubble) bubbles[i]).entry; boolean samePackage = entry.notification.getPackageName().equals( e.notification.getPackageName()); if (samePackage) { removeBubble(entry.key, DISMISS_BLOCKED); } // TEMP: refactor to change this to pass entry Bubble bubble = mBubbleData.getBubbleWithKey(key); if (bubble != null) { mBubbleData.notificationEntryRemoved(bubble.entry, reason); } } Loading Loading @@ -424,7 +406,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe updateShowInShadeForSuppressNotification(entry); entry.setBubbleDismissed(false); // updates come back as bubbles even if dismissed updateBubble(entry); mStackView.updateDotVisibility(entry.key); } } Loading @@ -446,44 +427,57 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe } }; @SuppressWarnings("FieldCanBeLocal") private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() { @Override public void onBubbleAdded(Bubble bubble) { ensureStackViewCreated(); mStackView.addBubble(bubble); } @Override public void onBubbleRemoved(Bubble bubble, int reason) { if (mStackView != null) { mStackView.removeBubble(bubble); } } public void onBubbleUpdated(Bubble bubble) { if (mStackView != null) { mStackView.updateBubble(bubble); } } @Override public void onOrderChanged(List<Bubble> bubbles) { } @Override public void onSelectionChanged(Bubble selectedBubble) { if (mStackView != null) { mStackView.setSelectedBubble(selectedBubble); } } @Override public void onExpandedChanged(boolean expanded) { if (mStackView != null) { mStackView.setExpanded(expanded); } } @Override public void showFlyoutText(Bubble bubble, String text) { if (mStackView != null) { mStackView.animateInFlyoutForBubble(bubble); } } @Override public void apply() { mNotificationEntryManager.updateNotifications(); updateVisibility(); } }; Loading Loading @@ -514,7 +508,6 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe mStackView.setVisibility(hasBubbles() ? VISIBLE : INVISIBLE); } else if (mStackView != null) { mStackView.setVisibility(INVISIBLE); collapseStack(); } updateBubblesShowing(); } Loading Loading @@ -621,14 +614,14 @@ public class BubbleController implements BubbleExpandedView.OnBubbleBlockedListe @Override public void onTaskMovedToFront(RunningTaskInfo taskInfo) { if (mStackView != null && taskInfo.displayId == Display.DEFAULT_DISPLAY) { mStackView.collapseStack(); mBubbleData.setExpanded(false); } } @Override public void onActivityLaunchOnSecondaryDisplayRerouted() { if (mStackView != null) { mStackView.collapseStack(); mBubbleData.setExpanded(false); } } } Loading
packages/SystemUI/src/com/android/systemui/bubbles/BubbleData.java +242 −23 Original line number Diff line number Diff line Loading @@ -15,14 +15,24 @@ */ package com.android.systemui.bubbles; import androidx.annotation.Nullable; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; import android.app.ActivityManager; import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.util.Log; import com.android.internal.annotations.VisibleForTesting; import com.android.systemui.bubbles.BubbleController.DismissReason; import com.android.systemui.statusbar.notification.collection.NotificationEntry; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Objects; import javax.inject.Inject; import javax.inject.Singleton; Loading @@ -33,6 +43,8 @@ import javax.inject.Singleton; @Singleton public class BubbleData { private static final String TAG = "BubbleData"; /** * This interface reports changes to the state and appearance of bubbles which should be applied * as necessary to the UI. Loading @@ -53,7 +65,7 @@ public class BubbleData { * A Bubble has been removed. A call to {@link #onOrderChanged(List)} will * follow. */ void onBubbleRemoved(Bubble bubble, @BubbleController.DismissReason int reason); void onBubbleRemoved(Bubble bubble, @DismissReason int reason); /** * An existing bubble has been updated. Loading Loading @@ -86,46 +98,253 @@ public class BubbleData { void apply(); } private HashMap<String, Bubble> mBubbles = new HashMap<>(); private final Context mContext; private final List<Bubble> mBubbles = new ArrayList<>(); private Bubble mSelectedBubble; private boolean mExpanded; private Listener mListener; @VisibleForTesting @Inject public BubbleData() {} public BubbleData(Context context) { mContext = context; } public boolean hasBubbles() { return !mBubbles.isEmpty(); } public boolean isExpanded() { return mExpanded; } public boolean hasBubbleWithKey(String key) { return getBubbleWithKey(key) != null; } public void setExpanded(boolean expanded) { if (setExpandedInternal(expanded)) { mListener.apply(); } } public void setSelectedBubble(Bubble bubble) { if (setSelectedBubbleInternal(bubble)) { mListener.apply(); } } public void notificationEntryUpdated(NotificationEntry entry) { Bubble bubble = getBubbleWithKey(entry.key); if (bubble == null) { // Create a new bubble bubble = new Bubble(entry, this::onBubbleBlocked); mBubbles.add(0, bubble); // TODO: reorder/group mListener.onBubbleAdded(bubble); } else { // Updates an existing bubble bubble.setEntry(entry); mListener.onBubbleUpdated(bubble); } if (shouldAutoExpand(entry)) { setSelectedBubbleInternal(bubble); if (!mExpanded) { setExpandedInternal(true); } } else if (mSelectedBubble == null) { setSelectedBubbleInternal(bubble); } // TODO: reorder/group mListener.apply(); } public void notificationEntryRemoved(NotificationEntry entry, @DismissReason int reason) { int indexToRemove = indexForKey(entry.key); if (indexToRemove >= 0) { Bubble removed = mBubbles.remove(indexToRemove); removed.setDismissed(); mListener.onBubbleRemoved(removed, reason); maybeSendDeleteIntent(reason, removed.entry); if (mBubbles.isEmpty()) { setExpandedInternal(false); setSelectedBubbleInternal(null); } else if (removed == mSelectedBubble) { int newIndex = Math.min(indexToRemove, mBubbles.size() - 1); Bubble newSelected = mBubbles.get(newIndex); setSelectedBubbleInternal(newSelected); } // TODO: reorder/group mListener.apply(); } } public void dismissAll(@DismissReason int reason) { boolean changed = setExpandedInternal(false); while (!mBubbles.isEmpty()) { Bubble bubble = mBubbles.remove(0); bubble.setDismissed(); maybeSendDeleteIntent(reason, bubble.entry); mListener.onBubbleRemoved(bubble, reason); changed = true; } if (setSelectedBubbleInternal(null)) { changed = true; } if (changed) { // TODO: reorder/group mListener.apply(); } } /** * The set of bubbles. * Requests a change to the selected bubble. Calls {@link Listener#onSelectionChanged} if * the value changes. * * @param bubble the new selected bubble * @return true if the state changed as a result */ public Collection<Bubble> getBubbles() { return mBubbles.values(); private boolean setSelectedBubbleInternal(Bubble bubble) { if (Objects.equals(bubble, mSelectedBubble)) { return false; } if (bubble != null && !mBubbles.contains(bubble)) { Log.e(TAG, "Cannot select bubble which doesn't exist!" + " (" + bubble + ") bubbles=" + mBubbles); return false; } if (mExpanded) { // TODO: bubble.markAsActive() ? bubble.entry.setShowInShadeWhenBubble(false); } mSelectedBubble = bubble; mListener.onSelectionChanged(mSelectedBubble); return true; } @Nullable public Bubble getBubble(String key) { return mBubbles.get(key); /** * Requests a change to the expanded state. Calls {@link Listener#onExpandedChanged} if * the value changes. * * @param shouldExpand the new requested state * @return true if the state changed as a result */ private boolean setExpandedInternal(boolean shouldExpand) { if (mExpanded == shouldExpand) { return false; } if (shouldExpand) { if (mBubbles.isEmpty()) { Log.e(TAG, "Attempt to expand stack when empty!"); return false; } if (mSelectedBubble == null) { Log.e(TAG, "Attempt to expand stack without selected bubble!"); return false; } // TODO: bubble.markAsActive() ? mSelectedBubble.entry.setShowInShadeWhenBubble(false); } // TODO: reorder/regroup mExpanded = shouldExpand; mListener.onExpandedChanged(mExpanded); return true; } public void addBubble(Bubble b) { mBubbles.put(b.getKey(), b); private void maybeSendDeleteIntent(@DismissReason int reason, NotificationEntry entry) { if (reason == BubbleController.DISMISS_USER_GESTURE) { Notification.BubbleMetadata bubbleMetadata = entry.getBubbleMetadata(); PendingIntent deleteIntent = bubbleMetadata != null ? bubbleMetadata.getDeleteIntent() : null; if (deleteIntent != null) { try { deleteIntent.send(); } catch (PendingIntent.CanceledException e) { Log.w(TAG, "Failed to send delete intent for bubble with key: " + entry.key); } } } } @Nullable public Bubble removeBubble(String key) { return mBubbles.remove(key); private void onBubbleBlocked(NotificationEntry entry) { boolean changed = false; final String blockedPackage = entry.notification.getPackageName(); for (Iterator<Bubble> i = mBubbles.iterator(); i.hasNext(); ) { Bubble bubble = i.next(); if (bubble.getPackageName().equals(blockedPackage)) { i.remove(); mListener.onBubbleRemoved(bubble, BubbleController.DISMISS_BLOCKED); changed = true; } } if (changed) { // TODO: reorder/group mListener.apply(); } } public void updateBubble(String key, NotificationEntry newEntry) { Bubble oldBubble = mBubbles.get(key); if (oldBubble != null) { oldBubble.setEntry(newEntry); private int indexForKey(String key) { for (int i = 0; i < mBubbles.size(); i++) { Bubble bubble = mBubbles.get(i); if (bubble.getKey().equals(key)) { return i; } } return -1; } public void clear() { mBubbles.clear(); private Bubble removeBubbleWithKey(String key) { for (int i = 0; i < mBubbles.size(); i++) { Bubble bubble = mBubbles.get(i); if (bubble.getKey().equals(key)) { mBubbles.remove(i); return bubble; } } return null; } /** * The set of bubbles. * * @deprecated */ @Deprecated public Collection<Bubble> getBubbles() { return Collections.unmodifiableList(mBubbles); } @VisibleForTesting(visibility = PRIVATE) Bubble getBubbleWithKey(String key) { for (int i = 0; i < mBubbles.size(); i++) { Bubble bubble = mBubbles.get(i); if (bubble.getKey().equals(key)) { return bubble; } } return null; } public void setListener(Listener listener) { mListener = listener; } boolean shouldAutoExpand(NotificationEntry entry) { Notification.BubbleMetadata metadata = entry.getBubbleMetadata(); return metadata != null && metadata.getAutoExpandBubble() && isForegroundApp(entry.notification.getPackageName()); } /** * Return true if the applications with the package name is running in foreground. * * @param pkgName application package name. */ boolean isForegroundApp(String pkgName) { ActivityManager am = mContext.getSystemService(ActivityManager.class); List<ActivityManager.RunningTaskInfo> tasks = am.getRunningTasks(1 /* maxNum */); return !tasks.isEmpty() && pkgName.equals(tasks.get(0).topActivity.getPackageName()); } } No newline at end of file
packages/SystemUI/src/com/android/systemui/bubbles/BubbleStackView.java +47 −157 File changed.Preview size limit exceeded, changes collapsed. Show changes
packages/SystemUI/src/com/android/systemui/statusbar/NotificationViewHierarchyManager.java +1 −2 Original line number Diff line number Diff line Loading @@ -22,7 +22,6 @@ import android.os.Trace; import android.util.Log; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import com.android.systemui.R; import com.android.systemui.bubbles.BubbleData; Loading Loading @@ -120,7 +119,7 @@ public class NotificationViewHierarchyManager { for (int i = 0; i < N; i++) { NotificationEntry ent = activeNotifications.get(i); if (ent.isRowDismissed() || ent.isRowRemoved() || (mBubbleData.getBubble(ent.key) != null && !ent.showInShadeWhenBubble())) { || (mBubbleData.hasBubbleWithKey(ent.key) && !ent.showInShadeWhenBubble())) { // we don't want to update removed notifications because they could // temporarily become children if they were isolated before. continue; Loading