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.
Merged-In: I86b626fb61c5844b968be33d982e418bf8938de8
Change-Id: I86b626fb61c5844b968be33d982e418bf8938de8
This commit is contained in:
Jeff DeCew
2021-07-13 16:47:25 -04:00
parent 35185d54b4
commit 627b68beea
12 changed files with 321 additions and 14 deletions

View File

@@ -0,0 +1,70 @@
/*
* 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();
}
}

View File

@@ -87,6 +87,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;
@@ -125,7 +126,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;
@@ -138,6 +140,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.
@@ -2753,17 +2756,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()) {

View File

@@ -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);
}
}

View File

@@ -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) {}
}

View File

@@ -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;
@@ -71,7 +72,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);
@@ -1994,4 +1995,39 @@ 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;
}
}

View File

@@ -21,6 +21,7 @@ import android.view.View
import com.android.internal.widget.CachingIconView
import com.android.internal.widget.CallLayout
import com.android.systemui.R
import com.android.systemui.statusbar.notification.NotificationFadeAware
import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
@@ -37,6 +38,7 @@ class NotificationCallTemplateViewWrapper constructor(
NotificationUtils.getFontScaledHeight(ctx, R.dimen.notification_max_height)
private val callLayout: CallLayout = view as CallLayout
private lateinit var conversationIconContainer: View
private lateinit var conversationIconView: CachingIconView
private lateinit var conversationBadgeBg: View
private lateinit var expandBtn: View
@@ -45,6 +47,8 @@ class NotificationCallTemplateViewWrapper constructor(
private fun resolveViews() {
with(callLayout) {
conversationIconContainer =
requireViewById(com.android.internal.R.id.conversation_icon_container)
conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
conversationBadgeBg =
requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
@@ -82,4 +86,14 @@ class NotificationCallTemplateViewWrapper constructor(
}
override fun getMinLayoutHeight(): Int = minHeightWithActions
/**
* Apply the faded state as a layer type change to the face pile view which needs to have
* overlapping contents render precisely.
*/
override fun setNotificationFaded(faded: Boolean) {
// Do not call super
NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded)
NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded)
}
}

View File

@@ -23,6 +23,7 @@ import com.android.internal.widget.CachingIconView
import com.android.internal.widget.ConversationLayout
import com.android.internal.widget.MessagingLinearLayout
import com.android.systemui.R
import com.android.systemui.statusbar.notification.NotificationFadeAware
import com.android.systemui.statusbar.notification.NotificationUtils
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
import com.android.systemui.statusbar.notification.row.wrapper.NotificationMessagingTemplateViewWrapper.setCustomImageMessageTransform
@@ -42,6 +43,7 @@ class NotificationConversationTemplateViewWrapper constructor(
)
private val conversationLayout: ConversationLayout = view as ConversationLayout
private lateinit var conversationIconContainer: View
private lateinit var conversationIconView: CachingIconView
private lateinit var conversationBadgeBg: View
private lateinit var expandBtn: View
@@ -59,6 +61,8 @@ class NotificationConversationTemplateViewWrapper constructor(
messagingLinearLayout = conversationLayout.messagingLinearLayout
imageMessageContainer = conversationLayout.imageMessageContainer
with(conversationLayout) {
conversationIconContainer =
requireViewById(com.android.internal.R.id.conversation_icon_container)
conversationIconView = requireViewById(com.android.internal.R.id.conversation_icon)
conversationBadgeBg =
requireViewById(com.android.internal.R.id.conversation_icon_badge_bg)
@@ -136,4 +140,10 @@ class NotificationConversationTemplateViewWrapper constructor(
minHeightWithActions
else
super.getMinLayoutHeight()
override fun setNotificationFaded(faded: Boolean) {
// Do not call super
NotificationFadeAware.setLayerTypeForFaded(expandBtn, faded)
NotificationFadeAware.setLayerTypeForFaded(conversationIconContainer, faded)
}
}

View File

@@ -22,6 +22,7 @@ import android.view.View;
import com.android.internal.graphics.ColorUtils;
import com.android.systemui.R;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
@@ -86,4 +87,14 @@ public class NotificationCustomViewWrapper extends NotificationViewWrapper {
public boolean shouldClipToRounding(boolean topRounded, boolean bottomRounded) {
return true;
}
/**
* Apply the faded state as a layer type change to the custom view which needs to have
* overlapping contents render precisely.
*/
@Override
public void setNotificationFaded(boolean faded) {
super.setNotificationFaded(faded);
NotificationFadeAware.setLayerTypeForFaded(mView, faded);
}
}

View File

@@ -20,6 +20,7 @@ import android.content.Context;
import android.view.View;
import android.view.ViewGroup;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
/**
@@ -67,4 +68,14 @@ public class NotificationDecoratedCustomViewWrapper extends NotificationTemplate
}
super.onContentUpdated(row);
}
/**
* Apply the faded state as a layer type change to the custom view which needs to have
* overlapping contents render precisely.
*/
@Override
public void setNotificationFaded(boolean faded) {
super.setNotificationFaded(faded);
NotificationFadeAware.setLayerTypeForFaded(mWrappedView, faded);
}
}

View File

@@ -42,6 +42,7 @@ import com.android.internal.widget.CachingIconView;
import com.android.settingslib.Utils;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.TransformableView;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.TransformState;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -395,4 +396,13 @@ public abstract class NotificationViewWrapper implements TransformableView {
*/
public void setRecentlyAudiblyAlerted(boolean audiblyAlerted) {
}
/**
* Apply the faded state as a layer type change to the views which need to have overlapping
* contents render precisely.
*/
public void setNotificationFaded(boolean faded) {
NotificationFadeAware.setLayerTypeForFaded(getIcon(), faded);
NotificationFadeAware.setLayerTypeForFaded(getExpandButton(), faded);
}
}

View File

@@ -38,6 +38,7 @@ import com.android.internal.widget.NotificationExpandButton;
import com.android.systemui.R;
import com.android.systemui.statusbar.CrossFadeHelper;
import com.android.systemui.statusbar.NotificationGroupingUtil;
import com.android.systemui.statusbar.notification.NotificationFadeAware;
import com.android.systemui.statusbar.notification.NotificationUtils;
import com.android.systemui.statusbar.notification.collection.legacy.VisualStabilityManager;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -51,7 +52,8 @@ import java.util.List;
/**
* A container containing child notifications
*/
public class NotificationChildrenContainer extends ViewGroup {
public class NotificationChildrenContainer extends ViewGroup
implements NotificationFadeAware {
@VisibleForTesting
static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
@@ -111,6 +113,7 @@ public class NotificationChildrenContainer extends ViewGroup {
private int mCurrentHeaderTranslation = 0;
private float mHeaderVisibleAmount = 1.0f;
private int mUntruncatedChildCount;
private boolean mContainingNotificationIsFaded = false;
public NotificationChildrenContainer(Context context) {
this(context, null);
@@ -277,6 +280,7 @@ public class NotificationChildrenContainer extends ViewGroup {
mDividers.add(newIndex, divider);
row.setContentTransformationAmount(0, false /* isLastChild */);
row.setNotificationFaded(mContainingNotificationIsFaded);
// It doesn't make sense to keep old animations around, lets cancel them!
ExpandableViewState viewState = row.getViewState();
if (viewState != null) {
@@ -301,6 +305,7 @@ public class NotificationChildrenContainer extends ViewGroup {
});
row.setSystemChildExpanded(false);
row.setNotificationFaded(false);
row.setUserLocked(false);
if (!row.isRemoved()) {
mGroupingUtil.restoreChildNotification(row);
@@ -1308,4 +1313,18 @@ public class NotificationChildrenContainer extends ViewGroup {
mNotificationHeaderWrapperLowPriority.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
}
}
@Override
public void setNotificationFaded(boolean faded) {
mContainingNotificationIsFaded = faded;
if (mNotificationHeaderWrapper != null) {
mNotificationHeaderWrapper.setNotificationFaded(faded);
}
if (mNotificationHeaderWrapperLowPriority != null) {
mNotificationHeaderWrapperLowPriority.setNotificationFaded(faded);
}
for (ExpandableNotificationRow child : mAttachedChildren) {
child.setNotificationFaded(faded);
}
}
}

View File

@@ -29,6 +29,7 @@ import com.android.systemui.Dumpable;
import com.android.systemui.R;
import com.android.systemui.animation.Interpolators;
import com.android.systemui.statusbar.notification.AnimatableProperty;
import com.android.systemui.statusbar.notification.NotificationFadeAware.FadeOptimizedNotification;
import com.android.systemui.statusbar.notification.PropertyAnimator;
import com.android.systemui.statusbar.notification.row.ExpandableView;
import com.android.systemui.statusbar.policy.HeadsUpUtil;
@@ -206,14 +207,26 @@ public class ViewState implements Dumpable {
} else if (view.getAlpha() != this.alpha) {
// apply layer type
boolean becomesFullyVisible = this.alpha == 1.0f;
boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible
&& view.hasOverlappingRendering();
int layerType = view.getLayerType();
int newLayerType = newLayerTypeIsHardware
? View.LAYER_TYPE_HARDWARE
: View.LAYER_TYPE_NONE;
if (layerType != newLayerType) {
view.setLayerType(newLayerType, null);
boolean becomesFaded = !becomesInvisible && !becomesFullyVisible;
if (FadeOptimizedNotification.FADE_LAYER_OPTIMIZATION_ENABLED
&& view instanceof FadeOptimizedNotification) {
// NOTE: A view that's going to utilize this interface to avoid having a hardware
// layer will have to return false from hasOverlappingRendering(), so we
// intentionally do not check that value in this if, even though we do in the else.
FadeOptimizedNotification fadeOptimizedView = (FadeOptimizedNotification) view;
boolean isFaded = fadeOptimizedView.isNotificationFaded();
if (isFaded != becomesFaded) {
fadeOptimizedView.setNotificationFaded(becomesFaded);
}
} else {
boolean newLayerTypeIsHardware = becomesFaded && view.hasOverlappingRendering();
int layerType = view.getLayerType();
int newLayerType = newLayerTypeIsHardware
? View.LAYER_TYPE_HARDWARE
: View.LAYER_TYPE_NONE;
if (layerType != newLayerType) {
view.setLayerType(newLayerType, null);
}
}
// apply alpha