Home | History | Annotate | Download | only in policy
      1 /*
      2  * Copyright (C) 2011 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.policy;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.ObjectAnimator;
     22 import android.animation.ValueAnimator;
     23 import android.content.Context;
     24 import android.content.res.Configuration;
     25 import android.content.res.TypedArray;
     26 import android.graphics.Rect;
     27 import android.util.AttributeSet;
     28 import android.util.Log;
     29 import android.util.Slog;
     30 import android.view.MotionEvent;
     31 import android.view.View;
     32 import android.view.ViewConfiguration;
     33 import android.view.ViewGroup;
     34 
     35 import com.android.systemui.R;
     36 import com.android.systemui.SwipeHelper;
     37 
     38 import java.util.HashMap;
     39 
     40 public class NotificationRowLayout extends ViewGroup implements SwipeHelper.Callback {
     41     private static final String TAG = "NotificationRowLayout";
     42     private static final boolean DEBUG = false;
     43     private static final boolean SLOW_ANIMATIONS = DEBUG;
     44 
     45     private static final int APPEAR_ANIM_LEN = SLOW_ANIMATIONS ? 5000 : 250;
     46     private static final int DISAPPEAR_ANIM_LEN = APPEAR_ANIM_LEN;
     47 
     48     boolean mAnimateBounds = true;
     49 
     50     Rect mTmpRect = new Rect();
     51     int mNumRows = 0;
     52     int mRowHeight = 0;
     53     int mHeight = 0;
     54 
     55     HashMap<View, ValueAnimator> mAppearingViews = new HashMap<View, ValueAnimator>();
     56     HashMap<View, ValueAnimator> mDisappearingViews = new HashMap<View, ValueAnimator>();
     57 
     58     private SwipeHelper mSwipeHelper;
     59 
     60     // Flag set during notification removal animation to avoid causing too much work until
     61     // animation is done
     62     boolean mRemoveViews = true;
     63 
     64     public NotificationRowLayout(Context context, AttributeSet attrs) {
     65         this(context, attrs, 0);
     66     }
     67 
     68     public NotificationRowLayout(Context context, AttributeSet attrs, int defStyle) {
     69         super(context, attrs, defStyle);
     70 
     71         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NotificationRowLayout,
     72                 defStyle, 0);
     73         mRowHeight = a.getDimensionPixelSize(R.styleable.NotificationRowLayout_rowHeight, 0);
     74         a.recycle();
     75 
     76         setLayoutTransition(null);
     77 
     78         if (DEBUG) {
     79             setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
     80                 @Override
     81                 public void onChildViewAdded(View parent, View child) {
     82                     Slog.d(TAG, "view added: " + child + "; new count: " + getChildCount());
     83                 }
     84                 @Override
     85                 public void onChildViewRemoved(View parent, View child) {
     86                     Slog.d(TAG, "view removed: " + child + "; new count: " + (getChildCount() - 1));
     87                 }
     88             });
     89 
     90             setBackgroundColor(0x80FF8000);
     91         }
     92 
     93         float densityScale = getResources().getDisplayMetrics().density;
     94         float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
     95         mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
     96     }
     97 
     98     public void setAnimateBounds(boolean anim) {
     99         mAnimateBounds = anim;
    100     }
    101 
    102     @Override
    103     public boolean onInterceptTouchEvent(MotionEvent ev) {
    104         if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
    105         return mSwipeHelper.onInterceptTouchEvent(ev) ||
    106             super.onInterceptTouchEvent(ev);
    107     }
    108 
    109     @Override
    110     public boolean onTouchEvent(MotionEvent ev) {
    111         return mSwipeHelper.onTouchEvent(ev) ||
    112             super.onTouchEvent(ev);
    113     }
    114 
    115     public boolean canChildBeDismissed(View v) {
    116         final View veto = v.findViewById(R.id.veto);
    117         return (veto != null && veto.getVisibility() != View.GONE);
    118     }
    119 
    120     public void onChildDismissed(View v) {
    121         final View veto = v.findViewById(R.id.veto);
    122         if (veto != null && veto.getVisibility() != View.GONE && mRemoveViews) {
    123             veto.performClick();
    124         }
    125     }
    126 
    127     public void onBeginDrag(View v) {
    128         // We need to prevent the surrounding ScrollView from intercepting us now;
    129         // the scroll position will be locked while we swipe
    130         requestDisallowInterceptTouchEvent(true);
    131     }
    132 
    133     public void onDragCancelled(View v) {
    134     }
    135 
    136     public View getChildAtPosition(MotionEvent ev) {
    137         // find the view under the pointer, accounting for GONE views
    138         final int count = getChildCount();
    139         int y = 0;
    140         int touchY = (int) ev.getY();
    141         int childIdx = 0;
    142         View slidingChild;
    143         for (; childIdx < count; childIdx++) {
    144             slidingChild = getChildAt(childIdx);
    145             if (slidingChild.getVisibility() == GONE) {
    146                 continue;
    147             }
    148             y += mRowHeight;
    149             if (touchY < y) return slidingChild;
    150         }
    151         return null;
    152     }
    153 
    154     public View getChildContentView(View v) {
    155         return v;
    156     }
    157 
    158     @Override
    159     protected void onConfigurationChanged(Configuration newConfig) {
    160         super.onConfigurationChanged(newConfig);
    161         float densityScale = getResources().getDisplayMetrics().density;
    162         mSwipeHelper.setDensityScale(densityScale);
    163         float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
    164         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
    165     }
    166 
    167     //**
    168     @Override
    169     public void addView(View child, int index, LayoutParams params) {
    170         super.addView(child, index, params);
    171 
    172         final View childF = child;
    173 
    174         if (mAnimateBounds) {
    175             final ObjectAnimator alphaFade = ObjectAnimator.ofFloat(child, "alpha", 0f, 1f);
    176             alphaFade.setDuration(APPEAR_ANIM_LEN);
    177             alphaFade.addListener(new AnimatorListenerAdapter() {
    178                 @Override
    179                 public void onAnimationEnd(Animator animation) {
    180                     mAppearingViews.remove(childF);
    181                     requestLayout(); // pick up any final changes in position
    182                 }
    183             });
    184 
    185             alphaFade.start();
    186 
    187             mAppearingViews.put(child, alphaFade);
    188 
    189             requestLayout(); // start the container animation
    190         }
    191     }
    192 
    193     /**
    194      * Sets a flag to tell us whether to actually remove views. Removal is delayed by setting this
    195      * to false during some animations to smooth out performance. Callers should restore the
    196      * flag to true after the animation is done, and then they should make sure that the views
    197      * get removed properly.
    198      */
    199     public void setViewRemoval(boolean removeViews) {
    200         mRemoveViews = removeViews;
    201     }
    202 
    203     public void dismissRowAnimated(View child) {
    204         dismissRowAnimated(child, 0);
    205     }
    206 
    207     public void dismissRowAnimated(View child, int vel) {
    208         mSwipeHelper.dismissChild(child, vel);
    209     }
    210 
    211     @Override
    212     public void removeView(View child) {
    213         if (!mRemoveViews) {
    214             // This flag is cleared during an animation that removes all notifications. There
    215             // should be a call to remove all notifications when the animation is done, at which
    216             // time the view will be removed.
    217             return;
    218         }
    219         if (mAnimateBounds) {
    220             if (mAppearingViews.containsKey(child)) {
    221                 mAppearingViews.remove(child);
    222             }
    223 
    224             // Don't fade it out if it already has a low alpha value, but run a non-visual
    225             // animation which is used by onLayout() to animate shrinking the gap that it left
    226             // in the list
    227             ValueAnimator anim;
    228             float currentAlpha = child.getAlpha();
    229             if (currentAlpha > .1) {
    230                 anim = ObjectAnimator.ofFloat(child, "alpha", currentAlpha, 0);
    231             } else {
    232                 if (currentAlpha > 0) {
    233                     // Just make it go away - no need to render it anymore
    234                     child.setAlpha(0);
    235                 }
    236                 anim = ValueAnimator.ofFloat(0, 1);
    237             }
    238             anim.setDuration(DISAPPEAR_ANIM_LEN);
    239             final View childF = child;
    240             anim.addListener(new AnimatorListenerAdapter() {
    241                 @Override
    242                 public void onAnimationEnd(Animator animation) {
    243                     if (DEBUG) Slog.d(TAG, "actually removing child: " + childF);
    244                     NotificationRowLayout.super.removeView(childF);
    245                     mDisappearingViews.remove(childF);
    246                     requestLayout(); // pick up any final changes in position
    247                 }
    248             });
    249 
    250             anim.start();
    251             mDisappearingViews.put(child, anim);
    252 
    253             requestLayout(); // start the container animation
    254         } else {
    255             super.removeView(child);
    256         }
    257     }
    258     //**
    259 
    260     @Override
    261     public void onFinishInflate() {
    262         super.onFinishInflate();
    263         setWillNotDraw(false);
    264     }
    265 
    266     @Override
    267     public void onDraw(android.graphics.Canvas c) {
    268         super.onDraw(c);
    269         if (DEBUG) {
    270             //Slog.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: "
    271             //        + getMeasuredHeight() + "px");
    272             c.save();
    273             c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6,
    274                     android.graphics.Region.Op.DIFFERENCE);
    275             c.drawColor(0xFFFF8000);
    276             c.restore();
    277         }
    278     }
    279 
    280     @Override
    281     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    282         final int count = getChildCount();
    283 
    284         // pass 1: count the number of non-GONE views
    285         int numRows = 0;
    286         for (int i = 0; i < count; i++) {
    287             final View child = getChildAt(i);
    288             if (child.getVisibility() == GONE) {
    289                 continue;
    290             }
    291             if (mDisappearingViews.containsKey(child)) {
    292                 continue;
    293             }
    294             numRows++;
    295         }
    296         if (numRows != mNumRows) {
    297             // uh oh, now you made us go and do work
    298 
    299             final int computedHeight = numRows * mRowHeight;
    300             if (DEBUG) {
    301                 Slog.d(TAG, String.format("rows went from %d to %d, resizing to %dpx",
    302                             mNumRows, numRows, computedHeight));
    303             }
    304 
    305             mNumRows = numRows;
    306 
    307             if (mAnimateBounds && isShown()) {
    308                 ObjectAnimator.ofInt(this, "forcedHeight", computedHeight)
    309                     .setDuration(APPEAR_ANIM_LEN)
    310                     .start();
    311             } else {
    312                 setForcedHeight(computedHeight);
    313             }
    314         }
    315 
    316         // pass 2: you know, do the measuring
    317         final int childWidthMS = widthMeasureSpec;
    318         final int childHeightMS = MeasureSpec.makeMeasureSpec(
    319                 mRowHeight, MeasureSpec.EXACTLY);
    320 
    321         for (int i = 0; i < count; i++) {
    322             final View child = getChildAt(i);
    323             if (child.getVisibility() == GONE) {
    324                 continue;
    325             }
    326 
    327             child.measure(childWidthMS, childHeightMS);
    328         }
    329 
    330         setMeasuredDimension(
    331                 getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
    332                 resolveSize(getForcedHeight(), heightMeasureSpec));
    333     }
    334 
    335     @Override
    336     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    337         final int width = right - left;
    338         final int height = bottom - top;
    339 
    340         if (DEBUG) Slog.d(TAG, "onLayout: height=" + height);
    341 
    342         final int count = getChildCount();
    343         int y = 0;
    344         for (int i = 0; i < count; i++) {
    345             final View child = getChildAt(i);
    346             if (child.getVisibility() == GONE) {
    347                 continue;
    348             }
    349             float progress = 1.0f;
    350             if (mDisappearingViews.containsKey(child)) {
    351                 progress = 1.0f - mDisappearingViews.get(child).getAnimatedFraction();
    352             } else if (mAppearingViews.containsKey(child)) {
    353                 progress = 1.0f - mAppearingViews.get(child).getAnimatedFraction();
    354             }
    355             if (progress > 1.0f) {
    356                 if (DEBUG) {
    357                     Slog.w(TAG, "progress=" + progress + " > 1!!! " + child);
    358                 }
    359                 progress = 1f;
    360             }
    361             final int thisRowHeight = (int)(progress * mRowHeight);
    362             if (DEBUG) {
    363                 Slog.d(TAG, String.format(
    364                             "laying out child #%d: (0, %d, %d, %d) h=%d",
    365                             i, y, width, y + thisRowHeight, thisRowHeight));
    366             }
    367             child.layout(0, y, width, y + thisRowHeight);
    368             y += thisRowHeight;
    369         }
    370         if (DEBUG) {
    371             Slog.d(TAG, "onLayout: final y=" + y);
    372         }
    373     }
    374 
    375     public void setForcedHeight(int h) {
    376         if (DEBUG) Slog.d(TAG, "forcedHeight: " + h);
    377         if (h != mHeight) {
    378             mHeight = h;
    379             requestLayout();
    380         }
    381     }
    382 
    383     public int getForcedHeight() {
    384         return mHeight;
    385     }
    386 }
    387