blob: 8ffada43c53f2d048962c3e20f804fb678df0070 [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 Cinekb5605e52015-02-20 18:21:41 +010025import android.util.AttributeSet;
26import android.view.LayoutInflater;
Mady Mellorb0a82462016-04-30 17:31:02 -070027import android.view.NotificationHeaderView;
Selim Cinekb5605e52015-02-20 18:21:41 +010028import android.view.View;
29import android.view.ViewGroup;
Mady Mellorb0a82462016-04-30 17:31:02 -070030import android.widget.RemoteViews;
Selim Cinekc897bd32016-03-18 17:32:31 -070031import android.widget.TextView;
Selim Cinek817abe72017-05-24 11:08:55 -070032
33import com.android.internal.annotations.VisibleForTesting;
Selim Cinekb5605e52015-02-20 18:21:41 +010034import com.android.systemui.R;
Selim Cinek7b73a4c2016-01-12 18:32:11 -080035import com.android.systemui.statusbar.CrossFadeHelper;
Mady Mellorb0a82462016-04-30 17:31:02 -070036import com.android.systemui.statusbar.NotificationHeaderUtil;
Gus Prevasab336792018-11-14 13:52:20 -050037import com.android.systemui.statusbar.notification.NotificationUtils;
38import com.android.systemui.statusbar.notification.VisualStabilityManager;
39import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
Rohan Shah20790b82018-07-02 17:21:04 -070040import com.android.systemui.statusbar.notification.row.HybridGroupManager;
41import com.android.systemui.statusbar.notification.row.HybridNotificationView;
Rohan Shah20790b82018-07-02 17:21:04 -070042import com.android.systemui.statusbar.notification.row.wrapper.NotificationViewWrapper;
Selim Cinekb5605e52015-02-20 18:21:41 +010043
44import java.util.ArrayList;
45import java.util.List;
46
47/**
48 * A container containing child notifications
49 */
50public class NotificationChildrenContainer extends ViewGroup {
51
Aaron Heuckroth17ce40e2018-06-19 15:44:40 -040052 @VisibleForTesting
53 static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2;
54 @VisibleForTesting
55 static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5;
56 @VisibleForTesting
57 static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8;
58 @VisibleForTesting
59 static final int NUMBER_OF_CHILDREN_WHEN_AMBIENT = 1;
Selim Cinek414ad332017-02-24 19:06:12 -080060 private static final AnimationProperties ALPHA_FADE_IN = new AnimationProperties() {
61 private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha();
62
63 @Override
64 public AnimationFilter getAnimationFilter() {
65 return mAnimationFilter;
66 }
67 }.setDuration(200);
Selim Cinek83bc7832015-10-22 13:26:54 -070068
Selim Cinekb5605e52015-02-20 18:21:41 +010069 private final List<View> mDividers = new ArrayList<>();
70 private final List<ExpandableNotificationRow> mChildren = new ArrayList<>();
Selim Cinekc897bd32016-03-18 17:32:31 -070071 private final HybridGroupManager mHybridGroupManager;
Selim Cinek01af3342016-02-09 19:25:31 -080072 private int mChildPadding;
73 private int mDividerHeight;
Anthony Chen6bf88a02017-04-10 14:41:44 -070074 private float mDividerAlpha;
Mady Mellorb0a82462016-04-30 17:31:02 -070075 private int mNotificationHeaderMargin;
Anthony Chen6bf88a02017-04-10 14:41:44 -070076
Selim Cinek01af3342016-02-09 19:25:31 -080077 private int mNotificatonTopPadding;
78 private float mCollapsedBottompadding;
Selim Cinek83bc7832015-10-22 13:26:54 -070079 private boolean mChildrenExpanded;
Selim Cinek414ad332017-02-24 19:06:12 -080080 private ExpandableNotificationRow mContainingNotification;
Selim Cinekc897bd32016-03-18 17:32:31 -070081 private TextView mOverflowNumber;
82 private ViewState mGroupOverFlowState;
Selim Cineka69f2a62015-12-11 17:28:12 -080083 private int mRealHeight;
Selim Cinek42357e02016-02-24 18:48:01 -080084 private boolean mUserLocked;
85 private int mActualHeight;
Selim Cinekc897bd32016-03-18 17:32:31 -070086 private boolean mNeverAppliedGroupState;
Mady Mellorb0a82462016-04-30 17:31:02 -070087 private int mHeaderHeight;
88
Anthony Chen6bf88a02017-04-10 14:41:44 -070089 /**
90 * Whether or not individual notifications that are part of this container will have shadows.
91 */
92 private boolean mEnableShadowOnChildNotifications;
93
Mady Mellorb0a82462016-04-30 17:31:02 -070094 private NotificationHeaderView mNotificationHeader;
95 private NotificationViewWrapper mNotificationHeaderWrapper;
Selim Cinek414ad332017-02-24 19:06:12 -080096 private NotificationHeaderView mNotificationHeaderLowPriority;
97 private NotificationViewWrapper mNotificationHeaderWrapperLowPriority;
Adrian Roos6f6e1592017-05-02 16:22:53 -070098 private ViewGroup mNotificationHeaderAmbient;
99 private NotificationViewWrapper mNotificationHeaderWrapperAmbient;
Mady Mellorb0a82462016-04-30 17:31:02 -0700100 private NotificationHeaderUtil mHeaderUtil;
101 private ViewState mHeaderViewState;
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800102 private int mClipBottomAmount;
Selim Cinek6743c0b2017-01-18 18:24:01 -0800103 private boolean mIsLowPriority;
Selim Cinek414ad332017-02-24 19:06:12 -0800104 private OnClickListener mHeaderClickListener;
Adrian Roos6f6e1592017-05-02 16:22:53 -0700105 private ViewGroup mCurrentHeader;
Selim Cinekb5605e52015-02-20 18:21:41 +0100106
Anthony Chen6bf88a02017-04-10 14:41:44 -0700107 private boolean mShowDividersWhenExpanded;
108 private boolean mHideDividersDuringExpand;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800109 private int mTranslationForHeader;
110 private int mCurrentHeaderTranslation = 0;
111 private float mHeaderVisibleAmount = 1.0f;
Anthony Chen6bf88a02017-04-10 14:41:44 -0700112
Selim Cinekb5605e52015-02-20 18:21:41 +0100113 public NotificationChildrenContainer(Context context) {
114 this(context, null);
115 }
116
117 public NotificationChildrenContainer(Context context, AttributeSet attrs) {
118 this(context, attrs, 0);
119 }
120
121 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) {
122 this(context, attrs, defStyleAttr, 0);
123 }
124
125 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr,
126 int defStyleRes) {
127 super(context, attrs, defStyleAttr, defStyleRes);
Selim Cinekc897bd32016-03-18 17:32:31 -0700128 mHybridGroupManager = new HybridGroupManager(getContext(), this);
dongwan0605.kim11e7dec2018-05-06 18:04:52 +0900129 initDimens();
Selim Cineked64a142018-02-06 18:06:01 -0800130 setClipChildren(false);
Selim Cinek01af3342016-02-09 19:25:31 -0800131 }
132
133 private void initDimens() {
Anthony Chen6bf88a02017-04-10 14:41:44 -0700134 Resources res = getResources();
135 mChildPadding = res.getDimensionPixelSize(R.dimen.notification_children_padding);
136 mDividerHeight = res.getDimensionPixelSize(
137 R.dimen.notification_children_container_divider_height);
138 mDividerAlpha = res.getFloat(R.dimen.notification_divider_alpha);
Anthony Chen6bf88a02017-04-10 14:41:44 -0700139 mNotificationHeaderMargin = res.getDimensionPixelSize(
140 R.dimen.notification_children_container_margin_top);
141 mNotificatonTopPadding = res.getDimensionPixelSize(
Selim Cinek7b836392015-12-04 20:02:59 -0800142 R.dimen.notification_children_container_top_padding);
Selim Cinekafeed292017-12-12 17:32:44 -0800143 mHeaderHeight = mNotificationHeaderMargin + mNotificatonTopPadding;
Anthony Chen6bf88a02017-04-10 14:41:44 -0700144 mCollapsedBottompadding = res.getDimensionPixelSize(
Selim Cineked64a142018-02-06 18:06:01 -0800145 com.android.internal.R.dimen.notification_content_margin);
Anthony Chen6bf88a02017-04-10 14:41:44 -0700146 mEnableShadowOnChildNotifications =
147 res.getBoolean(R.bool.config_enableShadowOnChildNotifications);
148 mShowDividersWhenExpanded =
149 res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded);
150 mHideDividersDuringExpand =
151 res.getBoolean(R.bool.config_hideDividersDuringExpand);
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800152 mTranslationForHeader = res.getDimensionPixelSize(
153 com.android.internal.R.dimen.notification_content_margin)
154 - mNotificationHeaderMargin;
dongwan0605.kim11e7dec2018-05-06 18:04:52 +0900155 mHybridGroupManager.initDimens();
Selim Cinekb5605e52015-02-20 18:21:41 +0100156 }
157
158 @Override
159 protected void onLayout(boolean changed, int l, int t, int r, int b) {
Selim Cinek471e31a2015-12-11 13:39:48 -0800160 int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
Selim Cinekb5605e52015-02-20 18:21:41 +0100161 for (int i = 0; i < childCount; i++) {
162 View child = mChildren.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 }
Adrian Roos6f6e1592017-05-02 16:22:53 -0700183 if (mNotificationHeaderAmbient != null) {
184 mNotificationHeaderAmbient.layout(0, 0,
185 mNotificationHeaderAmbient.getMeasuredWidth(),
186 mNotificationHeaderAmbient.getMeasuredHeight());
187 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100188 }
189
190 @Override
191 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100192 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
193 boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
194 boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
Selim Cineka69f2a62015-12-11 17:28:12 -0800195 int size = MeasureSpec.getSize(heightMeasureSpec);
Geoffrey Pitsch4dd50062016-12-06 16:41:22 -0500196 int newHeightSpec = heightMeasureSpec;
Selim Cinekb5605e52015-02-20 18:21:41 +0100197 if (hasFixedHeight || isHeightLimited) {
Geoffrey Pitsch4dd50062016-12-06 16:41:22 -0500198 newHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST);
Selim Cinekb5605e52015-02-20 18:21:41 +0100199 }
Selim Cinekc897bd32016-03-18 17:32:31 -0700200 int width = MeasureSpec.getSize(widthMeasureSpec);
201 if (mOverflowNumber != null) {
202 mOverflowNumber.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
203 newHeightSpec);
204 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100205 int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY);
Mady Mellorb0a82462016-04-30 17:31:02 -0700206 int height = mNotificationHeaderMargin + mNotificatonTopPadding;
Selim Cinek471e31a2015-12-11 13:39:48 -0800207 int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED);
Selim Cinekc897bd32016-03-18 17:32:31 -0700208 int collapsedChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
209 int overflowIndex = childCount > collapsedChildren ? collapsedChildren - 1 : -1;
Selim Cinekb5605e52015-02-20 18:21:41 +0100210 for (int i = 0; i < childCount; i++) {
Selim Cinekc897bd32016-03-18 17:32:31 -0700211 ExpandableNotificationRow child = mChildren.get(i);
Selim Cinekfa760d42016-05-10 15:50:53 -0400212 // We need to measure all children even the GONE ones, such that the heights are
213 // calculated correctly as they are used to calculate how many we can fit on the screen.
Selim Cinekc897bd32016-03-18 17:32:31 -0700214 boolean isOverflow = i == overflowIndex;
Lucas Dupin4c797d62018-05-07 15:32:13 -0700215 child.setSingleLineWidthIndention(isOverflow && mOverflowNumber != null &&
Kevina97ea052018-09-11 13:53:18 -0700216 !mContainingNotification.isOnAmbient()
Lucas Dupin4c797d62018-05-07 15:32:13 -0700217 ? mOverflowNumber.getMeasuredWidth() : 0);
Selim Cinekb5605e52015-02-20 18:21:41 +0100218 child.measure(widthMeasureSpec, newHeightSpec);
Selim Cinek7b836392015-12-04 20:02:59 -0800219 // layout the divider
220 View divider = mDividers.get(i);
221 divider.measure(widthMeasureSpec, dividerHeightSpec);
Selim Cinekfa760d42016-05-10 15:50:53 -0400222 if (child.getVisibility() != GONE) {
223 height += child.getMeasuredHeight() + mDividerHeight;
224 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100225 }
Selim Cineka69f2a62015-12-11 17:28:12 -0800226 mRealHeight = height;
227 if (heightMode != MeasureSpec.UNSPECIFIED) {
228 height = Math.min(height, size);
229 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700230
Selim Cinek414ad332017-02-24 19:06:12 -0800231 int headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
Mady Mellorb0a82462016-04-30 17:31:02 -0700232 if (mNotificationHeader != null) {
Mady Mellorb0a82462016-04-30 17:31:02 -0700233 mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec);
234 }
Selim Cinek414ad332017-02-24 19:06:12 -0800235 if (mNotificationHeaderLowPriority != null) {
236 headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
237 mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec);
238 }
Adrian Roos6f6e1592017-05-02 16:22:53 -0700239 if (mNotificationHeaderAmbient != null) {
240 headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY);
241 mNotificationHeaderAmbient.measure(widthMeasureSpec, headerHeightSpec);
242 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700243
Selim Cinekb5605e52015-02-20 18:21:41 +0100244 setMeasuredDimension(width, height);
245 }
246
Selim Cineka69f2a62015-12-11 17:28:12 -0800247 @Override
Selim Cinek0242fbb2016-10-19 13:38:32 -0700248 public boolean hasOverlappingRendering() {
249 return false;
250 }
251
252 @Override
Selim Cineka69f2a62015-12-11 17:28:12 -0800253 public boolean pointInView(float localX, float localY, float slop) {
254 return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) &&
255 localY < (mRealHeight + slop);
256 }
257
Selim Cinekb5605e52015-02-20 18:21:41 +0100258 /**
259 * Add a child notification to this view.
260 *
261 * @param row the row to add
262 * @param childIndex the index to add it at, if -1 it will be added at the end
263 */
264 public void addNotification(ExpandableNotificationRow row, int childIndex) {
265 int newIndex = childIndex < 0 ? mChildren.size() : childIndex;
266 mChildren.add(newIndex, row);
267 addView(row);
Selim Cinek8e0c4982016-02-29 20:03:08 -0800268 row.setUserLocked(mUserLocked);
Selim Cinek7b836392015-12-04 20:02:59 -0800269
270 View divider = inflateDivider();
271 addView(divider);
272 mDividers.add(newIndex, divider);
Selim Cinekc897bd32016-03-18 17:32:31 -0700273
274 updateGroupOverflow();
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) {
285 int childIndex = mChildren.indexOf(row);
286 mChildren.remove(row);
287 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);
Selim Cinekc897bd32016-03-18 17:32:31 -0700301 updateGroupOverflow();
Mady Mellorb0a82462016-04-30 17:31:02 -0700302 if (!row.isRemoved()) {
303 mHeaderUtil.restoreNotificationHeader(row);
304 }
305 }
306
307 /**
308 * @return The number of notification children in the container.
309 */
310 public int getNotificationChildCount() {
311 return mChildren.size();
312 }
313
Selim Cinek414ad332017-02-24 19:06:12 -0800314 public void recreateNotificationHeader(OnClickListener listener) {
315 mHeaderClickListener = listener;
316 StatusBarNotification notification = mContainingNotification.getStatusBarNotification();
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);
Selim Cinek414ad332017-02-24 19:06:12 -0800334 recreateLowPriorityHeader(builder);
Adrian Roos6f6e1592017-05-02 16:22:53 -0700335 recreateAmbientHeader(builder);
Lucas Dupin6ea27872017-05-30 12:00:04 -0700336 updateHeaderVisibility(false /* animate */);
Mady Mellorb0a82462016-04-30 17:31:02 -0700337 updateChildrenHeaderAppearance();
338 }
339
Adrian Roos6f6e1592017-05-02 16:22:53 -0700340 private void recreateAmbientHeader(Notification.Builder builder) {
341 RemoteViews header;
342 StatusBarNotification notification = mContainingNotification.getStatusBarNotification();
343 if (builder == null) {
344 builder = Notification.Builder.recoverBuilder(getContext(),
345 notification.getNotification());
346 }
Lucas Dupin00be88f2019-01-03 17:50:52 -0800347 header = builder.makeNotificationHeader();
Adrian Roos6f6e1592017-05-02 16:22:53 -0700348 if (mNotificationHeaderAmbient == null) {
349 mNotificationHeaderAmbient = (ViewGroup) header.apply(getContext(), this);
350 mNotificationHeaderWrapperAmbient = NotificationViewWrapper.wrap(getContext(),
351 mNotificationHeaderAmbient, mContainingNotification);
Selim Cinek131f1a42017-06-05 17:50:19 -0700352 mNotificationHeaderWrapperAmbient.onContentUpdated(mContainingNotification);
Adrian Roos6f6e1592017-05-02 16:22:53 -0700353 addView(mNotificationHeaderAmbient, 0);
354 invalidate();
355 } else {
356 header.reapply(getContext(), mNotificationHeaderAmbient);
357 }
358 resetHeaderVisibilityIfNeeded(mNotificationHeaderAmbient, calculateDesiredHeader());
Selim Cinek131f1a42017-06-05 17:50:19 -0700359 mNotificationHeaderWrapperAmbient.onContentUpdated(mContainingNotification);
Adrian Roos6f6e1592017-05-02 16:22:53 -0700360 }
361
Selim Cinek414ad332017-02-24 19:06:12 -0800362 /**
363 * Recreate the low-priority header.
364 *
365 * @param builder a builder to reuse. Otherwise the builder will be recovered.
366 */
367 private void recreateLowPriorityHeader(Notification.Builder builder) {
368 RemoteViews header;
369 StatusBarNotification notification = mContainingNotification.getStatusBarNotification();
370 if (mIsLowPriority) {
371 if (builder == null) {
372 builder = Notification.Builder.recoverBuilder(getContext(),
373 notification.getNotification());
374 }
375 header = builder.makeLowPriorityContentView(true /* useRegularSubtext */);
376 if (mNotificationHeaderLowPriority == null) {
377 mNotificationHeaderLowPriority = (NotificationHeaderView) header.apply(getContext(),
378 this);
379 final View expandButton = mNotificationHeaderLowPriority.findViewById(
380 com.android.internal.R.id.expand_button);
381 expandButton.setVisibility(VISIBLE);
382 mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener);
383 mNotificationHeaderWrapperLowPriority = NotificationViewWrapper.wrap(getContext(),
384 mNotificationHeaderLowPriority, mContainingNotification);
385 addView(mNotificationHeaderLowPriority, 0);
386 invalidate();
387 } else {
388 header.reapply(getContext(), mNotificationHeaderLowPriority);
389 }
Selim Cinek131f1a42017-06-05 17:50:19 -0700390 mNotificationHeaderWrapperLowPriority.onContentUpdated(mContainingNotification);
Adrian Roos6f6e1592017-05-02 16:22:53 -0700391 resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, calculateDesiredHeader());
Selim Cinek414ad332017-02-24 19:06:12 -0800392 } else {
Selim Cinek6fd06b52017-03-07 15:54:10 -0800393 removeView(mNotificationHeaderLowPriority);
Selim Cinek414ad332017-02-24 19:06:12 -0800394 mNotificationHeaderLowPriority = null;
395 mNotificationHeaderWrapperLowPriority = null;
396 }
397 }
398
Mady Mellorb0a82462016-04-30 17:31:02 -0700399 public void updateChildrenHeaderAppearance() {
400 mHeaderUtil.updateChildrenHeaderAppearance();
Selim Cinekc897bd32016-03-18 17:32:31 -0700401 }
402
403 public void updateGroupOverflow() {
404 int childCount = mChildren.size();
405 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
406 if (childCount > maxAllowedVisibleChildren) {
Lucas Dupin4c797d62018-05-07 15:32:13 -0700407 int number = childCount - maxAllowedVisibleChildren;
408 mOverflowNumber = mHybridGroupManager.bindOverflowNumber(mOverflowNumber, number);
Kevina97ea052018-09-11 13:53:18 -0700409 if (mContainingNotification.isOnAmbient()) {
Lucas Dupin4c797d62018-05-07 15:32:13 -0700410 ExpandableNotificationRow overflowView = mChildren.get(0);
411 HybridNotificationView ambientSingleLineView = overflowView == null ? null
412 : overflowView.getAmbientSingleLineView();
413 if (ambientSingleLineView != null) {
414 mHybridGroupManager.bindOverflowNumberAmbient(
415 ambientSingleLineView.getTitleView(),
416 mContainingNotification.getStatusBarNotification().getNotification(),
417 number);
418 }
419 }
Selim Cinekc897bd32016-03-18 17:32:31 -0700420 if (mGroupOverFlowState == null) {
421 mGroupOverFlowState = new ViewState();
422 mNeverAppliedGroupState = true;
423 }
424 } else if (mOverflowNumber != null) {
425 removeView(mOverflowNumber);
Eliot Courtneybb8af1c2018-01-11 16:44:45 +0900426 if (isShown() && isAttachedToWindow()) {
Selim Cinekc897bd32016-03-18 17:32:31 -0700427 final View removedOverflowNumber = mOverflowNumber;
428 addTransientView(removedOverflowNumber, getTransientViewCount());
429 CrossFadeHelper.fadeOut(removedOverflowNumber, new Runnable() {
430 @Override
431 public void run() {
432 removeTransientView(removedOverflowNumber);
433 }
434 });
435 }
436 mOverflowNumber = null;
Selim Cinekc897bd32016-03-18 17:32:31 -0700437 mGroupOverFlowState = null;
438 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100439 }
440
Selim Cineka3d3b912016-02-02 11:22:06 -0800441 @Override
442 protected void onConfigurationChanged(Configuration newConfig) {
443 super.onConfigurationChanged(newConfig);
Selim Cinekc897bd32016-03-18 17:32:31 -0700444 updateGroupOverflow();
Selim Cineka3d3b912016-02-02 11:22:06 -0800445 }
446
Selim Cinekb5605e52015-02-20 18:21:41 +0100447 private View inflateDivider() {
448 return LayoutInflater.from(mContext).inflate(
449 R.layout.notification_children_divider, this, false);
450 }
451
452 public List<ExpandableNotificationRow> getNotificationChildren() {
453 return mChildren;
454 }
455
456 /**
457 * Apply the order given in the list to the children.
458 *
459 * @param childOrder the new list order
Selim Cineka7d4f822016-12-06 14:34:47 -0800460 * @param visualStabilityManager
461 * @param callback
Selim Cinekb5605e52015-02-20 18:21:41 +0100462 * @return whether the list order has changed
463 */
Selim Cineka7d4f822016-12-06 14:34:47 -0800464 public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder,
465 VisualStabilityManager visualStabilityManager,
466 VisualStabilityManager.Callback callback) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100467 if (childOrder == null) {
468 return false;
469 }
470 boolean result = false;
471 for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) {
472 ExpandableNotificationRow child = mChildren.get(i);
473 ExpandableNotificationRow desiredChild = childOrder.get(i);
474 if (child != desiredChild) {
Selim Cineka7d4f822016-12-06 14:34:47 -0800475 if (visualStabilityManager.canReorderNotification(desiredChild)) {
476 mChildren.remove(desiredChild);
477 mChildren.add(i, desiredChild);
478 result = true;
479 } else {
480 visualStabilityManager.addReorderingAllowedCallback(callback);
481 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100482 }
483 }
Selim Cinek83bc7832015-10-22 13:26:54 -0700484 updateExpansionStates();
Selim Cinekb5605e52015-02-20 18:21:41 +0100485 return result;
486 }
487
Selim Cinek83bc7832015-10-22 13:26:54 -0700488 private void updateExpansionStates() {
Selim Cinek898d1732016-02-29 19:57:35 -0800489 if (mChildrenExpanded || mUserLocked) {
490 // we don't modify it the group is expanded or if we are expanding it
491 return;
492 }
493 int size = mChildren.size();
494 for (int i = 0; i < size; i++) {
Selim Cinek83bc7832015-10-22 13:26:54 -0700495 ExpandableNotificationRow child = mChildren.get(i);
Selim Cinek898d1732016-02-29 19:57:35 -0800496 child.setSystemChildExpanded(i == 0 && size == 1);
Selim Cinek83bc7832015-10-22 13:26:54 -0700497 }
498 }
499
500 /**
501 *
502 * @return the intrinsic size of this children container, i.e the natural fully expanded state
503 */
Selim Cinekb5605e52015-02-20 18:21:41 +0100504 public int getIntrinsicHeight() {
Selim Cinek83bc7832015-10-22 13:26:54 -0700505 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
506 return getIntrinsicHeight(maxAllowedVisibleChildren);
507 }
508
509 /**
510 * @return the intrinsic height with a number of children given
511 * in @param maxAllowedVisibleChildren
512 */
513 private int getIntrinsicHeight(float maxAllowedVisibleChildren) {
Selim Cinek414ad332017-02-24 19:06:12 -0800514 if (showingAsLowPriority()) {
515 return mNotificationHeaderLowPriority.getHeight();
516 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800517 int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100518 int visibleChildren = 0;
Selim Cinek83bc7832015-10-22 13:26:54 -0700519 int childCount = mChildren.size();
Selim Cinek42357e02016-02-24 18:48:01 -0800520 boolean firstChild = true;
521 float expandFactor = 0;
522 if (mUserLocked) {
Selim Cinekf07d0622016-03-21 19:52:52 -0700523 expandFactor = getGroupExpandFraction();
Selim Cinek42357e02016-02-24 18:48:01 -0800524 }
Kevina97ea052018-09-11 13:53:18 -0700525 boolean childrenExpanded = mChildrenExpanded || mContainingNotification.isOnAmbient();
Selim Cinekb5605e52015-02-20 18:21:41 +0100526 for (int i = 0; i < childCount; i++) {
Selim Cinek83bc7832015-10-22 13:26:54 -0700527 if (visibleChildren >= maxAllowedVisibleChildren) {
528 break;
529 }
Selim Cinek42357e02016-02-24 18:48:01 -0800530 if (!firstChild) {
531 if (mUserLocked) {
532 intrinsicHeight += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
533 expandFactor);
534 } else {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700535 intrinsicHeight += childrenExpanded ? mDividerHeight : mChildPadding;
Selim Cinek42357e02016-02-24 18:48:01 -0800536 }
537 } else {
538 if (mUserLocked) {
539 intrinsicHeight += NotificationUtils.interpolate(
540 0,
541 mNotificatonTopPadding + mDividerHeight,
542 expandFactor);
543 } else {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700544 intrinsicHeight += childrenExpanded
Selim Cinek42357e02016-02-24 18:48:01 -0800545 ? mNotificatonTopPadding + mDividerHeight
546 : 0;
547 }
548 firstChild = false;
549 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100550 ExpandableNotificationRow child = mChildren.get(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100551 intrinsicHeight += child.getIntrinsicHeight();
552 visibleChildren++;
553 }
Selim Cinek42357e02016-02-24 18:48:01 -0800554 if (mUserLocked) {
555 intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottompadding, 0.0f,
556 expandFactor);
Adrian Roos6f6e1592017-05-02 16:22:53 -0700557 } else if (!childrenExpanded) {
Selim Cinek83bc7832015-10-22 13:26:54 -0700558 intrinsicHeight += mCollapsedBottompadding;
559 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100560 return intrinsicHeight;
561 }
562
563 /**
564 * Update the state of all its children based on a linear layout algorithm.
Selim Cinekb5605e52015-02-20 18:21:41 +0100565 * @param parentState the state of the parent
Selim Cinekc25989e2018-02-16 16:42:14 -0800566 * @param ambientState
Selim Cinekb5605e52015-02-20 18:21:41 +0100567 */
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500568 public void updateState(ExpandableViewState parentState, AmbientState ambientState) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100569 int childCount = mChildren.size();
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800570 int yPosition = mNotificationHeaderMargin + mCurrentHeaderTranslation;
Selim Cinekb5605e52015-02-20 18:21:41 +0100571 boolean firstChild = true;
Selim Cinek83bc7832015-10-22 13:26:54 -0700572 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren();
Selim Cinek91c2a152016-03-17 15:01:38 -0700573 int lastVisibleIndex = maxAllowedVisibleChildren - 1;
Selim Cinekf07d0622016-03-21 19:52:52 -0700574 int firstOverflowIndex = lastVisibleIndex + 1;
Selim Cinek42357e02016-02-24 18:48:01 -0800575 float expandFactor = 0;
Selim Cinek414ad332017-02-24 19:06:12 -0800576 boolean expandingToExpandedGroup = mUserLocked && !showingAsLowPriority();
Selim Cinek42357e02016-02-24 18:48:01 -0800577 if (mUserLocked) {
Selim Cinekf07d0622016-03-21 19:52:52 -0700578 expandFactor = getGroupExpandFraction();
579 firstOverflowIndex = getMaxAllowedVisibleChildren(true /* likeCollapsed */);
Selim Cinek42357e02016-02-24 18:48:01 -0800580 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700581
Adrian Roos16fe4952017-05-18 16:10:21 -0700582 boolean childrenExpandedAndNotAnimating = mChildrenExpanded
583 && !mContainingNotification.isGroupExpansionChanging();
Selim Cinekc25989e2018-02-16 16:42:14 -0800584 int launchTransitionCompensation = 0;
Selim Cinekb5605e52015-02-20 18:21:41 +0100585 for (int i = 0; i < childCount; i++) {
586 ExpandableNotificationRow child = mChildren.get(i);
Selim Cinekb5605e52015-02-20 18:21:41 +0100587 if (!firstChild) {
Selim Cinek414ad332017-02-24 19:06:12 -0800588 if (expandingToExpandedGroup) {
Mady Mellorb0a82462016-04-30 17:31:02 -0700589 yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight,
590 expandFactor);
Selim Cinek42357e02016-02-24 18:48:01 -0800591 } else {
Adrian Roos16fe4952017-05-18 16:10:21 -0700592 yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding;
Selim Cinek42357e02016-02-24 18:48:01 -0800593 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100594 } else {
Selim Cinek414ad332017-02-24 19:06:12 -0800595 if (expandingToExpandedGroup) {
Selim Cinek42357e02016-02-24 18:48:01 -0800596 yPosition += NotificationUtils.interpolate(
597 0,
598 mNotificatonTopPadding + mDividerHeight,
599 expandFactor);
600 } else {
Adrian Roos16fe4952017-05-18 16:10:21 -0700601 yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0;
Selim Cinek42357e02016-02-24 18:48:01 -0800602 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100603 firstChild = false;
604 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700605
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500606 ExpandableViewState childState = child.getViewState();
Selim Cinekb5605e52015-02-20 18:21:41 +0100607 int intrinsicHeight = child.getIntrinsicHeight();
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800608 childState.height = intrinsicHeight;
Selim Cinekc25989e2018-02-16 16:42:14 -0800609 childState.yTranslation = yPosition + launchTransitionCompensation;
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800610 childState.hidden = false;
Mady Mellorb0a82462016-04-30 17:31:02 -0700611 // When the group is expanded, the children cast the shadows rather than the parent
612 // so use the parent's elevation here.
Adrian Roos16fe4952017-05-18 16:10:21 -0700613 childState.zTranslation =
614 (childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications)
Selim Cinekc25989e2018-02-16 16:42:14 -0800615 ? parentState.zTranslation
Mady Mellorb0a82462016-04-30 17:31:02 -0700616 : 0;
Selim Cinekb5605e52015-02-20 18:21:41 +0100617 childState.dimmed = parentState.dimmed;
618 childState.dark = parentState.dark;
619 childState.hideSensitive = parentState.hideSensitive;
Selim Cinekdb167372016-11-17 15:41:17 -0800620 childState.belowSpeedBump = parentState.belowSpeedBump;
Selim Cinekb5605e52015-02-20 18:21:41 +0100621 childState.clipTopAmount = 0;
Selim Cinekf07d0622016-03-21 19:52:52 -0700622 childState.alpha = 0;
623 if (i < firstOverflowIndex) {
Selim Cinek414ad332017-02-24 19:06:12 -0800624 childState.alpha = showingAsLowPriority() ? expandFactor : 1.0f;
Selim Cinekf07d0622016-03-21 19:52:52 -0700625 } else if (expandFactor == 1.0f && i <= lastVisibleIndex) {
626 childState.alpha = (mActualHeight - childState.yTranslation) / childState.height;
627 childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha));
628 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100629 childState.location = parentState.location;
Selim Cineka7d4f822016-12-06 14:34:47 -0800630 childState.inShelf = parentState.inShelf;
Selim Cinekb5605e52015-02-20 18:21:41 +0100631 yPosition += intrinsicHeight;
Selim Cinekc25989e2018-02-16 16:42:14 -0800632 if (child.isExpandAnimationRunning()) {
633 launchTransitionCompensation = -ambientState.getExpandAnimationTopChange();
634 }
Selim Cineka7d4f822016-12-06 14:34:47 -0800635
Selim Cinekb5605e52015-02-20 18:21:41 +0100636 }
Selim Cinekc897bd32016-03-18 17:32:31 -0700637 if (mOverflowNumber != null) {
638 ExpandableNotificationRow overflowView = mChildren.get(Math.min(
Lucas Dupin4c797d62018-05-07 15:32:13 -0700639 getMaxAllowedVisibleChildren(true /* likeCollapsed */), childCount) - 1);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500640 mGroupOverFlowState.copyFrom(overflowView.getViewState());
Adrian Roos6f6e1592017-05-02 16:22:53 -0700641
Kevina97ea052018-09-11 13:53:18 -0700642 if (mContainingNotification.isOnAmbient()) {
Lucas Dupin4c797d62018-05-07 15:32:13 -0700643 mGroupOverFlowState.alpha = 0.0f;
644 } else if (!mChildrenExpanded) {
645 HybridNotificationView alignView = overflowView.getSingleLineView();
Adrian Roos6f6e1592017-05-02 16:22:53 -0700646 if (alignView != null) {
647 View mirrorView = alignView.getTextView();
Selim Cinekc897bd32016-03-18 17:32:31 -0700648 if (mirrorView.getVisibility() == GONE) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700649 mirrorView = alignView.getTitleView();
Selim Cinekc897bd32016-03-18 17:32:31 -0700650 }
651 if (mirrorView.getVisibility() == GONE) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700652 mirrorView = alignView;
Selim Cinekc897bd32016-03-18 17:32:31 -0700653 }
Lucas Dupin4c797d62018-05-07 15:32:13 -0700654 mGroupOverFlowState.alpha = mirrorView.getAlpha();
Selim Cinekc897bd32016-03-18 17:32:31 -0700655 mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset(
656 mirrorView, overflowView);
Selim Cinekc897bd32016-03-18 17:32:31 -0700657 }
658 } else {
Mady Mellorb0a82462016-04-30 17:31:02 -0700659 mGroupOverFlowState.yTranslation += mNotificationHeaderMargin;
Selim Cinekc897bd32016-03-18 17:32:31 -0700660 mGroupOverFlowState.alpha = 0.0f;
661 }
662 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700663 if (mNotificationHeader != null) {
664 if (mHeaderViewState == null) {
665 mHeaderViewState = new ViewState();
666 }
667 mHeaderViewState.initFrom(mNotificationHeader);
Adrian Roos16fe4952017-05-18 16:10:21 -0700668 mHeaderViewState.zTranslation = childrenExpandedAndNotAnimating
Selim Cinekc25989e2018-02-16 16:42:14 -0800669 ? parentState.zTranslation
Mady Mellorb0a82462016-04-30 17:31:02 -0700670 : 0;
Selim Cinekaa9db1f2018-02-27 17:35:47 -0800671 mHeaderViewState.yTranslation = mCurrentHeaderTranslation;
672 mHeaderViewState.alpha = mHeaderVisibleAmount;
673 // The hiding is done automatically by the alpha, otherwise we'll pick it up again
674 // in the next frame with the initFrom call above and have an invisible header
675 mHeaderViewState.hidden = false;
Mady Mellorb0a82462016-04-30 17:31:02 -0700676 }
677 }
678
679 /**
680 * When moving into the bottom stack, the bottom visible child in an expanded group adjusts its
681 * height, children in the group after this are gone.
682 *
683 * @param child the child who's height to adjust.
684 * @param parentHeight the height of the parent.
685 * @param childState the state to update.
686 * @param yPosition the yPosition of the view.
687 * @return true if children after this one should be hidden.
688 */
689 private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child,
Selim Cinekbbcebde2016-11-09 18:28:20 -0800690 int parentHeight, ExpandableViewState childState, int yPosition) {
Mady Mellorb0a82462016-04-30 17:31:02 -0700691 final int top = yPosition + child.getClipTopAmount();
692 final int intrinsicHeight = child.getIntrinsicHeight();
693 final int bottom = top + intrinsicHeight;
694 int newHeight = intrinsicHeight;
695 if (bottom >= parentHeight) {
696 // Child is either clipped or gone
697 newHeight = Math.max((parentHeight - top), 0);
698 }
699 childState.hidden = newHeight == 0;
700 childState.height = newHeight;
701 return childState.height != intrinsicHeight && !childState.hidden;
Selim Cinek83bc7832015-10-22 13:26:54 -0700702 }
703
Aaron Heuckroth17ce40e2018-06-19 15:44:40 -0400704 @VisibleForTesting
705 int getMaxAllowedVisibleChildren() {
Selim Cinek83bc7832015-10-22 13:26:54 -0700706 return getMaxAllowedVisibleChildren(false /* likeCollapsed */);
707 }
708
Aaron Heuckroth17ce40e2018-06-19 15:44:40 -0400709 @VisibleForTesting
710 int getMaxAllowedVisibleChildren(boolean likeCollapsed) {
Kevina97ea052018-09-11 13:53:18 -0700711 if (mContainingNotification.isOnAmbient()) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700712 return NUMBER_OF_CHILDREN_WHEN_AMBIENT;
713 }
Aaron Heuckroth17ce40e2018-06-19 15:44:40 -0400714 if (!likeCollapsed && (mChildrenExpanded || mContainingNotification.isUserLocked())
715 && !showingAsLowPriority()) {
Selim Cinek83bc7832015-10-22 13:26:54 -0700716 return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED;
717 }
Selim Cinek414ad332017-02-24 19:06:12 -0800718 if (mIsLowPriority || !mContainingNotification.isOnKeyguard()
719 && (mContainingNotification.isExpanded() || mContainingNotification.isHeadsUp())) {
Selim Cinek83bc7832015-10-22 13:26:54 -0700720 return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED;
721 }
722 return NUMBER_OF_CHILDREN_WHEN_COLLAPSED;
Selim Cinekb5605e52015-02-20 18:21:41 +0100723 }
724
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500725 /** Applies state to children. */
726 public void applyState() {
Selim Cinekb5605e52015-02-20 18:21:41 +0100727 int childCount = mChildren.size();
Selim Cinek83bc7832015-10-22 13:26:54 -0700728 ViewState tmpState = new ViewState();
Selim Cinekc897bd32016-03-18 17:32:31 -0700729 float expandFraction = 0.0f;
730 if (mUserLocked) {
Selim Cinekf07d0622016-03-21 19:52:52 -0700731 expandFraction = getGroupExpandFraction();
Selim Cinekc897bd32016-03-18 17:32:31 -0700732 }
Selim Cinek414ad332017-02-24 19:06:12 -0800733 final boolean dividersVisible = mUserLocked && !showingAsLowPriority()
Anthony Chen6bf88a02017-04-10 14:41:44 -0700734 || (mChildrenExpanded && mShowDividersWhenExpanded)
735 || (mContainingNotification.isGroupExpansionChanging()
736 && !mHideDividersDuringExpand);
Selim Cinekb5605e52015-02-20 18:21:41 +0100737 for (int i = 0; i < childCount; i++) {
738 ExpandableNotificationRow child = mChildren.get(i);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500739 ExpandableViewState viewState = child.getViewState();
Selim Cinekbbcebde2016-11-09 18:28:20 -0800740 viewState.applyToView(child);
Selim Cinek7b836392015-12-04 20:02:59 -0800741
742 // layout the divider
743 View divider = mDividers.get(i);
744 tmpState.initFrom(divider);
745 tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
Anthony Chen6bf88a02017-04-10 14:41:44 -0700746 float alpha = mChildrenExpanded && viewState.alpha != 0 ? mDividerAlpha : 0;
Selim Cinek414ad332017-02-24 19:06:12 -0800747 if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
Selim Cinekf07d0622016-03-21 19:52:52 -0700748 alpha = NotificationUtils.interpolate(0, 0.5f,
749 Math.min(viewState.alpha, expandFraction));
Selim Cinek42357e02016-02-24 18:48:01 -0800750 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700751 tmpState.hidden = !dividersVisible;
Selim Cinek42357e02016-02-24 18:48:01 -0800752 tmpState.alpha = alpha;
Selim Cinekbbcebde2016-11-09 18:28:20 -0800753 tmpState.applyToView(divider);
Selim Cinekd9a3f8f2016-03-17 19:39:00 -0700754 // There is no fake shadow to be drawn on the children
755 child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
Selim Cinekb5605e52015-02-20 18:21:41 +0100756 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800757 if (mGroupOverFlowState != null) {
Selim Cinekbbcebde2016-11-09 18:28:20 -0800758 mGroupOverFlowState.applyToView(mOverflowNumber);
Selim Cinekc897bd32016-03-18 17:32:31 -0700759 mNeverAppliedGroupState = false;
760 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800761 if (mHeaderViewState != null) {
Selim Cinekbbcebde2016-11-09 18:28:20 -0800762 mHeaderViewState.applyToView(mNotificationHeader);
Mady Mellorb0a82462016-04-30 17:31:02 -0700763 }
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800764 updateChildrenClipping();
765 }
766
767 private void updateChildrenClipping() {
Selim Cinekc25989e2018-02-16 16:42:14 -0800768 if (mContainingNotification.hasExpandingChild()) {
769 return;
770 }
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800771 int childCount = mChildren.size();
Selim Cinek414ad332017-02-24 19:06:12 -0800772 int layoutEnd = mContainingNotification.getActualHeight() - mClipBottomAmount;
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800773 for (int i = 0; i < childCount; i++) {
774 ExpandableNotificationRow child = mChildren.get(i);
775 if (child.getVisibility() == GONE) {
776 continue;
777 }
778 float childTop = child.getTranslationY();
779 float childBottom = childTop + child.getActualHeight();
780 boolean visible = true;
781 int clipBottomAmount = 0;
782 if (childTop > layoutEnd) {
783 visible = false;
784 } else if (childBottom > layoutEnd) {
785 clipBottomAmount = (int) (childBottom - layoutEnd);
786 }
787
788 boolean isVisible = child.getVisibility() == VISIBLE;
789 if (visible != isVisible) {
790 child.setVisibility(visible ? VISIBLE : INVISIBLE);
791 }
792
793 child.setClipBottomAmount(clipBottomAmount);
794 }
Selim Cinekb5605e52015-02-20 18:21:41 +0100795 }
796
Selim Cinekb5605e52015-02-20 18:21:41 +0100797 /**
798 * This is called when the children expansion has changed and positions the children properly
799 * for an appear animation.
800 *
Selim Cinekb5605e52015-02-20 18:21:41 +0100801 */
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500802 public void prepareExpansionChanged() {
Selim Cinek277a8aa2016-01-22 12:12:37 -0800803 // TODO: do something that makes sense, like placing the invisible views correctly
804 return;
Selim Cinekb5605e52015-02-20 18:21:41 +0100805 }
806
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500807 /** Animate to a given state. */
808 public void startAnimationToState(AnimationProperties properties) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100809 int childCount = mChildren.size();
Selim Cinek83bc7832015-10-22 13:26:54 -0700810 ViewState tmpState = new ViewState();
Selim Cinekf07d0622016-03-21 19:52:52 -0700811 float expandFraction = getGroupExpandFraction();
Selim Cinek414ad332017-02-24 19:06:12 -0800812 final boolean dividersVisible = mUserLocked && !showingAsLowPriority()
Anthony Chen6bf88a02017-04-10 14:41:44 -0700813 || (mChildrenExpanded && mShowDividersWhenExpanded)
814 || (mContainingNotification.isGroupExpansionChanging()
815 && !mHideDividersDuringExpand);
Selim Cineke8126522015-12-08 21:21:17 -0800816 for (int i = childCount - 1; i >= 0; i--) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100817 ExpandableNotificationRow child = mChildren.get(i);
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500818 ExpandableViewState viewState = child.getViewState();
Selim Cinek0cfbef42016-11-09 19:06:36 -0800819 viewState.animateTo(child, properties);
Selim Cinek7b836392015-12-04 20:02:59 -0800820
821 // layout the divider
822 View divider = mDividers.get(i);
823 tmpState.initFrom(divider);
824 tmpState.yTranslation = viewState.yTranslation - mDividerHeight;
Selim Cinek42357e02016-02-24 18:48:01 -0800825 float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0;
Selim Cinek414ad332017-02-24 19:06:12 -0800826 if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) {
Selim Cinekf07d0622016-03-21 19:52:52 -0700827 alpha = NotificationUtils.interpolate(0, 0.5f,
828 Math.min(viewState.alpha, expandFraction));
Selim Cinek42357e02016-02-24 18:48:01 -0800829 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700830 tmpState.hidden = !dividersVisible;
Selim Cinek42357e02016-02-24 18:48:01 -0800831 tmpState.alpha = alpha;
Selim Cinek0cfbef42016-11-09 19:06:36 -0800832 tmpState.animateTo(divider, properties);
Selim Cinekd9a3f8f2016-03-17 19:39:00 -0700833 // There is no fake shadow to be drawn on the children
834 child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0);
Selim Cinekb5605e52015-02-20 18:21:41 +0100835 }
Selim Cinekc897bd32016-03-18 17:32:31 -0700836 if (mOverflowNumber != null) {
837 if (mNeverAppliedGroupState) {
838 float alpha = mGroupOverFlowState.alpha;
839 mGroupOverFlowState.alpha = 0;
Selim Cinekbbcebde2016-11-09 18:28:20 -0800840 mGroupOverFlowState.applyToView(mOverflowNumber);
Selim Cinekc897bd32016-03-18 17:32:31 -0700841 mGroupOverFlowState.alpha = alpha;
842 mNeverAppliedGroupState = false;
843 }
Selim Cinek0cfbef42016-11-09 19:06:36 -0800844 mGroupOverFlowState.animateTo(mOverflowNumber, properties);
Selim Cinekc897bd32016-03-18 17:32:31 -0700845 }
Mady Mellorb0a82462016-04-30 17:31:02 -0700846 if (mNotificationHeader != null) {
Selim Cinekbbcebde2016-11-09 18:28:20 -0800847 mHeaderViewState.applyToView(mNotificationHeader);
Mady Mellorb0a82462016-04-30 17:31:02 -0700848 }
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800849 updateChildrenClipping();
Selim Cinekb5605e52015-02-20 18:21:41 +0100850 }
851
852 public ExpandableNotificationRow getViewAtPosition(float y) {
853 // find the view under the pointer, accounting for GONE views
854 final int count = mChildren.size();
855 for (int childIdx = 0; childIdx < count; childIdx++) {
856 ExpandableNotificationRow slidingChild = mChildren.get(childIdx);
857 float childTop = slidingChild.getTranslationY();
858 float top = childTop + slidingChild.getClipTopAmount();
859 float bottom = childTop + slidingChild.getActualHeight();
860 if (y >= top && y <= bottom) {
861 return slidingChild;
862 }
863 }
864 return null;
865 }
866
Selim Cinek83bc7832015-10-22 13:26:54 -0700867 public void setChildrenExpanded(boolean childrenExpanded) {
868 mChildrenExpanded = childrenExpanded;
Selim Cinek898d1732016-02-29 19:57:35 -0800869 updateExpansionStates();
Mady Mellorb0a82462016-04-30 17:31:02 -0700870 if (mNotificationHeader != null) {
871 mNotificationHeader.setExpanded(childrenExpanded);
872 }
Selim Cinekddf1b392016-05-27 16:33:10 -0700873 final int count = mChildren.size();
874 for (int childIdx = 0; childIdx < count; childIdx++) {
875 ExpandableNotificationRow child = mChildren.get(childIdx);
876 child.setChildrenExpanded(childrenExpanded, false);
877 }
Selim Cinekbc342152018-12-05 18:45:42 -0800878 updateHeaderTouchability();
Selim Cinek83bc7832015-10-22 13:26:54 -0700879 }
880
Selim Cinek414ad332017-02-24 19:06:12 -0800881 public void setContainingNotification(ExpandableNotificationRow parent) {
882 mContainingNotification = parent;
883 mHeaderUtil = new NotificationHeaderUtil(mContainingNotification);
Mady Mellorb0a82462016-04-30 17:31:02 -0700884 }
885
Selim Cinek414ad332017-02-24 19:06:12 -0800886 public ExpandableNotificationRow getContainingNotification() {
887 return mContainingNotification;
Adrian Roos4a579672016-05-24 16:54:37 -0700888 }
889
Mady Mellorb0a82462016-04-30 17:31:02 -0700890 public NotificationHeaderView getHeaderView() {
891 return mNotificationHeader;
892 }
893
Selim Cinek414ad332017-02-24 19:06:12 -0800894 public NotificationHeaderView getLowPriorityHeaderView() {
895 return mNotificationHeaderLowPriority;
896 }
897
Lucas Dupin6ea27872017-05-30 12:00:04 -0700898 @VisibleForTesting
899 public ViewGroup getCurrentHeaderView() {
900 return mCurrentHeader;
901 }
902
Kevina97ea052018-09-11 13:53:18 -0700903 public void notifyDozingStateChanged() {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700904 updateHeaderVisibility(false);
Lucas Dupin4c797d62018-05-07 15:32:13 -0700905 updateGroupOverflow();
Adrian Roos6f6e1592017-05-02 16:22:53 -0700906 }
907
Selim Cinek414ad332017-02-24 19:06:12 -0800908 private void updateHeaderVisibility(boolean animate) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700909 ViewGroup desiredHeader;
910 ViewGroup currentHeader = mCurrentHeader;
911 desiredHeader = calculateDesiredHeader();
912
913 if (currentHeader == desiredHeader) {
914 return;
Mady Mellorb0a82462016-04-30 17:31:02 -0700915 }
Adrian Roos6f6e1592017-05-02 16:22:53 -0700916 if (desiredHeader == mNotificationHeaderAmbient
917 || currentHeader == mNotificationHeaderAmbient) {
918 animate = false;
919 }
920
Selim Cinek414ad332017-02-24 19:06:12 -0800921 if (animate) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700922 if (desiredHeader != null && currentHeader != null) {
923 currentHeader.setVisibility(VISIBLE);
924 desiredHeader.setVisibility(VISIBLE);
925 NotificationViewWrapper visibleWrapper = getWrapperForView(desiredHeader);
926 NotificationViewWrapper hiddenWrapper = getWrapperForView(currentHeader);
Selim Cinek414ad332017-02-24 19:06:12 -0800927 visibleWrapper.transformFrom(hiddenWrapper);
928 hiddenWrapper.transformTo(visibleWrapper, () -> updateHeaderVisibility(false));
Adrian Roos6f6e1592017-05-02 16:22:53 -0700929 startChildAlphaAnimations(desiredHeader == mNotificationHeader);
Selim Cinek414ad332017-02-24 19:06:12 -0800930 } else {
931 animate = false;
932 }
933 }
934 if (!animate) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700935 if (desiredHeader != null) {
936 getWrapperForView(desiredHeader).setVisible(true);
937 desiredHeader.setVisibility(VISIBLE);
Selim Cinek414ad332017-02-24 19:06:12 -0800938 }
Adrian Roos6f6e1592017-05-02 16:22:53 -0700939 if (currentHeader != null) {
Lucas Dupin6ea27872017-05-30 12:00:04 -0700940 // Wrapper can be null if we were a low priority notification
941 // and just destroyed it by calling setIsLowPriority(false)
942 NotificationViewWrapper wrapper = getWrapperForView(currentHeader);
943 if (wrapper != null) {
944 wrapper.setVisible(false);
945 }
Adrian Roos6f6e1592017-05-02 16:22:53 -0700946 currentHeader.setVisibility(INVISIBLE);
Selim Cinek414ad332017-02-24 19:06:12 -0800947 }
948 }
Adrian Roos6f6e1592017-05-02 16:22:53 -0700949
950 resetHeaderVisibilityIfNeeded(mNotificationHeader, desiredHeader);
951 resetHeaderVisibilityIfNeeded(mNotificationHeaderAmbient, desiredHeader);
952 resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, desiredHeader);
953
Lucas Dupin6ea27872017-05-30 12:00:04 -0700954 mCurrentHeader = desiredHeader;
Adrian Roos6f6e1592017-05-02 16:22:53 -0700955 }
956
957 private void resetHeaderVisibilityIfNeeded(View header, View desiredHeader) {
958 if (header == null) {
959 return;
960 }
961 if (header != mCurrentHeader && header != desiredHeader) {
962 getWrapperForView(header).setVisible(false);
963 header.setVisibility(INVISIBLE);
964 }
965 if (header == desiredHeader && header.getVisibility() != VISIBLE) {
966 getWrapperForView(header).setVisible(true);
967 header.setVisibility(VISIBLE);
968 }
969 }
970
971 private ViewGroup calculateDesiredHeader() {
972 ViewGroup desiredHeader;
Kevina97ea052018-09-11 13:53:18 -0700973 if (mContainingNotification.isOnAmbient()) {
Adrian Roos6f6e1592017-05-02 16:22:53 -0700974 desiredHeader = mNotificationHeaderAmbient;
975 } else if (showingAsLowPriority()) {
976 desiredHeader = mNotificationHeaderLowPriority;
977 } else {
978 desiredHeader = mNotificationHeader;
979 }
980 return desiredHeader;
Selim Cinek414ad332017-02-24 19:06:12 -0800981 }
982
983 private void startChildAlphaAnimations(boolean toVisible) {
984 float target = toVisible ? 1.0f : 0.0f;
985 float start = 1.0f - target;
986 int childCount = mChildren.size();
987 for (int i = 0; i < childCount; i++) {
988 if (i >= NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED) {
989 break;
990 }
991 ExpandableNotificationRow child = mChildren.get(i);
992 child.setAlpha(start);
993 ViewState viewState = new ViewState();
994 viewState.initFrom(child);
995 viewState.alpha = target;
996 ALPHA_FADE_IN.setDelay(i * 50);
997 viewState.animateTo(child, ALPHA_FADE_IN);
998 }
999 }
1000
1001
1002 private void updateHeaderTransformation() {
Selim Cinek1826d982017-03-06 20:47:37 -08001003 if (mUserLocked && showingAsLowPriority()) {
Selim Cinek414ad332017-02-24 19:06:12 -08001004 float fraction = getGroupExpandFraction();
1005 mNotificationHeaderWrapper.transformFrom(mNotificationHeaderWrapperLowPriority,
1006 fraction);
1007 mNotificationHeader.setVisibility(VISIBLE);
1008 mNotificationHeaderWrapperLowPriority.transformTo(mNotificationHeaderWrapper,
1009 fraction);
1010 }
1011
1012 }
1013
Adrian Roos6f6e1592017-05-02 16:22:53 -07001014 private NotificationViewWrapper getWrapperForView(View visibleHeader) {
Selim Cinek414ad332017-02-24 19:06:12 -08001015 if (visibleHeader == mNotificationHeader) {
1016 return mNotificationHeaderWrapper;
1017 }
Adrian Roos6f6e1592017-05-02 16:22:53 -07001018 if (visibleHeader == mNotificationHeaderAmbient) {
1019 return mNotificationHeaderWrapperAmbient;
1020 }
Selim Cinek414ad332017-02-24 19:06:12 -08001021 return mNotificationHeaderWrapperLowPriority;
Mady Mellorb0a82462016-04-30 17:31:02 -07001022 }
1023
1024 /**
1025 * Called when a groups expansion changes to adjust the background of the header view.
1026 *
1027 * @param expanded whether the group is expanded.
1028 */
1029 public void updateHeaderForExpansion(boolean expanded) {
1030 if (mNotificationHeader != null) {
1031 if (expanded) {
1032 ColorDrawable cd = new ColorDrawable();
Selim Cinek414ad332017-02-24 19:06:12 -08001033 cd.setColor(mContainingNotification.calculateBgColor());
Mady Mellorb0a82462016-04-30 17:31:02 -07001034 mNotificationHeader.setHeaderBackgroundDrawable(cd);
1035 } else {
1036 mNotificationHeader.setHeaderBackgroundDrawable(null);
1037 }
1038 }
Selim Cinek388df6d2015-10-22 13:25:11 -07001039 }
1040
Selim Cinek83bc7832015-10-22 13:26:54 -07001041 public int getMaxContentHeight() {
Selim Cinek414ad332017-02-24 19:06:12 -08001042 if (showingAsLowPriority()) {
1043 return getMinHeight(NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED, true
1044 /* likeHighPriority */);
1045 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001046 int maxContentHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation
1047 + mNotificatonTopPadding;
Selim Cinek42357e02016-02-24 18:48:01 -08001048 int visibleChildren = 0;
1049 int childCount = mChildren.size();
1050 for (int i = 0; i < childCount; i++) {
1051 if (visibleChildren >= NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED) {
1052 break;
1053 }
1054 ExpandableNotificationRow child = mChildren.get(i);
Selim Cineke81b82b2016-03-04 11:22:28 -08001055 float childHeight = child.isExpanded(true /* allowOnKeyguard */)
Selim Cinek42357e02016-02-24 18:48:01 -08001056 ? child.getMaxExpandHeight()
1057 : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
1058 maxContentHeight += childHeight;
1059 visibleChildren++;
1060 }
1061 if (visibleChildren > 0) {
1062 maxContentHeight += visibleChildren * mDividerHeight;
1063 }
1064 return maxContentHeight;
1065 }
1066
1067 public void setActualHeight(int actualHeight) {
1068 if (!mUserLocked) {
1069 return;
1070 }
1071 mActualHeight = actualHeight;
Selim Cinekf07d0622016-03-21 19:52:52 -07001072 float fraction = getGroupExpandFraction();
Selim Cinek414ad332017-02-24 19:06:12 -08001073 boolean showingLowPriority = showingAsLowPriority();
1074 updateHeaderTransformation();
Selim Cinekf07d0622016-03-21 19:52:52 -07001075 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
Selim Cinek42357e02016-02-24 18:48:01 -08001076 int childCount = mChildren.size();
1077 for (int i = 0; i < childCount; i++) {
1078 ExpandableNotificationRow child = mChildren.get(i);
Selim Cinek414ad332017-02-24 19:06:12 -08001079 float childHeight;
1080 if (showingLowPriority) {
1081 childHeight = child.getShowingLayout().getMinHeight(false /* likeGroupExpanded */);
1082 } else if (child.isExpanded(true /* allowOnKeyguard */)) {
1083 childHeight = child.getMaxExpandHeight();
1084 } else {
1085 childHeight = child.getShowingLayout().getMinHeight(
1086 true /* likeGroupExpanded */);
1087 }
Selim Cinekf07d0622016-03-21 19:52:52 -07001088 if (i < maxAllowedVisibleChildren) {
1089 float singleLineHeight = child.getShowingLayout().getMinHeight(
1090 false /* likeGroupExpanded */);
1091 child.setActualHeight((int) NotificationUtils.interpolate(singleLineHeight,
1092 childHeight, fraction), false);
1093 } else {
1094 child.setActualHeight((int) childHeight, false);
1095 }
Selim Cinek42357e02016-02-24 18:48:01 -08001096 }
1097 }
1098
Selim Cinekf07d0622016-03-21 19:52:52 -07001099 public float getGroupExpandFraction() {
Selim Cinek414ad332017-02-24 19:06:12 -08001100 int visibleChildrenExpandedHeight = showingAsLowPriority() ? getMaxContentHeight()
1101 : getVisibleChildrenExpandHeight();
Selim Cinek567e8452016-03-24 10:54:56 -07001102 int minExpandHeight = getCollapsedHeight();
Selim Cinekf07d0622016-03-21 19:52:52 -07001103 float factor = (mActualHeight - minExpandHeight)
1104 / (float) (visibleChildrenExpandedHeight - minExpandHeight);
Selim Cinek42357e02016-02-24 18:48:01 -08001105 return Math.max(0.0f, Math.min(1.0f, factor));
1106 }
1107
Selim Cinekf07d0622016-03-21 19:52:52 -07001108 private int getVisibleChildrenExpandHeight() {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001109 int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation
1110 + mNotificatonTopPadding + mDividerHeight;
Selim Cinek42357e02016-02-24 18:48:01 -08001111 int visibleChildren = 0;
1112 int childCount = mChildren.size();
Selim Cinekf07d0622016-03-21 19:52:52 -07001113 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */);
Selim Cinek42357e02016-02-24 18:48:01 -08001114 for (int i = 0; i < childCount; i++) {
Selim Cinekf07d0622016-03-21 19:52:52 -07001115 if (visibleChildren >= maxAllowedVisibleChildren) {
Selim Cinek42357e02016-02-24 18:48:01 -08001116 break;
1117 }
1118 ExpandableNotificationRow child = mChildren.get(i);
Selim Cinekf07d0622016-03-21 19:52:52 -07001119 float childHeight = child.isExpanded(true /* allowOnKeyguard */)
1120 ? child.getMaxExpandHeight()
1121 : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */);
1122 intrinsicHeight += childHeight;
Selim Cinek42357e02016-02-24 18:48:01 -08001123 visibleChildren++;
1124 }
Selim Cinek42357e02016-02-24 18:48:01 -08001125 return intrinsicHeight;
Selim Cinek83bc7832015-10-22 13:26:54 -07001126 }
1127
1128 public int getMinHeight() {
Kevina97ea052018-09-11 13:53:18 -07001129 return getMinHeight(mContainingNotification.isOnAmbient()
Adrian Roos6f6e1592017-05-02 16:22:53 -07001130 ? NUMBER_OF_CHILDREN_WHEN_AMBIENT
1131 : NUMBER_OF_CHILDREN_WHEN_COLLAPSED, false /* likeHighPriority */);
Selim Cinekb55386d2015-12-16 17:26:49 -08001132 }
1133
Selim Cinek567e8452016-03-24 10:54:56 -07001134 public int getCollapsedHeight() {
Selim Cinek414ad332017-02-24 19:06:12 -08001135 return getMinHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */),
1136 false /* likeHighPriority */);
Selim Cinek567e8452016-03-24 10:54:56 -07001137 }
1138
Selim Cinek414ad332017-02-24 19:06:12 -08001139 /**
1140 * Get the minimum Height for this group.
1141 *
1142 * @param maxAllowedVisibleChildren the number of children that should be visible
1143 * @param likeHighPriority if the height should be calculated as if it were not low priority
1144 */
1145 private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority) {
1146 if (!likeHighPriority && showingAsLowPriority()) {
1147 return mNotificationHeaderLowPriority.getHeight();
Selim Cinek6743c0b2017-01-18 18:24:01 -08001148 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001149 int minExpandHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation;
Selim Cinek2c584612016-02-29 16:14:25 -08001150 int visibleChildren = 0;
1151 boolean firstChild = true;
1152 int childCount = mChildren.size();
1153 for (int i = 0; i < childCount; i++) {
1154 if (visibleChildren >= maxAllowedVisibleChildren) {
1155 break;
1156 }
1157 if (!firstChild) {
1158 minExpandHeight += mChildPadding;
1159 } else {
1160 firstChild = false;
1161 }
1162 ExpandableNotificationRow child = mChildren.get(i);
Selim Cinekf1f270a2016-04-05 19:02:37 -07001163 minExpandHeight += child.getSingleLineView().getHeight();
Selim Cinek2c584612016-02-29 16:14:25 -08001164 visibleChildren++;
1165 }
1166 minExpandHeight += mCollapsedBottompadding;
1167 return minExpandHeight;
Selim Cinekb5605e52015-02-20 18:21:41 +01001168 }
Selim Cinek9c7712d2015-12-08 19:19:48 -08001169
Selim Cinek414ad332017-02-24 19:06:12 -08001170 public boolean showingAsLowPriority() {
1171 return mIsLowPriority && !mContainingNotification.isExpanded();
1172 }
1173
Mady Mellorb0a82462016-04-30 17:31:02 -07001174 public void reInflateViews(OnClickListener listener, StatusBarNotification notification) {
Selim Cinek414ad332017-02-24 19:06:12 -08001175 if (mNotificationHeader != null) {
1176 removeView(mNotificationHeader);
1177 mNotificationHeader = null;
1178 }
1179 if (mNotificationHeaderLowPriority != null) {
1180 removeView(mNotificationHeaderLowPriority);
1181 mNotificationHeaderLowPriority = null;
1182 }
Adrian Roos6f6e1592017-05-02 16:22:53 -07001183 if (mNotificationHeaderAmbient != null) {
1184 removeView(mNotificationHeaderAmbient);
1185 mNotificationHeaderAmbient = null;
1186 }
Selim Cinek414ad332017-02-24 19:06:12 -08001187 recreateNotificationHeader(listener);
Selim Cinek01af3342016-02-09 19:25:31 -08001188 initDimens();
1189 for (int i = 0; i < mDividers.size(); i++) {
1190 View prevDivider = mDividers.get(i);
1191 int index = indexOfChild(prevDivider);
1192 removeView(prevDivider);
1193 View divider = inflateDivider();
1194 addView(divider, index);
1195 mDividers.set(i, divider);
1196 }
Selim Cinek4bb59342016-04-08 19:29:35 -07001197 removeView(mOverflowNumber);
1198 mOverflowNumber = null;
Selim Cinek4bb59342016-04-08 19:29:35 -07001199 mGroupOverFlowState = null;
1200 updateGroupOverflow();
Selim Cinek01af3342016-02-09 19:25:31 -08001201 }
Selim Cinek42357e02016-02-24 18:48:01 -08001202
1203 public void setUserLocked(boolean userLocked) {
1204 mUserLocked = userLocked;
Selim Cinek414ad332017-02-24 19:06:12 -08001205 if (!mUserLocked) {
1206 updateHeaderVisibility(false /* animate */);
1207 }
Selim Cinek42357e02016-02-24 18:48:01 -08001208 int childCount = mChildren.size();
1209 for (int i = 0; i < childCount; i++) {
1210 ExpandableNotificationRow child = mChildren.get(i);
Selim Cinek414ad332017-02-24 19:06:12 -08001211 child.setUserLocked(userLocked && !showingAsLowPriority());
Selim Cinek42357e02016-02-24 18:48:01 -08001212 }
Selim Cinekbc342152018-12-05 18:45:42 -08001213 updateHeaderTouchability();
1214 }
1215
1216 private void updateHeaderTouchability() {
1217 if (mNotificationHeader != null) {
1218 mNotificationHeader.setAcceptAllTouches(mChildrenExpanded || mUserLocked);
1219 }
Selim Cinek42357e02016-02-24 18:48:01 -08001220 }
Selim Cinekc897bd32016-03-18 17:32:31 -07001221
1222 public void onNotificationUpdated() {
1223 mHybridGroupManager.setOverflowNumberColor(mOverflowNumber,
Lucas Dupin00be88f2019-01-03 17:50:52 -08001224 mContainingNotification.getNotificationColor());
Selim Cinekc897bd32016-03-18 17:32:31 -07001225 }
Adrian Roosd009ab12016-05-20 17:58:53 -07001226
Adrian Roos4a579672016-05-24 16:54:37 -07001227 public int getPositionInLinearLayout(View childInGroup) {
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001228 int position = mNotificationHeaderMargin + mCurrentHeaderTranslation
1229 + mNotificatonTopPadding;
Adrian Roos4a579672016-05-24 16:54:37 -07001230
1231 for (int i = 0; i < mChildren.size(); i++) {
1232 ExpandableNotificationRow child = mChildren.get(i);
1233 boolean notGone = child.getVisibility() != View.GONE;
1234 if (notGone) {
1235 position += mDividerHeight;
1236 }
1237 if (child == childInGroup) {
1238 return position;
1239 }
1240 if (notGone) {
1241 position += child.getIntrinsicHeight();
1242 }
1243 }
1244 return 0;
1245 }
Selim Cinek0242fbb2016-10-19 13:38:32 -07001246
1247 public void setIconsVisible(boolean iconsVisible) {
1248 if (mNotificationHeaderWrapper != null) {
1249 NotificationHeaderView header = mNotificationHeaderWrapper.getNotificationHeader();
1250 if (header != null) {
1251 header.getIcon().setForceHidden(!iconsVisible);
1252 }
1253 }
Selim Cinek414ad332017-02-24 19:06:12 -08001254 if (mNotificationHeaderWrapperLowPriority != null) {
1255 NotificationHeaderView header
1256 = mNotificationHeaderWrapperLowPriority.getNotificationHeader();
1257 if (header != null) {
1258 header.getIcon().setForceHidden(!iconsVisible);
1259 }
1260 }
Selim Cinek0242fbb2016-10-19 13:38:32 -07001261 }
Selim Cinekb3dadcc2016-11-21 17:21:13 -08001262
1263 public void setClipBottomAmount(int clipBottomAmount) {
1264 mClipBottomAmount = clipBottomAmount;
1265 updateChildrenClipping();
1266 }
Selim Cinek6743c0b2017-01-18 18:24:01 -08001267
1268 public void setIsLowPriority(boolean isLowPriority) {
1269 mIsLowPriority = isLowPriority;
Selim Cinek414ad332017-02-24 19:06:12 -08001270 if (mContainingNotification != null) { /* we're not yet set up yet otherwise */
1271 recreateLowPriorityHeader(null /* existingBuilder */);
1272 updateHeaderVisibility(false /* animate */);
1273 }
1274 if (mUserLocked) {
1275 setUserLocked(mUserLocked);
1276 }
1277 }
1278
1279 public NotificationHeaderView getVisibleHeader() {
1280 NotificationHeaderView header = mNotificationHeader;
1281 if (showingAsLowPriority()) {
1282 header = mNotificationHeaderLowPriority;
1283 }
1284 return header;
1285 }
1286
1287 public void onExpansionChanged() {
1288 if (mIsLowPriority) {
1289 if (mUserLocked) {
1290 setUserLocked(mUserLocked);
1291 }
1292 updateHeaderVisibility(true /* animate */);
1293 }
1294 }
1295
1296 public float getIncreasedPaddingAmount() {
1297 if (showingAsLowPriority()) {
1298 return 0.0f;
1299 }
1300 return getGroupExpandFraction();
Selim Cinek6743c0b2017-01-18 18:24:01 -08001301 }
Selim Cinek817abe72017-05-24 11:08:55 -07001302
1303 @VisibleForTesting
1304 public boolean isUserLocked() {
1305 return mUserLocked;
1306 }
Selim Cinek2871bef2017-11-22 08:40:00 -08001307
1308 public void setCurrentBottomRoundness(float currentBottomRoundness) {
1309 boolean last = true;
1310 for (int i = mChildren.size() - 1; i >= 0; i--) {
1311 ExpandableNotificationRow child = mChildren.get(i);
1312 if (child.getVisibility() == View.GONE) {
1313 continue;
1314 }
1315 float bottomRoundness = last ? currentBottomRoundness : 0.0f;
1316 child.setBottomRoundness(bottomRoundness, isShown() /* animate */);
1317 last = false;
1318 }
1319 }
Selim Cinekaa9db1f2018-02-27 17:35:47 -08001320
1321 public void setHeaderVisibleAmount(float headerVisibleAmount) {
1322 mHeaderVisibleAmount = headerVisibleAmount;
1323 mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader);
1324 }
Selim Cinekb5605e52015-02-20 18:21:41 +01001325}