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

Commit cb9390f8 authored by Jeff DeCew's avatar Jeff DeCew Committed by Android (Google) Code Review
Browse files

Merge "Reduce hardware render sizes for ExpandableNotificationRow."

parents b06ff485 8639dd97
Loading
Loading
Loading
Loading
+70 −0
Original line number Original line 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 Original line 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.StatusBarIconView;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.AboveShelfChangedListener;
import com.android.systemui.statusbar.notification.ExpandAnimationParameters;
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.NotificationLaunchAnimatorController;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
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).
 * the group summary (which contains 1 or more child notifications).
 */
 */
public class ExpandableNotificationRow extends ActivatableNotificationView
public class ExpandableNotificationRow extends ActivatableNotificationView
        implements PluginListener<NotificationMenuRowPlugin>, SwipeableView {
        implements PluginListener<NotificationMenuRowPlugin>, SwipeableView,
        NotificationFadeAware.FadeOptimizedNotification {


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


    /**
    /**
     * Listener for when {@link ExpandableNotificationRow} is laid out.
     * Listener for when {@link ExpandableNotificationRow} is laid out.
@@ -2774,17 +2777,112 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
            // alphas are reset
            // alphas are reset
            if (mChildrenContainer != null) {
            if (mChildrenContainer != null) {
                mChildrenContainer.setAlpha(1.0f);
                mChildrenContainer.setAlpha(1.0f);
                mChildrenContainer.setLayerType(LAYER_TYPE_NONE, null);
            }
            }
            for (NotificationContentView l : mLayouts) {
            for (NotificationContentView l : mLayouts) {
                l.setAlpha(1.0f);
                l.setAlpha(1.0f);
                l.setLayerType(LAYER_TYPE_NONE, null);
            }
            if (FADE_LAYER_OPTIMIZATION_ENABLED) {
                setNotificationFaded(false);
            } else {
                setNotificationFadedOnChildren(false);
            }
            }
        } else {
        } else {
            setHeadsUpAnimatingAway(false);
            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
    @Override
    public int getExtraBottomPadding() {
    public int getExtraBottomPadding() {
        if (mIsSummaryWithChildren && isGroupExpanded()) {
        if (mIsSummaryWithChildren && isGroupExpanded()) {
+11 −0
Original line number Original line Diff line number Diff line
@@ -28,6 +28,7 @@ import android.widget.TextView;


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


/**
/**
 * A hybrid view which may contain information about one ore more conversations.
 * A hybrid view which may contain information about one ore more conversations.
@@ -138,4 +139,14 @@ public class HybridConversationNotificationView extends HybridNotificationView {
        lp.height = size;
        lp.height = size;
        view.setLayoutParams(lp);
        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 Original line 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.CrossFadeHelper;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.ViewTransformationHelper;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.TransformState;


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


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

    @Override
    public void setNotificationFaded(boolean faded) {}
}
}
+37 −1
Original line number Original line 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.RemoteInputController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.TransformableView;
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.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.render.GroupMembershipManager;
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
 * 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.
 * 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 String TAG = "NotificationContentView";
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@@ -2045,6 +2046,41 @@ public class NotificationContentView extends FrameLayout {
        return Notification.COLOR_INVALID;
        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) {
    public void setRemoteInputViewSubcomponentFactory(RemoteInputViewSubcomponent.Factory factory) {
        mRemoteInputSubcomponentFactory = factory;
        mRemoteInputSubcomponentFactory = factory;
    }
    }
Loading