Merge changes I41959627,Iba316772 into rvc-dev

* changes:
  Bind/unbind notif children content dynamically
  Support freeing contracted/expanded view
This commit is contained in:
TreeHugger Robot
2020-02-27 18:17:01 +00:00
committed by Android (Google) Code Review
9 changed files with 345 additions and 46 deletions

View File

@@ -30,6 +30,7 @@ import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.plugins.statusbar.StatusBarStateController;
import com.android.systemui.statusbar.dagger.StatusBarModule;
import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -59,11 +60,12 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
private final Handler mHandler;
//TODO: change this top <Entry, List<Entry>>?
private final HashMap<ExpandableNotificationRow, List<ExpandableNotificationRow>>
mTmpChildOrderMap = new HashMap<>();
/** Re-usable map of notifications to their sorted children.*/
private final HashMap<NotificationEntry, List<NotificationEntry>> mTmpChildOrderMap =
new HashMap<>();
// Dependencies:
private final DynamicChildBindController mDynamicChildBindController;
protected final NotificationLockscreenUserManager mLockscreenUserManager;
protected final NotificationGroupManager mGroupManager;
protected final VisualStabilityManager mVisualStabilityManager;
@@ -105,7 +107,8 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
KeyguardBypassController bypassController,
BubbleController bubbleController,
DynamicPrivacyController privacyController,
ForegroundServiceSectionController fgsSectionController) {
ForegroundServiceSectionController fgsSectionController,
DynamicChildBindController dynamicChildBindController) {
mContext = context;
mHandler = mainHandler;
mLockscreenUserManager = notificationLockscreenUserManager;
@@ -121,6 +124,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
mBubbleController = bubbleController;
mDynamicPrivacyController = privacyController;
privacyController.addListener(this);
mDynamicChildBindController = dynamicChildBindController;
}
public void setUpWithPresenter(NotificationPresenter presenter,
@@ -175,13 +179,12 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
ent.getRow().setNeedsRedaction(needsRedaction);
if (mGroupManager.isChildInGroupWithSummary(ent.getSbn())) {
NotificationEntry summary = mGroupManager.getGroupSummary(ent.getSbn());
List<ExpandableNotificationRow> orderedChildren =
mTmpChildOrderMap.get(summary.getRow());
List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(summary);
if (orderedChildren == null) {
orderedChildren = new ArrayList<>();
mTmpChildOrderMap.put(summary.getRow(), orderedChildren);
mTmpChildOrderMap.put(summary, orderedChildren);
}
orderedChildren.add(ent.getRow());
orderedChildren.add(ent);
} else {
toShow.add(ent.getRow());
}
@@ -260,6 +263,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
}
mDynamicChildBindController.updateChildContentViews(mTmpChildOrderMap);
mVisualStabilityManager.onReorderingFinished();
// clear the map again for the next usage
mTmpChildOrderMap.clear();
@@ -274,6 +278,7 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
private void addNotificationChildrenAndSort() {
// Let's now add all notification children which are missing
boolean orderChanged = false;
ArrayList<ExpandableNotificationRow> orderedRows = new ArrayList<>();
for (int i = 0; i < mListContainer.getContainerChildCount(); i++) {
View view = mListContainer.getContainerChildAt(i);
if (!(view instanceof ExpandableNotificationRow)) {
@@ -283,11 +288,11 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
List<ExpandableNotificationRow> children = parent.getNotificationChildren();
List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
for (int childIndex = 0; orderedChildren != null && childIndex < orderedChildren.size();
childIndex++) {
ExpandableNotificationRow childView = orderedChildren.get(childIndex);
ExpandableNotificationRow childView = orderedChildren.get(childIndex).getRow();
if (children == null || !children.contains(childView)) {
if (childView.getParent() != null) {
Log.wtf(TAG, "trying to add a notification child that already has " +
@@ -300,11 +305,13 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
parent.addChildNotification(childView, childIndex);
mListContainer.notifyGroupChildAdded(childView);
}
orderedRows.add(childView);
}
// Finally after removing and adding has been performed we can apply the order.
orderChanged |= parent.applyChildOrder(orderedChildren, mVisualStabilityManager,
orderChanged |= parent.applyChildOrder(orderedRows, mVisualStabilityManager,
mEntryManager);
orderedRows.clear();
}
if (orderChanged) {
mListContainer.generateChildOrderChangedEvent();
@@ -323,13 +330,13 @@ public class NotificationViewHierarchyManager implements DynamicPrivacyControlle
ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
List<ExpandableNotificationRow> children = parent.getNotificationChildren();
List<ExpandableNotificationRow> orderedChildren = mTmpChildOrderMap.get(parent);
List<NotificationEntry> orderedChildren = mTmpChildOrderMap.get(parent.getEntry());
if (children != null) {
toRemove.clear();
for (ExpandableNotificationRow childRow : children) {
if ((orderedChildren == null
|| !orderedChildren.contains(childRow))
|| !orderedChildren.contains(childRow.getEntry()))
&& !childRow.keepInParent()) {
toRemove.add(childRow);
}

View File

@@ -32,6 +32,7 @@ import com.android.systemui.statusbar.NotificationMediaManager;
import com.android.systemui.statusbar.NotificationRemoteInputManager;
import com.android.systemui.statusbar.NotificationViewHierarchyManager;
import com.android.systemui.statusbar.SmartReplyController;
import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
@@ -138,7 +139,8 @@ public interface StatusBarDependenciesModule {
KeyguardBypassController bypassController,
BubbleController bubbleController,
DynamicPrivacyController privacyController,
ForegroundServiceSectionController fgsSectionController) {
ForegroundServiceSectionController fgsSectionController,
DynamicChildBindController dynamicChildBindController) {
return new NotificationViewHierarchyManager(
context,
mainHandler,
@@ -150,7 +152,8 @@ public interface StatusBarDependenciesModule {
bypassController,
bubbleController,
privacyController,
fgsSectionController);
fgsSectionController,
dynamicChildBindController);
}
/**

View File

@@ -0,0 +1,118 @@
/*
* Copyright (C) 2019 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 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.stack.NotificationChildrenContainer.NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
import com.android.systemui.statusbar.notification.collection.NotifPipeline;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import java.util.List;
import java.util.Map;
import javax.inject.Inject;
/**
* Controller that binds/unbinds views content views on notification group children.
*
* We currently only show a limited number of notification children even if more exist, so we
* can save memory by freeing content views when they're not visible and binding them again when
* they get close to being visible.
*
* Eventually, when {@link NotifPipeline} takes over as the new notification pipeline, we'll have
* more control over which notifications even make it to inflation in the first place and be able
* to enforce this at an earlier stage at the level of the {@link ExpandableNotificationRow}, but
* for now, we're just doing it at the level of content views.
*/
public class DynamicChildBindController {
private final RowContentBindStage mStage;
private final int mChildBindCutoff;
@Inject
public DynamicChildBindController(RowContentBindStage stage) {
this(stage, CHILD_BIND_CUTOFF);
}
/**
* @param childBindCutoff the cutoff where we no longer bother having content views bound
*/
DynamicChildBindController(
RowContentBindStage stage,
int childBindCutoff) {
mStage = stage;
mChildBindCutoff = childBindCutoff;
}
/**
* Update the child content views, unbinding content views on children that won't be visible
* and binding content views on children that will be visible eventually.
*
* @param groupNotifs map of notification summaries to their children
*/
public void updateChildContentViews(
Map<NotificationEntry, List<NotificationEntry>> groupNotifs) {
for (NotificationEntry entry : groupNotifs.keySet()) {
List<NotificationEntry> children = groupNotifs.get(entry);
for (int j = 0; j < children.size(); j++) {
NotificationEntry childEntry = children.get(j);
if (j >= mChildBindCutoff) {
if (hasChildContent(childEntry)) {
freeChildContent(childEntry);
}
} else {
if (!hasChildContent(childEntry)) {
bindChildContent(childEntry);
}
}
}
}
}
private boolean hasChildContent(NotificationEntry entry) {
ExpandableNotificationRow row = entry.getRow();
return row.getPrivateLayout().getContractedChild() != null
|| row.getPrivateLayout().getExpandedChild() != null;
}
private void freeChildContent(NotificationEntry entry) {
RowContentBindParams params = mStage.getStageParams(entry);
params.freeContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
params.freeContentViews(FLAG_CONTENT_VIEW_EXPANDED);
mStage.requestRebind(entry, null);
}
private void bindChildContent(NotificationEntry entry) {
RowContentBindParams params = mStage.getStageParams(entry);
params.requireContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
params.requireContentViews(FLAG_CONTENT_VIEW_EXPANDED);
mStage.requestRebind(entry, null);
}
/**
* How big the buffer of extra views we keep around to be ready to show when we do need to
* dynamically inflate.
*/
private static final int EXTRA_VIEW_BUFFER_COUNT = 1;
private static final int CHILD_BIND_CUTOFF =
NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED + EXTRA_VIEW_BUFFER_COUNT;
}

View File

@@ -18,7 +18,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.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_PUBLIC;
@@ -467,6 +470,14 @@ public class ExpandableNotificationRow extends ActivatableNotificationView
}
};
switch (inflationFlag) {
case FLAG_CONTENT_VIEW_CONTRACTED:
getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_CONTRACTED,
freeViewRunnable);
break;
case FLAG_CONTENT_VIEW_EXPANDED:
getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_EXPANDED,
freeViewRunnable);
break;
case FLAG_CONTENT_VIEW_HEADS_UP:
getPrivateLayout().performWhenContentInactive(VISIBLE_TYPE_HEADSUP,
freeViewRunnable);

View File

@@ -18,6 +18,7 @@ package com.android.systemui.statusbar.notification.row;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_EXPANDED;
import static com.android.systemui.statusbar.notification.row.NotificationContentView.VISIBLE_TYPE_HEADSUP;
import android.annotation.NonNull;
@@ -191,6 +192,18 @@ public class NotificationContentInflater implements NotificationRowContentBinder
private void freeNotificationView(NotificationEntry entry, ExpandableNotificationRow row,
@InflationFlag int inflateFlag) {
switch (inflateFlag) {
case FLAG_CONTENT_VIEW_CONTRACTED:
if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_CONTRACTED)) {
row.getPrivateLayout().setContractedChild(null);
mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_CONTRACTED);
}
break;
case FLAG_CONTENT_VIEW_EXPANDED:
if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_EXPANDED)) {
row.getPrivateLayout().setExpandedChild(null);
mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_EXPANDED);
}
break;
case FLAG_CONTENT_VIEW_HEADS_UP:
if (row.getPrivateLayout().isContentViewInactive(VISIBLE_TYPE_HEADSUP)) {
row.getPrivateLayout().setHeadsUpChild(null);
@@ -204,8 +217,6 @@ public class NotificationContentInflater implements NotificationRowContentBinder
mRemoteViewCache.removeCachedView(entry, FLAG_CONTENT_VIEW_PUBLIC);
}
break;
case FLAG_CONTENT_VIEW_CONTRACTED:
case FLAG_CONTENT_VIEW_EXPANDED:
default:
break;
}

View File

@@ -71,7 +71,13 @@ public class NotificationContentView extends FrameLayout {
public static final int VISIBLE_TYPE_EXPANDED = 1;
public static final int VISIBLE_TYPE_HEADSUP = 2;
private static final int VISIBLE_TYPE_SINGLELINE = 3;
public static final int UNDEFINED = -1;
/**
* Used when there is no content on the view such as when we're a public layout but don't
* need to show.
*/
private static final int VISIBLE_TYPE_NONE = -1;
private static final int UNDEFINED = -1;
private final Rect mClipBounds = new Rect();
@@ -99,7 +105,7 @@ public class NotificationContentView extends FrameLayout {
private HybridGroupManager mHybridGroupManager;
private int mClipTopAmount;
private int mContentHeight;
private int mVisibleType = VISIBLE_TYPE_CONTRACTED;
private int mVisibleType = VISIBLE_TYPE_NONE;
private boolean mAnimate;
private boolean mIsHeadsUp;
private boolean mLegacy;
@@ -141,7 +147,7 @@ public class NotificationContentView extends FrameLayout {
/** The visible type at the start of a touch driven transformation */
private int mTransformationStartVisibleType;
/** The visible type at the start of an animation driven transformation */
private int mAnimationStartVisibleType = UNDEFINED;
private int mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
private boolean mUserExpanding;
private int mSingleLineWidthIndention;
private boolean mForceSelectNextLayout = true;
@@ -386,7 +392,7 @@ public class NotificationContentView extends FrameLayout {
mContractedChild = null;
mContractedWrapper = null;
if (mTransformationStartVisibleType == VISIBLE_TYPE_CONTRACTED) {
mTransformationStartVisibleType = UNDEFINED;
mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
}
return;
}
@@ -434,7 +440,7 @@ public class NotificationContentView extends FrameLayout {
mExpandedChild = null;
mExpandedWrapper = null;
if (mTransformationStartVisibleType == VISIBLE_TYPE_EXPANDED) {
mTransformationStartVisibleType = UNDEFINED;
mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
}
if (mVisibleType == VISIBLE_TYPE_EXPANDED) {
selectLayout(false /* animate */, true /* force */);
@@ -472,7 +478,7 @@ public class NotificationContentView extends FrameLayout {
mHeadsUpChild = null;
mHeadsUpWrapper = null;
if (mTransformationStartVisibleType == VISIBLE_TYPE_HEADSUP) {
mTransformationStartVisibleType = UNDEFINED;
mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
}
if (mVisibleType == VISIBLE_TYPE_HEADSUP) {
selectLayout(false /* animate */, true /* force */);
@@ -597,7 +603,7 @@ public class NotificationContentView extends FrameLayout {
}
// Size change of the expanded version
if ((mVisibleType == VISIBLE_TYPE_EXPANDED) && mContentHeightAtAnimationStart >= 0
if ((mVisibleType == VISIBLE_TYPE_EXPANDED) && mContentHeightAtAnimationStart != UNDEFINED
&& mExpandedChild != null) {
return Math.min(mContentHeightAtAnimationStart, getViewHeight(VISIBLE_TYPE_EXPANDED));
}
@@ -607,10 +613,12 @@ public class NotificationContentView extends FrameLayout {
hint = getViewHeight(VISIBLE_TYPE_HEADSUP);
} else if (mExpandedChild != null) {
hint = getViewHeight(VISIBLE_TYPE_EXPANDED);
} else {
} else if (mContractedChild != null) {
hint = getViewHeight(VISIBLE_TYPE_CONTRACTED)
+ mContext.getResources().getDimensionPixelSize(
com.android.internal.R.dimen.notification_action_list_height);
} else {
hint = getMinHeight();
}
if (mExpandedChild != null && isVisibleOrTransitioning(VISIBLE_TYPE_EXPANDED)) {
@@ -646,7 +654,7 @@ public class NotificationContentView extends FrameLayout {
if (mForceSelectNextLayout) {
forceUpdateVisibilities();
}
if (mTransformationStartVisibleType != UNDEFINED
if (mTransformationStartVisibleType != VISIBLE_TYPE_NONE
&& mVisibleType != mTransformationStartVisibleType
&& getViewForVisibleType(mTransformationStartVisibleType) != null) {
final TransformableView shownView = getTransformableViewForVisibleType(mVisibleType);
@@ -823,7 +831,7 @@ public class NotificationContentView extends FrameLayout {
fireExpandedVisibleListenerIfVisible();
// forceUpdateVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
mAnimationStartVisibleType = UNDEFINED;
mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
}
private void fireExpandedVisibleListenerIfVisible() {
@@ -898,7 +906,7 @@ public class NotificationContentView extends FrameLayout {
fireExpandedVisibleListenerIfVisible();
// updateViewVisibilities cancels outstanding animations without updating the
// mAnimationStartVisibleType. Do so here instead.
mAnimationStartVisibleType = UNDEFINED;
mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
}
private void updateViewVisibility(int visibleType, int type, View view,
@@ -924,7 +932,7 @@ public class NotificationContentView extends FrameLayout {
if (hiddenView != getTransformableViewForVisibleType(mVisibleType)) {
hiddenView.setVisible(false);
}
mAnimationStartVisibleType = UNDEFINED;
mAnimationStartVisibleType = VISIBLE_TYPE_NONE;
}
});
fireExpandedVisibleListenerIfVisible();
@@ -1041,8 +1049,10 @@ public class NotificationContentView extends FrameLayout {
&& (!mIsChildInGroup || isGroupExpanded()
|| !mContainingNotification.isExpanded(true /* allowOnKeyguard */)))) {
return VISIBLE_TYPE_CONTRACTED;
} else {
} else if (!noExpandedChild) {
return VISIBLE_TYPE_EXPANDED;
} else {
return VISIBLE_TYPE_NONE;
}
}
}
@@ -1423,7 +1433,8 @@ public class NotificationContentView extends FrameLayout {
if (mExpandedChild != null && mExpandedChild.getHeight() != 0) {
if ((!mIsHeadsUp && !mHeadsUpAnimatingAway)
|| mHeadsUpChild == null || !mContainingNotification.canShowHeadsUp()) {
if (mExpandedChild.getHeight() <= mContractedChild.getHeight()) {
if (mContractedChild == null
|| mExpandedChild.getHeight() <= mContractedChild.getHeight()) {
expandable = false;
}
} else if (mExpandedChild.getHeight() <= mHeadsUpChild.getHeight()) {
@@ -1514,7 +1525,7 @@ public class NotificationContentView extends FrameLayout {
if (userExpanding) {
mTransformationStartVisibleType = mVisibleType;
} else {
mTransformationStartVisibleType = UNDEFINED;
mTransformationStartVisibleType = VISIBLE_TYPE_NONE;
mVisibleType = calculateVisibleType();
updateViewVisibilities(mVisibleType);
updateBackgroundColor(false);
@@ -1558,6 +1569,7 @@ public class NotificationContentView extends FrameLayout {
}
public void setContentHeightAnimating(boolean animating) {
//TODO: It's odd that this does nothing when animating is true
if (!animating) {
mContentHeightAtAnimationStart = UNDEFINED;
}
@@ -1565,7 +1577,7 @@ public class NotificationContentView extends FrameLayout {
@VisibleForTesting
boolean isAnimatingVisibleType() {
return mAnimationStartVisibleType != UNDEFINED;
return mAnimationStartVisibleType != VISIBLE_TYPE_NONE;
}
public void setHeadsUpAnimatingAway(boolean headsUpAnimatingAway) {
@@ -1758,17 +1770,25 @@ public class NotificationContentView extends FrameLayout {
}
public int getExpandHeight() {
int viewType = VISIBLE_TYPE_EXPANDED;
if (mExpandedChild == null) {
int viewType;
if (mExpandedChild != null) {
viewType = VISIBLE_TYPE_EXPANDED;
} else if (mContractedChild != null) {
viewType = VISIBLE_TYPE_CONTRACTED;
} else {
return getMinHeight();
}
return getViewHeight(viewType) + getExtraRemoteInputHeight(mExpandedRemoteInput);
}
public int getHeadsUpHeight(boolean forceNoHeader) {
int viewType = VISIBLE_TYPE_HEADSUP;
if (mHeadsUpChild == null) {
int viewType;
if (mHeadsUpChild != null) {
viewType = VISIBLE_TYPE_HEADSUP;
} else if (mContractedChild != null) {
viewType = VISIBLE_TYPE_CONTRACTED;
} else {
return getMinHeight();
}
// The headsUp remote input quickly switches to the expanded one, so lets also include that
// one

View File

@@ -53,8 +53,7 @@ public class NotificationChildrenContainer extends ViewGroup {
static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
@VisibleForTesting
static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
@VisibleForTesting
static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
public static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
private static final AnimationProperties ALPHA_FADE_IN = new AnimationProperties() {
private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();

View File

@@ -41,11 +41,11 @@ import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import com.android.systemui.bubbles.BubbleController;
import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper;
import com.android.systemui.statusbar.notification.DynamicChildBindController;
import com.android.systemui.statusbar.notification.DynamicPrivacyController;
import com.android.systemui.statusbar.notification.NotificationEntryManager;
import com.android.systemui.statusbar.notification.VisualStabilityManager;
import com.android.systemui.statusbar.notification.collection.NotificationEntry;
import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
import com.android.systemui.statusbar.notification.logging.NotificationLogger;
import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -106,17 +106,14 @@ public class NotificationViewHierarchyManagerTest extends SysuiTestCase {
mock(KeyguardBypassController.class),
mock(BubbleController.class),
mock(DynamicPrivacyController.class),
mock(ForegroundServiceSectionController.class));
mock(ForegroundServiceSectionController.class),
mock(DynamicChildBindController.class));
mViewHierarchyManager.setUpWithPresenter(mPresenter, mListContainer);
}
private NotificationEntry createEntry() throws Exception {
ExpandableNotificationRow row = mHelper.createRow();
NotificationEntry entry = new NotificationEntryBuilder()
.setSbn(row.getEntry().getSbn())
.build();
entry.setRow(row);
return entry;
return row.getEntry();
}
@Test

View File

@@ -0,0 +1,133 @@
/*
* Copyright (C) 2020 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 static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED;
import static com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.util.ArrayMap;
import android.view.LayoutInflater;
import android.view.View;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
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.RowContentBindParams;
import com.android.systemui.statusbar.notification.row.RowContentBindStage;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class DynamicChildBindControllerTest extends SysuiTestCase {
private DynamicChildBindController mDynamicChildBindController;
private Map<NotificationEntry, List<NotificationEntry>> mGroupNotifs = new ArrayMap<>();
private static final int TEST_CHILD_BIND_CUTOFF = 5;
@Mock private RowContentBindStage mBindStage;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
allowTestableLooperAsMainThread();
when(mBindStage.getStageParams(any())).thenReturn(new RowContentBindParams());
mDynamicChildBindController =
new DynamicChildBindController(mBindStage, TEST_CHILD_BIND_CUTOFF);
}
@Test
public void testContentViewsOfChildrenBeyondCutoffAreFreed() {
// GIVEN a group notification with one view beyond the cutoff with content bound
NotificationEntry summary = addGroup(TEST_CHILD_BIND_CUTOFF + 1);
NotificationEntry lastChild = mGroupNotifs.get(summary).get(TEST_CHILD_BIND_CUTOFF);
RowContentBindParams bindParams = mock(RowContentBindParams.class);
when(mBindStage.getStageParams(lastChild)).thenReturn(bindParams);
// WHEN the controller gets the list
mDynamicChildBindController.updateChildContentViews(mGroupNotifs);
// THEN we free content views
verify(bindParams).freeContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
verify(bindParams).freeContentViews(FLAG_CONTENT_VIEW_EXPANDED);
verify(mBindStage).requestRebind(eq(lastChild), any());
}
@Test
public void testContentViewsBeforeCutoffAreBound() {
// GIVEN a group notification with one view before the cutoff with content unbound
NotificationEntry summary = addGroup(TEST_CHILD_BIND_CUTOFF);
NotificationEntry lastChild = mGroupNotifs.get(summary).get(TEST_CHILD_BIND_CUTOFF - 1);
lastChild.getRow().getPrivateLayout().setContractedChild(null);
lastChild.getRow().getPrivateLayout().setExpandedChild(null);
RowContentBindParams bindParams = mock(RowContentBindParams.class);
when(mBindStage.getStageParams(lastChild)).thenReturn(bindParams);
// WHEN the controller gets the list
mDynamicChildBindController.updateChildContentViews(mGroupNotifs);
// THEN we bind content views
verify(bindParams).requireContentViews(FLAG_CONTENT_VIEW_CONTRACTED);
verify(bindParams).requireContentViews(FLAG_CONTENT_VIEW_EXPANDED);
verify(mBindStage).requestRebind(eq(lastChild), any());
}
private NotificationEntry addGroup(int size) {
NotificationEntry summary = new NotificationEntryBuilder().build();
summary.setRow(createRow());
ArrayList<NotificationEntry> children = new ArrayList<>();
for (int i = 0; i < size; i++) {
NotificationEntry child = new NotificationEntryBuilder().build();
child.setRow(createRow());
children.add(child);
}
mGroupNotifs.put(summary, children);
return summary;
}
private ExpandableNotificationRow createRow() {
ExpandableNotificationRow row = (ExpandableNotificationRow)
LayoutInflater.from(mContext).inflate(R.layout.status_bar_notification_row, null);
row.getPrivateLayout().setContractedChild(new View(mContext));
row.getPrivateLayout().setExpandedChild(new View(mContext));
return row;
}
}