blob: 7ed8350249ec2330e5c12567ef1813da8c1e3369 [file] [log] [blame]
Jorim Jaggibe565df2014-04-28 17:51:23 +02001/*
2 * Copyright (C) 2014 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.row;
Jorim Jaggibe565df2014-04-28 17:51:23 +020018
Selim Cinek332c23f2018-03-16 17:37:50 -070019import android.animation.AnimatorListenerAdapter;
Jorim Jaggibe565df2014-04-28 17:51:23 +020020import android.content.Context;
Selim Cinek6df13852020-02-25 12:39:00 -080021import android.content.res.Configuration;
Selim Cinek471e31a2015-12-11 13:39:48 -080022import android.graphics.Paint;
Selim Cineke32010a2014-08-20 23:50:41 +020023import android.graphics.Rect;
Jorim Jaggibe565df2014-04-28 17:51:23 +020024import android.util.AttributeSet;
25import android.view.View;
Selim Cinekc9c00ae2014-05-20 03:33:40 +020026import android.view.ViewGroup;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020027import android.widget.FrameLayout;
Selim Cinekd84a5932015-12-15 11:45:36 -080028
Dave Mankoffa4d195d2018-11-16 13:33:27 -050029import androidx.annotation.Nullable;
30
Selim Cinek30887662018-10-15 17:37:21 -070031import com.android.systemui.Dumpable;
Selim Cinek6df13852020-02-25 12:39:00 -080032import com.android.systemui.Interpolators;
33import com.android.systemui.R;
34import com.android.systemui.statusbar.StatusBarIconView;
Rohan Shah20790b82018-07-02 17:21:04 -070035import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
36import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020037
Selim Cinek30887662018-10-15 17:37:21 -070038import java.io.FileDescriptor;
39import java.io.PrintWriter;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020040import java.util.ArrayList;
Dave Mankoffa4d195d2018-11-16 13:33:27 -050041import java.util.List;
Jorim Jaggibe565df2014-04-28 17:51:23 +020042
43/**
44 * An abstract view for expandable views.
45 */
Selim Cinek30887662018-10-15 17:37:21 -070046public abstract class ExpandableView extends FrameLayout implements Dumpable {
Dave Mankoffa4d195d2018-11-16 13:33:27 -050047 private static final String TAG = "ExpandableView";
Selim Cinek24d7cfa2014-05-20 13:50:57 +020048
Selim Cinekeccf4942018-05-30 09:55:36 -070049 public static final float NO_ROUNDNESS = -1;
Selim Cinekb5605e52015-02-20 18:21:41 +010050 protected OnHeightChangedListener mOnHeightChangedListener;
Chris Wren310df312014-10-31 14:29:46 -040051 private int mActualHeight;
Jorim Jaggibe565df2014-04-28 17:51:23 +020052 protected int mClipTopAmount;
Selim Cinekb3dadcc2016-11-21 17:21:13 -080053 protected int mClipBottomAmount;
Selim Cinek53328492018-12-05 14:58:44 -080054 protected int mMinimumHeightForClipping = 0;
55 protected float mExtraWidthForClipping = 0;
Selim Cinek24d7cfa2014-05-20 13:50:57 +020056 private ArrayList<View> mMatchParentViews = new ArrayList<View>();
Selim Cineka272dfe2015-02-20 18:12:28 +010057 private static Rect mClipRect = new Rect();
Selim Cinek2cd45df2015-06-09 18:00:07 -070058 private boolean mWillBeGone;
Selim Cinek9c17b772015-07-07 20:37:09 -070059 private int mMinClipTopAmount = 0;
Selim Cinek4ffd6362015-12-29 15:12:23 +010060 private boolean mClipToActualHeight = true;
Adrian Roos14503e22016-03-09 14:01:24 -080061 private boolean mChangingPosition = false;
Selim Cinekd1395642016-04-28 12:22:42 -070062 private ViewGroup mTransientContainer;
Selim Cinekeccb5de2016-10-28 15:04:05 -070063 private boolean mInShelf;
64 private boolean mTransformingInShelf;
Selim Cinek6df13852020-02-25 12:39:00 -080065 protected float mContentTransformationAmount;
66 protected boolean mIsLastChild;
67 protected int mContentShift;
Dave Mankoffc56db332018-12-10 11:31:49 -050068 private final ExpandableViewState mViewState;
Selim Cinek9ed6e042020-03-26 15:45:51 -070069 private float mContentTranslation;
Selim Cinek4b9710e2020-05-26 15:54:24 -070070 protected boolean mLastInSection;
71 protected boolean mFirstInSection;
Jorim Jaggibe565df2014-04-28 17:51:23 +020072
73 public ExpandableView(Context context, AttributeSet attrs) {
74 super(context, attrs);
Dave Mankoffc56db332018-12-10 11:31:49 -050075 mViewState = createExpandableViewState();
Selim Cinek6df13852020-02-25 12:39:00 -080076 initDimens();
77 }
78
79 private void initDimens() {
80 mContentShift = getResources().getDimensionPixelSize(
81 R.dimen.shelf_transform_content_shift);
82 }
83
84 @Override
85 protected void onConfigurationChanged(Configuration newConfig) {
86 super.onConfigurationChanged(newConfig);
87 initDimens();
Selim Cinek24d7cfa2014-05-20 13:50:57 +020088 }
89
90 @Override
91 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Steve Elliottb0940382020-02-20 14:24:02 -050092 final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
felkachang79ca96d2018-04-27 16:55:40 +080093 final int viewHorizontalPadding = getPaddingStart() + getPaddingEnd();
Steve Elliottb0940382020-02-20 14:24:02 -050094
95 // Max height is as large as possible, unless otherwise requested
Selim Cinekd84a5932015-12-15 11:45:36 -080096 int ownMaxHeight = Integer.MAX_VALUE;
Selim Cinek6f145cf2015-05-18 15:16:08 -070097 int heightMode = MeasureSpec.getMode(heightMeasureSpec);
Steve Elliottb0940382020-02-20 14:24:02 -050098 if (heightMode != MeasureSpec.UNSPECIFIED && givenHeight != 0) {
99 // Set our max height to what was requested from the parent
100 ownMaxHeight = Math.min(givenHeight, ownMaxHeight);
Selim Cinek6f145cf2015-05-18 15:16:08 -0700101 }
Steve Elliottb0940382020-02-20 14:24:02 -0500102
103 // height of the largest child
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200104 int maxChildHeight = 0;
Steve Elliottb0940382020-02-20 14:24:02 -0500105 int atMostOwnMaxHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200106 int childCount = getChildCount();
107 for (int i = 0; i < childCount; i++) {
108 View child = getChildAt(i);
Selim Cinek263398f2015-10-21 17:40:23 -0700109 if (child.getVisibility() == GONE) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100110 continue;
111 }
Steve Elliottb0940382020-02-20 14:24:02 -0500112 int childHeightSpec = atMostOwnMaxHeightSpec;
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200113 ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
114 if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
115 if (layoutParams.height >= 0) {
Steve Elliottb0940382020-02-20 14:24:02 -0500116 // If an actual height is set, cap it to the max height
117 childHeightSpec = MeasureSpec.makeMeasureSpec(
118 Math.min(layoutParams.height, ownMaxHeight),
119 MeasureSpec.EXACTLY);
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200120 }
felkachang79ca96d2018-04-27 16:55:40 +0800121 child.measure(getChildMeasureSpec(
122 widthMeasureSpec, viewHorizontalPadding, layoutParams.width),
Jorim Jaggib741f052014-06-03 19:57:26 +0200123 childHeightSpec);
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200124 int childHeight = child.getMeasuredHeight();
125 maxChildHeight = Math.max(maxChildHeight, childHeight);
126 } else {
127 mMatchParentViews.add(child);
128 }
129 }
Steve Elliottb0940382020-02-20 14:24:02 -0500130
131 // Set our own height to the given height, or the height of the largest child
Selim Cineka69f2a62015-12-11 17:28:12 -0800132 int ownHeight = heightMode == MeasureSpec.EXACTLY
Steve Elliottb0940382020-02-20 14:24:02 -0500133 ? givenHeight
134 : Math.min(ownMaxHeight, maxChildHeight);
135 int exactlyOwnHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
136
137 // Now that we know our own height, measure the children that are MATCH_PARENT
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200138 for (View child : mMatchParentViews) {
Jorim Jaggib741f052014-06-03 19:57:26 +0200139 child.measure(getChildMeasureSpec(
felkachang79ca96d2018-04-27 16:55:40 +0800140 widthMeasureSpec, viewHorizontalPadding, child.getLayoutParams().width),
Steve Elliottb0940382020-02-20 14:24:02 -0500141 exactlyOwnHeightSpec);
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200142 }
143 mMatchParentViews.clear();
Steve Elliottb0940382020-02-20 14:24:02 -0500144
145 // Finish up
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200146 int width = MeasureSpec.getSize(widthMeasureSpec);
147 setMeasuredDimension(width, ownHeight);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200148 }
149
150 @Override
Jorim Jaggibe565df2014-04-28 17:51:23 +0200151 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
Selim Cinek24d7cfa2014-05-20 13:50:57 +0200152 super.onLayout(changed, left, top, right, bottom);
Selim Cinekba67acf2015-04-10 17:00:42 -0700153 updateClipping();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200154 }
155
Jorim Jaggi00ebdfe2014-05-02 17:29:56 +0200156 @Override
Selim Cineka69f2a62015-12-11 17:28:12 -0800157 public boolean pointInView(float localX, float localY, float slop) {
158 float top = mClipTopAmount;
159 float bottom = mActualHeight;
160 return localX >= -slop && localY >= top - slop && localX < ((mRight - mLeft) + slop) &&
161 localY < (bottom + slop);
Jorim Jaggi00ebdfe2014-05-02 17:29:56 +0200162 }
163
Jorim Jaggibe565df2014-04-28 17:51:23 +0200164 /**
Selim Cinekd1695402020-02-24 20:29:36 -0800165 * @return if this view needs to be clipped to the shelf
166 */
167 public boolean needsClippingToShelf() {
168 return true;
169 }
170
171
172 public boolean isPinned() {
173 return false;
174 }
175
176 public boolean isHeadsUpAnimatingAway() {
177 return false;
178 }
179
180 /**
Jorim Jaggibe565df2014-04-28 17:51:23 +0200181 * Sets the actual height of this notification. This is different than the laid out
182 * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200183 *
184 * @param actualHeight The height of this notification.
185 * @param notifyListeners Whether the listener should be informed about the change.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200186 */
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200187 public void setActualHeight(int actualHeight, boolean notifyListeners) {
Jorim Jaggibe565df2014-04-28 17:51:23 +0200188 mActualHeight = actualHeight;
Selim Cineka272dfe2015-02-20 18:12:28 +0100189 updateClipping();
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200190 if (notifyListeners) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100191 notifyHeightChanged(false /* needsAnimation */);
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200192 }
193 }
194
Selim Cinekb0ee18f2017-12-21 16:15:53 -0800195 /**
196 * Set the distance to the top roundness, from where we should start clipping a value above
197 * or equal to 0 is the effective distance, and if a value below 0 is received, there should
198 * be no clipping.
199 */
200 public void setDistanceToTopRoundness(float distanceToTopRoundness) {
201 }
202
Selim Cinekeef84282015-10-30 16:28:00 -0700203 public void setActualHeight(int actualHeight) {
204 setActualHeight(actualHeight, true /* notifyListeners */);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200205 }
206
207 /**
208 * See {@link #setActualHeight}.
209 *
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200210 * @return The current actual height of this notification.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200211 */
212 public int getActualHeight() {
213 return mActualHeight;
214 }
215
Selim Cinek2627d722018-01-19 12:16:49 -0800216 public boolean isExpandAnimationRunning() {
217 return false;
218 }
219
Jorim Jaggibe565df2014-04-28 17:51:23 +0200220 /**
221 * @return The maximum height of this notification.
222 */
Selim Cinekb5605e52015-02-20 18:21:41 +0100223 public int getMaxContentHeight() {
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200224 return getHeight();
225 }
226
227 /**
Selim Cinekeb3fc3d2017-09-15 13:37:14 -0700228 * @return The minimum content height of this notification. This also respects the temporary
229 * states of the view.
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200230 */
231 public int getMinHeight() {
Selim Cinekeb3fc3d2017-09-15 13:37:14 -0700232 return getMinHeight(false /* ignoreTemporaryStates */);
233 }
234
235 /**
236 * Get the minimum height of this view.
237 *
238 * @param ignoreTemporaryStates should temporary states be ignored like the guts or heads-up.
239 *
240 * @return The minimum height that this view needs.
241 */
242 public int getMinHeight(boolean ignoreTemporaryStates) {
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200243 return getHeight();
244 }
Jorim Jaggibe565df2014-04-28 17:51:23 +0200245
246 /**
Selim Cinek567e8452016-03-24 10:54:56 -0700247 * @return The collapsed height of this view. Note that this might be different
248 * than {@link #getMinHeight()} because some elements like groups may have different sizes when
249 * they are system expanded.
Selim Cinek816c8e42015-11-19 12:00:45 -0800250 */
Selim Cinek567e8452016-03-24 10:54:56 -0700251 public int getCollapsedHeight() {
Selim Cinek816c8e42015-11-19 12:00:45 -0800252 return getHeight();
253 }
254
255 /**
Jorim Jaggid552d9d2014-05-07 19:41:13 +0200256 * Sets the notification as dimmed. The default implementation does nothing.
257 *
258 * @param dimmed Whether the notification should be dimmed.
259 * @param fade Whether an animation should be played to change the state.
260 */
261 public void setDimmed(boolean dimmed, boolean fade) {
262 }
263
Selim Cinekd9b7dd42017-11-10 17:53:47 -0800264 public boolean isRemoved() {
265 return false;
266 }
267
John Spurlockbf370992014-06-17 13:58:31 -0400268 /**
Jorim Jaggiae441282014-08-01 02:45:18 +0200269 * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
270 * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
271 * of a stack scroller update such that the updated intrinsic height (which is dependent on
272 * whether private or public layout is showing) gets taken into account into all layout
273 * calculations.
274 */
275 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
276 }
277
278 /**
279 * Sets whether the notification should hide its private contents if it is sensitive.
280 */
281 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
282 long duration) {
283 }
284
285 /**
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200286 * @return The desired notification height.
287 */
288 public int getIntrinsicHeight() {
Selim Cineka5eaa602014-05-12 21:27:47 +0200289 return getHeight();
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200290 }
291
292 /**
Jorim Jaggibe565df2014-04-28 17:51:23 +0200293 * Sets the amount this view should be clipped from the top. This is used when an expanded
294 * notification is scrolling in the top or bottom stack.
295 *
296 * @param clipTopAmount The amount of pixels this view should be clipped from top.
297 */
298 public void setClipTopAmount(int clipTopAmount) {
299 mClipTopAmount = clipTopAmount;
Mady Mellorc128f222016-04-26 11:42:46 -0700300 updateClipping();
Jorim Jaggibe565df2014-04-28 17:51:23 +0200301 }
302
Selim Cineka686b2c2016-10-26 13:58:27 -0700303 /**
304 * Set the amount the the notification is clipped on the bottom in addition to the regular
305 * clipping. This is mainly used to clip something in a non-animated way without changing the
306 * actual height of the notification and is purely visual.
307 *
308 * @param clipBottomAmount the amount to clip.
309 */
310 public void setClipBottomAmount(int clipBottomAmount) {
311 mClipBottomAmount = clipBottomAmount;
312 updateClipping();
313 }
314
Selim Cinekeb973562014-05-02 17:07:49 +0200315 public int getClipTopAmount() {
316 return mClipTopAmount;
317 }
318
Selim Cinekb3dadcc2016-11-21 17:21:13 -0800319 public int getClipBottomAmount() {
Selim Cineka686b2c2016-10-26 13:58:27 -0700320 return mClipBottomAmount;
321 }
322
Jorim Jaggibe565df2014-04-28 17:51:23 +0200323 public void setOnHeightChangedListener(OnHeightChangedListener listener) {
324 mOnHeightChangedListener = listener;
325 }
326
327 /**
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200328 * @return Whether we can expand this views content.
Jorim Jaggibe565df2014-04-28 17:51:23 +0200329 */
Jorim Jaggi4222d9a2014-04-23 16:13:15 +0200330 public boolean isContentExpandable() {
331 return false;
Jorim Jaggibe565df2014-04-28 17:51:23 +0200332 }
333
Selim Cinekb5605e52015-02-20 18:21:41 +0100334 public void notifyHeightChanged(boolean needsAnimation) {
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200335 if (mOnHeightChangedListener != null) {
Selim Cinekb5605e52015-02-20 18:21:41 +0100336 mOnHeightChangedListener.onHeightChanged(this, needsAnimation);
Jorim Jaggi9cbadd32014-05-01 20:18:31 +0200337 }
338 }
339
Selim Cinekc27437b2014-05-14 10:23:33 +0200340 public boolean isTransparent() {
341 return false;
342 }
343
Jorim Jaggibe565df2014-04-28 17:51:23 +0200344 /**
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200345 * Perform a remove animation on this view.
Jorim Jaggi60d07c52014-07-31 15:38:21 +0200346 * @param duration The duration of the remove animation.
Selim Cinek332c23f2018-03-16 17:37:50 -0700347 * @param delay The delay of the animation
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200348 * @param translationDirection The direction value from [-1 ... 1] indicating in which the
Gus Prevas211181532018-12-13 14:49:33 -0500349 * animation should be performed. A value of -1 means that The
350 * remove animation should be performed upwards,
351 * such that the child appears to be going away to the top. 1
352 * Should mean the opposite.
Selim Cinek332c23f2018-03-16 17:37:50 -0700353 * @param isHeadsUpAnimation Is this a headsUp animation.
354 * @param endLocation The location where the horizonal heads up disappear animation should end.
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200355 * @param onFinishedRunnable A runnable which should be run when the animation is finished.
Selim Cinek332c23f2018-03-16 17:37:50 -0700356 * @param animationListener An animation listener to add to the animation.
Gus Prevas211181532018-12-13 14:49:33 -0500357 *
358 * @return The additional delay, in milliseconds, that this view needs to add before the
359 * animation starts.
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200360 */
Gus Prevas211181532018-12-13 14:49:33 -0500361 public abstract long performRemoveAnimation(long duration,
Selim Cinek332c23f2018-03-16 17:37:50 -0700362 long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation,
363 Runnable onFinishedRunnable,
364 AnimatorListenerAdapter animationListener);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200365
Selim Cinek332c23f2018-03-16 17:37:50 -0700366 public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear);
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200367
Selim Cinek281c2022016-10-13 19:14:43 -0700368 /**
Selim Cinekdb167372016-11-17 15:41:17 -0800369 * Set the notification appearance to be below the speed bump.
Selim Cinek281c2022016-10-13 19:14:43 -0700370 * @param below true if it is below.
371 */
Selim Cinekdb167372016-11-17 15:41:17 -0800372 public void setBelowSpeedBump(boolean below) {
Selim Cinek3d2b94bf2014-07-02 22:12:47 +0200373 }
374
Selim Cinekd127d792016-11-01 19:11:41 -0700375 public int getPinnedHeadsUpHeight() {
376 return getIntrinsicHeight();
377 }
378
379
Mady Mellor34958fa2016-02-23 09:52:17 -0800380 /**
381 * Sets the translation of the view.
382 */
383 public void setTranslation(float translation) {
384 setTranslationX(translation);
385 }
386
387 /**
388 * Gets the translation of the view.
389 */
390 public float getTranslation() {
391 return getTranslationX();
392 }
393
Selim Cinek31094df2014-08-14 19:28:15 +0200394 public void onHeightReset() {
Selim Cineke34c6512014-08-14 11:19:41 +0200395 if (mOnHeightChangedListener != null) {
396 mOnHeightChangedListener.onReset(this);
397 }
Selim Cineka5e211b2014-08-11 17:35:48 +0200398 }
399
Selim Cinek8efa6dd2014-05-19 16:27:37 +0200400 /**
Selim Cineke32010a2014-08-20 23:50:41 +0200401 * This method returns the drawing rect for the view which is different from the regular
402 * drawing rect, since we layout all children in the {@link NotificationStackScrollLayout} at
403 * position 0 and usually the translation is neglected. Since we are manually clipping this
404 * view,we also need to subtract the clipTopAmount from the top. This is needed in order to
405 * ensure that accessibility and focusing work correctly.
406 *
407 * @param outRect The (scrolled) drawing bounds of the view.
408 */
409 @Override
410 public void getDrawingRect(Rect outRect) {
411 super.getDrawingRect(outRect);
412 outRect.left += getTranslationX();
413 outRect.right += getTranslationX();
414 outRect.bottom = (int) (outRect.top + getTranslationY() + getActualHeight());
415 outRect.top += getTranslationY() + getClipTopAmount();
416 }
417
Adrian Rooscd55f432015-06-10 16:42:53 -0700418 @Override
419 public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
420 super.getBoundsOnScreen(outRect, clipToParent);
Adrian Roos98dd7f12016-06-14 15:51:40 -0700421 if (getTop() + getTranslationY() < 0) {
422 // We got clipped to the parent here - make sure we undo that.
423 outRect.top += getTop() + getTranslationY();
424 }
Adrian Roosb44f5482015-06-11 12:19:16 -0700425 outRect.bottom = outRect.top + getActualHeight();
Mady Mellorc128f222016-04-26 11:42:46 -0700426 outRect.top += getClipTopAmount();
Adrian Rooscd55f432015-06-10 16:42:53 -0700427 }
428
Selim Cinek263398f2015-10-21 17:40:23 -0700429 public boolean isSummaryWithChildren() {
Selim Cinekb5605e52015-02-20 18:21:41 +0100430 return false;
431 }
432
433 public boolean areChildrenExpanded() {
434 return false;
435 }
436
Selim Cinek2627d722018-01-19 12:16:49 -0800437 protected void updateClipping() {
438 if (mClipToActualHeight && shouldClipToActualHeight()) {
Mady Mellorc128f222016-04-26 11:42:46 -0700439 int top = getClipTopAmount();
Selim Cinek53328492018-12-05 14:58:44 -0800440 int bottom = Math.max(Math.max(getActualHeight() + getExtraBottomPadding()
441 - mClipBottomAmount, top), mMinimumHeightForClipping);
442 int halfExtraWidth = (int) (mExtraWidthForClipping / 2.0f);
443 mClipRect.set(-halfExtraWidth, top, getWidth() + halfExtraWidth, bottom);
Selim Cinek4ffd6362015-12-29 15:12:23 +0100444 setClipBounds(mClipRect);
445 } else {
446 setClipBounds(null);
Selim Cinekf92a1fd2015-07-31 16:10:32 -0700447 }
Selim Cinek4ffd6362015-12-29 15:12:23 +0100448 }
449
Selim Cinek53328492018-12-05 14:58:44 -0800450 public void setMinimumHeightForClipping(int minimumHeightForClipping) {
451 mMinimumHeightForClipping = minimumHeightForClipping;
452 updateClipping();
453 }
454
455 public void setExtraWidthForClipping(float extraWidthForClipping) {
456 mExtraWidthForClipping = extraWidthForClipping;
457 updateClipping();
458 }
459
Selim Cinek99e9adf2018-03-15 09:17:47 -0700460 public float getHeaderVisibleAmount() {
461 return 1.0f;
462 }
463
Selim Cinek2627d722018-01-19 12:16:49 -0800464 protected boolean shouldClipToActualHeight() {
465 return true;
466 }
467
Selim Cinek4ffd6362015-12-29 15:12:23 +0100468 public void setClipToActualHeight(boolean clipToActualHeight) {
469 mClipToActualHeight = clipToActualHeight;
470 updateClipping();
Selim Cineka272dfe2015-02-20 18:12:28 +0100471 }
472
Selim Cinek2cd45df2015-06-09 18:00:07 -0700473 public boolean willBeGone() {
474 return mWillBeGone;
475 }
476
477 public void setWillBeGone(boolean willBeGone) {
478 mWillBeGone = willBeGone;
479 }
480
Selim Cinek9c17b772015-07-07 20:37:09 -0700481 public int getMinClipTopAmount() {
482 return mMinClipTopAmount;
483 }
484
485 public void setMinClipTopAmount(int minClipTopAmount) {
486 mMinClipTopAmount = minClipTopAmount;
487 }
488
Selim Cinek471e31a2015-12-11 13:39:48 -0800489 @Override
490 public void setLayerType(int layerType, Paint paint) {
491 if (hasOverlappingRendering()) {
492 super.setLayerType(layerType, paint);
493 }
494 }
495
496 @Override
497 public boolean hasOverlappingRendering() {
Selim Cineka69f2a62015-12-11 17:28:12 -0800498 // Otherwise it will be clipped
499 return super.hasOverlappingRendering() && getActualHeight() <= getHeight();
Selim Cinek471e31a2015-12-11 13:39:48 -0800500 }
501
Selim Cinek42357e02016-02-24 18:48:01 -0800502 /**
Selim Cineka7ed2c12017-01-23 20:47:24 -0800503 * @return an amount between -1 and 1 of increased padding that this child needs. 1 means it
504 * needs a full increased padding while -1 means it needs no padding at all. For 0.0f the normal
505 * padding is applied.
Selim Cinek42357e02016-02-24 18:48:01 -0800506 */
507 public float getIncreasedPaddingAmount() {
508 return 0.0f;
Selim Cinek61633a82016-01-25 15:54:10 -0800509 }
510
Selim Cinek3776fe02016-02-04 13:32:43 -0800511 public boolean mustStayOnScreen() {
512 return false;
513 }
514
Selim Cinek33223572016-02-19 19:32:22 -0800515 public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
516 int outlineTranslation) {
517 }
518
519 public float getOutlineAlpha() {
520 return 0.0f;
521 }
522
523 public int getOutlineTranslation() {
524 return 0;
525 }
526
Adrian Roos14503e22016-03-09 14:01:24 -0800527 public void setChangingPosition(boolean changingPosition) {
528 mChangingPosition = changingPosition;
529 }
530
531 public boolean isChangingPosition() {
532 return mChangingPosition;
533 }
534
Selim Cinekd1395642016-04-28 12:22:42 -0700535 public void setTransientContainer(ViewGroup transientContainer) {
536 mTransientContainer = transientContainer;
537 }
538
539 public ViewGroup getTransientContainer() {
540 return mTransientContainer;
541 }
542
Selim Cineke32010a2014-08-20 23:50:41 +0200543 /**
Mady Mellorb0a82462016-04-30 17:31:02 -0700544 * @return padding used to alter how much of the view is clipped.
545 */
546 public int getExtraBottomPadding() {
547 return 0;
548 }
549
550 /**
551 * @return true if the group's expansion state is changing, false otherwise.
552 */
553 public boolean isGroupExpansionChanging() {
554 return false;
555 }
556
557 public boolean isGroupExpanded() {
558 return false;
559 }
560
Selim Cinek9b9d6e12017-11-30 12:29:47 +0100561 public void setHeadsUpIsVisible() {
562 }
563
Selim Cinekc3fec682019-06-06 18:11:07 -0700564 public boolean showingPulsing() {
Selim Cinek5040f2e2019-02-14 18:22:42 -0800565 return false;
566 }
567
Mady Mellorb0a82462016-04-30 17:31:02 -0700568 public boolean isChildInGroup() {
569 return false;
570 }
571
Adrian Roos599be342016-06-13 14:54:39 -0700572 public void setActualHeightAnimating(boolean animating) {}
573
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500574 protected ExpandableViewState createExpandableViewState() {
Selim Cinekbbcebde2016-11-09 18:28:20 -0800575 return new ExpandableViewState();
576 }
577
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500578 /** Sets {@link ExpandableViewState} to default state. */
579 public ExpandableViewState resetViewState() {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500580 // initialize with the default values of the view
581 mViewState.height = getIntrinsicHeight();
582 mViewState.gone = getVisibility() == View.GONE;
583 mViewState.alpha = 1f;
584 mViewState.notGoneIndex = -1;
585 mViewState.xTranslation = getTranslationX();
586 mViewState.hidden = false;
587 mViewState.scaleX = getScaleX();
588 mViewState.scaleY = getScaleY();
589 mViewState.inShelf = false;
590 mViewState.headsUpIsVisible = false;
591
592 // handling reset for child notifications
593 if (this instanceof ExpandableNotificationRow) {
594 ExpandableNotificationRow row = (ExpandableNotificationRow) this;
Kevin Han43077f92020-02-28 12:51:53 -0800595 List<ExpandableNotificationRow> children = row.getAttachedChildren();
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500596 if (row.isSummaryWithChildren() && children != null) {
597 for (ExpandableNotificationRow childRow : children) {
598 childRow.resetViewState();
599 }
600 }
601 }
602
603 return mViewState;
604 }
605
606 @Nullable public ExpandableViewState getViewState() {
607 return mViewState;
608 }
609
610 /** Applies internal {@link ExpandableViewState} to this view. */
611 public void applyViewState() {
Dave Mankoffc56db332018-12-10 11:31:49 -0500612 if (!mViewState.gone) {
Dave Mankoffa4d195d2018-11-16 13:33:27 -0500613 mViewState.applyToView(this);
614 }
615 }
616
Mady Mellorb0a82462016-04-30 17:31:02 -0700617 /**
Selim Cinek281c2022016-10-13 19:14:43 -0700618 * @return whether the current view doesn't add height to the overall content. This means that
Selim Cinek6df13852020-02-25 12:39:00 -0800619 * if it is added to a list of items, its content will still have the same height.
Selim Cinek281c2022016-10-13 19:14:43 -0700620 * An example is the notification shelf, that is always placed on top of another view.
621 */
622 public boolean hasNoContentHeight() {
623 return false;
624 }
625
626 /**
Selim Cinekeccb5de2016-10-28 15:04:05 -0700627 * @param inShelf whether the view is currently fully in the notification shelf.
628 */
629 public void setInShelf(boolean inShelf) {
630 mInShelf = inShelf;
631 }
632
633 public boolean isInShelf() {
634 return mInShelf;
635 }
636
Selim Cinek6df13852020-02-25 12:39:00 -0800637 public @Nullable StatusBarIconView getShelfIcon() {
638 return null;
639 }
640
641 /**
Selim Cinek79d98632020-03-24 19:16:02 -0700642 * @return get the transformation target of the shelf, which usually is the icon
643 */
644 public View getShelfTransformationTarget() {
645 return null;
646 }
647
648 /**
649 * Get the relative top padding of a view relative to this view. This recursively walks up the
650 * hierarchy and does the corresponding measuring.
651 *
652 * @param view the view to get the padding for. The requested view has to be a child of this
653 * notification.
654 * @return the toppadding
655 */
656 public int getRelativeTopPadding(View view) {
657 int topPadding = 0;
658 while (view.getParent() instanceof ViewGroup) {
659 topPadding += view.getTop();
660 view = (View) view.getParent();
661 if (view == this) {
662 return topPadding;
663 }
664 }
665 return topPadding;
666 }
667
668
669 /**
670 * Get the relative start padding of a view relative to this view. This recursively walks up the
671 * hierarchy and does the corresponding measuring.
672 *
673 * @param view the view to get the padding for. The requested view has to be a child of this
674 * notification.
675 * @return the start padding
676 */
677 public int getRelativeStartPadding(View view) {
678 boolean isRtl = isLayoutRtl();
679 int startPadding = 0;
680 while (view.getParent() instanceof ViewGroup) {
681 View parent = (View) view.getParent();
682 startPadding += isRtl ? parent.getWidth() - view.getRight() : view.getLeft();
683 view = parent;
684 if (view == this) {
685 return startPadding;
686 }
687 }
688 return startPadding;
689 }
690
691 /**
Selim Cinek6df13852020-02-25 12:39:00 -0800692 * Set how much this notification is transformed into the shelf.
693 *
694 * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed
695 * to the content away
696 * @param isLastChild is this the last child in the list. If true, then the transformation is
697 * different since its content fades out.
698 */
699 public void setContentTransformationAmount(float contentTransformationAmount,
700 boolean isLastChild) {
701 boolean changeTransformation = isLastChild != mIsLastChild;
702 changeTransformation |= mContentTransformationAmount != contentTransformationAmount;
703 mIsLastChild = isLastChild;
704 mContentTransformationAmount = contentTransformationAmount;
705 if (changeTransformation) {
706 updateContentTransformation();
707 }
708 }
709
710 /**
711 * Update the content representation based on the amount we are transformed into the shelf.
712 */
713 protected void updateContentTransformation() {
714 float translationY = -mContentTransformationAmount * getContentTransformationShift();
715 float contentAlpha = 1.0f - mContentTransformationAmount;
716 contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f);
717 contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha);
718 if (mIsLastChild) {
719 translationY *= 0.4f;
720 }
Selim Cinek9ed6e042020-03-26 15:45:51 -0700721 mContentTranslation = translationY;
Selim Cinek6df13852020-02-25 12:39:00 -0800722 applyContentTransformation(contentAlpha, translationY);
723 }
724
725 /**
726 * @return how much the content shifts up when going into the shelf
727 */
728 protected float getContentTransformationShift() {
729 return mContentShift;
730 }
731
732 /**
733 * Apply the contentTransformation when going into the shelf.
734 *
735 * @param contentAlpha The alpha that should be applied
736 * @param translationY the translationY that should be applied
737 */
738 protected void applyContentTransformation(float contentAlpha, float translationY) {
739 }
740
Selim Cinekeccb5de2016-10-28 15:04:05 -0700741 /**
742 * @param transformingInShelf whether the view is currently transforming into the shelf in an
743 * animated way
744 */
745 public void setTransformingInShelf(boolean transformingInShelf) {
746 mTransformingInShelf = transformingInShelf;
747 }
748
749 public boolean isTransformingIntoShelf() {
750 return mTransformingInShelf;
751 }
752
Selim Cinekd127d792016-11-01 19:11:41 -0700753 public boolean isAboveShelf() {
754 return false;
755 }
756
Selim Cinekc25989e2018-02-16 16:42:14 -0800757 public boolean hasExpandingChild() {
758 return false;
759 }
760
Selim Cinek30887662018-10-15 17:37:21 -0700761 @Override
762 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
763 }
764
Selim Cinekeccb5de2016-10-28 15:04:05 -0700765 /**
Selim Cinek9ed6e042020-03-26 15:45:51 -0700766 * return the amount that the content is translated
767 */
768 public float getContentTranslation() {
769 return mContentTranslation;
770 }
771
Selim Cinekf0f74952020-04-21 11:45:16 -0700772 public boolean wantsAddAndRemoveAnimations() {
773 return true;
774 }
775
Selim Cinek4b9710e2020-05-26 15:54:24 -0700776 /** Sets whether this view is the first notification in a section. */
777 public void setFirstInSection(boolean firstInSection) {
778 mFirstInSection = firstInSection;
779 }
780
781 /** Sets whether this view is the last notification in a section. */
782 public void setLastInSection(boolean lastInSection) {
783 mLastInSection = lastInSection;
784 }
785
786 public boolean isLastInSection() {
787 return mLastInSection;
788 }
789
790 public boolean isFirstInSection() {
791 return mFirstInSection;
792 }
793
794 /**
795 * Set the topRoundness of this view.
796 * @return Whether the roundness was changed.
797 */
798 public boolean setTopRoundness(float topRoundness, boolean animate) {
799 return false;
800 }
801
802 /**
803 * Set the bottom roundness of this view.
804 * @return Whether the roundness was changed.
805 */
806 public boolean setBottomRoundness(float bottomRoundness, boolean animate) {
807 return false;
808 }
809
810 public int getHeadsUpHeightWithoutHeader() {
811 return getHeight();
812 }
813
Selim Cinek9ed6e042020-03-26 15:45:51 -0700814 /**
Jorim Jaggibe565df2014-04-28 17:51:23 +0200815 * A listener notifying when {@link #getActualHeight} changes.
816 */
817 public interface OnHeightChangedListener {
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200818
819 /**
820 * @param view the view for which the height changed, or {@code null} if just the top
821 * padding or the padding between the elements changed
Selim Cinekb5605e52015-02-20 18:21:41 +0100822 * @param needsAnimation whether the view height needs to be animated
Jorim Jaggi30c305c2014-07-01 23:34:41 +0200823 */
Selim Cinekb5605e52015-02-20 18:21:41 +0100824 void onHeightChanged(ExpandableView view, boolean needsAnimation);
Selim Cineka5e211b2014-08-11 17:35:48 +0200825
826 /**
827 * Called when the view is reset and therefore the height will change abruptly
828 *
829 * @param view The view which was reset.
830 */
831 void onReset(ExpandableView view);
Jorim Jaggibe565df2014-04-28 17:51:23 +0200832 }
833}