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

Commit ce7451cd authored by Kevin Han's avatar Kevin Han
Browse files

Move control logic out of NotificationContentInflater

NotificationContentInflater's sole responsibility should be to provide
an API to inflate views based off the inflation parameters set on it.
Currently, this is violated by several setters that automatically
trigger inflations and its secondary role as the inflation flag source
of truth. This blurs the class's responsibility and makes it harder to
use, so we move it out.

Right now, most of this control logic simply went one level up to
ExpandableNotificationRow which realistically also isn't where it should
be ultimately. However, this is the most reasonable place for it to live
in the interim while we slowly migrate inflation/bind logic out of
ExpandableNotificaitonRow piece by piece.

Bug: 145749521
Test: atest SystemUITests
Test: smoke test
Change-Id: Ia51a6db667f0aca5b410ab7f769d061f8e9a617e
parent 9908f047
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -258,7 +258,7 @@ public class NotificationRowBinderImpl implements NotificationRowBinder {
        row.setEntry(entry);

        if (mNotificationInterruptionStateProvider.shouldHeadsUp(entry)) {
            row.updateInflationFlag(FLAG_CONTENT_VIEW_HEADS_UP, true /* shouldInflate */);
            row.setInflationFlags(FLAG_CONTENT_VIEW_HEADS_UP);
        }
        row.setNeedsRedaction(
                Dependency.get(NotificationLockscreenUserManager.class).needsRedaction(entry));
+56 −14
Original line number Diff line number Diff line
@@ -17,9 +17,10 @@
package com.android.systemui.statusbar.notification.row;

import static com.android.systemui.statusbar.notification.ActivityLaunchAnimator.ExpandAnimationParameters;
import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_HEADS_UP;
import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.FLAG_CONTENT_VIEW_PUBLIC;
import static com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationCallback;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;

@@ -88,6 +89,7 @@ import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.logging.NotificationCounters;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationCallback;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
import com.android.systemui.statusbar.notification.stack.AmbientState;
@@ -124,8 +126,18 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
    private static final String TAG = "ExpandableNotifRow";
    public static final float DEFAULT_HEADER_VISIBLE_AMOUNT = 1.0f;
    private static final long RECENTLY_ALERTED_THRESHOLD_MS = TimeUnit.SECONDS.toMillis(30);

    /**
     * Content views that must be inflated at all times.
     */
    @InflationFlag
    static final int REQUIRED_INFLATION_FLAGS =
            FLAG_CONTENT_VIEW_CONTRACTED
            | FLAG_CONTENT_VIEW_EXPANDED;

    private boolean mUpdateBackgroundOnUpdate;
    private boolean mNotificationTranslationFinished = false;

    /**
     * Listener for when {@link ExpandableNotificationRow} is laid out.
     */
@@ -232,6 +244,9 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
    private ExpandableNotificationRow mNotificationParent;
    private OnExpandClickListener mOnExpandClickListener;
    private View.OnClickListener mOnAppOpsClickListener;
    private InflationCallback mInflationCallback;
    private boolean mIsChildInGroup;
    private @InflationFlag int mInflationFlags = REQUIRED_INFLATION_FLAGS;

    // Listener will be called when receiving a long click event.
    // Use #setLongPressPosition to optionally assign positional data with the long press.
@@ -447,7 +462,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
     * Inflate views based off the inflation flags set. Inflation happens asynchronously.
     */
    public void inflateViews() {
        mNotificationInflater.inflateNotificationViews();
        mNotificationInflater.inflateNotificationViews(mInflationFlags, false /* forceInflate */,
                mInflationCallback);
    }

    /**
@@ -458,7 +474,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
     */
    public void freeContentViewWhenSafe(@InflationFlag int inflationFlag) {
        // View should not be reinflated in the future
        updateInflationFlag(inflationFlag, false);
        clearInflationFlags(inflationFlag);
        Runnable freeViewRunnable = () ->
                mNotificationInflater.freeNotificationView(inflationFlag);
        switch (inflationFlag) {
@@ -475,13 +491,22 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
    }

    /**
     * Update whether or not a content view should be inflated.
     * Set flags for content views that should be inflated
     *
     * @param flag the flag corresponding to the content view
     * @param shouldInflate true if it should be inflated, false if it should not
     * @param flags flags to inflate
     */
    public void updateInflationFlag(@InflationFlag int flag, boolean shouldInflate) {
        mNotificationInflater.updateInflationFlag(flag, shouldInflate);
    public void setInflationFlags(@InflationFlag int flags) {
        mInflationFlags |= flags;
    }

    /**
     * Clear flags for content views that should not be inflated
     *
     * @param flags flags that should not be inflated
     */
    public void clearInflationFlags(@InflationFlag int flags) {
        mInflationFlags &= ~flags;
        mInflationFlags |= REQUIRED_INFLATION_FLAGS;
    }

    /**
@@ -491,7 +516,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
     * @return true if the flag is set, false otherwise
     */
    public boolean isInflationFlagSet(@InflationFlag int flag) {
        return mNotificationInflater.isInflationFlagSet(flag);
        return ((mInflationFlags & flag) != 0);
    }

    /**
@@ -821,6 +846,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
        mNotificationParent = isChildInGroup ? parent : null;
        mPrivateLayout.setIsChildInGroup(isChildInGroup);
        mNotificationInflater.setIsChildInGroup(isChildInGroup);
        if (mIsChildInGroup != isChildInGroup) {
            mIsChildInGroup = isChildInGroup;
            if (mIsLowPriority) {
                int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
                mNotificationInflater.inflateNotificationViews(flags, false /* forceInflate */,
                        mInflationCallback);
            }
        }
        resetBackgroundAlpha();
        updateBackgroundForGroupState();
        updateClickAndFocus();
@@ -1224,7 +1257,8 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
            l.reInflateViews();
        }
        mEntry.getSbn().clearPackageContext();
        mNotificationInflater.clearCachesAndReInflate();
        mNotificationInflater.inflateNotificationViews(mInflationFlags, true /* forceInflate */,
                mInflationCallback);
    }

    @Override
@@ -1602,16 +1636,24 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
        mNotificationInflater.setRemoteViewClickHandler(remoteViewClickHandler);
    }

    /**
     * Set callback for notification content inflation
     *
     * @param callback inflation callback
     */
    public void setInflationCallback(InflationCallback callback) {
        mNotificationInflater.setInflationCallback(callback);
        mInflationCallback = callback;
    }

    public void setNeedsRedaction(boolean needsRedaction) {
        if (mNeedsRedaction != needsRedaction) {
            mNeedsRedaction = needsRedaction;
            updateInflationFlag(FLAG_CONTENT_VIEW_PUBLIC, needsRedaction /* shouldInflate */);
            mNotificationInflater.updateNeedsRedaction(needsRedaction);
            if (!needsRedaction) {
            if (needsRedaction) {
                setInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
                mNotificationInflater.inflateNotificationViews(FLAG_CONTENT_VIEW_PUBLIC,
                        false /* forceInflate */, mInflationCallback);
            } else {
                clearInflationFlags(FLAG_CONTENT_VIEW_PUBLIC);
                freeContentViewWhenSafe(FLAG_CONTENT_VIEW_PUBLIC);
            }
        }
+24 −110
Original line number Diff line number Diff line
@@ -93,27 +93,12 @@ public class NotificationContentInflater {

    public static final int FLAG_CONTENT_VIEW_ALL = ~0;

    /**
     * Content views that must be inflated at all times.
     */
    @InflationFlag
    private static final int REQUIRED_INFLATION_FLAGS =
            FLAG_CONTENT_VIEW_CONTRACTED
            | FLAG_CONTENT_VIEW_EXPANDED;

    /**
     * The set of content views to inflate.
     */
    @InflationFlag
    private int mInflationFlags = REQUIRED_INFLATION_FLAGS;

    private final ExpandableNotificationRow mRow;
    private boolean mIsLowPriority;
    private boolean mUsesIncreasedHeight;
    private boolean mUsesIncreasedHeadsUpHeight;
    private RemoteViews.OnClickHandler mRemoteViewClickHandler;
    private boolean mIsChildInGroup;
    private InflationCallback mCallback;
    private boolean mInflateSynchronously = false;
    private final ArrayMap<Integer, RemoteViews> mCachedContentViews = new ArrayMap<>();

@@ -131,13 +116,7 @@ public class NotificationContentInflater {
     * @return whether the view was re-inflated
     */
    public void setIsChildInGroup(boolean childInGroup) {
        if (childInGroup != mIsChildInGroup) {
        mIsChildInGroup = childInGroup;
            if (mIsLowPriority) {
                int flags = FLAG_CONTENT_VIEW_CONTRACTED | FLAG_CONTENT_VIEW_EXPANDED;
                inflateNotificationViews(flags);
            }
        }
    }

    public void setUsesIncreasedHeight(boolean usesIncreasedHeight) {
@@ -153,99 +132,43 @@ public class NotificationContentInflater {
    }

    /**
     * Update whether or not the notification is redacted on the lock screen.  If the notification
     * is now redacted, we should inflate the public contracted view to now show on the lock screen.
     *
     * @param needsRedaction true if the notification should now be redacted on the lock screen
     */
    public void updateNeedsRedaction(boolean needsRedaction) {
        if (mRow.getEntry() == null) {
            return;
        }
        if (needsRedaction) {
            int flags = FLAG_CONTENT_VIEW_PUBLIC;
            inflateNotificationViews(flags);
        }
    }

    /**
     * Set whether or not a particular content view is needed and whether or not it should be
     * inflated.  These flags will be used when we inflate or reinflate.
     *
     * @param flag the {@link InflationFlag} corresponding to the view that should/should not be
     *             inflated
     * @param shouldInflate true if the view should be inflated, false otherwise
     */
    public void updateInflationFlag(@InflationFlag int flag, boolean shouldInflate) {
        if (shouldInflate) {
            mInflationFlags |= flag;
        } else if ((REQUIRED_INFLATION_FLAGS & flag) == 0) {
            mInflationFlags &= ~flag;
        }
    }

    /**
     * Convenience method for setting multiple flags at once.
     * Inflate notification content views and bind to the row
     *
     * @param flags a set of {@link InflationFlag} corresponding to content views that should be
     *              inflated
     * @param contentToBind content views that should be inflated and bound
     * @param forceInflate true to force reinflation even if views are cached
     * @param callback callback after inflation is finished
     */
    @VisibleForTesting
    public void addInflationFlags(@InflationFlag int flags) {
        mInflationFlags |= flags;
    }

    /**
     * Whether or not the view corresponding to the flag is set to be inflated currently.
     *
     * @param flag the {@link InflationFlag} corresponding to the view
     * @return true if the flag is set and view will be inflated, false o/w
     */
    public boolean isInflationFlagSet(@InflationFlag int flag) {
        return ((mInflationFlags & flag) != 0);
    }

    /**
     * Inflate views for set flags on a background thread. This is asynchronous and will
     * notify the callback once it's finished.
     */
    public void inflateNotificationViews() {
        inflateNotificationViews(mInflationFlags);
    }

    /**
     * Inflate all views for the specified flags on a background thread.  This is asynchronous and
     * will notify the callback once it's finished.  If the content view is already inflated, this
     * will reinflate it.
     *
     * @param reInflateFlags flags which views should be inflated. Should be a subset of
     *                       {@link #mInflationFlags} as only those will be inflated/reinflated.
     */
    private void inflateNotificationViews(@InflationFlag int reInflateFlags) {
    void inflateNotificationViews(
            @InflationFlag int contentToBind,
            boolean forceInflate,
            @Nullable InflationCallback callback) {
        if (mRow.isRemoved()) {
            // We don't want to reinflate anything for removed notifications. Otherwise views might
            // be readded to the stack, leading to leaks. This may happen with low-priority groups
            // where the removal of already removed children can lead to a reinflation.
            return;
        }
        // Only inflate the ones that are set.
        reInflateFlags &= mInflationFlags;

        StatusBarNotification sbn = mRow.getEntry().getSbn();

        // To check if the notification has inline image and preload inline image if necessary.
        mRow.getImageResolver().preloadImages(sbn.getNotification());

        if (forceInflate) {
            mCachedContentViews.clear();
        }

        AsyncInflationTask task = new AsyncInflationTask(
                sbn,
                mInflateSynchronously,
                reInflateFlags,
                contentToBind,
                mCachedContentViews,
                mRow,
                mIsLowPriority,
                mIsChildInGroup,
                mUsesIncreasedHeight,
                mUsesIncreasedHeadsUpHeight,
                mCallback,
                callback,
                mRemoteViewClickHandler);
        if (mInflateSynchronously) {
            task.onPostExecute(task.doInBackground());
@@ -284,10 +207,6 @@ public class NotificationContentInflater {
     * @param inflateFlag the flag corresponding to the content view which should be freed
     */
    public void freeNotificationView(@InflationFlag int inflateFlag) {
        if ((mInflationFlags & inflateFlag) != 0) {
            // The view should still be inflated.
            return;
        }
        switch (inflateFlag) {
            case FLAG_CONTENT_VIEW_HEADS_UP:
                if (mRow.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
@@ -718,10 +637,6 @@ public class NotificationContentInflater {
                        && !oldView.hasFlags(RemoteViews.FLAG_REAPPLY_DISALLOWED));
    }

    public void setInflationCallback(InflationCallback callback) {
        mCallback = callback;
    }

    public interface InflationCallback {
        void handleInflationException(StatusBarNotification notification, Exception e);

@@ -734,17 +649,12 @@ public class NotificationContentInflater {
        void onAsyncInflationFinished(NotificationEntry entry, @InflationFlag int inflatedFlags);
    }

    public void clearCachesAndReInflate() {
        mCachedContentViews.clear();
        inflateNotificationViews();
    }

    /**
     * Sets whether to perform inflation on the same thread as the caller. This method should only
     * be used in tests, not in production.
     */
    @VisibleForTesting
    void setInflateSynchronously(boolean inflateSynchronously) {
    public void setInflateSynchronously(boolean inflateSynchronously) {
        mInflateSynchronously = inflateSynchronously;
    }

@@ -842,9 +752,11 @@ public class NotificationContentInflater {
            final String ident = sbn.getPackageName() + "/0x"
                    + Integer.toHexString(sbn.getId());
            Log.e(StatusBar.TAG, "couldn't inflate view for notification " + ident, e);
            if (mCallback != null) {
                mCallback.handleInflationException(sbn,
                        new InflationException("Couldn't inflate contentViews" + e));
            }
        }

        @Override
        public void abort() {
@@ -872,7 +784,9 @@ public class NotificationContentInflater {
                @InflationFlag int inflatedFlags) {
            mRow.getEntry().onInflationTaskFinished();
            mRow.onNotificationUpdated();
            if (mCallback != null) {
                mCallback.onAsyncInflationFinished(mRow.getEntry(), inflatedFlags);
            }

            // Notify the resolver that the inflation task has finished,
            // try to purge unnecessary cached entries.
+1 −1
Original line number Diff line number Diff line
@@ -395,7 +395,7 @@ public class NotificationGroupAlertTransferHelper implements OnHeadsUpChangedLis
        @InflationFlag int contentFlag = alertManager.getContentFlag();
        if (!entry.getRow().isInflationFlagSet(contentFlag)) {
            mPendingAlerts.put(entry.getKey(), new PendingAlertInfo(entry));
            entry.getRow().updateInflationFlag(contentFlag, true /* shouldInflate */);
            entry.getRow().setInflationFlags(contentFlag);
            entry.getRow().inflateViews();
            return;
        }
+29 −5
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH;

import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;

import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.mock;

import android.annotation.Nullable;
@@ -46,14 +47,17 @@ import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater;
import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag;
import com.android.systemui.statusbar.notification.row.NotificationContentInflaterTest;
import com.android.systemui.statusbar.phone.HeadsUpManagerPhone;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.NotificationGroupManager;
import com.android.systemui.statusbar.phone.StatusBarWindowController;
import com.android.systemui.tests.R;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * A helper class to create {@link ExpandableNotificationRow} (for both individual and group
 * notifications).
@@ -325,10 +329,8 @@ public class NotificationTestHelper {
        entry.setRow(row);
        entry.createIcons(mContext, entry.getSbn());
        row.setEntry(entry);
        row.getNotificationInflater().addInflationFlags(extraInflationFlags);
        NotificationContentInflaterTest.runThenWaitForInflation(
                () -> row.inflateViews(),
                row.getNotificationInflater());
        row.setInflationFlags(extraInflationFlags);
        inflateAndWait(row);

        // This would be done as part of onAsyncInflationFinished, but we skip large amounts of
        // the callback chain, so we need to make up for not adding it to the group manager
@@ -337,6 +339,28 @@ public class NotificationTestHelper {
        return row;
    }

    private static void inflateAndWait(ExpandableNotificationRow row) throws Exception {
        CountDownLatch countDownLatch = new CountDownLatch(1);
        row.getNotificationInflater().setInflateSynchronously(true);
        NotificationContentInflater.InflationCallback callback =
                new NotificationContentInflater.InflationCallback() {
                    @Override
                    public void handleInflationException(StatusBarNotification notification,
                            Exception e) {
                        countDownLatch.countDown();
                    }

                    @Override
                    public void onAsyncInflationFinished(NotificationEntry entry,
                            int inflatedFlags) {
                        countDownLatch.countDown();
                    }
                };
        row.setInflationCallback(callback);
        row.inflateViews();
        assertTrue(countDownLatch.await(500, TimeUnit.MILLISECONDS));
    }

    private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent) {
        Intent target = new Intent(mContext, BubblesTestActivity.class);
        PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, 0);
Loading