• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
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 
17 package com.android.systemui.statusbar.notification.row;
18 
19 import android.animation.AnimatorListenerAdapter;
20 import android.content.Context;
21 import android.content.res.Configuration;
22 import android.graphics.Paint;
23 import android.graphics.Rect;
24 import android.util.AttributeSet;
25 import android.util.IndentingPrintWriter;
26 import android.util.Log;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.view.ViewParent;
30 import android.widget.FrameLayout;
31 
32 import androidx.annotation.NonNull;
33 import androidx.annotation.Nullable;
34 
35 import com.android.systemui.Dumpable;
36 import com.android.systemui.R;
37 import com.android.systemui.animation.Interpolators;
38 import com.android.systemui.statusbar.StatusBarIconView;
39 import com.android.systemui.statusbar.notification.Roundable;
40 import com.android.systemui.statusbar.notification.RoundableState;
41 import com.android.systemui.statusbar.notification.stack.ExpandableViewState;
42 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
43 import com.android.systemui.util.DumpUtilsKt;
44 
45 import java.io.PrintWriter;
46 import java.util.ArrayList;
47 import java.util.List;
48 
49 /**
50  * An abstract view for expandable views.
51  */
52 public abstract class ExpandableView extends FrameLayout implements Dumpable, Roundable {
53     private static final String TAG = "ExpandableView";
54     /** whether the dump() for this class should include verbose details */
55     protected static final boolean DUMP_VERBOSE = false;
56 
57     private RoundableState mRoundableState = null;
58     protected OnHeightChangedListener mOnHeightChangedListener;
59     private int mActualHeight;
60     protected int mClipTopAmount;
61     protected int mClipBottomAmount;
62     protected int mMinimumHeightForClipping = 0;
63     protected float mExtraWidthForClipping = 0;
64     private ArrayList<View> mMatchParentViews = new ArrayList<View>();
65     private static Rect mClipRect = new Rect();
66     private boolean mWillBeGone;
67     private boolean mClipToActualHeight = true;
68     private boolean mChangingPosition = false;
69     private ViewGroup mTransientContainer;
70     private boolean mInShelf;
71     private boolean mTransformingInShelf;
72     protected float mContentTransformationAmount;
73     protected boolean mIsLastChild;
74     protected int mContentShift;
75     @NonNull private final ExpandableViewState mViewState;
76     private float mContentTranslation;
77     protected boolean mLastInSection;
78     protected boolean mFirstInSection;
79 
ExpandableView(Context context, AttributeSet attrs)80     public ExpandableView(Context context, AttributeSet attrs) {
81         super(context, attrs);
82         mViewState = createExpandableViewState();
83         initDimens();
84     }
85 
86     @Override
getRoundableState()87     public RoundableState getRoundableState() {
88         if (mRoundableState == null) {
89             mRoundableState = new RoundableState(this, this, 0f);
90         }
91         return mRoundableState;
92     }
93 
initDimens()94     private void initDimens() {
95         mContentShift = getResources().getDimensionPixelSize(
96                 R.dimen.shelf_transform_content_shift);
97     }
98 
99     @Override
onConfigurationChanged(Configuration newConfig)100     protected void onConfigurationChanged(Configuration newConfig) {
101         super.onConfigurationChanged(newConfig);
102         initDimens();
103     }
104 
105     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)106     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
107         final int givenHeight = MeasureSpec.getSize(heightMeasureSpec);
108         final int viewHorizontalPadding = getPaddingStart() + getPaddingEnd();
109 
110         // Max height is as large as possible, unless otherwise requested
111         int ownMaxHeight = Integer.MAX_VALUE;
112         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
113         if (heightMode != MeasureSpec.UNSPECIFIED && givenHeight != 0) {
114             // Set our max height to what was requested from the parent
115             ownMaxHeight = Math.min(givenHeight, ownMaxHeight);
116         }
117 
118         // height of the largest child
119         int maxChildHeight = 0;
120         int atMostOwnMaxHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
121         int childCount = getChildCount();
122         for (int i = 0; i < childCount; i++) {
123             View child = getChildAt(i);
124             if (child.getVisibility() == GONE) {
125                 continue;
126             }
127             int childHeightSpec = atMostOwnMaxHeightSpec;
128             ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
129             if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
130                 if (layoutParams.height >= 0) {
131                     // If an actual height is set, cap it to the max height
132                     childHeightSpec = MeasureSpec.makeMeasureSpec(
133                             Math.min(layoutParams.height, ownMaxHeight),
134                             MeasureSpec.EXACTLY);
135                 }
136                 child.measure(getChildMeasureSpec(
137                         widthMeasureSpec, viewHorizontalPadding, layoutParams.width),
138                         childHeightSpec);
139                 int childHeight = child.getMeasuredHeight();
140                 maxChildHeight = Math.max(maxChildHeight, childHeight);
141             } else {
142                 mMatchParentViews.add(child);
143             }
144         }
145 
146         // Set our own height to the given height, or the height of the largest child
147         int ownHeight = heightMode == MeasureSpec.EXACTLY
148                 ? givenHeight
149                 : Math.min(ownMaxHeight, maxChildHeight);
150         int exactlyOwnHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
151 
152         // Now that we know our own height, measure the children that are MATCH_PARENT
153         for (View child : mMatchParentViews) {
154             child.measure(getChildMeasureSpec(
155                     widthMeasureSpec, viewHorizontalPadding, child.getLayoutParams().width),
156                     exactlyOwnHeightSpec);
157         }
158         mMatchParentViews.clear();
159 
160         // Finish up
161         int width = MeasureSpec.getSize(widthMeasureSpec);
162         setMeasuredDimension(width, ownHeight);
163     }
164 
165     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)166     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
167         super.onLayout(changed, left, top, right, bottom);
168         updateClipping();
169     }
170 
171     @Override
pointInView(float localX, float localY, float slop)172     public boolean pointInView(float localX, float localY, float slop) {
173         float top = Math.max(0, mClipTopAmount);
174         float bottom = mActualHeight;
175         return localX >= -slop && localY >= top - slop && localX < ((mRight - mLeft) + slop) &&
176                 localY < (bottom + slop);
177     }
178 
179     /**
180      * @return if this view needs to be clipped to the shelf
181      */
needsClippingToShelf()182     public boolean needsClippingToShelf() {
183         return true;
184     }
185 
186 
isPinned()187     public boolean isPinned() {
188         return false;
189     }
190 
isHeadsUpAnimatingAway()191     public boolean isHeadsUpAnimatingAway() {
192         return false;
193     }
194 
195     /**
196      * Sets the actual height of this notification. This is different than the laid out
197      * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
198      *
199      * @param actualHeight The height of this notification.
200      * @param notifyListeners Whether the listener should be informed about the change.
201      */
setActualHeight(int actualHeight, boolean notifyListeners)202     public void setActualHeight(int actualHeight, boolean notifyListeners) {
203         if (mActualHeight != actualHeight) {
204             mActualHeight = actualHeight;
205             updateClipping();
206             if (notifyListeners) {
207                 notifyHeightChanged(false  /* needsAnimation */);
208             }
209         }
210     }
211 
setActualHeight(int actualHeight)212     public void setActualHeight(int actualHeight) {
213         setActualHeight(actualHeight, true /* notifyListeners */);
214     }
215 
216     /**
217      * See {@link #setActualHeight}.
218      *
219      * @return The current actual height of this notification.
220      */
getActualHeight()221     public int getActualHeight() {
222         return mActualHeight;
223     }
224 
isExpandAnimationRunning()225     public boolean isExpandAnimationRunning() {
226         return false;
227     }
228 
229     /**
230      * @return The maximum height of this notification.
231      */
getMaxContentHeight()232     public int getMaxContentHeight() {
233         return getHeight();
234     }
235 
236     /**
237      * @return The minimum content height of this notification. This also respects the temporary
238      * states of the view.
239      */
getMinHeight()240     public int getMinHeight() {
241         return getMinHeight(false /* ignoreTemporaryStates */);
242     }
243 
244     /**
245      * Get the minimum height of this view.
246      *
247      * @param ignoreTemporaryStates should temporary states be ignored like the guts or heads-up.
248      *
249      * @return The minimum height that this view needs.
250      */
getMinHeight(boolean ignoreTemporaryStates)251     public int getMinHeight(boolean ignoreTemporaryStates) {
252         return getHeight();
253     }
254 
255     /**
256      * @return The collapsed height of this view. Note that this might be different
257      * than {@link #getMinHeight()} because some elements like groups may have different sizes when
258      * they are system expanded.
259      */
getCollapsedHeight()260     public int getCollapsedHeight() {
261         return getHeight();
262     }
263 
264     /**
265      * Sets the notification as dimmed. The default implementation does nothing.
266      *
267      * @param dimmed Whether the notification should be dimmed.
268      * @param fade Whether an animation should be played to change the state.
269      */
setDimmed(boolean dimmed, boolean fade)270     public void setDimmed(boolean dimmed, boolean fade) {
271     }
272 
isRemoved()273     public boolean isRemoved() {
274         return false;
275     }
276 
277     /**
278      * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
279      * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
280      * of a stack scroller update such that the updated intrinsic height (which is dependent on
281      * whether private or public layout is showing) gets taken into account into all layout
282      * calculations.
283      */
setHideSensitiveForIntrinsicHeight(boolean hideSensitive)284     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
285     }
286 
287     /**
288      * Sets whether the notification should hide its private contents if it is sensitive.
289      */
setHideSensitive(boolean hideSensitive, boolean animated, long delay, long duration)290     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
291             long duration) {
292     }
293 
294     /**
295      * @return The desired notification height.
296      */
getIntrinsicHeight()297     public int getIntrinsicHeight() {
298         return getHeight();
299     }
300 
301     /**
302      * Sets the amount this view should be clipped from the top. This is used when an expanded
303      * notification is scrolling in the top or bottom stack.
304      *
305      * @param clipTopAmount The amount of pixels this view should be clipped from top.
306      */
setClipTopAmount(int clipTopAmount)307     public void setClipTopAmount(int clipTopAmount) {
308         mClipTopAmount = clipTopAmount;
309         updateClipping();
310     }
311 
312     /**
313      * Set the amount the the notification is clipped on the bottom in addition to the regular
314      * clipping. This is mainly used to clip something in a non-animated way without changing the
315      * actual height of the notification and is purely visual.
316      *
317      * @param clipBottomAmount the amount to clip.
318      */
setClipBottomAmount(int clipBottomAmount)319     public void setClipBottomAmount(int clipBottomAmount) {
320         mClipBottomAmount = clipBottomAmount;
321         updateClipping();
322     }
323 
getClipTopAmount()324     public int getClipTopAmount() {
325         return mClipTopAmount;
326     }
327 
getClipBottomAmount()328     public int getClipBottomAmount() {
329         return mClipBottomAmount;
330     }
331 
setOnHeightChangedListener(OnHeightChangedListener listener)332     public void setOnHeightChangedListener(OnHeightChangedListener listener) {
333         mOnHeightChangedListener = listener;
334     }
335 
336     /**
337      * @return Whether we can expand this views content.
338      */
isContentExpandable()339     public boolean isContentExpandable() {
340         return false;
341     }
342 
notifyHeightChanged(boolean needsAnimation)343     public void notifyHeightChanged(boolean needsAnimation) {
344         if (mOnHeightChangedListener != null) {
345             mOnHeightChangedListener.onHeightChanged(this, needsAnimation);
346         }
347     }
348 
isTransparent()349     public boolean isTransparent() {
350         return false;
351     }
352 
353     /**
354      * Perform a remove animation on this view.
355      * @param duration The duration of the remove animation.
356      * @param delay The delay of the animation
357      * @param translationDirection The direction value from [-1 ... 1] indicating in which the
358      *                             animation should be performed. A value of -1 means that The
359      *                             remove animation should be performed upwards,
360      *                             such that the  child appears to be going away to the top. 1
361      *                             Should mean the opposite.
362      * @param isHeadsUpAnimation Is this a headsUp animation.
363      * @param endLocation The location where the horizonal heads up disappear animation should end.
364      * @param onFinishedRunnable A runnable which should be run when the animation is finished.
365      * @param animationListener An animation listener to add to the animation.
366      *
367      * @return The additional delay, in milliseconds, that this view needs to add before the
368      * animation starts.
369      */
performRemoveAnimation(long duration, long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation, Runnable onFinishedRunnable, AnimatorListenerAdapter animationListener)370     public abstract long performRemoveAnimation(long duration,
371             long delay, float translationDirection, boolean isHeadsUpAnimation, float endLocation,
372             Runnable onFinishedRunnable,
373             AnimatorListenerAdapter animationListener);
374 
performAddAnimation(long delay, long duration, boolean isHeadsUpAppear)375     public void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear) {
376         performAddAnimation(delay, duration, isHeadsUpAppear, null);
377     }
378 
performAddAnimation(long delay, long duration, boolean isHeadsUpAppear, Runnable onEndRunnable)379     public abstract void performAddAnimation(long delay, long duration, boolean isHeadsUpAppear,
380             Runnable onEndRunnable);
381 
382     /**
383      * Set the notification appearance to be below the speed bump.
384      * @param below true if it is below.
385      */
setBelowSpeedBump(boolean below)386     public void setBelowSpeedBump(boolean below) {
387     }
388 
getPinnedHeadsUpHeight()389     public int getPinnedHeadsUpHeight() {
390         return getIntrinsicHeight();
391     }
392 
393 
394     /**
395      * Sets the translation of the view.
396      */
setTranslation(float translation)397     public void setTranslation(float translation) {
398         setTranslationX(translation);
399     }
400 
401     /**
402      * Gets the translation of the view.
403      */
getTranslation()404     public float getTranslation() {
405         return getTranslationX();
406     }
407 
onHeightReset()408     public void onHeightReset() {
409         if (mOnHeightChangedListener != null) {
410             mOnHeightChangedListener.onReset(this);
411         }
412     }
413 
414     /**
415      * This method returns the drawing rect for the view which is different from the regular
416      * drawing rect, since we layout all children in the {@link NotificationStackScrollLayout} at
417      * position 0 and usually the translation is neglected. Since we are manually clipping this
418      * view,we also need to subtract the clipTopAmount from the top. This is needed in order to
419      * ensure that accessibility and focusing work correctly.
420      *
421      * @param outRect The (scrolled) drawing bounds of the view.
422      */
423     @Override
getDrawingRect(Rect outRect)424     public void getDrawingRect(Rect outRect) {
425         super.getDrawingRect(outRect);
426         outRect.left += getTranslationX();
427         outRect.right += getTranslationX();
428         outRect.bottom = (int) (outRect.top + getTranslationY() + getActualHeight());
429         outRect.top += getTranslationY() + getClipTopAmount();
430     }
431 
432     @Override
getBoundsOnScreen(Rect outRect, boolean clipToParent)433     public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
434         super.getBoundsOnScreen(outRect, clipToParent);
435         if (getTop() + getTranslationY() < 0) {
436             // We got clipped to the parent here - make sure we undo that.
437             outRect.top += getTop() + getTranslationY();
438         }
439         outRect.bottom = outRect.top + getActualHeight();
440         outRect.top += Math.max(0, getClipTopAmount());
441     }
442 
isSummaryWithChildren()443     public boolean isSummaryWithChildren() {
444         return false;
445     }
446 
areChildrenExpanded()447     public boolean areChildrenExpanded() {
448         return false;
449     }
450 
updateClipping()451     protected void updateClipping() {
452         if (mClipToActualHeight && shouldClipToActualHeight()) {
453             int top = getClipTopAmount();
454             int bottom = Math.max(Math.max(getActualHeight()
455                     - mClipBottomAmount, top), mMinimumHeightForClipping);
456             mClipRect.set(Integer.MIN_VALUE, top, Integer.MAX_VALUE, bottom);
457             setClipBounds(mClipRect);
458         } else {
459             setClipBounds(null);
460         }
461     }
462 
setMinimumHeightForClipping(int minimumHeightForClipping)463     public void setMinimumHeightForClipping(int minimumHeightForClipping) {
464         mMinimumHeightForClipping = minimumHeightForClipping;
465         updateClipping();
466     }
467 
setExtraWidthForClipping(float extraWidthForClipping)468     public void setExtraWidthForClipping(float extraWidthForClipping) {
469         mExtraWidthForClipping = extraWidthForClipping;
470     }
471 
getHeaderVisibleAmount()472     public float getHeaderVisibleAmount() {
473         return 1.0f;
474     }
475 
shouldClipToActualHeight()476     protected boolean shouldClipToActualHeight() {
477         return true;
478     }
479 
setClipToActualHeight(boolean clipToActualHeight)480     public void setClipToActualHeight(boolean clipToActualHeight) {
481         mClipToActualHeight = clipToActualHeight;
482         updateClipping();
483     }
484 
willBeGone()485     public boolean willBeGone() {
486         return mWillBeGone;
487     }
488 
setWillBeGone(boolean willBeGone)489     public void setWillBeGone(boolean willBeGone) {
490         mWillBeGone = willBeGone;
491     }
492 
493     @Override
setLayerType(int layerType, Paint paint)494     public void setLayerType(int layerType, Paint paint) {
495         // Allow resetting the layerType to NONE regardless of overlappingRendering
496         if (layerType == LAYER_TYPE_NONE || hasOverlappingRendering()) {
497             super.setLayerType(layerType, paint);
498         }
499     }
500 
501     @Override
hasOverlappingRendering()502     public boolean hasOverlappingRendering() {
503         // Otherwise it will be clipped
504         return super.hasOverlappingRendering() && getActualHeight() <= getHeight();
505     }
506 
mustStayOnScreen()507     public boolean mustStayOnScreen() {
508         return false;
509     }
510 
setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, int outlineTranslation)511     public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
512             int outlineTranslation) {
513     }
514 
getOutlineAlpha()515     public float getOutlineAlpha() {
516         return 0.0f;
517     }
518 
getOutlineTranslation()519     public int getOutlineTranslation() {
520         return 0;
521     }
522 
setChangingPosition(boolean changingPosition)523     public void setChangingPosition(boolean changingPosition) {
524         mChangingPosition = changingPosition;
525     }
526 
isChangingPosition()527     public boolean isChangingPosition() {
528         return mChangingPosition;
529     }
530 
531     /**
532      * Called when removing a view from its transient container, such as at the end of an animation.
533      * Generally, when operating on ExpandableView instances, this should be used rather than
534      * {@link ExpandableView#removeTransientView(View)} to ensure that the
535      * {@link #getTransientContainer() transient container} is correctly reset.
536      */
removeFromTransientContainer()537     public void removeFromTransientContainer() {
538         final ViewGroup transientContainer = getTransientContainer();
539         if (transientContainer == null) {
540             return;
541         }
542         final ViewParent parent = getParent();
543         if (parent != transientContainer) {
544             Log.w(TAG, "Expandable view " + this
545                     + " has transient container " + transientContainer
546                     + " but different parent " + parent);
547             setTransientContainer(null);
548             return;
549         }
550         transientContainer.removeTransientView(this);
551         setTransientContainer(null);
552     }
553 
554     /**
555      * Called before adding this view to a group, which would always throw an exception if this view
556      * has a different parent, so clean up the transient container and throw an exception if the
557      * parent isn't a transient container.  Provide as much detail as possible in the crash.
558      */
removeFromTransientContainerForAdditionTo(ViewGroup newParent)559     public void removeFromTransientContainerForAdditionTo(ViewGroup newParent) {
560         final ViewParent parent = getParent();
561         final ViewGroup transientContainer = getTransientContainer();
562         if (parent == null || parent == newParent) {
563             // If this view's current parent is null or the same as the new parent, the add will
564             // succeed as long as it's a true child, so just make sure the view isn't transient.
565             removeFromTransientContainer();
566             return;
567         }
568         if (transientContainer == null) {
569             throw new IllegalStateException("Can't add view " + this + " to container " + newParent
570                     + "; current parent " + parent + " is not a transient container");
571         }
572         if (transientContainer != parent) {
573             // Crash with details before addView() crashes without any; the view is being added
574             // to a different parent, and the transient container isn't the parent, so we can't
575             // even (safely) clean that up.
576             throw new IllegalStateException("Expandable view " + this
577                     + " has transient container " + transientContainer
578                     + " but different parent " + parent);
579         }
580         Log.w(TAG, "Removing view " + this + " from transient container "
581                 + transientContainer + " in preparation for moving to parent " + newParent);
582         transientContainer.removeTransientView(this);
583         setTransientContainer(null);
584     }
585 
setTransientContainer(ViewGroup transientContainer)586     public void setTransientContainer(ViewGroup transientContainer) {
587         mTransientContainer = transientContainer;
588     }
589 
getTransientContainer()590     public ViewGroup getTransientContainer() {
591         return mTransientContainer;
592     }
593 
594     /**
595      * @return true if the group's expansion state is changing, false otherwise.
596      */
isGroupExpansionChanging()597     public boolean isGroupExpansionChanging() {
598         return false;
599     }
600 
isGroupExpanded()601     public boolean isGroupExpanded() {
602         return false;
603     }
604 
setHeadsUpIsVisible()605     public void setHeadsUpIsVisible() {
606     }
607 
showingPulsing()608     public boolean showingPulsing() {
609         return false;
610     }
611 
isChildInGroup()612     public boolean isChildInGroup() {
613         return false;
614     }
615 
setActualHeightAnimating(boolean animating)616     public void setActualHeightAnimating(boolean animating) {}
617 
618     @NonNull
createExpandableViewState()619     protected ExpandableViewState createExpandableViewState() {
620         return new ExpandableViewState();
621     }
622 
623     /** Sets {@link ExpandableViewState} to default state. */
resetViewState()624     public ExpandableViewState resetViewState() {
625         // initialize with the default values of the view
626         mViewState.height = getIntrinsicHeight();
627         mViewState.gone = getVisibility() == View.GONE;
628         mViewState.setAlpha(1f);
629         mViewState.notGoneIndex = -1;
630         mViewState.setXTranslation(getTranslationX());
631         mViewState.hidden = false;
632         mViewState.setScaleX(getScaleX());
633         mViewState.setScaleY(getScaleY());
634         mViewState.inShelf = false;
635         mViewState.headsUpIsVisible = false;
636 
637         // handling reset for child notifications
638         if (this instanceof ExpandableNotificationRow) {
639             ExpandableNotificationRow row = (ExpandableNotificationRow) this;
640             List<ExpandableNotificationRow> children = row.getAttachedChildren();
641             if (row.isSummaryWithChildren() && children != null) {
642                 for (ExpandableNotificationRow childRow : children) {
643                     childRow.resetViewState();
644                 }
645             }
646         }
647 
648         return mViewState;
649     }
650 
651     /**
652      * Get the {@link ExpandableViewState} associated with the view.
653      *
654      * @return the ExpandableView's view state.
655      */
getViewState()656     @NonNull public ExpandableViewState getViewState() {
657         return mViewState;
658     }
659 
660     /** Applies internal {@link ExpandableViewState} to this view. */
applyViewState()661     public void applyViewState() {
662         if (!mViewState.gone) {
663             mViewState.applyToView(this);
664         }
665     }
666 
667     /**
668      * @return whether the current view doesn't add height to the overall content. This means that
669      * if it is added to a list of items, its content will still have the same height.
670      * An example is the notification shelf, that is always placed on top of another view.
671      */
hasNoContentHeight()672     public boolean hasNoContentHeight() {
673         return false;
674     }
675 
676     /**
677      * @param inShelf whether the view is currently fully in the notification shelf.
678      */
setInShelf(boolean inShelf)679     public void setInShelf(boolean inShelf) {
680         mInShelf = inShelf;
681     }
682 
isInShelf()683     public boolean isInShelf() {
684         return mInShelf;
685     }
686 
getShelfIcon()687     public @Nullable StatusBarIconView getShelfIcon() {
688         return null;
689     }
690 
691     /**
692      * @return get the transformation target of the shelf, which usually is the icon
693      */
getShelfTransformationTarget()694     public View getShelfTransformationTarget() {
695         return null;
696     }
697 
698     /**
699      * Get the relative top padding of a view relative to this view. This recursively walks up the
700      * hierarchy and does the corresponding measuring.
701      *
702      * @param view the view to get the padding for. The requested view has to be a child of this
703      *             notification.
704      * @return the toppadding
705      */
getRelativeTopPadding(View view)706     public int getRelativeTopPadding(View view) {
707         int topPadding = 0;
708         while (view.getParent() instanceof ViewGroup) {
709             topPadding += view.getTop();
710             view = (View) view.getParent();
711             if (view == this) {
712                 return topPadding;
713             }
714         }
715         return topPadding;
716     }
717 
718 
719     /**
720      * Get the relative start padding of a view relative to this view. This recursively walks up the
721      * hierarchy and does the corresponding measuring.
722      *
723      * @param view the view to get the padding for. The requested view has to be a child of this
724      *             notification.
725      * @return the start padding
726      */
getRelativeStartPadding(View view)727     public int getRelativeStartPadding(View view) {
728         boolean isRtl = isLayoutRtl();
729         int startPadding = 0;
730         while (view.getParent() instanceof ViewGroup) {
731             View parent = (View) view.getParent();
732             startPadding += isRtl ? parent.getWidth() - view.getRight() : view.getLeft();
733             view = parent;
734             if (view == this) {
735                 return startPadding;
736             }
737         }
738         return startPadding;
739     }
740 
741     /**
742      * Set how much this notification is transformed into the shelf.
743      *
744      * @param contentTransformationAmount A value from 0 to 1 indicating how much we are transformed
745      *                                 to the content away
746      * @param isLastChild is this the last child in the list. If true, then the transformation is
747      *                    different since its content fades out.
748      */
setContentTransformationAmount(float contentTransformationAmount, boolean isLastChild)749     public void setContentTransformationAmount(float contentTransformationAmount,
750             boolean isLastChild) {
751         boolean changeTransformation = isLastChild != mIsLastChild;
752         changeTransformation |= mContentTransformationAmount != contentTransformationAmount;
753         mIsLastChild = isLastChild;
754         mContentTransformationAmount = contentTransformationAmount;
755         if (changeTransformation) {
756             updateContentTransformation();
757         }
758     }
759 
760     /**
761      * Update the content representation based on the amount we are transformed into the shelf.
762      */
updateContentTransformation()763     protected void updateContentTransformation() {
764         float translationY = -mContentTransformationAmount * getContentTransformationShift();
765         float contentAlpha = 1.0f - mContentTransformationAmount;
766         contentAlpha = Math.min(contentAlpha / 0.5f, 1.0f);
767         contentAlpha = Interpolators.ALPHA_OUT.getInterpolation(contentAlpha);
768         if (mIsLastChild) {
769             translationY *= 0.4f;
770         }
771         mContentTranslation = translationY;
772         applyContentTransformation(contentAlpha, translationY);
773     }
774 
775     /**
776      * @return how much the content shifts up when going into the shelf
777      */
getContentTransformationShift()778     protected float getContentTransformationShift() {
779         return mContentShift;
780     }
781 
782     /**
783      * Apply the contentTransformation when going into the shelf.
784      *
785      * @param contentAlpha The alpha that should be applied
786      * @param translationY the translationY that should be applied
787      */
applyContentTransformation(float contentAlpha, float translationY)788     protected void applyContentTransformation(float contentAlpha, float translationY) {
789     }
790 
791     /**
792      * @param transformingInShelf whether the view is currently transforming into the shelf in an
793      *                            animated way
794      */
setTransformingInShelf(boolean transformingInShelf)795     public void setTransformingInShelf(boolean transformingInShelf) {
796         mTransformingInShelf = transformingInShelf;
797     }
798 
isTransformingIntoShelf()799     public boolean isTransformingIntoShelf() {
800         return mTransformingInShelf;
801     }
802 
isAboveShelf()803     public boolean isAboveShelf() {
804         return false;
805     }
806 
hasExpandingChild()807     public boolean hasExpandingChild() {
808         return false;
809     }
810 
811     @Override
dump(PrintWriter pwOriginal, String[] args)812     public void dump(PrintWriter pwOriginal, String[] args) {
813         IndentingPrintWriter pw = DumpUtilsKt.asIndenting(pwOriginal);
814         pw.println(getClass().getSimpleName());
815         DumpUtilsKt.withIncreasedIndent(pw, () -> {
816             ExpandableViewState viewState = getViewState();
817             if (viewState == null) {
818                 pw.println("no viewState!!!");
819             } else {
820                 viewState.dump(pw, args);
821                 pw.println();
822             }
823             if (DUMP_VERBOSE) {
824                 pw.println("mClipTopAmount: " + mClipTopAmount);
825                 pw.println("mClipBottomAmount " + mClipBottomAmount);
826                 pw.println("mClipToActualHeight: " + mClipToActualHeight);
827                 pw.println("mExtraWidthForClipping: " + mExtraWidthForClipping);
828                 pw.println("mMinimumHeightForClipping: " + mMinimumHeightForClipping);
829                 pw.println("getClipBounds(): " + getClipBounds());
830             }
831         });
832     }
833 
834     /**
835      * return the amount that the content is translated
836      */
getContentTranslation()837     public float getContentTranslation() {
838         return mContentTranslation;
839     }
840 
841     /** Sets whether this view is the first notification in a section. */
setFirstInSection(boolean firstInSection)842     public void setFirstInSection(boolean firstInSection) {
843         mFirstInSection = firstInSection;
844     }
845 
846     /** Sets whether this view is the last notification in a section. */
setLastInSection(boolean lastInSection)847     public void setLastInSection(boolean lastInSection) {
848         mLastInSection = lastInSection;
849     }
850 
isLastInSection()851     public boolean isLastInSection() {
852         return mLastInSection;
853     }
854 
isFirstInSection()855     public boolean isFirstInSection() {
856         return mFirstInSection;
857     }
858 
getHeadsUpHeightWithoutHeader()859     public int getHeadsUpHeightWithoutHeader() {
860         return getHeight();
861     }
862 
863     /**
864      * A listener notifying when {@link #getActualHeight} changes.
865      */
866     public interface OnHeightChangedListener {
867 
868         /**
869          * @param view the view for which the height changed, or {@code null} if just the top
870          *             padding or the padding between the elements changed
871          * @param needsAnimation whether the view height needs to be animated
872          */
onHeightChanged(ExpandableView view, boolean needsAnimation)873         void onHeightChanged(ExpandableView view, boolean needsAnimation);
874 
875         /**
876          * Called when the view is reset and therefore the height will change abruptly
877          *
878          * @param view The view which was reset.
879          */
onReset(ExpandableView view)880         void onReset(ExpandableView view);
881     }
882 }
883