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

Commit 8639dd97 authored by Jeff DeCew's avatar Jeff DeCew
Browse files

Reduce hardware render sizes for ExpandableNotificationRow.

Do not create a hardware layer for entire notifications during shade expand/collapse; instead just layer the elements with actually overlapping rendering.

Bug: 191969252
Test: lots of shade expansion; use of perfetto to validate phone reaches rendering deadlines more often.
Change-Id: I86b626fb61c5844b968be33d982e418bf8938de8
parent c4c2240e
Loading
Loading
Loading
Loading
+70 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.statusbar.notification;

import android.view.View;

import androidx.annotation.Nullable;

/**
 * Used to let views that have an alpha not apply the HARDWARE layer type directly, and instead
 * delegate that to specific children.  This is useful if we want to fake not having overlapping
 * rendering to avoid layer trashing, when fading out a view that is also changing.
 */
public interface NotificationFadeAware {
    /**
     * Calls {@link View#setLayerType} with {@link View#LAYER_TYPE_HARDWARE} if faded and
     * {@link View#LAYER_TYPE_NONE} otherwise.
     */
    static void setLayerTypeForFaded(@Nullable View view, boolean faded) {
        if (view != null) {
            int newLayerType = faded ? View.LAYER_TYPE_HARDWARE : View.LAYER_TYPE_NONE;
            view.setLayerType(newLayerType, null);
        }
    }

    /**
     * Used like {@link View#setLayerType} with {@link View#LAYER_TYPE_HARDWARE} or
     * {@link View#LAYER_TYPE_NONE} except that instead of necessarily affecting this view
     * specifically, this may delegate the call to child views.
     *
     * When set to <code>true</code>, the view has two possible paths:
     *  1. If a hardware layer is required to ensure correct appearance of this view, then
     *    set that layer type.
     *  2. Otherwise, delegate this call to children, who might make that call for themselves.
     *
     * When set to <code>false</code>, the view should undo the above, typically by calling
     *  {@link View#setLayerType} with {@link View#LAYER_TYPE_NONE} on itself and children, and
     *  delegating to this method on children where implemented.
     *
     * When this delegates to {@link View#setLayerType} on this view or a subview, `null` will be
     * passed for the `paint` argument of that call.
     */
    void setNotificationFaded(boolean faded);

    /**
     * Interface for the top level notification view that fades and optimizes that through deep
     * awareness of individual components.
     */
    interface FadeOptimizedNotification extends NotificationFadeAware {
        /** Top-level feature switch */
        boolean FADE_LAYER_OPTIMIZATION_ENABLED = true;

        /** Determine if the notification is currently faded. */
        boolean isNotificationFaded();
    }
}
+101 −3
Original line number Diff line number Diff line
@@ -92,6 +92,7 @@ import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.StatusBarIconView;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
@@ -131,7 +132,8 @@ import java.util.function.Consumer;
 * the group summary (which contains 1 or more child notifications).
 */
public class ExpandableNotificationRow extends ActivatableNotificationView
        implements PluginListener<NotificationMenuRowPlugin>, SwipeableView {
        implements PluginListener<NotificationMenuRowPlugin>, SwipeableView,
        NotificationFadeAware.FadeOptimizedNotification {

    private static final boolean DEBUG = false;
    private static final int DEFAULT_DIVIDER_ALPHA = 0x29;
@@ -144,6 +146,7 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
    private boolean mUpdateBackgroundOnUpdate;
    private boolean mNotificationTranslationFinished = false;
    private boolean mIsSnoozed;
    private boolean mIsFaded;

    /**
     * Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -2774,17 +2777,112 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
            // alphas are reset
            if (mChildrenContainer != null) {
                mChildrenContainer.setAlpha(1.0f);
                mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null);
            }
            for (NotificationContentView l : mLayouts) {
                l.setAlpha(1.0f);
                l.setLayerType(LAYER_TYPE_NONE, null);
            }
            if (FADE_LAYER_OPTIMIZATION_ENABLED) {
                setNotificationFaded(false);
            } else {
                setNotificationFadedOnChildren(false);
            }
        } else {
            setHeadsUpAnimatingAway(false);
        }
    }

    /** Gets the last value set with {@link #setNotificationFaded(boolean)} */
    @Override
    public boolean isNotificationFaded() {
        return mIsFaded;
    }

    /**
     * This class needs to delegate the faded state set on it by
     * {@link com.android.systemui.statusbar.notification.stack.ViewState} to its children.
     * Having each notification use layerType of HARDWARE anytime it fades in/out can result in
     * extremely large layers (in the case of groups, it can even exceed the device height).
     * Because these large renders can cause serious jank when rendering, we instead have
     * notifications return false from {@link #hasOverlappingRendering()} and delegate the
     * layerType to child views which really need it in order to render correctly, such as icon
     * views or the conversation face pile.
     *
     * Another compounding factor for notifications is that we change clipping on each frame of the
     * animation, so the hardware layer isn't able to do any caching at the top level, but the
     * individual elements we render with hardware layers (e.g. icons) cache wonderfully because we
     * never invalidate them.
     */
    @Override
    public void setNotificationFaded(boolean faded) {
        mIsFaded = faded;
        if (childrenRequireOverlappingRendering()) {
            // == Simple Scenario ==
            // If a child (like remote input) needs this to have overlapping rendering, then set
            // the layerType of this view and reset the children to render as if the notification is
            // not fading.
            NotificationFadeAware.setLayerTypeForFaded(this, faded);
            setNotificationFadedOnChildren(false);
        } else {
            // == Delegating Scenario ==
            // This is the new normal for alpha: Explicitly reset this view's layer type to NONE,
            // and require that all children use their own hardware layer if they have bad
            // overlapping rendering.
            NotificationFadeAware.setLayerTypeForFaded(this, false);
            setNotificationFadedOnChildren(faded);
        }
    }

    /** Private helper for iterating over the layouts and children containers to set faded state */
    private void setNotificationFadedOnChildren(boolean faded) {
        delegateNotificationFaded(mChildrenContainer, faded);
        for (NotificationContentView layout : mLayouts) {
            delegateNotificationFaded(layout, faded);
        }
    }

    private static void delegateNotificationFaded(@Nullable View view, boolean faded) {
        if (FADE_LAYER_OPTIMIZATION_ENABLED && view instanceof NotificationFadeAware) {
            ((NotificationFadeAware) view).setNotificationFaded(faded);
        } else {
            NotificationFadeAware.setLayerTypeForFaded(view, faded);
        }
    }

    /**
     * Only declare overlapping rendering if independent children of the view require it.
     */
    @Override
    public boolean hasOverlappingRendering() {
        return super.hasOverlappingRendering() && childrenRequireOverlappingRendering();
    }

    /**
     * Because RemoteInputView is designed to be an opaque view that overlaps the Actions row, the
     * row should require overlapping rendering to ensure that the overlapped view doesn't bleed
     * through when alpha fading.
     *
     * Note that this currently works for top-level notifications which squish their height down
     * while collapsing the shade, but does not work for children inside groups, because the
     * accordion affect does not apply to those views, so super.hasOverlappingRendering() will
     * always return false to avoid the clipping caused when the view's measured height is less than
     * the 'actual height'.
     */
    private boolean childrenRequireOverlappingRendering() {
        if (!FADE_LAYER_OPTIMIZATION_ENABLED) {
            return true;
        }
        // The colorized background is another layer with which all other elements overlap
        if (getEntry().getSbn().getNotification().isColorized()) {
            return true;
        }
        // Check if the showing layout has a need for overlapping rendering.
        // NOTE: We could check both public and private layouts here, but becuause these states
        //  don't animate well, there are bigger visual artifacts if we start changing the shown
        //  layout during shade expansion.
        NotificationContentView showingLayout = getShowingLayout();
        return showingLayout != null && showingLayout.requireRowToHaveOverlappingRendering();
    }

    @Override
    public int getExtraBottomPadding() {
        if (mIsSummaryWithChildren && isGroupExpanded()) {
+11 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ import android.widget.TextView;

import com.android.internal.widget.ConversationLayout;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationFadeAware;

/**
 * A hybrid view which may contain information about one ore more conversations.
@@ -138,4 +139,14 @@ public class HybridConversationNotificationView extends HybridNotificationView {
        lp.height = size;
        view.setLayoutParams(lp);
    }

    /**
     * Apply the faded state as a layer type change to the face pile view which needs to have
     * overlapping contents render precisely.
     */
    @Override
    public void setNotificationFaded(boolean faded) {
        super.setNotificationFaded(faded);
        NotificationFadeAware.setLayerTypeForFaded(mConversationFacePile, faded);
    }
}
+5 −1
Original line number Diff line number Diff line
@@ -28,13 +28,14 @@ import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.TransformState;

/**
 * A hybrid view which may contain information about one ore more notifications.
 */
public class HybridNotificationView extends AlphaOptimizedLinearLayout
        implements TransformableView {
        implements TransformableView, NotificationFadeAware {

    protected final ViewTransformationHelper mTransformationHelper = new ViewTransformationHelper();
    protected TextView mTitleView;
@@ -148,4 +149,7 @@ public class HybridNotificationView extends AlphaOptimizedLinearLayout
        setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
        mTransformationHelper.setVisible(visible);
    }

    @Override
    public void setNotificationFaded(boolean faded) {}
}
+37 −1
Original line number Diff line number Diff line
@@ -46,6 +46,7 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
import com.android.systemui.statusbar.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
@@ -73,7 +74,7 @@ import java.util.List;
 * expanded and heads up layout. This class is responsible for clipping the content and and
 * switching between the expanded, contracted and the heads up view depending on its clipped size.
 */
public class NotificationContentView extends FrameLayout {
public class NotificationContentView extends FrameLayout implements NotificationFadeAware {

    private static final String TAG = "NotificationContentView";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -2045,6 +2046,41 @@ public class NotificationContentView extends FrameLayout {
        return Notification.COLOR_INVALID;
    }

    /**
     * Delegate the faded state to the notification content views which actually
     * need to have overlapping contents render precisely.
     */
    @Override
    public void setNotificationFaded(boolean faded) {
        if (mContractedWrapper != null) {
            mContractedWrapper.setNotificationFaded(faded);
        }
        if (mHeadsUpWrapper != null) {
            mHeadsUpWrapper.setNotificationFaded(faded);
        }
        if (mExpandedWrapper != null) {
            mExpandedWrapper.setNotificationFaded(faded);
        }
        if (mSingleLineView != null) {
            mSingleLineView.setNotificationFaded(faded);
        }
    }

    /**
     * @return true if a visible view has a remote input active, as this requires that the entire
     * row report that it has overlapping rendering.
     */
    public boolean requireRowToHaveOverlappingRendering() {
        // This inexpensive check is done on both states to avoid state invalidating the result.
        if (mHeadsUpRemoteInput != null && mHeadsUpRemoteInput.isActive()) {
            return true;
        }
        if (mExpandedRemoteInput != null && mExpandedRemoteInput.isActive()) {
            return true;
        }
        return false;
    }

    public void setRemoteInputViewSubcomponentFactory(RemoteInputViewSubcomponent.Factory factory) {
        mRemoteInputSubcomponentFactory = factory;
    }
Loading