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.content.Context;
     20 import android.graphics.Rect;
     21 import android.util.AttributeSet;
     22 import android.view.MotionEvent;
     23 import android.view.View;
     24 import android.view.ViewGroup;
     25 import android.widget.FrameLayout;
     26 import com.android.systemui.R;
     27 import com.android.systemui.statusbar.stack.NotificationStackScrollLayout;
     28 
     29 import java.util.ArrayList;
     30 
     31 /**
     32  * An abstract view for expandable views.
     33  */
     34 public abstract class ExpandableView extends FrameLayout {
     35 
     36     private final int mMaxNotificationHeight;
     37 
     38     private OnHeightChangedListener mOnHeightChangedListener;
     39     protected int mActualHeight;
     40     protected int mClipTopAmount;
     41     private boolean mActualHeightInitialized;
     42     private ArrayList<View> mMatchParentViews = new ArrayList<View>();
     43 
     44     public ExpandableView(Context context, AttributeSet attrs) {
     45         super(context, attrs);
     46         mMaxNotificationHeight = getResources().getDimensionPixelSize(
     47                 R.dimen.notification_max_height);
     48     }
     49 
     50     @Override
     51     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     52         int ownMaxHeight = mMaxNotificationHeight;
     53         int heightMode = MeasureSpec.getMode(heightMeasureSpec);
     54         boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY;
     55         boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST;
     56         if (hasFixedHeight || isHeightLimited) {
     57             int size = MeasureSpec.getSize(heightMeasureSpec);
     58             ownMaxHeight = Math.min(ownMaxHeight, size);
     59         }
     60         int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST);
     61         int maxChildHeight = 0;
     62         int childCount = getChildCount();
     63         for (int i = 0; i < childCount; i++) {
     64             View child = getChildAt(i);
     65             int childHeightSpec = newHeightSpec;
     66             ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
     67             if (layoutParams.height != ViewGroup.LayoutParams.MATCH_PARENT) {
     68                 if (layoutParams.height >= 0) {
     69                     // An actual height is set
     70                     childHeightSpec = layoutParams.height > ownMaxHeight
     71                         ? MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.EXACTLY)
     72                         : MeasureSpec.makeMeasureSpec(layoutParams.height, MeasureSpec.EXACTLY);
     73                 }
     74                 child.measure(
     75                         getChildMeasureSpec(widthMeasureSpec, 0 /* padding */, layoutParams.width),
     76                         childHeightSpec);
     77                 int childHeight = child.getMeasuredHeight();
     78                 maxChildHeight = Math.max(maxChildHeight, childHeight);
     79             } else {
     80                 mMatchParentViews.add(child);
     81             }
     82         }
     83         int ownHeight = hasFixedHeight ? ownMaxHeight : maxChildHeight;
     84         newHeightSpec = MeasureSpec.makeMeasureSpec(ownHeight, MeasureSpec.EXACTLY);
     85         for (View child : mMatchParentViews) {
     86             child.measure(getChildMeasureSpec(
     87                     widthMeasureSpec, 0 /* padding */, child.getLayoutParams().width),
     88                     newHeightSpec);
     89         }
     90         mMatchParentViews.clear();
     91         int width = MeasureSpec.getSize(widthMeasureSpec);
     92         setMeasuredDimension(width, ownHeight);
     93     }
     94 
     95     @Override
     96     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
     97         super.onLayout(changed, left, top, right, bottom);
     98         if (!mActualHeightInitialized && mActualHeight == 0) {
     99             int initialHeight = getInitialHeight();
    100             if (initialHeight != 0) {
    101                 setActualHeight(initialHeight);
    102             }
    103         }
    104     }
    105 
    106     protected int getInitialHeight() {
    107         return getHeight();
    108     }
    109 
    110     @Override
    111     public boolean dispatchTouchEvent(MotionEvent ev) {
    112         if (filterMotionEvent(ev)) {
    113             return super.dispatchTouchEvent(ev);
    114         }
    115         return false;
    116     }
    117 
    118     private boolean filterMotionEvent(MotionEvent event) {
    119         return event.getActionMasked() != MotionEvent.ACTION_DOWN
    120                 || event.getY() > mClipTopAmount && event.getY() < mActualHeight;
    121     }
    122 
    123     /**
    124      * Sets the actual height of this notification. This is different than the laid out
    125      * {@link View#getHeight()}, as we want to avoid layouting during scrolling and expanding.
    126      *
    127      * @param actualHeight The height of this notification.
    128      * @param notifyListeners Whether the listener should be informed about the change.
    129      */
    130     public void setActualHeight(int actualHeight, boolean notifyListeners) {
    131         mActualHeightInitialized = true;
    132         mActualHeight = actualHeight;
    133         if (notifyListeners) {
    134             notifyHeightChanged();
    135         }
    136     }
    137 
    138     public void setActualHeight(int actualHeight) {
    139         setActualHeight(actualHeight, true);
    140     }
    141 
    142     /**
    143      * See {@link #setActualHeight}.
    144      *
    145      * @return The current actual height of this notification.
    146      */
    147     public int getActualHeight() {
    148         return mActualHeight;
    149     }
    150 
    151     /**
    152      * @return The maximum height of this notification.
    153      */
    154     public int getMaxHeight() {
    155         return getHeight();
    156     }
    157 
    158     /**
    159      * @return The minimum height of this notification.
    160      */
    161     public int getMinHeight() {
    162         return getHeight();
    163     }
    164 
    165     /**
    166      * Sets the notification as dimmed. The default implementation does nothing.
    167      *
    168      * @param dimmed Whether the notification should be dimmed.
    169      * @param fade Whether an animation should be played to change the state.
    170      */
    171     public void setDimmed(boolean dimmed, boolean fade) {
    172     }
    173 
    174     /**
    175      * Sets the notification as dark. The default implementation does nothing.
    176      *
    177      * @param dark Whether the notification should be dark.
    178      * @param fade Whether an animation should be played to change the state.
    179      */
    180     public void setDark(boolean dark, boolean fade) {
    181     }
    182 
    183     /**
    184      * See {@link #setHideSensitive}. This is a variant which notifies this view in advance about
    185      * the upcoming state of hiding sensitive notifications. It gets called at the very beginning
    186      * of a stack scroller update such that the updated intrinsic height (which is dependent on
    187      * whether private or public layout is showing) gets taken into account into all layout
    188      * calculations.
    189      */
    190     public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) {
    191     }
    192 
    193     /**
    194      * Sets whether the notification should hide its private contents if it is sensitive.
    195      */
    196     public void setHideSensitive(boolean hideSensitive, boolean animated, long delay,
    197             long duration) {
    198     }
    199 
    200     /**
    201      * @return The desired notification height.
    202      */
    203     public int getIntrinsicHeight() {
    204         return getHeight();
    205     }
    206 
    207     /**
    208      * Sets the amount this view should be clipped from the top. This is used when an expanded
    209      * notification is scrolling in the top or bottom stack.
    210      *
    211      * @param clipTopAmount The amount of pixels this view should be clipped from top.
    212      */
    213     public void setClipTopAmount(int clipTopAmount) {
    214         mClipTopAmount = clipTopAmount;
    215     }
    216 
    217     public int getClipTopAmount() {
    218         return mClipTopAmount;
    219     }
    220 
    221     public void setOnHeightChangedListener(OnHeightChangedListener listener) {
    222         mOnHeightChangedListener = listener;
    223     }
    224 
    225     /**
    226      * @return Whether we can expand this views content.
    227      */
    228     public boolean isContentExpandable() {
    229         return false;
    230     }
    231 
    232     public void notifyHeightChanged() {
    233         if (mOnHeightChangedListener != null) {
    234             mOnHeightChangedListener.onHeightChanged(this);
    235         }
    236     }
    237 
    238     public boolean isTransparent() {
    239         return false;
    240     }
    241 
    242     /**
    243      * Perform a remove animation on this view.
    244      *
    245      * @param duration The duration of the remove animation.
    246      * @param translationDirection The direction value from [-1 ... 1] indicating in which the
    247      *                             animation should be performed. A value of -1 means that The
    248      *                             remove animation should be performed upwards,
    249      *                             such that the  child appears to be going away to the top. 1
    250      *                             Should mean the opposite.
    251      * @param onFinishedRunnable A runnable which should be run when the animation is finished.
    252      */
    253     public abstract void performRemoveAnimation(long duration, float translationDirection,
    254             Runnable onFinishedRunnable);
    255 
    256     public abstract void performAddAnimation(long delay, long duration);
    257 
    258     public void setBelowSpeedBump(boolean below) {
    259     }
    260 
    261     public void onHeightReset() {
    262         if (mOnHeightChangedListener != null) {
    263             mOnHeightChangedListener.onReset(this);
    264         }
    265     }
    266 
    267     /**
    268      * This method returns the drawing rect for the view which is different from the regular
    269      * drawing rect, since we layout all children in the {@link NotificationStackScrollLayout} at
    270      * position 0 and usually the translation is neglected. Since we are manually clipping this
    271      * view,we also need to subtract the clipTopAmount from the top. This is needed in order to
    272      * ensure that accessibility and focusing work correctly.
    273      *
    274      * @param outRect The (scrolled) drawing bounds of the view.
    275      */
    276     @Override
    277     public void getDrawingRect(Rect outRect) {
    278         super.getDrawingRect(outRect);
    279         outRect.left += getTranslationX();
    280         outRect.right += getTranslationX();
    281         outRect.bottom = (int) (outRect.top + getTranslationY() + getActualHeight());
    282         outRect.top += getTranslationY() + getClipTopAmount();
    283     }
    284 
    285     /**
    286      * A listener notifying when {@link #getActualHeight} changes.
    287      */
    288     public interface OnHeightChangedListener {
    289 
    290         /**
    291          * @param view the view for which the height changed, or {@code null} if just the top
    292          *             padding or the padding between the elements changed
    293          */
    294         void onHeightChanged(ExpandableView view);
    295 
    296         /**
    297          * Called when the view is reset and therefore the height will change abruptly
    298          *
    299          * @param view The view which was reset.
    300          */
    301         void onReset(ExpandableView view);
    302     }
    303 }
    304