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

Commit d8b5154b authored by Ned Burns's avatar Ned Burns
Browse files

Move icon logic out of NotificationEntry

It's been on the list of things to remove for a while and now the
conversations feature has some extra needs that can't be addressed by
the existing system.

Moves all logic into IconManager. Icons are still stored on the
NotificationEntry (boo) but they're all encapsulated in a getIcons()
object. All of this logic should eventually move into the content
inflation pipeline (probably?) but for now at least it's self-contained.

Bug: 112656837
Test: atest SystemUITests
Change-Id: Iecbd8999c2c866c7217ad9d0e090fb1db7ddd690
parent 88aeaf7d
Loading
Loading
Loading
Loading
+17 −6
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import com.android.keyguard.AlphaOptimizedLinearLayout;
import com.android.systemui.R;
import com.android.systemui.plugins.DarkIconDispatcher;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry.OnSensitivityChangedListener;

import java.util.List;

@@ -159,20 +160,30 @@ public class HeadsUpStatusBarView extends AlphaOptimizedLinearLayout {
    }

    public void setEntry(NotificationEntry entry) {
        if (entry != null) {
        if (mShowingEntry != null) {
            mShowingEntry.removeOnSensitivityChangedListener(mOnSensitivityChangedListener);
        }
        mShowingEntry = entry;

        if (mShowingEntry != null) {
            CharSequence text = entry.headsUpStatusBarText;
            if (entry.isSensitive()) {
                text = entry.headsUpStatusBarTextPublic;
            }
            mTextView.setText(text);
            mShowingEntry.setOnSensitiveChangedListener(() -> setEntry(entry));
        } else if (mShowingEntry != null){
            mShowingEntry.setOnSensitiveChangedListener(null);
            mShowingEntry = null;
            mShowingEntry.addOnSensitivityChangedListener(mOnSensitivityChangedListener);
        }
    }

    private final OnSensitivityChangedListener mOnSensitivityChangedListener = entry -> {
        if (entry != mShowingEntry) {
            throw new IllegalStateException("Got a sensitivity change for " + entry
                    + " but mShowingEntry is " + mShowingEntry);
        }
        // Update the text
        setEntry(entry);
    };

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
+2 −2
Original line number Diff line number Diff line
@@ -263,11 +263,11 @@ public class NotificationMediaManager implements Dumpable {
        synchronized (mEntryManager) {
            NotificationEntry entry = mEntryManager
                    .getActiveNotificationUnfiltered(mMediaNotificationKey);
            if (entry == null || entry.expandedIcon == null) {
            if (entry == null || entry.getIcons().getShelfIcon() == null) {
                return null;
            }

            return entry.expandedIcon.getSourceIcon();
            return entry.getIcons().getShelfIcon().getSourceIcon();
        }
    }

+3 −3
Original line number Diff line number Diff line
@@ -329,7 +329,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
                    expandableRow.setAboveShelf(false);
                }
                if (notGoneIndex == 0) {
                    StatusBarIconView icon = expandableRow.getEntry().expandedIcon;
                    StatusBarIconView icon = expandableRow.getEntry().getIcons().getShelfIcon();
                    NotificationIconContainer.IconState iconState = getIconState(icon);
                    // The icon state might be null in rare cases where the notification is actually
                    // added to the layout, but not to the shelf. An example are replied messages,
@@ -432,7 +432,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
            // if the shelf is clipped, lets make sure we also clip the icon
            maxTop = Math.max(maxTop, getTranslationY() + getClipTopAmount());
        }
        StatusBarIconView icon = row.getEntry().expandedIcon;
        StatusBarIconView icon = row.getEntry().getIcons().getShelfIcon();
        float shelfIconPosition = getTranslationY() + icon.getTop() + icon.getTranslationY();
        if (shelfIconPosition < maxTop && !mAmbientState.isFullyHidden()) {
            int top = (int) (maxTop - shelfIconPosition);
@@ -444,7 +444,7 @@ public class NotificationShelf extends ActivatableNotificationView implements
    }

    private void updateContinuousClipping(final ExpandableNotificationRow row) {
        StatusBarIconView icon = row.getEntry().expandedIcon;
        StatusBarIconView icon = row.getEntry().getIcons().getShelfIcon();
        boolean needsContinuousClipping = ViewState.isAnimatingY(icon) && !mAmbientState.isDozing();
        boolean isContinuousClipping = icon.getTag(TAG_CONTINUOUS_CLIPPING) != null;
        if (needsContinuousClipping && !isContinuousClipping) {
+1 −1
Original line number Diff line number Diff line
@@ -841,7 +841,7 @@ public class NotificationEntryManager implements

    private void dumpEntry(PrintWriter pw, String indent, int i, NotificationEntry e) {
        pw.print(indent);
        pw.println("  [" + i + "] key=" + e.getKey() + " icon=" + e.icon);
        pw.println("  [" + i + "] key=" + e.getKey() + " icon=" + e.getIcons().getStatusBarIcon());
        StatusBarNotification n = e.getSbn();
        pw.print(indent);
        pw.println("      pkg=" + n.getPackageName() + " id=" + n.getId() + " importance="
+29 −260
Original line number Diff line number Diff line
@@ -29,8 +29,6 @@ import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_FULL_SCRE
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_NOTIFICATION_LIST;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_PEEK;
import static android.app.NotificationManager.Policy.SUPPRESSED_EFFECT_STATUS_BAR;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;

import static com.android.systemui.statusbar.notification.collection.NotifCollection.REASON_NOT_CANCELED;
import static com.android.systemui.statusbar.notification.stack.NotificationSectionsManager.BUCKET_ALERTING;
@@ -44,10 +42,7 @@ import android.app.NotificationManager.Policy;
import android.app.Person;
import android.app.RemoteInputHistoryItem;
import android.content.Context;
import android.content.pm.LauncherApps;
import android.content.pm.LauncherApps.ShortcutQuery;
import android.content.pm.ShortcutInfo;
import android.graphics.drawable.Icon;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
@@ -55,25 +50,20 @@ import android.service.notification.NotificationListenerService.Ranking;
import android.service.notification.SnoozeCriterion;
import android.service.notification.StatusBarNotification;
import android.util.ArraySet;
import android.util.Log;
import android.view.View;
import android.widget.ImageView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.statusbar.StatusBarIcon;
import com.android.internal.util.ArrayUtils;
import com.android.internal.util.ContrastColorUtil;
import com.android.systemui.statusbar.InflationTask;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.InflationException;
import com.android.systemui.statusbar.notification.collection.NotifCollection.CancellationReason;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifFilter;
import com.android.systemui.statusbar.notification.collection.listbuilder.pluggable.NotifPromoter;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifDismissInterceptor;
import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender;
import com.android.systemui.statusbar.notification.icon.IconPack;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRowController;
import com.android.systemui.statusbar.notification.row.NotificationGuts;
@@ -81,8 +71,6 @@ import com.android.systemui.statusbar.notification.row.NotificationRowContentBin
import com.android.systemui.statusbar.notification.stack.NotificationSectionsManager;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

@@ -102,15 +90,11 @@ import java.util.Objects;
 * clean this up in the future.
 */
public final class NotificationEntry extends ListEntry {
    private static final String TAG = "NotificationEntry";

    private final String mKey;
    private StatusBarNotification mSbn;
    private Ranking mRanking;

    private StatusBarIcon mSmallIcon;
    private StatusBarIcon mPeopleAvatar;

    /*
     * Bookkeeping members
     */
@@ -142,10 +126,7 @@ public final class NotificationEntry extends ListEntry {
    * TODO: Remove every member beneath this line if possible
    */

    public StatusBarIconView icon;
    public StatusBarIconView expandedIcon;
    public StatusBarIconView centeredIcon;
    public StatusBarIconView aodIcon;
    private IconPack mIcons = IconPack.buildEmptyPack(null);
    private boolean interruption;
    public int targetSdk;
    private long lastFullScreenIntentLaunchTime = NOT_LAUNCHED_YET;
@@ -191,7 +172,8 @@ public final class NotificationEntry extends ListEntry {
    private boolean hasSentReply;

    private boolean mSensitive = true;
    private Runnable mOnSensitiveChangedListener;
    private List<OnSensitivityChangedListener> mOnSensitivityChangedListeners = new ArrayList<>();

    private boolean mAutoHeadsUp;
    private boolean mPulseSupressed;
    private boolean mAllowFgsDismissal;
@@ -347,6 +329,15 @@ public final class NotificationEntry extends ListEntry {
     * TODO: Remove as many of these as possible
     */

    @NonNull
    public IconPack getIcons() {
        return mIcons;
    }

    public void setIcons(@NonNull IconPack icons) {
        mIcons = icons;
    }

    public void setInterruption() {
        interruption = true;
    }
@@ -464,239 +455,6 @@ public final class NotificationEntry extends ListEntry {
                || SystemClock.elapsedRealtime() > initializationTime + INITIALIZATION_DELAY;
    }

    /**
     * Create the icons for a notification
     * @param context the context to create the icons with
     * @param sbn the notification
     * @throws InflationException Exception if required icons are not valid or specified
     */
    public void createIcons(Context context, StatusBarNotification sbn)
            throws InflationException {
        StatusBarIcon ic = getIcon(context, sbn, false /* redact */);

        // Construct the icon.
        icon = new StatusBarIconView(context,
                sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
        icon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);

        // Construct the expanded icon.
        expandedIcon = new StatusBarIconView(context,
                sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
        expandedIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);

        // Construct the expanded icon.
        aodIcon = new StatusBarIconView(context,
                sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
        aodIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        aodIcon.setIncreasedSize(true);

        try {
            setIcons(ic, Collections.singletonList(icon));
            if (isSensitive()) {
                ic = getIcon(context, sbn, true /* redact */);
            }
            setIcons(ic, Arrays.asList(expandedIcon, aodIcon));
        } catch (InflationException e) {
            icon = null;
            expandedIcon = null;
            centeredIcon = null;
            aodIcon = null;
            throw e;
        }

        expandedIcon.setVisibility(View.INVISIBLE);
        expandedIcon.setOnVisibilityChangedListener(
                newVisibility -> {
                    if (row != null) {
                        row.setIconsVisible(newVisibility != View.VISIBLE);
                    }
                });

        // Construct the centered icon
        if (mSbn.getNotification().isMediaNotification()) {
            centeredIcon = new StatusBarIconView(context,
                    sbn.getPackageName() + "/0x" + Integer.toHexString(sbn.getId()), sbn);
            centeredIcon.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
            try {
                setIcons(ic, Collections.singletonList(centeredIcon));
            } catch (InflationException e) {
                centeredIcon = null;
                throw e;
            }
        }
    }

    /**
     * Determines if this icon should be tinted based on the sensitivity of the icon, its context
     * and the user's indicated sensitivity preference.
     *
     * @param ic The icon that should/should not be tinted.
     * @return
     */
    private boolean shouldTintIcon(StatusBarIconView ic) {
        boolean usedInSensitiveContext = (ic == expandedIcon || ic == aodIcon);
        return !isImportantConversation() || (usedInSensitiveContext && isSensitive());
    }


    private void setIcons(StatusBarIcon ic, List<StatusBarIconView> icons)
            throws InflationException {
        for (StatusBarIconView icon: icons) {
            if (icon == null) {
              continue;
            }
            icon.setTintIcons(shouldTintIcon(icon));
            if (!icon.set(ic)) {
                throw new InflationException("Couldn't create icon" + ic);
            }
        }
    }

    private StatusBarIcon getIcon(Context context, StatusBarNotification sbn, boolean redact)
            throws InflationException {
        Notification n = sbn.getNotification();
        final boolean showPeopleAvatar = isImportantConversation() && !redact;

        // If cached, return corresponding cached values
        if (showPeopleAvatar && mPeopleAvatar != null) {
            return mPeopleAvatar;
        } else if (!showPeopleAvatar && mSmallIcon != null) {
            return mSmallIcon;
        }

        Icon icon = showPeopleAvatar ? createPeopleAvatar(context) : n.getSmallIcon();
        if (icon == null) {
            throw new InflationException("No icon in notification from " + sbn.getPackageName());
        }

        StatusBarIcon ic = new StatusBarIcon(
                    sbn.getUser(),
                    sbn.getPackageName(),
                    icon,
                    n.iconLevel,
                    n.number,
                    StatusBarIconView.contentDescForNotification(context, n));

        // Cache if important conversation.
        if (isImportantConversation()) {
            if (showPeopleAvatar) {
                mPeopleAvatar = ic;
            } else {
                mSmallIcon = ic;
            }
        }
        return ic;
    }

    private Icon createPeopleAvatar(Context context) throws InflationException {
        // Attempt to extract form shortcut.
        String conversationId = getChannel().getConversationId();
        ShortcutQuery query = new ShortcutQuery()
                .setPackage(mSbn.getPackageName())
                .setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED)
                .setShortcutIds(Collections.singletonList(conversationId));
        List<ShortcutInfo> shortcuts = context.getSystemService(LauncherApps.class)
                .getShortcuts(query, mSbn.getUser());
        Icon ic = null;
        if (shortcuts != null && !shortcuts.isEmpty()) {
            ic = shortcuts.get(0).getIcon();
        }

        // Fall back to notification large icon if available
        if (ic == null) {
            ic = mSbn.getNotification().getLargeIcon();
        }

        // Fall back to extract from message
        if (ic == null) {
            Bundle extras = mSbn.getNotification().extras;
            List<Message> messages = Message.getMessagesFromBundleArray(
                    extras.getParcelableArray(Notification.EXTRA_MESSAGES));
            Person user = extras.getParcelable(Notification.EXTRA_MESSAGING_PERSON);

            for (int i = messages.size() - 1; i >= 0; i--) {
                Message message = messages.get(i);
                Person sender = message.getSenderPerson();
                if (sender != null && sender != user) {
                    ic = message.getSenderPerson().getIcon();
                    break;
                }
            }
        }

        // Revert to small icon if still not available
        if (ic == null) {
            ic = mSbn.getNotification().getSmallIcon();
        }
        if (ic == null) {
            throw new InflationException("No icon in notification from " + mSbn.getPackageName());
        }
        return ic;
    }

    private void updateSensitiveIconState() {
        try {
            StatusBarIcon ic = getIcon(getRow().getContext(), mSbn, isSensitive());
            setIcons(ic, Arrays.asList(expandedIcon, aodIcon));
        } catch (InflationException e) {
            if (Log.isLoggable(TAG, Log.DEBUG)) {
                Log.d(TAG, "Unable to update icon", e);
            }
        }
    }

    public void setIconTag(int key, Object tag) {
        if (icon != null) {
            icon.setTag(key, tag);
            expandedIcon.setTag(key, tag);
        }

        if (centeredIcon != null) {
            centeredIcon.setTag(key, tag);
        }

        if (aodIcon != null) {
            aodIcon.setTag(key, tag);
        }
    }

    /**
     * Update the notification icons.
     *
     * @param context the context to create the icons with.
     * @param sbn the notification to read the icon from.
     * @throws InflationException Exception if required icons are not valid or specified
     */
    public void updateIcons(Context context, StatusBarNotification sbn)
            throws InflationException {
        if (icon != null) {
            // Update the icon
            mSmallIcon = null;
            mPeopleAvatar = null;

            StatusBarIcon ic = getIcon(context, sbn, false /* redact */);

            icon.setNotification(sbn);
            expandedIcon.setNotification(sbn);
            aodIcon.setNotification(sbn);
            setIcons(ic, Arrays.asList(icon, expandedIcon));

            if (isSensitive()) {
                ic = getIcon(context, sbn, true /* redact */);
            }
            setIcons(ic, Collections.singletonList(aodIcon));

            if (centeredIcon != null) {
                centeredIcon.setNotification(sbn);
                setIcons(ic, Collections.singletonList(centeredIcon));
            }
        }
    }

    private boolean isImportantConversation() {
        return getChannel() != null && getChannel().isImportantConversation();
    }

    public int getContrastedColor(Context context, boolean isLowPriority,
            int backgroundColor) {
        int rawColor = isLowPriority ? Notification.COLOR_DEFAULT :
@@ -1125,9 +883,8 @@ public final class NotificationEntry extends ListEntry {
        getRow().setSensitive(sensitive, deviceSensitive);
        if (sensitive != mSensitive) {
            mSensitive = sensitive;
            updateSensitiveIconState();
            if (mOnSensitiveChangedListener != null) {
                mOnSensitiveChangedListener.run();
            for (int i = 0; i < mOnSensitivityChangedListeners.size(); i++) {
                mOnSensitivityChangedListeners.get(i).onSensitivityChanged(this);
            }
        }
    }
@@ -1136,8 +893,14 @@ public final class NotificationEntry extends ListEntry {
        return mSensitive;
    }

    public void setOnSensitiveChangedListener(Runnable listener) {
        mOnSensitiveChangedListener = listener;
    /** Add a listener to be notified when the entry's sensitivity changes. */
    public void addOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
        mOnSensitivityChangedListeners.add(listener);
    }

    /** Remove a listener that was registered above. */
    public void removeOnSensitivityChangedListener(OnSensitivityChangedListener listener) {
        mOnSensitivityChangedListeners.remove(listener);
    }

    public boolean isPulseSuppressed() {
@@ -1167,6 +930,12 @@ public final class NotificationEntry extends ListEntry {
        }
    }

    /** Listener interface for {@link #addOnSensitivityChangedListener} */
    public interface OnSensitivityChangedListener {
        /** Called when the sensitivity changes */
        void onSensitivityChanged(@NonNull NotificationEntry entry);
    }

    /** @see #getDismissState() */
    public enum DismissState {
        /** User has not dismissed this notif or its parent */
Loading