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

Commit 99a30260 authored by Mady Mellor's avatar Mady Mellor
Browse files

Base stuff on Bubble rather than NotifEntry; move stuff into Bubble

* BubbleView & BubbleExpandedView now go off of a Bubble rather than a NotificationEntry
* moved getUpdateMessage off of NotifEntry and into Bubble (also moves tests to new BubbleTest)
* moved height & settings intent lookups out of BubbleExpandedView and into Bubble

Bug: 135214687
Test: atest BubbleControllerTest BubbleDataTest BubbleTest
Change-Id: I1c5970c3cd00fb4f0136850daea3c9699a939f94
parent ed99c27a
Loading
Loading
Loading
Loading
+157 −15
Original line number Diff line number Diff line
@@ -20,30 +20,43 @@ import static android.view.Display.INVALID_DISPLAY;

import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;

import android.annotation.Nullable;
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.PackageManager;
import android.content.res.Resources;
import android.os.Parcelable;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;

import java.util.List;
import java.util.Objects;

/**
 * Encapsulates the data and UI elements of a bubble.
 */
class Bubble {
    private static final String TAG = "Bubble";

    private NotificationEntry mEntry;
    private final String mKey;
    private final String mGroupId;
    private String mAppName;
    private NotificationEntry mEntry;

    private boolean mInflated;
    private BubbleView mIconView;
    private BubbleExpandedView mExpandedView;

    private long mLastUpdated;
    private long mLastAccessed;
    private boolean mIsRemoved;
@@ -86,6 +99,14 @@ class Bubble {
        return mEntry;
    }

    public boolean showInShadeWhenBubble() {
        return mEntry.showInShadeWhenBubble();
    }

    public void setShowInShadeWhenBubble(boolean showInShade) {
        mEntry.setShowInShadeWhenBubble(showInShade);
    }

    public String getGroupId() {
        return mGroupId;
    }
@@ -102,7 +123,7 @@ class Bubble {
        return mInflated;
    }

    public void updateDotVisibility() {
    void updateDotVisibility() {
        if (mIconView != null) {
            mIconView.updateDotVisibility(true /* animate */);
        }
@@ -122,11 +143,11 @@ class Bubble {
        }
        mIconView = (BubbleView) inflater.inflate(
                R.layout.bubble_view, stackView, false /* attachToRoot */);
        mIconView.setNotif(mEntry);
        mIconView.setBubble(this);

        mExpandedView = (BubbleExpandedView) inflater.inflate(
                R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
        mExpandedView.setEntry(mEntry, stackView, mAppName);
        mExpandedView.setBubble(this, stackView, mAppName);

        mInflated = true;
    }
@@ -157,37 +178,37 @@ class Bubble {
        mIsRemoved = removed;
    }

    public boolean isRemoved() {
    boolean isRemoved() {
        return mIsRemoved;
    }

    void setEntry(NotificationEntry entry) {
        this.mEntry = entry;
        mEntry = entry;
        mLastUpdated = entry.notification.getPostTime();
        if (mInflated) {
            mIconView.update(entry);
            mExpandedView.update(entry);
            mIconView.update(this);
            mExpandedView.update(this);
        }
    }

    /**
     * @return the newer of {@link #getLastUpdateTime()} and {@link #getLastAccessTime()}
     */
    public long getLastActivity() {
    long getLastActivity() {
        return Math.max(mLastUpdated, mLastAccessed);
    }

    /**
     * @return the timestamp in milliseconds of the most recent notification entry for this bubble
     */
    public long getLastUpdateTime() {
    long getLastUpdateTime() {
        return mLastUpdated;
    }

    /**
     * @return the timestamp in milliseconds when this bubble was last displayed in expanded state
     */
    public long getLastAccessTime() {
    long getLastAccessTime() {
        return mLastAccessed;
    }

@@ -203,14 +224,135 @@ class Bubble {
     */
    void markAsAccessedAt(long lastAccessedMillis) {
        mLastAccessed = lastAccessedMillis;
        mEntry.setShowInShadeWhenBubble(false);
        setShowInShadeWhenBubble(false);
    }

    /**
     * Returns whether the notification for this bubble is a foreground service. It shows that this
     * is an ongoing bubble.
     */
    boolean isOngoing() {
        int flags = mEntry.notification.getNotification().flags;
        return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0;
    }

    float getDesiredHeight(Context context) {
        Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
        boolean useRes = data.getDesiredHeightResId() != 0;
        if (useRes) {
            return getDimenForPackageUser(context, data.getDesiredHeightResId(),
                    mEntry.notification.getPackageName(),
                    mEntry.notification.getUser().getIdentifier());
        } else {
            return data.getDesiredHeight()
                    * context.getResources().getDisplayMetrics().density;
        }
    }

    @Nullable
    PendingIntent getBubbleIntent(Context context) {
        Notification notif = mEntry.notification.getNotification();
        Notification.BubbleMetadata data = notif.getBubbleMetadata();
        if (BubbleController.canLaunchInActivityView(context, mEntry) && data != null) {
            return data.getIntent();
        }
        return null;
    }

    Intent getSettingsIntent() {
        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.notification.getUid());
        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        return intent;
    }

    /**
     * @return whether bubble is from a notification associated with a foreground service.
     * Returns our best guess for the most relevant text summary of the latest update to this
     * notification, based on its type. Returns null if there should not be an update message.
     */
    public boolean isOngoing() {
        return mEntry.isForegroundService();
    CharSequence getUpdateMessage(Context context) {
        final Notification underlyingNotif = mEntry.notification.getNotification();
        final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();

        try {
            if (Notification.BigTextStyle.class.equals(style)) {
                // Return the big text, it is big so probably important. If it's not there use the
                // normal text.
                CharSequence bigText =
                        underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
                return !TextUtils.isEmpty(bigText)
                        ? bigText
                        : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
            } else if (Notification.MessagingStyle.class.equals(style)) {
                final List<Notification.MessagingStyle.Message> messages =
                        Notification.MessagingStyle.Message.getMessagesFromBundleArray(
                                (Parcelable[]) underlyingNotif.extras.get(
                                        Notification.EXTRA_MESSAGES));

                final Notification.MessagingStyle.Message latestMessage =
                        Notification.MessagingStyle.findLatestIncomingMessage(messages);

                if (latestMessage != null) {
                    final CharSequence personName = latestMessage.getSenderPerson() != null
                            ? latestMessage.getSenderPerson().getName()
                            : null;

                    // Prepend the sender name if available since group chats also use messaging
                    // style.
                    if (!TextUtils.isEmpty(personName)) {
                        return context.getResources().getString(
                                R.string.notification_summary_message_format,
                                personName,
                                latestMessage.getText());
                    } else {
                        return latestMessage.getText();
                    }
                }
            } else if (Notification.InboxStyle.class.equals(style)) {
                CharSequence[] lines =
                        underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);

                // Return the last line since it should be the most recent.
                if (lines != null && lines.length > 0) {
                    return lines[lines.length - 1];
                }
            } else if (Notification.MediaStyle.class.equals(style)) {
                // Return nothing, media updates aren't typically useful as a text update.
                return null;
            } else {
                // Default to text extra.
                return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
            }
        } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
            // No use crashing, we'll just return null and the caller will assume there's no update
            // message.
            e.printStackTrace();
        }

        return null;
    }

    private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) {
        PackageManager pm = context.getPackageManager();
        Resources r;
        if (pkg != null) {
            try {
                if (userId == UserHandle.USER_ALL) {
                    userId = UserHandle.USER_SYSTEM;
                }
                r = pm.getResourcesForApplicationAsUser(pkg, userId);
                return r.getDimensionPixelSize(resId);
            } catch (PackageManager.NameNotFoundException ex) {
                // Uninstalled, don't care
            } catch (Resources.NotFoundException e) {
                // Invalid res id, return 0 and user our default
                Log.e(TAG, "Couldn't find desired height res id", e);
            }
        }
        return 0;
    }

    @Override
+1 −1
Original line number Diff line number Diff line
@@ -533,7 +533,7 @@ public class BubbleController implements ConfigurationController.ConfigurationLi
                mStackView.removeBubble(bubble);

                if (!mBubbleData.hasBubbleWithKey(bubble.getKey())
                        && !bubble.getEntry().showInShadeWhenBubble()) {
                        && !bubble.showInShadeWhenBubble()) {
                    // The bubble is gone & the notification is gone, time to actually remove it
                    mNotificationEntryManager.performRemoveNotification(
                            bubble.getEntry().notification, UNDEFINED_DISMISS_REASON);
+27 −89
Original line number Diff line number Diff line
@@ -21,11 +21,9 @@ import static android.view.Display.INVALID_DISPLAY;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;

import android.annotation.Nullable;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.ActivityView;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
@@ -40,8 +38,6 @@ import android.graphics.Point;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.ShapeDrawable;
import android.os.RemoteException;
import android.os.UserHandle;
import android.provider.Settings;
import android.service.notification.StatusBarNotification;
import android.util.AttributeSet;
import android.util.Log;
@@ -56,7 +52,6 @@ import com.android.systemui.Dependency;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
import com.android.systemui.statusbar.AlphaOptimizedButton;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;

/**
 * Container for the expanded bubble view, handles rendering the caret and settings icon.
@@ -99,7 +94,7 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
    private int mPointerHeight;
    private ShapeDrawable mPointerDrawable;

    private NotificationEntry mEntry;
    private Bubble mBubble;
    private PackageManager mPm;
    private String mAppName;
    private Drawable mAppIcon;
@@ -144,9 +139,9 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
         */
        @Override
        public void onTaskRemovalStarted(int taskId) {
            if (mEntry != null) {
            if (mBubble != null) {
                // Must post because this is called from a binder thread.
                post(() -> mBubbleController.removeBubble(mEntry.key,
                post(() -> mBubbleController.removeBubble(mBubble.getKey(),
                        BubbleController.DISMISS_TASK_FINISHED));
            }
        }
@@ -286,16 +281,16 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
    }

    /**
     * Sets the notification entry used to populate this view.
     * Sets the bubble used to populate this view.
     */
    public void setEntry(NotificationEntry entry, BubbleStackView stackView, String appName) {
    public void setBubble(Bubble bubble, BubbleStackView stackView, String appName) {
        mStackView = stackView;
        mEntry = entry;
        mBubble = bubble;
        mAppName = appName;

        try {
            ApplicationInfo info = mPm.getApplicationInfo(
                    entry.notification.getPackageName(),
                    bubble.getPackageName(),
                    PackageManager.MATCH_UNINSTALLED_PACKAGES
                            | PackageManager.MATCH_DISABLED_COMPONENTS
                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
@@ -324,22 +319,22 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
    }

    /**
     * Updates the entry backing this view. This will not re-populate ActivityView, it will
     * Updates the bubble backing this view. This will not re-populate ActivityView, it will
     * only update the deep-links in the title, and the height of the view.
     */
    public void update(NotificationEntry entry) {
        if (entry.key.equals(mEntry.key)) {
            mEntry = entry;
    public void update(Bubble bubble) {
        if (bubble.getKey().equals(mBubble.getKey())) {
            mBubble = bubble;
            updateSettingsContentDescription();
            updateHeight();
        } else {
            Log.w(TAG, "Trying to update entry with different key, new entry: "
                    + entry.key + " old entry: " + mEntry.key);
            Log.w(TAG, "Trying to update entry with different key, new bubble: "
                    + bubble.getKey() + " old bubble: " + bubble.getKey());
        }
    }

    private void updateExpandedView() {
        mBubbleIntent = getBubbleIntent(mEntry);
        mBubbleIntent = mBubble.getBubbleIntent(mContext);
        if (mBubbleIntent != null) {
            setContentVisibility(false);
            mActivityView.setVisibility(VISIBLE);
@@ -357,26 +352,10 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList

    void updateHeight() {
        if (usingActivityView()) {
            Notification.BubbleMetadata data = mEntry.getBubbleMetadata();
            float desiredHeight;
            if (data == null) {
                // This is a contentIntent based bubble, lets allow it to be the max height
                // as it was forced into this mode and not prepared to be small
                desiredHeight = getMaxExpandedHeight();
            } else {
                boolean useRes = data.getDesiredHeightResId() != 0;
                float desiredPx;
                if (useRes) {
                    desiredPx = getDimenForPackageUser(data.getDesiredHeightResId(),
                            mEntry.notification.getPackageName(),
                            mEntry.notification.getUser().getIdentifier());
                } else {
                    desiredPx = data.getDesiredHeight()
                            * getContext().getResources().getDisplayMetrics().density;
                }
                desiredHeight = desiredPx > 0 ? desiredPx : mMinHeight;
            }
            float height = Math.min(desiredHeight, getMaxExpandedHeight());
            int max = getMaxExpandedHeight() - mSettingsIconHeight - mPointerHeight
                    - mPointerMargin;
            float desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight);
            float height = Math.min(desiredHeight, max);
            height = Math.max(height, mMinHeight);
            LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams();
            mNeedsNewHeight =  lp.height != height;
@@ -397,17 +376,15 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList

    @Override
    public void onClick(View view) {
        if (mEntry == null) {
        if (mBubble == null) {
            return;
        }
        Notification n = mEntry.notification.getNotification();
        int id = view.getId();
        if (id == R.id.settings_button) {
            Intent intent = getSettingsIntent(mEntry.notification.getPackageName(),
                    mEntry.notification.getUid());
            Intent intent = mBubble.getSettingsIntent();
            mStackView.collapseStack(() -> {
                mContext.startActivityAsUser(intent, mEntry.notification.getUser());
                logBubbleClickEvent(mEntry,
                mContext.startActivityAsUser(intent, mBubble.getEntry().notification.getUser());
                logBubbleClickEvent(mBubble,
                        StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS);
            });
        }
@@ -494,34 +471,14 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
        return INVALID_DISPLAY;
    }

    private Intent getSettingsIntent(String packageName, final int appUid) {
        final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS);
        intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName);
        intent.putExtra(Settings.EXTRA_APP_UID, appUid);
        intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
        return intent;
    }

    @Nullable
    private PendingIntent getBubbleIntent(NotificationEntry entry) {
        Notification notif = entry.notification.getNotification();
        Notification.BubbleMetadata data = notif.getBubbleMetadata();
        if (BubbleController.canLaunchInActivityView(mContext, entry) && data != null) {
            return data.getIntent();
        }
        return null;
    }

    /**
     * Logs bubble UI click event.
     *
     * @param entry the bubble notification entry that user is interacting with.
     * @param bubble the bubble notification entry that user is interacting with.
     * @param action the user interaction enum.
     */
    private void logBubbleClickEvent(NotificationEntry entry, int action) {
        StatusBarNotification notification = entry.notification;
    private void logBubbleClickEvent(Bubble bubble, int action) {
        StatusBarNotification notification = bubble.getEntry().notification;
        StatsLog.write(StatsLog.BUBBLE_UI_CHANGED,
                notification.getPackageName(),
                notification.getNotification().getChannelId(),
@@ -531,27 +488,8 @@ public class BubbleExpandedView extends LinearLayout implements View.OnClickList
                action,
                mStackView.getNormalizedXPosition(),
                mStackView.getNormalizedYPosition(),
                entry.showInShadeWhenBubble(),
                entry.isForegroundService(),
                bubble.showInShadeWhenBubble(),
                bubble.isOngoing(),
                false /* isAppForeground (unused) */);
    }

    private int getDimenForPackageUser(int resId, String pkg, int userId) {
        Resources r;
        if (pkg != null) {
            try {
                if (userId == UserHandle.USER_ALL) {
                    userId = UserHandle.USER_SYSTEM;
                }
                r = mPm.getResourcesForApplicationAsUser(pkg, userId);
                return r.getDimensionPixelSize(resId);
            } catch (PackageManager.NameNotFoundException ex) {
                // Uninstalled, don't care
            } catch (Resources.NotFoundException e) {
                // Invalid res id, return 0 and user our default
                Log.e(TAG, "Couldn't find desired height res id", e);
            }
        }
        return 0;
    }
}
+11 −24
Original line number Diff line number Diff line
@@ -401,6 +401,7 @@ public class BubbleStackView extends FrameLayout {
            if (!mIsExpanded || mIsExpansionAnimating) {
                return view.onApplyWindowInsets(insets);
            }

            float newY = getExpandedViewY();
            if (newY < 0) {
                // TODO: This means our expanded content is too big to fit on screen. Right now
@@ -675,24 +676,11 @@ public class BubbleStackView extends FrameLayout {
        Bubble bubbleToExpand = mBubbleData.getBubbleWithKey(key);
        if (bubbleToExpand != null) {
            setSelectedBubble(bubbleToExpand);
            bubbleToExpand.getEntry().setShowInShadeWhenBubble(false);
            bubbleToExpand.setShowInShadeWhenBubble(false);
            setExpanded(true);
        }
    }

    /**
     * Sets the entry that should be expanded and expands if needed.
     */
    @VisibleForTesting
    void setExpandedBubble(NotificationEntry entry) {
        for (int i = 0; i < mBubbleContainer.getChildCount(); i++) {
            BubbleView bv = (BubbleView) mBubbleContainer.getChildAt(i);
            if (entry.equals(bv.getEntry())) {
                setExpandedBubble(entry.key);
            }
        }
    }

    // via BubbleData.Listener
    void addBubble(Bubble bubble) {
        if (DEBUG_BUBBLE_STACK_VIEW) {
@@ -774,9 +762,8 @@ public class BubbleStackView extends FrameLayout {
                requestUpdate();
                logBubbleEvent(previouslySelected, StatsLog.BUBBLE_UICHANGED__ACTION__COLLAPSED);
                logBubbleEvent(bubbleToSelect, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
                notifyExpansionChanged(previouslySelected.getEntry(), false /* expanded */);
                notifyExpansionChanged(bubbleToSelect == null ? null : bubbleToSelect.getEntry(),
                        true /* expanded */);
                notifyExpansionChanged(previouslySelected, false /* expanded */);
                notifyExpansionChanged(bubbleToSelect, true /* expanded */);
            });
        }
    }
@@ -803,7 +790,7 @@ public class BubbleStackView extends FrameLayout {
            logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__EXPANDED);
            logBubbleEvent(mExpandedBubble, StatsLog.BUBBLE_UICHANGED__ACTION__STACK_EXPANDED);
        }
        notifyExpansionChanged(mExpandedBubble.getEntry(), mIsExpanded);
        notifyExpansionChanged(mExpandedBubble, mIsExpanded);
    }

    /**
@@ -965,9 +952,9 @@ public class BubbleStackView extends FrameLayout {
                mExpandedAnimateYDistance);
    }

    private void notifyExpansionChanged(NotificationEntry entry, boolean expanded) {
        if (mExpandListener != null) {
            mExpandListener.onBubbleExpandChanged(expanded, entry != null ? entry.key : null);
    private void notifyExpansionChanged(Bubble bubble, boolean expanded) {
        if (mExpandListener != null && bubble != null) {
            mExpandListener.onBubbleExpandChanged(expanded, bubble.getKey());
        }
    }

@@ -1353,7 +1340,7 @@ public class BubbleStackView extends FrameLayout {
     */
    @VisibleForTesting
    void animateInFlyoutForBubble(Bubble bubble) {
        final CharSequence updateMessage = bubble.getEntry().getUpdateMessage(getContext());
        final CharSequence updateMessage = bubble.getUpdateMessage(getContext());

        // Show the message if one exists, and we're not expanded or animating expansion.
        if (updateMessage != null
@@ -1631,8 +1618,8 @@ public class BubbleStackView extends FrameLayout {
                    action,
                    getNormalizedXPosition(),
                    getNormalizedYPosition(),
                    bubble.getEntry().showInShadeWhenBubble(),
                    bubble.getEntry().isForegroundService(),
                    bubble.showInShadeWhenBubble(),
                    bubble.isOngoing(),
                    false /* isAppForeground (unused) */);
        }
    }
+21 −27

File changed.

Preview size limit exceeded, changes collapsed.

Loading