• 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;
18 
19 import android.content.Context;
20 import android.graphics.Paint;
21 import android.graphics.Rect;
22 import android.util.AttributeSet;
23 import android.view.View;
24 import android.view.ViewGroup;
25 import android.widget.FrameLayout;
26 
27 import com.android.systemui.statusbar.stack.ExpandableViewState;
28 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
29 import com.android.systemui.statusbar.stack.StackScrollState;
30 
31 import java.util.ArrayList;
32 
33 /**
34  * An abstract view for expandable views.
35  */
36 public abstract class ExpandableView extends FrameLayout {
37 
38     protected OnHeightChangedListener mOnHeightChangedListener;
39     private int mActualHeight;
40     protected int mClipTopAmount;
41     protected int mClipBottomAmount;
42     private boolean mDark;
43     private ArrayList<View> mMatchParentViews = new ArrayList<View>();
44     private static Rect mClipRect = new Rect();
45     private boolean mWillBeGone;
46     private int mMinClipTopAmount = 0;
47     private boolean mClipToActualHeight = true;
48     private boolean mChangingPosition = false;
49     private ViewGroup mTransientContainer;
50     private boolean mInShelf;
51     private boolean mTransformingInShelf;
52 
ExpandableView(Context context, AttributeSet attrs)53     public ExpandableView(Context context, AttributeSet attrs) {
54         super(context, attrs);
55     }
56 
57     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)58     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
59         final int givenSize = MeasureSpec.getSize(heightMeasureSpec);
60         int ownMaxHeight = Integer.MAX_VALUE;
61         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
62         if (heightMode != MeasureSpec.UNSPECIFIED && givenSize != 0) {
63             ownMaxHeight = Math.min(givenSize, ownMaxHeight);
64         }
65         int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
66         int maxChildHeight = 0;
67         int childCount = getChildCount();
68         for (int i = 0; i < childCount; i++) {
69             View child = getChildAt(i);
70             if (child.getVisibility() == GONE) {
71                 continue;
72             }
73             int childHeightSpec = newHeightSpec;
74             ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
75             if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
76                 if (layoutParams.height >= 0) {
77                     // An actual height is set
78                     childHeightSpec = layoutParams.height > ownMaxHeight
79                         ? MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.EXACTLY)
80                         : MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
81                 }
82                 child.measure(
83                         getChildMeasureSpec(widthMeasureSpec, 0 /* padding */, layoutParams.width),
84                         childHeightSpec);
85                 int childHeight = child.getMeasuredHeight();
86                 maxChildHeight = Math.max(maxChildHeight, childHeight);
87             } else {
88                 mMatchParentViews.add(child);
89             }
90         }
91         int ownHeight = heightMode == MeasureSpec.EXACTLY
92                 ? givenSize : Math.min(ownMaxHeight, maxChildHeight);
93         newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
94         for (View child : mMatchParentViews) {
95             child.measure(getChildMeasureSpec(
96                     widthMeasureSpec, 0 /* padding */, child.getLayoutParams().width),
97                     newHeightSpec);
98         }
99         mMatchParentViews.clear();
100         int width = MeasureSpec.getSize(widthMeasureSpec);
101         setMeasuredDimension(width, ownHeight);
102     }
103 
104     @Override
onLayout(boolean changed, int left, int top, int right, int bottom)105     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
106         super.onLayout(changed, left, top, right, bottom);
107         updateClipping();
108     }
109 
110     @Override
pointInView(float localX, float localY, float slop)111     public boolean pointInView(float localX, float localY, float slop) {
112         float top = mClipTopAmount;
113         float bottom = mActualHeight;
114         return localX >= -slop && localY >= top - slop && localX < ((mRight - mLeft) + slop) &&
115                 localY < (bottom + slop);
116     }
117 
118     /**
119      * Sets the actual height of this notification. This is different than the laid out
120      * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
121      *
122      * @param actualHeight The height of this notification.
123      * @param notifyListeners Whether the listener should be informed about the change.
124      */
setActualHeight(int actualHeight, boolean notifyListeners)125     public void setActualHeight(int actualHeight, boolean notifyListeners) {
126         mActualHeight = actualHeight;
127         updateClipping();
128         if (notifyListeners) {
129             notifyHeightChanged(false  /* needsAnimation */);
130         }
131     }
132 
setActualHeight(int actualHeight)133     public void setActualHeight(int actualHeight) {
134         setActualHeight(actualHeight, true /* notifyListeners */);
135     }
136 
137     /**
138      * See {@link #setActualHeight}.
139      *
140      * @return The current actual height of this notification.
141      */
getActualHeight()142     public int getActualHeight() {
143         return mActualHeight;
144     }
145 
146     /**
147      * @return The maximum height of this notification.
148      */
getMaxContentHeight()149     public int getMaxContentHeight() {
150         return getHeight();
151     }
152 
153     /**
154      * @return The minimum content height of this notification. This also respects the temporary
155      * states of the view.
156      */
getMinHeight()157     public int getMinHeight() {
158         return getMinHeight(false /* ignoreTemporaryStates */);
159     }
160 
161     /**
162      * Get the minimum height of this view.
163      *
164      * @param ignoreTemporaryStates should temporary states be ignored like the guts or heads-up.
165      *
166      * @return The minimum height that this view needs.
167      */
getMinHeight(boolean ignoreTemporaryStates)168     public int getMinHeight(boolean ignoreTemporaryStates) {
169         return getHeight();
170     }
171 
172     /**
173      * @return The collapsed height of this view. Note that this might be different
174      * than {@link #getMinHeight()} because some elements like groups may have different sizes when
175      * they are system expanded.
176      */
getCollapsedHeight()177     public int getCollapsedHeight() {
178         return getHeight();
179     }
180 
181     /**
182      * Sets the notification as dimmed. The default implementation does nothing.
183      *
184      * @param dimmed Whether the notification should be dimmed.
185      * @param fade Whether an animation should be played to change the state.
186      */
setDimmed(boolean dimmed, boolean fade)187     public void setDimmed(boolean dimmed, boolean fade) {
188     }
189 
190     /**
191      * Sets the notification as dark. The default implementation does nothing.
192      *
193      * @param dark Whether the notification should be dark.
194      * @param fade Whether an animation should be played to change the state.
195      * @param delay If fading, the delay of the animation.
196      */
setDark(boolean dark, boolean fade, long delay)197     public void setDark(boolean dark, boolean fade, long delay) {
198         mDark = dark;
199     }
200 
isDark()201     public boolean isDark() {
202         return mDark;
203     }
204 
205     /**
206      * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
207      * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
208      * of a stack scroller update such that the updated intrinsic height (which is dependent on
209      * whether private or public layout is showing) gets taken into account into all layout
210      * calculations.
211      */
setHideSensitiveForIntrinsicHeight(boolean hideSensitive)212     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
213     }
214 
215     /**
216      * Sets whether the notification should hide its private contents if it is sensitive.
217      */
setHideSensitive(boolean hideSensitive, boolean animated, long delay, long duration)218     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
219             long duration) {
220     }
221 
222     /**
223      * @return The desired notification height.
224      */
getIntrinsicHeight()225     public int getIntrinsicHeight() {
226         return getHeight();
227     }
228 
229     /**
230      * Sets the amount this view should be clipped from the top. This is used when an expanded
231      * notification is scrolling in the top or bottom stack.
232      *
233      * @param clipTopAmount The amount of pixels this view should be clipped from top.
234      */
setClipTopAmount(int clipTopAmount)235     public void setClipTopAmount(int clipTopAmount) {
236         mClipTopAmount = clipTopAmount;
237         updateClipping();
238     }
239 
240     /**
241      * Set the amount the the notification is clipped on the bottom in addition to the regular
242      * clipping. This is mainly used to clip something in a non-animated way without changing the
243      * actual height of the notification and is purely visual.
244      *
245      * @param clipBottomAmount the amount to clip.
246      */
setClipBottomAmount(int clipBottomAmount)247     public void setClipBottomAmount(int clipBottomAmount) {
248         mClipBottomAmount = clipBottomAmount;
249         updateClipping();
250     }
251 
getClipTopAmount()252     public int getClipTopAmount() {
253         return mClipTopAmount;
254     }
255 
getClipBottomAmount()256     public int getClipBottomAmount() {
257         return mClipBottomAmount;
258     }
259 
setOnHeightChangedListener(OnHeightChangedListener listener)260     public void setOnHeightChangedListener(OnHeightChangedListener listener) {
261         mOnHeightChangedListener = listener;
262     }
263 
264     /**
265      * @return Whether we can expand this views content.
266      */
isContentExpandable()267     public boolean isContentExpandable() {
268         return false;
269     }
270 
notifyHeightChanged(boolean needsAnimation)271     public void notifyHeightChanged(boolean needsAnimation) {
272         if (mOnHeightChangedListener != null) {
273             mOnHeightChangedListener.onHeightChanged(this, needsAnimation);
274         }
275     }
276 
isTransparent()277     public boolean isTransparent() {
278         return false;
279     }
280 
281     /**
282      * Perform a remove animation on this view.
283      *
284      * @param duration The duration of the remove animation.
285      * @param translationDirection The direction value from [-1 ... 1] indicating in which the
286      *                             animation should be performed. A value of -1 means that The
287      *                             remove animation should be performed upwards,
288      *                             such that the  child appears to be going away to the top. 1
289      *                             Should mean the opposite.
290      * @param onFinishedRunnable A runnable which should be run when the animation is finished.
291      */
performRemoveAnimation(long duration, float translationDirection, Runnable onFinishedRunnable)292     public abstract void performRemoveAnimation(long duration, float translationDirection,
293             Runnable onFinishedRunnable);
294 
performAddAnimation(long delay, long duration)295     public abstract void performAddAnimation(long delay, long duration);
296 
297     /**
298      * Set the notification appearance to be below the speed bump.
299      * @param below true if it is below.
300      */
setBelowSpeedBump(boolean below)301     public void setBelowSpeedBump(boolean below) {
302     }
303 
getPinnedHeadsUpHeight()304     public int getPinnedHeadsUpHeight() {
305         return getIntrinsicHeight();
306     }
307 
308 
309     /**
310      * Sets the translation of the view.
311      */
setTranslation(float translation)312     public void setTranslation(float translation) {
313         setTranslationX(translation);
314     }
315 
316     /**
317      * Gets the translation of the view.
318      */
getTranslation()319     public float getTranslation() {
320         return getTranslationX();
321     }
322 
onHeightReset()323     public void onHeightReset() {
324         if (mOnHeightChangedListener != null) {
325             mOnHeightChangedListener.onReset(this);
326         }
327     }
328 
329     /**
330      * This method returns the drawing rect for the view which is different from the regular
331      * drawing rect, since we layout all children in the {@link NotificationStackScrollLayout} at
332      * position 0 and usually the translation is neglected. Since we are manually clipping this
333      * view,we also need to subtract the clipTopAmount from the top. This is needed in order to
334      * ensure that accessibility and focusing work correctly.
335      *
336      * @param outRect The (scrolled) drawing bounds of the view.
337      */
338     @Override
getDrawingRect(Rect outRect)339     public void getDrawingRect(Rect outRect) {
340         super.getDrawingRect(outRect);
341         outRect.left += getTranslationX();
342         outRect.right += getTranslationX();
343         outRect.bottom = (int) (outRect.top + getTranslationY() + getActualHeight());
344         outRect.top += getTranslationY() + getClipTopAmount();
345     }
346 
347     @Override
getBoundsOnScreen(Rect outRect, boolean clipToParent)348     public void getBoundsOnScreen(Rect outRect, boolean clipToParent) {
349         super.getBoundsOnScreen(outRect, clipToParent);
350         if (getTop() + getTranslationY() < 0) {
351             // We got clipped to the parent here - make sure we undo that.
352             outRect.top += getTop() + getTranslationY();
353         }
354         outRect.bottom = outRect.top + getActualHeight();
355         outRect.top += getClipTopAmount();
356     }
357 
isSummaryWithChildren()358     public boolean isSummaryWithChildren() {
359         return false;
360     }
361 
areChildrenExpanded()362     public boolean areChildrenExpanded() {
363         return false;
364     }
365 
updateClipping()366     private void updateClipping() {
367         if (mClipToActualHeight) {
368             int top = getClipTopAmount();
369             mClipRect.set(0, top, getWidth(), Math.max(getActualHeight() + getExtraBottomPadding()
370                     - mClipBottomAmount, top));
371             setClipBounds(mClipRect);
372         } else {
373             setClipBounds(null);
374         }
375     }
376 
setClipToActualHeight(boolean clipToActualHeight)377     public void setClipToActualHeight(boolean clipToActualHeight) {
378         mClipToActualHeight = clipToActualHeight;
379         updateClipping();
380     }
381 
willBeGone()382     public boolean willBeGone() {
383         return mWillBeGone;
384     }
385 
setWillBeGone(boolean willBeGone)386     public void setWillBeGone(boolean willBeGone) {
387         mWillBeGone = willBeGone;
388     }
389 
getMinClipTopAmount()390     public int getMinClipTopAmount() {
391         return mMinClipTopAmount;
392     }
393 
setMinClipTopAmount(int minClipTopAmount)394     public void setMinClipTopAmount(int minClipTopAmount) {
395         mMinClipTopAmount = minClipTopAmount;
396     }
397 
398     @Override
setLayerType(int layerType, Paint paint)399     public void setLayerType(int layerType, Paint paint) {
400         if (hasOverlappingRendering()) {
401             super.setLayerType(layerType, paint);
402         }
403     }
404 
405     @Override
hasOverlappingRendering()406     public boolean hasOverlappingRendering() {
407         // Otherwise it will be clipped
408         return super.hasOverlappingRendering() && getActualHeight() <= getHeight();
409     }
410 
getShadowAlpha()411     public float getShadowAlpha() {
412         return 0.0f;
413     }
414 
setShadowAlpha(float shadowAlpha)415     public void setShadowAlpha(float shadowAlpha) {
416     }
417 
418     /**
419      * @return an amount between -1 and 1 of increased padding that this child needs. 1 means it
420      * needs a full increased padding while -1 means it needs no padding at all. For 0.0f the normal
421      * padding is applied.
422      */
getIncreasedPaddingAmount()423     public float getIncreasedPaddingAmount() {
424         return 0.0f;
425     }
426 
mustStayOnScreen()427     public boolean mustStayOnScreen() {
428         return false;
429     }
430 
setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd, int outlineTranslation)431     public void setFakeShadowIntensity(float shadowIntensity, float outlineAlpha, int shadowYEnd,
432             int outlineTranslation) {
433     }
434 
getOutlineAlpha()435     public float getOutlineAlpha() {
436         return 0.0f;
437     }
438 
getOutlineTranslation()439     public int getOutlineTranslation() {
440         return 0;
441     }
442 
setChangingPosition(boolean changingPosition)443     public void setChangingPosition(boolean changingPosition) {
444         mChangingPosition = changingPosition;
445     }
446 
isChangingPosition()447     public boolean isChangingPosition() {
448         return mChangingPosition;
449     }
450 
setTransientContainer(ViewGroup transientContainer)451     public void setTransientContainer(ViewGroup transientContainer) {
452         mTransientContainer = transientContainer;
453     }
454 
getTransientContainer()455     public ViewGroup getTransientContainer() {
456         return mTransientContainer;
457     }
458 
459     /**
460      * @return padding used to alter how much of the view is clipped.
461      */
getExtraBottomPadding()462     public int getExtraBottomPadding() {
463         return 0;
464     }
465 
466     /**
467      * @return true if the group's expansion state is changing, false otherwise.
468      */
isGroupExpansionChanging()469     public boolean isGroupExpansionChanging() {
470         return false;
471     }
472 
isGroupExpanded()473     public boolean isGroupExpanded() {
474         return false;
475     }
476 
isChildInGroup()477     public boolean isChildInGroup() {
478         return false;
479     }
480 
setActualHeightAnimating(boolean animating)481     public void setActualHeightAnimating(boolean animating) {}
482 
createNewViewState(StackScrollState stackScrollState)483     public ExpandableViewState createNewViewState(StackScrollState stackScrollState) {
484         return new ExpandableViewState();
485     }
486 
487     /**
488      * @return whether the current view doesn't add height to the overall content. This means that
489      * if it is added to a list of items, it's content will still have the same height.
490      * An example is the notification shelf, that is always placed on top of another view.
491      */
hasNoContentHeight()492     public boolean hasNoContentHeight() {
493         return false;
494     }
495 
496     /**
497      * @param inShelf whether the view is currently fully in the notification shelf.
498      */
setInShelf(boolean inShelf)499     public void setInShelf(boolean inShelf) {
500         mInShelf = inShelf;
501     }
502 
isInShelf()503     public boolean isInShelf() {
504         return mInShelf;
505     }
506 
507     /**
508      * @param transformingInShelf whether the view is currently transforming into the shelf in an
509      *                            animated way
510      */
setTransformingInShelf(boolean transformingInShelf)511     public void setTransformingInShelf(boolean transformingInShelf) {
512         mTransformingInShelf = transformingInShelf;
513     }
514 
isTransformingIntoShelf()515     public boolean isTransformingIntoShelf() {
516         return mTransformingInShelf;
517     }
518 
isAboveShelf()519     public boolean isAboveShelf() {
520         return false;
521     }
522 
523     /**
524      * A listener notifying when {@link #getActualHeight} changes.
525      */
526     public interface OnHeightChangedListener {
527 
528         /**
529          * @param view the view for which the height changed, or {@code null} if just the top
530          *             padding or the padding between the elements changed
531          * @param needsAnimation whether the view height needs to be animated
532          */
onHeightChanged(ExpandableView view, boolean needsAnimation)533         void onHeightChanged(ExpandableView view, boolean needsAnimation);
534 
535         /**
536          * Called when the view is reset and therefore the height will change abruptly
537          *
538          * @param view The view which was reset.
539          */
onReset(ExpandableView view)540         void onReset(ExpandableView view);
541     }
542 }
543