blob: 99691b710cc262bc443006ef8a92f6c749a33eb0 [file] [log] [blame]
Selim Cinekb5605e52015-02-20 18:21:41 +01001/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
Rohan Shah20790b82018-07-02 17:21:04 -070017package com.android.systemui.statusbar.notification.stack;
Selim Cinekb5605e52015-02-20 18:21:41 +010018
Mady Mellorb0a82462016-04-30 17:31:02 -070019import android.app.Notification;
Selim Cinekb5605e52015-02-20 18:21:41 +010020import android.content.Context;
Selim Cineka3d3b912016-02-02 11:22:06 -080021import android.content.res.Configuration;
Anthony Chen6bf88a02017-04-10 14:41:44 -070022import android.content.res.Resources;
Mady Mellorb0a82462016-04-30 17:31:02 -070023import android.graphics.drawable.ColorDrawable;
24import android.service.notification.StatusBarNotification;
Selim Cinek4237e822020-03-31 17:22:28 -070025import android.util.ArraySet;
Selim Cinekb5605e52015-02-20 18:21:41 +010026import android.util.AttributeSet;
27import android.view.LayoutInflater;
Mady Mellorb0a82462016-04-30 17:31:02 -070028import android.view.NotificationHeaderView;
Selim Cinekb5605e52015-02-20 18:21:41 +010029import android.view.View;
30import android.view.ViewGroup;
Mady Mellorb0a82462016-04-30 17:31:02 -070031import android.widget.RemoteViews;
Selim Cinekc897bd32016-03-18 17:32:31 -070032import android.widget.TextView;
Selim Cinek817abe72017-05-24 11:08:55 -070033
34import com.android.internal.annotations.VisibleForTesting;
Selim Cinekb5605e52015-02-20 18:21:41 +010035import com.android.systemui.R;
Selim Cinek7b73a4c2016-01-12 18:32:11 -080036import com.android.systemui.statusbar.CrossFadeHelper;
Mady Mellorb0a82462016-04-30 17:31:02 -070037import com.android.systemui.statusbar.NotificationHeaderUtil;
Gus Prevasab336792018-11-14 13:52:20 -050038import com.android.systemui.statusbar.notification.NotificationUtils;
39import com.android.systemui.statusbar.notification.VisualStabilityManager;
40import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Rohan Shah20790b82018-07-02 17:21:04 -070041import com.android.systemui.statusbar.notification.row.HybridGroupManager;
42import com.android.systemui.statusbar.notification.row.HybridNotificationView;
Steve Elliott936df152020-04-14 13:59:53 -040043import com.android.systemui.statusbar.notification.row.wrapper.NotificationHeaderViewWrapper;
Rohan Shah20790b82018-07-02 17:21:04 -070044import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
Selim Cinekb5605e52015-02-20 18:21:41 +010045
46import java.util.ArrayList;
47import java.util.List;
48
49/**
50 * A container containing child notifications
51 */
52public class NotificationChildrenContainer extends ViewGroup {
53
Aaron Heuckroth17ce40e2018-06-19 15:44:40 -040054 @VisibleForTesting
55 static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
56 @VisibleForTesting
57 static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
Kevin Han3cb379c2019-12-03 13:31:44 -080058 public static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
Selim Cinek414ad332017-02-24 19:06:12 -080059 private static final AnimationProperties ALPHA_FADE_IN = new AnimationProperties() {
60 private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
61
62 @Override
63 public AnimationFilter getAnimationFilter() {
64 return mAnimationFilter;
65 }
66 }.setDuration(200);
Selim Cinek83bc7832015-10-22 13:26:54 -070067
Selim Cinekb5605e52015-02-20 18:21:41 +010068 private final List<View> mDividers = new ArrayList<>();
Kevin Han43077f92020-02-28 12:51:53 -080069 private final List<ExpandableNotificationRow> mAttachedChildren = new ArrayList<>();
Selim Cinekc897bd32016-03-18 17:32:31 -070070 private final HybridGroupManager mHybridGroupManager;
Selim Cinek01af3342016-02-09 19:25:31 -080071 private int mChildPadding;
72 private int mDividerHeight;
Anthony Chen6bf88a02017-04-10 14:41:44 -070073 private float mDividerAlpha;
Mady Mellorb0a82462016-04-30 17:31:02 -070074 private int mNotificationHeaderMargin;
Anthony Chen6bf88a02017-04-10 14:41:44 -070075
Selim Cinek01af3342016-02-09 19:25:31 -080076 private int mNotificatonTopPadding;
77 private float mCollapsedBottompadding;
Selim Cinek83bc7832015-10-22 13:26:54 -070078 private boolean mChildrenExpanded;
Selim Cinek414ad332017-02-24 19:06:12 -080079 private ExpandableNotificationRow mContainingNotification;
Selim Cinekc897bd32016-03-18 17:32:31 -070080 private TextView mOverflowNumber;
81 private ViewState mGroupOverFlowState;
Selim Cineka69f2a62015-12-11 17:28:12 -080082 private int mRealHeight;
Selim Cinek42357e02016-02-24 18:48:01 -080083 private boolean mUserLocked;
84 private int mActualHeight;
Selim Cinekc897bd32016-03-18 17:32:31 -070085 private boolean mNeverAppliedGroupState;
Mady Mellorb0a82462016-04-30 17:31:02 -070086 private int mHeaderHeight;
87
Anthony Chen6bf88a02017-04-10 14:41:44 -070088 /**
89 * Whether or not individual notifications that are part of this container will have shadows.
90 */
91 private boolean mEnableShadowOnChildNotifications;
92
Mady Mellorb0a82462016-04-30 17:31:02 -070093 private NotificationHeaderView mNotificationHeader;
94 private NotificationViewWrapper mNotificationHeaderWrapper;
Selim Cinek414ad332017-02-24 19:06:12 -080095 private NotificationHeaderView mNotificationHeaderLowPriority;
96 private NotificationViewWrapper mNotificationHeaderWrapperLowPriority;
Mady Mellorb0a82462016-04-30 17:31:02 -070097 private NotificationHeaderUtil mHeaderUtil;
98 private ViewState mHeaderViewState;
Selim Cinekb3dadcc2016-11-21 17:21:13 -080099 private int mClipBottomAmount;
Selim Cinek6743c0b2017-01-18 18:24:01 -0800100 private boolean mIsLowPriority;
Selim Cinek414ad332017-02-24 19:06:12 -0800101 private OnClickListener mHeaderClickListener;
Adrian Roos6f6e1592017-05-02 16:22:53 -0700102 private ViewGroup mCurrentHeader;
Steve Elliott936df152020-04-14 13:59:53 -0400103 private boolean mIsConversation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100104
Anthony Chen6bf88a02017-04-10 14:41:44 -0700105 private boolean mShowDividersWhenExpanded;
106 private boolean mHideDividersDuringExpand;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800107 private int mTranslationForHeader;
108 private int mCurrentHeaderTranslation = 0;
109 private float mHeaderVisibleAmount = 1.0f;
Kevin Han43077f92020-02-28 12:51:53 -0800110 private int mUntruncatedChildCount;
Anthony Chen6bf88a02017-04-10 14:41:44 -0700111
Selim Cinekb5605e52015-02-20 18:21:41 +0100112 public NotificationChildrenContainer(Context context) {
113 this(context, null);
114 }
115
116 public NotificationChildrenContainer(Context context, AttributeSet attrs) {
117 this(context, attrs, 0);
118 }
119
120 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) {
121 this(context, attrs, defStyleAttr, 0);
122 }
123
124 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
125 int defStyleRes) {
126 super(context, attrs, defStyleAttr, defStyleRes);
Steve Elliott936df152020-04-14 13:59:53 -0400127 mHybridGroupManager = new HybridGroupManager(getContext());
dongwan0605.kim11e7dec2018-05-06 18:04:52 +0900128 initDimens();
Selim Cineked64a142018-02-06 18:06:01 -0800129 setClipChildren(false);
Selim Cinek01af3342016-02-09 19:25:31 -0800130 }
131
132 private void initDimens() {
Anthony Chen6bf88a02017-04-10 14:41:44 -0700133 Resources res = getResources();
134 mChildPadding = res.getDimensionPixelSize(R.dimen.notification_children_padding);
135 mDividerHeight = res.getDimensionPixelSize(
136 R.dimen.notification_children_container_divider_height);
137 mDividerAlpha = res.getFloat(R.dimen.notification_divider_alpha);
Anthony Chen6bf88a02017-04-10 14:41:44 -0700138 mNotificationHeaderMargin = res.getDimensionPixelSize(
139 R.dimen.notification_children_container_margin_top);
140 mNotificatonTopPadding = res.getDimensionPixelSize(
Selim Cinek7b836392015-12-04 20:02:59 -0800141 R.dimen.notification_children_container_top_padding);
Selim Cinekafeed292017-12-12 17:32:44 -0800142 mHeaderHeight = mNotificationHeaderMargin + mNotificatonTopPadding;
Anthony Chen6bf88a02017-04-10 14:41:44 -0700143 mCollapsedBottompadding = res.getDimensionPixelSize(
Selim Cineked64a142018-02-06 18:06:01 -0800144 com.android.internal.R.dimen.notification_content_margin);
Anthony Chen6bf88a02017-04-10 14:41:44 -0700145 mEnableShadowOnChildNotifications =
146 res.getBoolean(R.bool.config_enableShadowOnChildNotifications);
147 mShowDividersWhenExpanded =
148 res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded);
149 mHideDividersDuringExpand =
150 res.getBoolean(R.bool.config_hideDividersDuringExpand);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800151 mTranslationForHeader = res.getDimensionPixelSize(
152 com.android.internal.R.dimen.notification_content_margin)
153 - mNotificationHeaderMargin;
dongwan0605.kim11e7dec2018-05-06 18:04:52 +0900154 mHybridGroupManager.initDimens();
Selim Cinekb5605e52015-02-20 18:21:41 +0100155 }
156
157 @Override
158 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Kevin Han43077f92020-02-28 12:51:53 -0800159 int childCount =
160 Math.min(mAttachedChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
Selim Cinekb5605e52015-02-20 18:21:41 +0100161 for (int i = 0; i < childCount; i++) {
Kevin Han43077f92020-02-28 12:51:53 -0800162 View child = mAttachedChildren.get(i);
Selim Cinekfa760d42016-05-10 15:50:53 -0400163 // We need to layout all children even the GONE ones, such that the heights are
164 // calculated correctly as they are used to calculate how many we can fit on the screen
Selim Cinekc897bd32016-03-18 17:32:31 -0700165 child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight());
Selim Cinek7b836392015-12-04 20:02:59 -0800166 mDividers.get(i).layout(0, 0, getWidth(), mDividerHeight);
Selim Cinekb5605e52015-02-20 18:21:41 +0100167 }
Selim Cinekc897bd32016-03-18 17:32:31 -0700168 if (mOverflowNumber != null) {
Selim Cinek09b7dea2016-08-30 11:28:19 -0700169 boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL;
170 int left = (isRtl ? 0 : getWidth() - mOverflowNumber.getMeasuredWidth());
171 int right = left + mOverflowNumber.getMeasuredWidth();
172 mOverflowNumber.layout(left, 0, right, mOverflowNumber.getMeasuredHeight());
Selim Cinekc897bd32016-03-18 17:32:31 -0700173 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700174 if (mNotificationHeader != null) {
175 mNotificationHeader.layout(0, 0, mNotificationHeader.getMeasuredWidth(),
176 mNotificationHeader.getMeasuredHeight());
177 }
Selim Cinek414ad332017-02-24 19:06:12 -0800178 if (mNotificationHeaderLowPriority != null) {
179 mNotificationHeaderLowPriority.layout(0, 0,
180 mNotificationHeaderLowPriority.getMeasuredWidth(),
181 mNotificationHeaderLowPriority.getMeasuredHeight());
182 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100183 }
184
185 @Override
186 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100187 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
188 boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
189 boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
Selim Cineka69f2a62015-12-11 17:28:12 -0800190 int size = MeasureSpec.getSize(heightMeasureSpec);
Geoffrey Pitsch4dd50062016-12-06 16:41:22 -0500191 int newHeightSpec = heightMeasureSpec;
Selim Cinekb5605e52015-02-20 18:21:41 +0100192 if (hasFixedHeight || isHeightLimited) {
Geoffrey Pitsch4dd50062016-12-06 16:41:22 -0500193 newHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
Selim Cinekb5605e52015-02-20 18:21:41 +0100194 }
Selim Cinekc897bd32016-03-18 17:32:31 -0700195 int width = MeasureSpec.getSize(widthMeasureSpec);
196 if (mOverflowNumber != null) {
197 mOverflowNumber.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
198 newHeightSpec);
199 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100200 int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY);
Mady Mellorb0a82462016-04-30 17:31:02 -0700201 int height = mNotificationHeaderMargin + mNotificatonTopPadding;
Kevin Han43077f92020-02-28 12:51:53 -0800202 int childCount =
203 Math.min(mAttachedChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
Selim Cinekc897bd32016-03-18 17:32:31 -0700204 int collapsedChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
205 int overflowIndex = childCount > collapsedChildren ? collapsedChildren - 1 : -1;
Selim Cinekb5605e52015-02-20 18:21:41 +0100206 for (int i = 0; i < childCount; i++) {
Kevin Han43077f92020-02-28 12:51:53 -0800207 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cinekfa760d42016-05-10 15:50:53 -0400208 // We need to measure all children even the GONE ones, such that the heights are
209 // calculated correctly as they are used to calculate how many we can fit on the screen.
Selim Cinekc897bd32016-03-18 17:32:31 -0700210 boolean isOverflow = i == overflowIndex;
Selim Cinek7927c732019-03-06 15:21:39 -0800211 child.setSingleLineWidthIndention(isOverflow && mOverflowNumber != null
Lucas Dupin4c797d62018-05-07 15:32:13 -0700212 ? mOverflowNumber.getMeasuredWidth() : 0);
Selim Cinekb5605e52015-02-20 18:21:41 +0100213 child.measure(widthMeasureSpec, newHeightSpec);
Selim Cinek7b836392015-12-04 20:02:59 -0800214 // layout the divider
215 View divider = mDividers.get(i);
216 divider.measure(widthMeasureSpec, dividerHeightSpec);
Selim Cinekfa760d42016-05-10 15:50:53 -0400217 if (child.getVisibility() != GONE) {
218 height += child.getMeasuredHeight() + mDividerHeight;
219 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100220 }
Selim Cineka69f2a62015-12-11 17:28:12 -0800221 mRealHeight = height;
222 if (heightMode != MeasureSpec.UNSPECIFIED) {
223 height = Math.min(height, size);
224 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700225
Selim Cinek414ad332017-02-24 19:06:12 -0800226 int headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
Mady Mellorb0a82462016-04-30 17:31:02 -0700227 if (mNotificationHeader != null) {
Mady Mellorb0a82462016-04-30 17:31:02 -0700228 mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec);
229 }
Selim Cinek414ad332017-02-24 19:06:12 -0800230 if (mNotificationHeaderLowPriority != null) {
231 headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
232 mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec);
233 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700234
Selim Cinekb5605e52015-02-20 18:21:41 +0100235 setMeasuredDimension(width, height);
236 }
237
Selim Cineka69f2a62015-12-11 17:28:12 -0800238 @Override
Selim Cinek0242fbb2016-10-19 13:38:32 -0700239 public boolean hasOverlappingRendering() {
240 return false;
241 }
242
243 @Override
Selim Cineka69f2a62015-12-11 17:28:12 -0800244 public boolean pointInView(float localX, float localY, float slop) {
245 return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
246 localY < (mRealHeight + slop);
247 }
248
Selim Cinekb5605e52015-02-20 18:21:41 +0100249 /**
Kevin Han43077f92020-02-28 12:51:53 -0800250 * Set the untruncated number of children in the group so that the view can update the UI
251 * appropriately. Note that this may differ from the number of views attached as truncated
252 * children will not have views.
253 */
254 public void setUntruncatedChildCount(int childCount) {
255 mUntruncatedChildCount = childCount;
256 updateGroupOverflow();
257 }
258
259 /**
Selim Cinekb5605e52015-02-20 18:21:41 +0100260 * Add a child notification to this view.
261 *
262 * @param row the row to add
263 * @param childIndex the index to add it at, if -1 it will be added at the end
264 */
265 public void addNotification(ExpandableNotificationRow row, int childIndex) {
Kevin Han43077f92020-02-28 12:51:53 -0800266 int newIndex = childIndex < 0 ? mAttachedChildren.size() : childIndex;
267 mAttachedChildren.add(newIndex, row);
Selim Cinekb5605e52015-02-20 18:21:41 +0100268 addView(row);
Selim Cinek8e0c4982016-02-29 20:03:08 -0800269 row.setUserLocked(mUserLocked);
Selim Cinek7b836392015-12-04 20:02:59 -0800270
271 View divider = inflateDivider();
272 addView(divider);
273 mDividers.add(newIndex, divider);
Selim Cinekc897bd32016-03-18 17:32:31 -0700274
Selim Cinek2b549f42016-11-22 16:38:51 -0800275 row.setContentTransformationAmount(0, false /* isLastChild */);
Selim Cinekf93bf3e2018-05-08 14:43:21 -0700276 // It doesn't make sense to keep old animations around, lets cancel them!
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500277 ExpandableViewState viewState = row.getViewState();
Selim Cinekf93bf3e2018-05-08 14:43:21 -0700278 if (viewState != null) {
279 viewState.cancelAnimations(row);
280 row.cancelAppearDrawing();
281 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100282 }
283
284 public void removeNotification(ExpandableNotificationRow row) {
Kevin Han43077f92020-02-28 12:51:53 -0800285 int childIndex = mAttachedChildren.indexOf(row);
286 mAttachedChildren.remove(row);
Selim Cinekb5605e52015-02-20 18:21:41 +0100287 removeView(row);
Selim Cinek7b836392015-12-04 20:02:59 -0800288
Selim Cinek7b73a4c2016-01-12 18:32:11 -0800289 final View divider = mDividers.remove(childIndex);
Selim Cinek7b836392015-12-04 20:02:59 -0800290 removeView(divider);
Selim Cinek7b73a4c2016-01-12 18:32:11 -0800291 getOverlay().add(divider);
292 CrossFadeHelper.fadeOut(divider, new Runnable() {
293 @Override
294 public void run() {
295 getOverlay().remove(divider);
296 }
297 });
Selim Cinek7b836392015-12-04 20:02:59 -0800298
Selim Cinekb5605e52015-02-20 18:21:41 +0100299 row.setSystemChildExpanded(false);
Selim Cinek8e0c4982016-02-29 20:03:08 -0800300 row.setUserLocked(false);
Mady Mellorb0a82462016-04-30 17:31:02 -0700301 if (!row.isRemoved()) {
302 mHeaderUtil.restoreNotificationHeader(row);
303 }
304 }
305
306 /**
307 * @return The number of notification children in the container.
308 */
309 public int getNotificationChildCount() {
Kevin Han43077f92020-02-28 12:51:53 -0800310 return mAttachedChildren.size();
Mady Mellorb0a82462016-04-30 17:31:02 -0700311 }
312
Steve Elliott936df152020-04-14 13:59:53 -0400313 public void recreateNotificationHeader(OnClickListener listener, boolean isConversation) {
Selim Cinek414ad332017-02-24 19:06:12 -0800314 mHeaderClickListener = listener;
Steve Elliott936df152020-04-14 13:59:53 -0400315 mIsConversation = isConversation;
Ned Burns1c2b85a42019-11-14 15:37:03 -0500316 StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
Mady Mellorb0a82462016-04-30 17:31:02 -0700317 final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(),
Selim Cinek414ad332017-02-24 19:06:12 -0800318 notification.getNotification());
Lucas Dupin00be88f2019-01-03 17:50:52 -0800319 RemoteViews header = builder.makeNotificationHeader();
Mady Mellorb0a82462016-04-30 17:31:02 -0700320 if (mNotificationHeader == null) {
321 mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this);
322 final View expandButton = mNotificationHeader.findViewById(
323 com.android.internal.R.id.expand_button);
324 expandButton.setVisibility(VISIBLE);
Selim Cinek414ad332017-02-24 19:06:12 -0800325 mNotificationHeader.setOnClickListener(mHeaderClickListener);
Mady Mellorb0a82462016-04-30 17:31:02 -0700326 mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(),
Selim Cinek414ad332017-02-24 19:06:12 -0800327 mNotificationHeader, mContainingNotification);
Mady Mellorb0a82462016-04-30 17:31:02 -0700328 addView(mNotificationHeader, 0);
329 invalidate();
330 } else {
331 header.reapply(getContext(), mNotificationHeader);
Mady Mellorb0a82462016-04-30 17:31:02 -0700332 }
Selim Cinek131f1a42017-06-05 17:50:19 -0700333 mNotificationHeaderWrapper.onContentUpdated(mContainingNotification);
Steve Elliott936df152020-04-14 13:59:53 -0400334 if (mNotificationHeaderWrapper instanceof NotificationHeaderViewWrapper) {
335 NotificationHeaderViewWrapper headerWrapper =
336 (NotificationHeaderViewWrapper) mNotificationHeaderWrapper;
337 if (isConversation) {
338 headerWrapper.applyConversationSkin();
339 } else {
340 headerWrapper.clearConversationSkin();
341 }
342 }
343 recreateLowPriorityHeader(builder, isConversation);
Lucas Dupin6ea27872017-05-30 12:00:04 -0700344 updateHeaderVisibility(false /* animate */);
Mady Mellorb0a82462016-04-30 17:31:02 -0700345 updateChildrenHeaderAppearance();
346 }
347
Selim Cinek414ad332017-02-24 19:06:12 -0800348 /**
349 * Recreate the low-priority header.
350 *
351 * @param builder a builder to reuse. Otherwise the builder will be recovered.
352 */
Steve Elliott936df152020-04-14 13:59:53 -0400353 private void recreateLowPriorityHeader(Notification.Builder builder, boolean isConversation) {
Selim Cinek414ad332017-02-24 19:06:12 -0800354 RemoteViews header;
Ned Burns1c2b85a42019-11-14 15:37:03 -0500355 StatusBarNotification notification = mContainingNotification.getEntry().getSbn();
Selim Cinek414ad332017-02-24 19:06:12 -0800356 if (mIsLowPriority) {
357 if (builder == null) {
358 builder = Notification.Builder.recoverBuilder(getContext(),
359 notification.getNotification());
360 }
361 header = builder.makeLowPriorityContentView(true /* useRegularSubtext */);
362 if (mNotificationHeaderLowPriority == null) {
363 mNotificationHeaderLowPriority = (NotificationHeaderView) header.apply(getContext(),
364 this);
365 final View expandButton = mNotificationHeaderLowPriority.findViewById(
366 com.android.internal.R.id.expand_button);
367 expandButton.setVisibility(VISIBLE);
368 mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener);
369 mNotificationHeaderWrapperLowPriority = NotificationViewWrapper.wrap(getContext(),
370 mNotificationHeaderLowPriority, mContainingNotification);
371 addView(mNotificationHeaderLowPriority, 0);
372 invalidate();
373 } else {
374 header.reapply(getContext(), mNotificationHeaderLowPriority);
375 }
Selim Cinek131f1a42017-06-05 17:50:19 -0700376 mNotificationHeaderWrapperLowPriority.onContentUpdated(mContainingNotification);
Steve Elliott936df152020-04-14 13:59:53 -0400377 if (mNotificationHeaderWrapper instanceof NotificationHeaderViewWrapper) {
378 NotificationHeaderViewWrapper headerWrapper =
379 (NotificationHeaderViewWrapper) mNotificationHeaderWrapper;
380 if (isConversation) {
381 headerWrapper.applyConversationSkin();
382 } else {
383 headerWrapper.clearConversationSkin();
384 }
385 }
Adrian Roos6f6e1592017-05-02 16:22:53 -0700386 resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, calculateDesiredHeader());
Selim Cinek414ad332017-02-24 19:06:12 -0800387 } else {
Selim Cinek6fd06b52017-03-07 15:54:10 -0800388 removeView(mNotificationHeaderLowPriority);
Selim Cinek414ad332017-02-24 19:06:12 -0800389 mNotificationHeaderLowPriority = null;
390 mNotificationHeaderWrapperLowPriority = null;
391 }
392 }
393
Mady Mellorb0a82462016-04-30 17:31:02 -0700394 public void updateChildrenHeaderAppearance() {
395 mHeaderUtil.updateChildrenHeaderAppearance();
Selim Cinekc897bd32016-03-18 17:32:31 -0700396 }
397
398 public void updateGroupOverflow() {
Selim Cinekc897bd32016-03-18 17:32:31 -0700399 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
Kevin Han43077f92020-02-28 12:51:53 -0800400 if (mUntruncatedChildCount > maxAllowedVisibleChildren) {
401 int number = mUntruncatedChildCount - maxAllowedVisibleChildren;
Steve Elliott936df152020-04-14 13:59:53 -0400402 mOverflowNumber = mHybridGroupManager.bindOverflowNumber(mOverflowNumber, number, this);
Selim Cinekc897bd32016-03-18 17:32:31 -0700403 if (mGroupOverFlowState == null) {
404 mGroupOverFlowState = new ViewState();
405 mNeverAppliedGroupState = true;
406 }
407 } else if (mOverflowNumber != null) {
408 removeView(mOverflowNumber);
Eliot Courtneybb8af1c2018-01-11 16:44:45 +0900409 if (isShown() && isAttachedToWindow()) {
Selim Cinekc897bd32016-03-18 17:32:31 -0700410 final View removedOverflowNumber = mOverflowNumber;
411 addTransientView(removedOverflowNumber, getTransientViewCount());
412 CrossFadeHelper.fadeOut(removedOverflowNumber, new Runnable() {
413 @Override
414 public void run() {
415 removeTransientView(removedOverflowNumber);
416 }
417 });
418 }
419 mOverflowNumber = null;
Selim Cinekc897bd32016-03-18 17:32:31 -0700420 mGroupOverFlowState = null;
421 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100422 }
423
Selim Cineka3d3b912016-02-02 11:22:06 -0800424 @Override
425 protected void onConfigurationChanged(Configuration newConfig) {
426 super.onConfigurationChanged(newConfig);
Selim Cinekc897bd32016-03-18 17:32:31 -0700427 updateGroupOverflow();
Selim Cineka3d3b912016-02-02 11:22:06 -0800428 }
429
Selim Cinekb5605e52015-02-20 18:21:41 +0100430 private View inflateDivider() {
431 return LayoutInflater.from(mContext).inflate(
432 R.layout.notification_children_divider, this, false);
433 }
434
Kevin Han43077f92020-02-28 12:51:53 -0800435 /**
436 * Get notification children that are attached currently.
437 */
438 public List<ExpandableNotificationRow> getAttachedChildren() {
439 return mAttachedChildren;
Selim Cinekb5605e52015-02-20 18:21:41 +0100440 }
441
442 /**
443 * Apply the order given in the list to the children.
444 *
445 * @param childOrder the new list order
Selim Cineka7d4f822016-12-06 14:34:47 -0800446 * @param visualStabilityManager
447 * @param callback
Selim Cinekb5605e52015-02-20 18:21:41 +0100448 * @return whether the list order has changed
449 */
Evan Laird9a9a6192019-12-19 10:44:21 -0500450 public boolean applyChildOrder(List<? extends NotificationListItem> childOrder,
Selim Cineka7d4f822016-12-06 14:34:47 -0800451 VisualStabilityManager visualStabilityManager,
452 VisualStabilityManager.Callback callback) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100453 if (childOrder == null) {
454 return false;
455 }
456 boolean result = false;
Kevin Han43077f92020-02-28 12:51:53 -0800457 for (int i = 0; i < mAttachedChildren.size() && i < childOrder.size(); i++) {
458 ExpandableNotificationRow child = mAttachedChildren.get(i);
Evan Laird9a9a6192019-12-19 10:44:21 -0500459 ExpandableNotificationRow desiredChild = (ExpandableNotificationRow) childOrder.get(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100460 if (child != desiredChild) {
Selim Cineka7d4f822016-12-06 14:34:47 -0800461 if (visualStabilityManager.canReorderNotification(desiredChild)) {
Kevin Han43077f92020-02-28 12:51:53 -0800462 mAttachedChildren.remove(desiredChild);
463 mAttachedChildren.add(i, desiredChild);
Selim Cineka7d4f822016-12-06 14:34:47 -0800464 result = true;
465 } else {
Selim Cinek0260c752020-05-11 16:03:52 -0700466 visualStabilityManager.addReorderingAllowedCallback(callback,
467 false /* persistent */);
Selim Cineka7d4f822016-12-06 14:34:47 -0800468 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100469 }
470 }
Selim Cinek83bc7832015-10-22 13:26:54 -0700471 updateExpansionStates();
Selim Cinekb5605e52015-02-20 18:21:41 +0100472 return result;
473 }
474
Selim Cinek83bc7832015-10-22 13:26:54 -0700475 private void updateExpansionStates() {
Selim Cinek898d1732016-02-29 19:57:35 -0800476 if (mChildrenExpanded || mUserLocked) {
477 // we don't modify it the group is expanded or if we are expanding it
478 return;
479 }
Kevin Han43077f92020-02-28 12:51:53 -0800480 int size = mAttachedChildren.size();
Selim Cinek898d1732016-02-29 19:57:35 -0800481 for (int i = 0; i < size; i++) {
Kevin Han43077f92020-02-28 12:51:53 -0800482 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cinek898d1732016-02-29 19:57:35 -0800483 child.setSystemChildExpanded(i == 0 && size == 1);
Selim Cinek83bc7832015-10-22 13:26:54 -0700484 }
485 }
486
487 /**
488 *
489 * @return the intrinsic size of this children container, i.e the natural fully expanded state
490 */
Selim Cinekb5605e52015-02-20 18:21:41 +0100491 public int getIntrinsicHeight() {
Selim Cinek83bc7832015-10-22 13:26:54 -0700492 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
493 return getIntrinsicHeight(maxAllowedVisibleChildren);
494 }
495
496 /**
497 * @return the intrinsic height with a number of children given
498 * in @param maxAllowedVisibleChildren
499 */
500 private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
Selim Cinek414ad332017-02-24 19:06:12 -0800501 if (showingAsLowPriority()) {
502 return mNotificationHeaderLowPriority.getHeight();
503 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800504 int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100505 int visibleChildren = 0;
Kevin Han43077f92020-02-28 12:51:53 -0800506 int childCount = mAttachedChildren.size();
Selim Cinek42357e02016-02-24 18:48:01 -0800507 boolean firstChild = true;
508 float expandFactor = 0;
509 if (mUserLocked) {
Selim Cinekf07d0622016-03-21 19:52:52 -0700510 expandFactor = getGroupExpandFraction();
Selim Cinek42357e02016-02-24 18:48:01 -0800511 }
Selim Cinek7927c732019-03-06 15:21:39 -0800512 boolean childrenExpanded = mChildrenExpanded;
Selim Cinekb5605e52015-02-20 18:21:41 +0100513 for (int i = 0; i < childCount; i++) {
Selim Cinek83bc7832015-10-22 13:26:54 -0700514 if (visibleChildren >= maxAllowedVisibleChildren) {
515 break;
516 }
Selim Cinek42357e02016-02-24 18:48:01 -0800517 if (!firstChild) {
518 if (mUserLocked) {
519 intrinsicHeight += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
520 expandFactor);
521 } else {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700522 intrinsicHeight += childrenExpanded ? mDividerHeight : mChildPadding;
Selim Cinek42357e02016-02-24 18:48:01 -0800523 }
524 } else {
525 if (mUserLocked) {
526 intrinsicHeight += NotificationUtils.interpolate(
527 0,
528 mNotificatonTopPadding + mDividerHeight,
529 expandFactor);
530 } else {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700531 intrinsicHeight += childrenExpanded
Selim Cinek42357e02016-02-24 18:48:01 -0800532 ? mNotificatonTopPadding + mDividerHeight
533 : 0;
534 }
535 firstChild = false;
536 }
Kevin Han43077f92020-02-28 12:51:53 -0800537 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100538 intrinsicHeight += child.getIntrinsicHeight();
539 visibleChildren++;
540 }
Selim Cinek42357e02016-02-24 18:48:01 -0800541 if (mUserLocked) {
542 intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottompadding, 0.0f,
543 expandFactor);
Adrian Roos6f6e1592017-05-02 16:22:53 -0700544 } else if (!childrenExpanded) {
Selim Cinek83bc7832015-10-22 13:26:54 -0700545 intrinsicHeight += mCollapsedBottompadding;
546 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100547 return intrinsicHeight;
548 }
549
550 /**
551 * Update the state of all its children based on a linear layout algorithm.
Selim Cinekb5605e52015-02-20 18:21:41 +0100552 * @param parentState the state of the parent
Selim Cinek7927c732019-03-06 15:21:39 -0800553 * @param ambientState the ambient state containing ambient information
Selim Cinekb5605e52015-02-20 18:21:41 +0100554 */
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500555 public void updateState(ExpandableViewState parentState, AmbientState ambientState) {
Kevin Han43077f92020-02-28 12:51:53 -0800556 int childCount = mAttachedChildren.size();
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800557 int yPosition = mNotificationHeaderMargin + mCurrentHeaderTranslation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100558 boolean firstChild = true;
Selim Cinek83bc7832015-10-22 13:26:54 -0700559 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
Selim Cinek91c2a152016-03-17 15:01:38 -0700560 int lastVisibleIndex = maxAllowedVisibleChildren - 1;
Selim Cinekf07d0622016-03-21 19:52:52 -0700561 int firstOverflowIndex = lastVisibleIndex + 1;
Selim Cinek42357e02016-02-24 18:48:01 -0800562 float expandFactor = 0;
Selim Cinek414ad332017-02-24 19:06:12 -0800563 boolean expandingToExpandedGroup = mUserLocked && !showingAsLowPriority();
Selim Cinek42357e02016-02-24 18:48:01 -0800564 if (mUserLocked) {
Selim Cinekf07d0622016-03-21 19:52:52 -0700565 expandFactor = getGroupExpandFraction();
566 firstOverflowIndex = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
Selim Cinek42357e02016-02-24 18:48:01 -0800567 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700568
Adrian Roos16fe4952017-05-18 16:10:21 -0700569 boolean childrenExpandedAndNotAnimating = mChildrenExpanded
570 && !mContainingNotification.isGroupExpansionChanging();
Selim Cinekc25989e2018-02-16 16:42:14 -0800571 int launchTransitionCompensation = 0;
Selim Cinekb5605e52015-02-20 18:21:41 +0100572 for (int i = 0; i < childCount; i++) {
Kevin Han43077f92020-02-28 12:51:53 -0800573 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100574 if (!firstChild) {
Selim Cinek414ad332017-02-24 19:06:12 -0800575 if (expandingToExpandedGroup) {
Mady Mellorb0a82462016-04-30 17:31:02 -0700576 yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
577 expandFactor);
Selim Cinek42357e02016-02-24 18:48:01 -0800578 } else {
Adrian Roos16fe4952017-05-18 16:10:21 -0700579 yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding;
Selim Cinek42357e02016-02-24 18:48:01 -0800580 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100581 } else {
Selim Cinek414ad332017-02-24 19:06:12 -0800582 if (expandingToExpandedGroup) {
Selim Cinek42357e02016-02-24 18:48:01 -0800583 yPosition += NotificationUtils.interpolate(
584 0,
585 mNotificatonTopPadding + mDividerHeight,
586 expandFactor);
587 } else {
Adrian Roos16fe4952017-05-18 16:10:21 -0700588 yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0;
Selim Cinek42357e02016-02-24 18:48:01 -0800589 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100590 firstChild = false;
591 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700592
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500593 ExpandableViewState childState = child.getViewState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100594 int intrinsicHeight = child.getIntrinsicHeight();
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800595 childState.height = intrinsicHeight;
Selim Cinekc25989e2018-02-16 16:42:14 -0800596 childState.yTranslation = yPosition + launchTransitionCompensation;
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800597 childState.hidden = false;
Mady Mellorb0a82462016-04-30 17:31:02 -0700598 // When the group is expanded, the children cast the shadows rather than the parent
599 // so use the parent's elevation here.
Adrian Roos16fe4952017-05-18 16:10:21 -0700600 childState.zTranslation =
601 (childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications)
Selim Cinekc25989e2018-02-16 16:42:14 -0800602 ? parentState.zTranslation
Mady Mellorb0a82462016-04-30 17:31:02 -0700603 : 0;
Selim Cinekb5605e52015-02-20 18:21:41 +0100604 childState.dimmed = parentState.dimmed;
Selim Cinekb5605e52015-02-20 18:21:41 +0100605 childState.hideSensitive = parentState.hideSensitive;
Selim Cinekdb167372016-11-17 15:41:17 -0800606 childState.belowSpeedBump = parentState.belowSpeedBump;
Selim Cinekb5605e52015-02-20 18:21:41 +0100607 childState.clipTopAmount = 0;
Selim Cinekf07d0622016-03-21 19:52:52 -0700608 childState.alpha = 0;
609 if (i < firstOverflowIndex) {
Selim Cinek414ad332017-02-24 19:06:12 -0800610 childState.alpha = showingAsLowPriority() ? expandFactor : 1.0f;
Selim Cinekf07d0622016-03-21 19:52:52 -0700611 } else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
612 childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
613 childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
614 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100615 childState.location = parentState.location;
Selim Cineka7d4f822016-12-06 14:34:47 -0800616 childState.inShelf = parentState.inShelf;
Selim Cinekb5605e52015-02-20 18:21:41 +0100617 yPosition += intrinsicHeight;
Selim Cinekc25989e2018-02-16 16:42:14 -0800618 if (child.isExpandAnimationRunning()) {
619 launchTransitionCompensation = -ambientState.getExpandAnimationTopChange();
620 }
Selim Cineka7d4f822016-12-06 14:34:47 -0800621
Selim Cinekb5605e52015-02-20 18:21:41 +0100622 }
Selim Cinekc897bd32016-03-18 17:32:31 -0700623 if (mOverflowNumber != null) {
Kevin Han43077f92020-02-28 12:51:53 -0800624 ExpandableNotificationRow overflowView = mAttachedChildren.get(Math.min(
Lucas Dupin4c797d62018-05-07 15:32:13 -0700625 getMaxAllowedVisibleChildren(true /* likeCollapsed */), childCount) - 1);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500626 mGroupOverFlowState.copyFrom(overflowView.getViewState());
Adrian Roos6f6e1592017-05-02 16:22:53 -0700627
Selim Cinek7927c732019-03-06 15:21:39 -0800628 if (!mChildrenExpanded) {
Lucas Dupin4c797d62018-05-07 15:32:13 -0700629 HybridNotificationView alignView = overflowView.getSingleLineView();
Adrian Roos6f6e1592017-05-02 16:22:53 -0700630 if (alignView != null) {
631 View mirrorView = alignView.getTextView();
Selim Cinekc897bd32016-03-18 17:32:31 -0700632 if (mirrorView.getVisibility() == GONE) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700633 mirrorView = alignView.getTitleView();
Selim Cinekc897bd32016-03-18 17:32:31 -0700634 }
635 if (mirrorView.getVisibility() == GONE) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700636 mirrorView = alignView;
Selim Cinekc897bd32016-03-18 17:32:31 -0700637 }
Lucas Dupin4c797d62018-05-07 15:32:13 -0700638 mGroupOverFlowState.alpha = mirrorView.getAlpha();
Selim Cinekc897bd32016-03-18 17:32:31 -0700639 mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset(
640 mirrorView, overflowView);
Selim Cinekc897bd32016-03-18 17:32:31 -0700641 }
642 } else {
Mady Mellorb0a82462016-04-30 17:31:02 -0700643 mGroupOverFlowState.yTranslation += mNotificationHeaderMargin;
Selim Cinekc897bd32016-03-18 17:32:31 -0700644 mGroupOverFlowState.alpha = 0.0f;
645 }
646 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700647 if (mNotificationHeader != null) {
648 if (mHeaderViewState == null) {
649 mHeaderViewState = new ViewState();
650 }
651 mHeaderViewState.initFrom(mNotificationHeader);
Adrian Roos16fe4952017-05-18 16:10:21 -0700652 mHeaderViewState.zTranslation = childrenExpandedAndNotAnimating
Selim Cinekc25989e2018-02-16 16:42:14 -0800653 ? parentState.zTranslation
Mady Mellorb0a82462016-04-30 17:31:02 -0700654 : 0;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800655 mHeaderViewState.yTranslation = mCurrentHeaderTranslation;
656 mHeaderViewState.alpha = mHeaderVisibleAmount;
657 // The hiding is done automatically by the alpha, otherwise we'll pick it up again
658 // in the next frame with the initFrom call above and have an invisible header
659 mHeaderViewState.hidden = false;
Mady Mellorb0a82462016-04-30 17:31:02 -0700660 }
661 }
662
663 /**
664 * When moving into the bottom stack, the bottom visible child in an expanded group adjusts its
665 * height, children in the group after this are gone.
666 *
667 * @param child the child who's height to adjust.
668 * @param parentHeight the height of the parent.
669 * @param childState the state to update.
670 * @param yPosition the yPosition of the view.
671 * @return true if children after this one should be hidden.
672 */
673 private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child,
Selim Cinekbbcebde2016-11-09 18:28:20 -0800674 int parentHeight, ExpandableViewState childState, int yPosition) {
Mady Mellorb0a82462016-04-30 17:31:02 -0700675 final int top = yPosition + child.getClipTopAmount();
676 final int intrinsicHeight = child.getIntrinsicHeight();
677 final int bottom = top + intrinsicHeight;
678 int newHeight = intrinsicHeight;
679 if (bottom >= parentHeight) {
680 // Child is either clipped or gone
681 newHeight = Math.max((parentHeight - top), 0);
682 }
683 childState.hidden = newHeight == 0;
684 childState.height = newHeight;
685 return childState.height != intrinsicHeight && !childState.hidden;
Selim Cinek83bc7832015-10-22 13:26:54 -0700686 }
687
Aaron Heuckroth17ce40e2018-06-19 15:44:40 -0400688 @VisibleForTesting
689 int getMaxAllowedVisibleChildren() {
Selim Cinek83bc7832015-10-22 13:26:54 -0700690 return getMaxAllowedVisibleChildren(false /* likeCollapsed */);
691 }
692
Aaron Heuckroth17ce40e2018-06-19 15:44:40 -0400693 @VisibleForTesting
694 int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
Aaron Heuckroth17ce40e2018-06-19 15:44:40 -0400695 if (!likeCollapsed && (mChildrenExpanded || mContainingNotification.isUserLocked())
696 && !showingAsLowPriority()) {
Selim Cinek83bc7832015-10-22 13:26:54 -0700697 return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
698 }
Selim Cinekc3fec682019-06-06 18:11:07 -0700699 if (mIsLowPriority
700 || (!mContainingNotification.isOnKeyguard() && mContainingNotification.isExpanded())
701 || (mContainingNotification.isHeadsUpState()
702 && mContainingNotification.canShowHeadsUp())) {
Selim Cinek83bc7832015-10-22 13:26:54 -0700703 return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
704 }
705 return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
Selim Cinekb5605e52015-02-20 18:21:41 +0100706 }
707
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500708 /** Applies state to children. */
709 public void applyState() {
Kevin Han43077f92020-02-28 12:51:53 -0800710 int childCount = mAttachedChildren.size();
Selim Cinek83bc7832015-10-22 13:26:54 -0700711 ViewState tmpState = new ViewState();
Selim Cinekc897bd32016-03-18 17:32:31 -0700712 float expandFraction = 0.0f;
713 if (mUserLocked) {
Selim Cinekf07d0622016-03-21 19:52:52 -0700714 expandFraction = getGroupExpandFraction();
Selim Cinekc897bd32016-03-18 17:32:31 -0700715 }
Selim Cinek414ad332017-02-24 19:06:12 -0800716 final boolean dividersVisible = mUserLocked && !showingAsLowPriority()
Anthony Chen6bf88a02017-04-10 14:41:44 -0700717 || (mChildrenExpanded && mShowDividersWhenExpanded)
718 || (mContainingNotification.isGroupExpansionChanging()
719 && !mHideDividersDuringExpand);
Selim Cinekb5605e52015-02-20 18:21:41 +0100720 for (int i = 0; i < childCount; i++) {
Kevin Han43077f92020-02-28 12:51:53 -0800721 ExpandableNotificationRow child = mAttachedChildren.get(i);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500722 ExpandableViewState viewState = child.getViewState();
Selim Cinekbbcebde2016-11-09 18:28:20 -0800723 viewState.applyToView(child);
Selim Cinek7b836392015-12-04 20:02:59 -0800724
725 // layout the divider
726 View divider = mDividers.get(i);
727 tmpState.initFrom(divider);
728 tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
Anthony Chen6bf88a02017-04-10 14:41:44 -0700729 float alpha = mChildrenExpanded && viewState.alpha != 0 ? mDividerAlpha : 0;
Selim Cinek414ad332017-02-24 19:06:12 -0800730 if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
Selim Cinekf07d0622016-03-21 19:52:52 -0700731 alpha = NotificationUtils.interpolate(0, 0.5f,
732 Math.min(viewState.alpha, expandFraction));
Selim Cinek42357e02016-02-24 18:48:01 -0800733 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700734 tmpState.hidden = !dividersVisible;
Selim Cinek42357e02016-02-24 18:48:01 -0800735 tmpState.alpha = alpha;
Selim Cinekbbcebde2016-11-09 18:28:20 -0800736 tmpState.applyToView(divider);
Selim Cinekd9a3f8f2016-03-17 19:39:00 -0700737 // There is no fake shadow to be drawn on the children
738 child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
Selim Cinekb5605e52015-02-20 18:21:41 +0100739 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800740 if (mGroupOverFlowState != null) {
Selim Cinekbbcebde2016-11-09 18:28:20 -0800741 mGroupOverFlowState.applyToView(mOverflowNumber);
Selim Cinekc897bd32016-03-18 17:32:31 -0700742 mNeverAppliedGroupState = false;
743 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800744 if (mHeaderViewState != null) {
Selim Cinekbbcebde2016-11-09 18:28:20 -0800745 mHeaderViewState.applyToView(mNotificationHeader);
Mady Mellorb0a82462016-04-30 17:31:02 -0700746 }
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800747 updateChildrenClipping();
748 }
749
750 private void updateChildrenClipping() {
Selim Cinekc25989e2018-02-16 16:42:14 -0800751 if (mContainingNotification.hasExpandingChild()) {
752 return;
753 }
Kevin Han43077f92020-02-28 12:51:53 -0800754 int childCount = mAttachedChildren.size();
Selim Cinek414ad332017-02-24 19:06:12 -0800755 int layoutEnd = mContainingNotification.getActualHeight() - mClipBottomAmount;
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800756 for (int i = 0; i < childCount; i++) {
Kevin Han43077f92020-02-28 12:51:53 -0800757 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800758 if (child.getVisibility() == GONE) {
759 continue;
760 }
761 float childTop = child.getTranslationY();
762 float childBottom = childTop + child.getActualHeight();
763 boolean visible = true;
764 int clipBottomAmount = 0;
765 if (childTop > layoutEnd) {
766 visible = false;
767 } else if (childBottom > layoutEnd) {
768 clipBottomAmount = (int) (childBottom - layoutEnd);
769 }
770
771 boolean isVisible = child.getVisibility() == VISIBLE;
772 if (visible != isVisible) {
773 child.setVisibility(visible ? VISIBLE : INVISIBLE);
774 }
775
776 child.setClipBottomAmount(clipBottomAmount);
777 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100778 }
779
Selim Cinekb5605e52015-02-20 18:21:41 +0100780 /**
781 * This is called when the children expansion has changed and positions the children properly
782 * for an appear animation.
783 *
Selim Cinekb5605e52015-02-20 18:21:41 +0100784 */
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500785 public void prepareExpansionChanged() {
Selim Cinek277a8aa2016-01-22 12:12:37 -0800786 // TODO: do something that makes sense, like placing the invisible views correctly
787 return;
Selim Cinekb5605e52015-02-20 18:21:41 +0100788 }
789
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500790 /** Animate to a given state. */
791 public void startAnimationToState(AnimationProperties properties) {
Kevin Han43077f92020-02-28 12:51:53 -0800792 int childCount = mAttachedChildren.size();
Selim Cinek83bc7832015-10-22 13:26:54 -0700793 ViewState tmpState = new ViewState();
Selim Cinekf07d0622016-03-21 19:52:52 -0700794 float expandFraction = getGroupExpandFraction();
Selim Cinek414ad332017-02-24 19:06:12 -0800795 final boolean dividersVisible = mUserLocked && !showingAsLowPriority()
Anthony Chen6bf88a02017-04-10 14:41:44 -0700796 || (mChildrenExpanded && mShowDividersWhenExpanded)
797 || (mContainingNotification.isGroupExpansionChanging()
798 && !mHideDividersDuringExpand);
Selim Cineke8126522015-12-08 21:21:17 -0800799 for (int i = childCount - 1; i >= 0; i--) {
Kevin Han43077f92020-02-28 12:51:53 -0800800 ExpandableNotificationRow child = mAttachedChildren.get(i);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500801 ExpandableViewState viewState = child.getViewState();
Selim Cinek0cfbef42016-11-09 19:06:36 -0800802 viewState.animateTo(child, properties);
Selim Cinek7b836392015-12-04 20:02:59 -0800803
804 // layout the divider
805 View divider = mDividers.get(i);
806 tmpState.initFrom(divider);
807 tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
Selim Cinek42357e02016-02-24 18:48:01 -0800808 float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
Selim Cinek414ad332017-02-24 19:06:12 -0800809 if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
Selim Cinekf07d0622016-03-21 19:52:52 -0700810 alpha = NotificationUtils.interpolate(0, 0.5f,
811 Math.min(viewState.alpha, expandFraction));
Selim Cinek42357e02016-02-24 18:48:01 -0800812 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700813 tmpState.hidden = !dividersVisible;
Selim Cinek42357e02016-02-24 18:48:01 -0800814 tmpState.alpha = alpha;
Selim Cinek0cfbef42016-11-09 19:06:36 -0800815 tmpState.animateTo(divider, properties);
Selim Cinekd9a3f8f2016-03-17 19:39:00 -0700816 // There is no fake shadow to be drawn on the children
817 child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
Selim Cinekb5605e52015-02-20 18:21:41 +0100818 }
Selim Cinekc897bd32016-03-18 17:32:31 -0700819 if (mOverflowNumber != null) {
820 if (mNeverAppliedGroupState) {
821 float alpha = mGroupOverFlowState.alpha;
822 mGroupOverFlowState.alpha = 0;
Selim Cinekbbcebde2016-11-09 18:28:20 -0800823 mGroupOverFlowState.applyToView(mOverflowNumber);
Selim Cinekc897bd32016-03-18 17:32:31 -0700824 mGroupOverFlowState.alpha = alpha;
825 mNeverAppliedGroupState = false;
826 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800827 mGroupOverFlowState.animateTo(mOverflowNumber, properties);
Selim Cinekc897bd32016-03-18 17:32:31 -0700828 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700829 if (mNotificationHeader != null) {
Selim Cinekbbcebde2016-11-09 18:28:20 -0800830 mHeaderViewState.applyToView(mNotificationHeader);
Mady Mellorb0a82462016-04-30 17:31:02 -0700831 }
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800832 updateChildrenClipping();
Selim Cinekb5605e52015-02-20 18:21:41 +0100833 }
834
835 public ExpandableNotificationRow getViewAtPosition(float y) {
836 // find the view under the pointer, accounting for GONE views
Kevin Han43077f92020-02-28 12:51:53 -0800837 final int count = mAttachedChildren.size();
Selim Cinekb5605e52015-02-20 18:21:41 +0100838 for (int childIdx = 0; childIdx < count; childIdx++) {
Kevin Han43077f92020-02-28 12:51:53 -0800839 ExpandableNotificationRow slidingChild = mAttachedChildren.get(childIdx);
Selim Cinekb5605e52015-02-20 18:21:41 +0100840 float childTop = slidingChild.getTranslationY();
841 float top = childTop + slidingChild.getClipTopAmount();
842 float bottom = childTop + slidingChild.getActualHeight();
843 if (y >= top && y <= bottom) {
844 return slidingChild;
845 }
846 }
847 return null;
848 }
849
Selim Cinek83bc7832015-10-22 13:26:54 -0700850 public void setChildrenExpanded(boolean childrenExpanded) {
851 mChildrenExpanded = childrenExpanded;
Selim Cinek898d1732016-02-29 19:57:35 -0800852 updateExpansionStates();
Mady Mellorb0a82462016-04-30 17:31:02 -0700853 if (mNotificationHeader != null) {
854 mNotificationHeader.setExpanded(childrenExpanded);
855 }
Kevin Han43077f92020-02-28 12:51:53 -0800856 final int count = mAttachedChildren.size();
Selim Cinekddf1b392016-05-27 16:33:10 -0700857 for (int childIdx = 0; childIdx < count; childIdx++) {
Kevin Han43077f92020-02-28 12:51:53 -0800858 ExpandableNotificationRow child = mAttachedChildren.get(childIdx);
Selim Cinekddf1b392016-05-27 16:33:10 -0700859 child.setChildrenExpanded(childrenExpanded, false);
860 }
Selim Cinekbc342152018-12-05 18:45:42 -0800861 updateHeaderTouchability();
Selim Cinek83bc7832015-10-22 13:26:54 -0700862 }
863
Selim Cinek414ad332017-02-24 19:06:12 -0800864 public void setContainingNotification(ExpandableNotificationRow parent) {
865 mContainingNotification = parent;
866 mHeaderUtil = new NotificationHeaderUtil(mContainingNotification);
Mady Mellorb0a82462016-04-30 17:31:02 -0700867 }
868
Selim Cinek414ad332017-02-24 19:06:12 -0800869 public ExpandableNotificationRow getContainingNotification() {
870 return mContainingNotification;
Adrian Roos4a579672016-05-24 16:54:37 -0700871 }
872
Mady Mellorb0a82462016-04-30 17:31:02 -0700873 public NotificationHeaderView getHeaderView() {
874 return mNotificationHeader;
875 }
876
Selim Cinek414ad332017-02-24 19:06:12 -0800877 public NotificationHeaderView getLowPriorityHeaderView() {
878 return mNotificationHeaderLowPriority;
879 }
880
Lucas Dupin6ea27872017-05-30 12:00:04 -0700881 @VisibleForTesting
882 public ViewGroup getCurrentHeaderView() {
883 return mCurrentHeader;
884 }
885
Selim Cinek414ad332017-02-24 19:06:12 -0800886 private void updateHeaderVisibility(boolean animate) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700887 ViewGroup desiredHeader;
888 ViewGroup currentHeader = mCurrentHeader;
889 desiredHeader = calculateDesiredHeader();
890
891 if (currentHeader == desiredHeader) {
892 return;
Mady Mellorb0a82462016-04-30 17:31:02 -0700893 }
Adrian Roos6f6e1592017-05-02 16:22:53 -0700894
Selim Cinek414ad332017-02-24 19:06:12 -0800895 if (animate) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700896 if (desiredHeader != null && currentHeader != null) {
897 currentHeader.setVisibility(VISIBLE);
898 desiredHeader.setVisibility(VISIBLE);
899 NotificationViewWrapper visibleWrapper = getWrapperForView(desiredHeader);
900 NotificationViewWrapper hiddenWrapper = getWrapperForView(currentHeader);
Selim Cinek414ad332017-02-24 19:06:12 -0800901 visibleWrapper.transformFrom(hiddenWrapper);
902 hiddenWrapper.transformTo(visibleWrapper, () -> updateHeaderVisibility(false));
Adrian Roos6f6e1592017-05-02 16:22:53 -0700903 startChildAlphaAnimations(desiredHeader == mNotificationHeader);
Selim Cinek414ad332017-02-24 19:06:12 -0800904 } else {
905 animate = false;
906 }
907 }
908 if (!animate) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700909 if (desiredHeader != null) {
910 getWrapperForView(desiredHeader).setVisible(true);
911 desiredHeader.setVisibility(VISIBLE);
Selim Cinek414ad332017-02-24 19:06:12 -0800912 }
Adrian Roos6f6e1592017-05-02 16:22:53 -0700913 if (currentHeader != null) {
Lucas Dupin6ea27872017-05-30 12:00:04 -0700914 // Wrapper can be null if we were a low priority notification
915 // and just destroyed it by calling setIsLowPriority(false)
916 NotificationViewWrapper wrapper = getWrapperForView(currentHeader);
917 if (wrapper != null) {
918 wrapper.setVisible(false);
919 }
Adrian Roos6f6e1592017-05-02 16:22:53 -0700920 currentHeader.setVisibility(INVISIBLE);
Selim Cinek414ad332017-02-24 19:06:12 -0800921 }
922 }
Adrian Roos6f6e1592017-05-02 16:22:53 -0700923
924 resetHeaderVisibilityIfNeeded(mNotificationHeader, desiredHeader);
Adrian Roos6f6e1592017-05-02 16:22:53 -0700925 resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, desiredHeader);
926
Lucas Dupin6ea27872017-05-30 12:00:04 -0700927 mCurrentHeader = desiredHeader;
Adrian Roos6f6e1592017-05-02 16:22:53 -0700928 }
929
930 private void resetHeaderVisibilityIfNeeded(View header, View desiredHeader) {
931 if (header == null) {
932 return;
933 }
934 if (header != mCurrentHeader && header != desiredHeader) {
935 getWrapperForView(header).setVisible(false);
936 header.setVisibility(INVISIBLE);
937 }
938 if (header == desiredHeader && header.getVisibility() != VISIBLE) {
939 getWrapperForView(header).setVisible(true);
940 header.setVisibility(VISIBLE);
941 }
942 }
943
944 private ViewGroup calculateDesiredHeader() {
945 ViewGroup desiredHeader;
Selim Cinek7927c732019-03-06 15:21:39 -0800946 if (showingAsLowPriority()) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700947 desiredHeader = mNotificationHeaderLowPriority;
948 } else {
949 desiredHeader = mNotificationHeader;
950 }
951 return desiredHeader;
Selim Cinek414ad332017-02-24 19:06:12 -0800952 }
953
954 private void startChildAlphaAnimations(boolean toVisible) {
955 float target = toVisible ? 1.0f : 0.0f;
956 float start = 1.0f - target;
Kevin Han43077f92020-02-28 12:51:53 -0800957 int childCount = mAttachedChildren.size();
Selim Cinek414ad332017-02-24 19:06:12 -0800958 for (int i = 0; i < childCount; i++) {
959 if (i >= NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED) {
960 break;
961 }
Kevin Han43077f92020-02-28 12:51:53 -0800962 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cinek414ad332017-02-24 19:06:12 -0800963 child.setAlpha(start);
964 ViewState viewState = new ViewState();
965 viewState.initFrom(child);
966 viewState.alpha = target;
967 ALPHA_FADE_IN.setDelay(i * 50);
968 viewState.animateTo(child, ALPHA_FADE_IN);
969 }
970 }
971
972
973 private void updateHeaderTransformation() {
Selim Cinek1826d982017-03-06 20:47:37 -0800974 if (mUserLocked && showingAsLowPriority()) {
Selim Cinek414ad332017-02-24 19:06:12 -0800975 float fraction = getGroupExpandFraction();
976 mNotificationHeaderWrapper.transformFrom(mNotificationHeaderWrapperLowPriority,
977 fraction);
978 mNotificationHeader.setVisibility(VISIBLE);
979 mNotificationHeaderWrapperLowPriority.transformTo(mNotificationHeaderWrapper,
980 fraction);
981 }
982
983 }
984
Adrian Roos6f6e1592017-05-02 16:22:53 -0700985 private NotificationViewWrapper getWrapperForView(View visibleHeader) {
Selim Cinek414ad332017-02-24 19:06:12 -0800986 if (visibleHeader == mNotificationHeader) {
987 return mNotificationHeaderWrapper;
988 }
989 return mNotificationHeaderWrapperLowPriority;
Mady Mellorb0a82462016-04-30 17:31:02 -0700990 }
991
992 /**
993 * Called when a groups expansion changes to adjust the background of the header view.
994 *
995 * @param expanded whether the group is expanded.
996 */
997 public void updateHeaderForExpansion(boolean expanded) {
998 if (mNotificationHeader != null) {
999 if (expanded) {
1000 ColorDrawable cd = new ColorDrawable();
Selim Cinek414ad332017-02-24 19:06:12 -08001001 cd.setColor(mContainingNotification.calculateBgColor());
Mady Mellorb0a82462016-04-30 17:31:02 -07001002 mNotificationHeader.setHeaderBackgroundDrawable(cd);
1003 } else {
1004 mNotificationHeader.setHeaderBackgroundDrawable(null);
1005 }
1006 }
Selim Cinek388df6d2015-10-22 13:25:11 -07001007 }
1008
Selim Cinek83bc7832015-10-22 13:26:54 -07001009 public int getMaxContentHeight() {
Selim Cinek414ad332017-02-24 19:06:12 -08001010 if (showingAsLowPriority()) {
1011 return getMinHeight(NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED, true
1012 /* likeHighPriority */);
1013 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001014 int maxContentHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation
1015 + mNotificatonTopPadding;
Selim Cinek42357e02016-02-24 18:48:01 -08001016 int visibleChildren = 0;
Kevin Han43077f92020-02-28 12:51:53 -08001017 int childCount = mAttachedChildren.size();
Selim Cinek42357e02016-02-24 18:48:01 -08001018 for (int i = 0; i < childCount; i++) {
1019 if (visibleChildren >= NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED) {
1020 break;
1021 }
Kevin Han43077f92020-02-28 12:51:53 -08001022 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cineke81b82b2016-03-04 11:22:28 -08001023 float childHeight = child.isExpanded(true /* allowOnKeyguard */)
Selim Cinek42357e02016-02-24 18:48:01 -08001024 ? child.getMaxExpandHeight()
1025 : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
1026 maxContentHeight += childHeight;
1027 visibleChildren++;
1028 }
1029 if (visibleChildren > 0) {
1030 maxContentHeight += visibleChildren * mDividerHeight;
1031 }
1032 return maxContentHeight;
1033 }
1034
1035 public void setActualHeight(int actualHeight) {
1036 if (!mUserLocked) {
1037 return;
1038 }
1039 mActualHeight = actualHeight;
Selim Cinekf07d0622016-03-21 19:52:52 -07001040 float fraction = getGroupExpandFraction();
Selim Cinek414ad332017-02-24 19:06:12 -08001041 boolean showingLowPriority = showingAsLowPriority();
1042 updateHeaderTransformation();
Selim Cinekf07d0622016-03-21 19:52:52 -07001043 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
Kevin Han43077f92020-02-28 12:51:53 -08001044 int childCount = mAttachedChildren.size();
Selim Cinek42357e02016-02-24 18:48:01 -08001045 for (int i = 0; i < childCount; i++) {
Kevin Han43077f92020-02-28 12:51:53 -08001046 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cinek414ad332017-02-24 19:06:12 -08001047 float childHeight;
1048 if (showingLowPriority) {
1049 childHeight = child.getShowingLayout().getMinHeight(false /* likeGroupExpanded */);
1050 } else if (child.isExpanded(true /* allowOnKeyguard */)) {
1051 childHeight = child.getMaxExpandHeight();
1052 } else {
1053 childHeight = child.getShowingLayout().getMinHeight(
1054 true /* likeGroupExpanded */);
1055 }
Selim Cinekf07d0622016-03-21 19:52:52 -07001056 if (i < maxAllowedVisibleChildren) {
1057 float singleLineHeight = child.getShowingLayout().getMinHeight(
1058 false /* likeGroupExpanded */);
1059 child.setActualHeight((int) NotificationUtils.interpolate(singleLineHeight,
1060 childHeight, fraction), false);
1061 } else {
1062 child.setActualHeight((int) childHeight, false);
1063 }
Selim Cinek42357e02016-02-24 18:48:01 -08001064 }
1065 }
1066
Selim Cinekf07d0622016-03-21 19:52:52 -07001067 public float getGroupExpandFraction() {
Selim Cinek414ad332017-02-24 19:06:12 -08001068 int visibleChildrenExpandedHeight = showingAsLowPriority() ? getMaxContentHeight()
1069 : getVisibleChildrenExpandHeight();
Selim Cinek567e8452016-03-24 10:54:56 -07001070 int minExpandHeight = getCollapsedHeight();
Selim Cinekf07d0622016-03-21 19:52:52 -07001071 float factor = (mActualHeight - minExpandHeight)
1072 / (float) (visibleChildrenExpandedHeight - minExpandHeight);
Selim Cinek42357e02016-02-24 18:48:01 -08001073 return Math.max(0.0f, Math.min(1.0f, factor));
1074 }
1075
Selim Cinekf07d0622016-03-21 19:52:52 -07001076 private int getVisibleChildrenExpandHeight() {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001077 int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation
1078 + mNotificatonTopPadding + mDividerHeight;
Selim Cinek42357e02016-02-24 18:48:01 -08001079 int visibleChildren = 0;
Kevin Han43077f92020-02-28 12:51:53 -08001080 int childCount = mAttachedChildren.size();
Selim Cinekf07d0622016-03-21 19:52:52 -07001081 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
Selim Cinek42357e02016-02-24 18:48:01 -08001082 for (int i = 0; i < childCount; i++) {
Selim Cinekf07d0622016-03-21 19:52:52 -07001083 if (visibleChildren >= maxAllowedVisibleChildren) {
Selim Cinek42357e02016-02-24 18:48:01 -08001084 break;
1085 }
Kevin Han43077f92020-02-28 12:51:53 -08001086 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cinekf07d0622016-03-21 19:52:52 -07001087 float childHeight = child.isExpanded(true /* allowOnKeyguard */)
1088 ? child.getMaxExpandHeight()
1089 : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
1090 intrinsicHeight += childHeight;
Selim Cinek42357e02016-02-24 18:48:01 -08001091 visibleChildren++;
1092 }
Selim Cinek42357e02016-02-24 18:48:01 -08001093 return intrinsicHeight;
Selim Cinek83bc7832015-10-22 13:26:54 -07001094 }
1095
1096 public int getMinHeight() {
Selim Cinek7927c732019-03-06 15:21:39 -08001097 return getMinHeight(NUMBER_OF_CHILDREN_WHEN_COLLAPSED, false /* likeHighPriority */);
Selim Cinekb55386d2015-12-16 17:26:49 -08001098 }
1099
Selim Cinek567e8452016-03-24 10:54:56 -07001100 public int getCollapsedHeight() {
Selim Cinek414ad332017-02-24 19:06:12 -08001101 return getMinHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */),
1102 false /* likeHighPriority */);
Selim Cinek567e8452016-03-24 10:54:56 -07001103 }
1104
Selim Cinekc7e4cb52019-06-20 15:41:45 -07001105 public int getCollapsedHeightWithoutHeader() {
1106 return getMinHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */),
1107 false /* likeHighPriority */, 0);
1108 }
1109
Selim Cinek414ad332017-02-24 19:06:12 -08001110 /**
1111 * Get the minimum Height for this group.
1112 *
1113 * @param maxAllowedVisibleChildren the number of children that should be visible
1114 * @param likeHighPriority if the height should be calculated as if it were not low priority
1115 */
1116 private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority) {
Selim Cinekc7e4cb52019-06-20 15:41:45 -07001117 return getMinHeight(maxAllowedVisibleChildren, likeHighPriority, mCurrentHeaderTranslation);
1118 }
1119
1120 /**
1121 * Get the minimum Height for this group.
1122 *
1123 * @param maxAllowedVisibleChildren the number of children that should be visible
1124 * @param likeHighPriority if the height should be calculated as if it were not low priority
1125 * @param headerTranslation the translation amount of the header
1126 */
1127 private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority,
1128 int headerTranslation) {
Selim Cinek414ad332017-02-24 19:06:12 -08001129 if (!likeHighPriority && showingAsLowPriority()) {
1130 return mNotificationHeaderLowPriority.getHeight();
Selim Cinek6743c0b2017-01-18 18:24:01 -08001131 }
Selim Cinekc7e4cb52019-06-20 15:41:45 -07001132 int minExpandHeight = mNotificationHeaderMargin + headerTranslation;
Selim Cinek2c584612016-02-29 16:14:25 -08001133 int visibleChildren = 0;
1134 boolean firstChild = true;
Kevin Han43077f92020-02-28 12:51:53 -08001135 int childCount = mAttachedChildren.size();
Selim Cinek2c584612016-02-29 16:14:25 -08001136 for (int i = 0; i < childCount; i++) {
1137 if (visibleChildren >= maxAllowedVisibleChildren) {
1138 break;
1139 }
1140 if (!firstChild) {
1141 minExpandHeight += mChildPadding;
1142 } else {
1143 firstChild = false;
1144 }
Kevin Han43077f92020-02-28 12:51:53 -08001145 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cinekf1f270a2016-04-05 19:02:37 -07001146 minExpandHeight += child.getSingleLineView().getHeight();
Selim Cinek2c584612016-02-29 16:14:25 -08001147 visibleChildren++;
1148 }
1149 minExpandHeight += mCollapsedBottompadding;
1150 return minExpandHeight;
Selim Cinekb5605e52015-02-20 18:21:41 +01001151 }
Selim Cinek9c7712d2015-12-08 19:19:48 -08001152
Selim Cinek414ad332017-02-24 19:06:12 -08001153 public boolean showingAsLowPriority() {
1154 return mIsLowPriority && !mContainingNotification.isExpanded();
1155 }
1156
Mady Mellorb0a82462016-04-30 17:31:02 -07001157 public void reInflateViews(OnClickListener listener, StatusBarNotification notification) {
Selim Cinek414ad332017-02-24 19:06:12 -08001158 if (mNotificationHeader != null) {
1159 removeView(mNotificationHeader);
1160 mNotificationHeader = null;
1161 }
1162 if (mNotificationHeaderLowPriority != null) {
1163 removeView(mNotificationHeaderLowPriority);
1164 mNotificationHeaderLowPriority = null;
1165 }
Steve Elliott936df152020-04-14 13:59:53 -04001166 recreateNotificationHeader(listener, mIsConversation);
Selim Cinek01af3342016-02-09 19:25:31 -08001167 initDimens();
1168 for (int i = 0; i < mDividers.size(); i++) {
1169 View prevDivider = mDividers.get(i);
1170 int index = indexOfChild(prevDivider);
1171 removeView(prevDivider);
1172 View divider = inflateDivider();
1173 addView(divider, index);
1174 mDividers.set(i, divider);
1175 }
Selim Cinek4bb59342016-04-08 19:29:35 -07001176 removeView(mOverflowNumber);
1177 mOverflowNumber = null;
Selim Cinek4bb59342016-04-08 19:29:35 -07001178 mGroupOverFlowState = null;
1179 updateGroupOverflow();
Selim Cinek01af3342016-02-09 19:25:31 -08001180 }
Selim Cinek42357e02016-02-24 18:48:01 -08001181
1182 public void setUserLocked(boolean userLocked) {
1183 mUserLocked = userLocked;
Selim Cinek414ad332017-02-24 19:06:12 -08001184 if (!mUserLocked) {
1185 updateHeaderVisibility(false /* animate */);
1186 }
Kevin Han43077f92020-02-28 12:51:53 -08001187 int childCount = mAttachedChildren.size();
Selim Cinek42357e02016-02-24 18:48:01 -08001188 for (int i = 0; i < childCount; i++) {
Kevin Han43077f92020-02-28 12:51:53 -08001189 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cinek414ad332017-02-24 19:06:12 -08001190 child.setUserLocked(userLocked && !showingAsLowPriority());
Selim Cinek42357e02016-02-24 18:48:01 -08001191 }
Selim Cinekbc342152018-12-05 18:45:42 -08001192 updateHeaderTouchability();
1193 }
1194
1195 private void updateHeaderTouchability() {
1196 if (mNotificationHeader != null) {
1197 mNotificationHeader.setAcceptAllTouches(mChildrenExpanded || mUserLocked);
1198 }
Selim Cinek42357e02016-02-24 18:48:01 -08001199 }
Selim Cinekc897bd32016-03-18 17:32:31 -07001200
1201 public void onNotificationUpdated() {
1202 mHybridGroupManager.setOverflowNumberColor(mOverflowNumber,
Lucas Dupin00be88f2019-01-03 17:50:52 -08001203 mContainingNotification.getNotificationColor());
Selim Cinekc897bd32016-03-18 17:32:31 -07001204 }
Adrian Roosd009ab12016-05-20 17:58:53 -07001205
Adrian Roos4a579672016-05-24 16:54:37 -07001206 public int getPositionInLinearLayout(View childInGroup) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001207 int position = mNotificationHeaderMargin + mCurrentHeaderTranslation
1208 + mNotificatonTopPadding;
Adrian Roos4a579672016-05-24 16:54:37 -07001209
Kevin Han43077f92020-02-28 12:51:53 -08001210 for (int i = 0; i < mAttachedChildren.size(); i++) {
1211 ExpandableNotificationRow child = mAttachedChildren.get(i);
Adrian Roos4a579672016-05-24 16:54:37 -07001212 boolean notGone = child.getVisibility() != View.GONE;
1213 if (notGone) {
1214 position += mDividerHeight;
1215 }
1216 if (child == childInGroup) {
1217 return position;
1218 }
1219 if (notGone) {
1220 position += child.getIntrinsicHeight();
1221 }
1222 }
1223 return 0;
1224 }
Selim Cinek0242fbb2016-10-19 13:38:32 -07001225
Selim Cinek9ed6e042020-03-26 15:45:51 -07001226 public void setShelfIconVisible(boolean iconVisible) {
Selim Cinek0242fbb2016-10-19 13:38:32 -07001227 if (mNotificationHeaderWrapper != null) {
1228 NotificationHeaderView header = mNotificationHeaderWrapper.getNotificationHeader();
1229 if (header != null) {
Selim Cinek9ed6e042020-03-26 15:45:51 -07001230 header.getIcon().setForceHidden(iconVisible);
Selim Cinek0242fbb2016-10-19 13:38:32 -07001231 }
1232 }
Selim Cinek414ad332017-02-24 19:06:12 -08001233 if (mNotificationHeaderWrapperLowPriority != null) {
1234 NotificationHeaderView header
1235 = mNotificationHeaderWrapperLowPriority.getNotificationHeader();
1236 if (header != null) {
Selim Cinek9ed6e042020-03-26 15:45:51 -07001237 header.getIcon().setForceHidden(iconVisible);
Selim Cinek414ad332017-02-24 19:06:12 -08001238 }
1239 }
Selim Cinek0242fbb2016-10-19 13:38:32 -07001240 }
Selim Cinekb3dadcc2016-11-21 17:21:13 -08001241
1242 public void setClipBottomAmount(int clipBottomAmount) {
1243 mClipBottomAmount = clipBottomAmount;
1244 updateChildrenClipping();
1245 }
Selim Cinek6743c0b2017-01-18 18:24:01 -08001246
1247 public void setIsLowPriority(boolean isLowPriority) {
1248 mIsLowPriority = isLowPriority;
Selim Cinek414ad332017-02-24 19:06:12 -08001249 if (mContainingNotification != null) { /* we're not yet set up yet otherwise */
Steve Elliott936df152020-04-14 13:59:53 -04001250 recreateLowPriorityHeader(null /* existingBuilder */, mIsConversation);
Selim Cinek414ad332017-02-24 19:06:12 -08001251 updateHeaderVisibility(false /* animate */);
1252 }
1253 if (mUserLocked) {
1254 setUserLocked(mUserLocked);
1255 }
1256 }
1257
1258 public NotificationHeaderView getVisibleHeader() {
1259 NotificationHeaderView header = mNotificationHeader;
1260 if (showingAsLowPriority()) {
1261 header = mNotificationHeaderLowPriority;
1262 }
1263 return header;
1264 }
1265
1266 public void onExpansionChanged() {
1267 if (mIsLowPriority) {
1268 if (mUserLocked) {
1269 setUserLocked(mUserLocked);
1270 }
1271 updateHeaderVisibility(true /* animate */);
1272 }
1273 }
1274
1275 public float getIncreasedPaddingAmount() {
1276 if (showingAsLowPriority()) {
1277 return 0.0f;
1278 }
1279 return getGroupExpandFraction();
Selim Cinek6743c0b2017-01-18 18:24:01 -08001280 }
Selim Cinek817abe72017-05-24 11:08:55 -07001281
1282 @VisibleForTesting
1283 public boolean isUserLocked() {
1284 return mUserLocked;
1285 }
Selim Cinek2871bef2017-11-22 08:40:00 -08001286
1287 public void setCurrentBottomRoundness(float currentBottomRoundness) {
1288 boolean last = true;
Kevin Han43077f92020-02-28 12:51:53 -08001289 for (int i = mAttachedChildren.size() - 1; i >= 0; i--) {
1290 ExpandableNotificationRow child = mAttachedChildren.get(i);
Selim Cinek2871bef2017-11-22 08:40:00 -08001291 if (child.getVisibility() == View.GONE) {
1292 continue;
1293 }
1294 float bottomRoundness = last ? currentBottomRoundness : 0.0f;
1295 child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
1296 last = false;
1297 }
1298 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001299
1300 public void setHeaderVisibleAmount(float headerVisibleAmount) {
1301 mHeaderVisibleAmount = headerVisibleAmount;
1302 mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader);
1303 }
Selim Cinek4237e822020-03-31 17:22:28 -07001304
1305 /**
1306 * Show a set of app opp icons in the layout.
1307 *
1308 * @param appOps which app ops to show
1309 */
1310 public void showAppOpsIcons(ArraySet<Integer> appOps) {
1311 if (mNotificationHeaderWrapper != null) {
1312 mNotificationHeaderWrapper.showAppOpsIcons(appOps);
1313 }
1314 if (mNotificationHeaderWrapperLowPriority != null) {
1315 mNotificationHeaderWrapperLowPriority.showAppOpsIcons(appOps);
1316 }
1317 }
Selim Cineka2b1d372020-03-31 17:54:19 -07001318
1319 public void setRecentlyAudiblyAlerted(boolean audiblyAlertedRecently) {
1320 if (mNotificationHeaderWrapper != null) {
1321 mNotificationHeaderWrapper.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
1322 }
1323 if (mNotificationHeaderWrapperLowPriority != null) {
1324 mNotificationHeaderWrapperLowPriority.setRecentlyAudiblyAlerted(audiblyAlertedRecently);
1325 }
1326 }
Selim Cinekb5605e52015-02-20 18:21:41 +01001327}