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