Home | History | Annotate | Download | only in recent
      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.recent;
     18 
     19 import android.animation.LayoutTransition;
     20 import android.content.Context;
     21 import android.content.res.Configuration;
     22 import android.database.DataSetObserver;
     23 import android.graphics.Canvas;
     24 import android.util.AttributeSet;
     25 import android.util.DisplayMetrics;
     26 import android.util.FloatMath;
     27 import android.util.Log;
     28 import android.view.MotionEvent;
     29 import android.view.View;
     30 import android.view.View.MeasureSpec;
     31 import android.view.View.OnClickListener;
     32 import android.view.View.OnLongClickListener;
     33 import android.view.View.OnTouchListener;
     34 import android.view.ViewConfiguration;
     35 import android.view.ViewTreeObserver;
     36 import android.view.ViewTreeObserver.OnGlobalLayoutListener;
     37 import android.widget.LinearLayout;
     38 import android.widget.ScrollView;
     39 
     40 import com.android.systemui.R;
     41 import com.android.systemui.SwipeHelper;
     42 import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
     43 
     44 import java.util.HashSet;
     45 import java.util.Iterator;
     46 
     47 public class RecentsVerticalScrollView extends ScrollView
     48         implements SwipeHelper.Callback, RecentsPanelView.RecentsScrollView {
     49     private static final String TAG = RecentsPanelView.TAG;
     50     private static final boolean DEBUG = RecentsPanelView.DEBUG;
     51     private LinearLayout mLinearLayout;
     52     private TaskDescriptionAdapter mAdapter;
     53     private RecentsCallback mCallback;
     54     protected int mLastScrollPosition;
     55     private SwipeHelper mSwipeHelper;
     56     private RecentsScrollViewPerformanceHelper mPerformanceHelper;
     57     private HashSet<View> mRecycledViews;
     58     private int mNumItemsInOneScreenful;
     59 
     60     public RecentsVerticalScrollView(Context context, AttributeSet attrs) {
     61         super(context, attrs, 0);
     62         float densityScale = getResources().getDisplayMetrics().density;
     63         float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
     64         mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, pagingTouchSlop);
     65 
     66         mPerformanceHelper = RecentsScrollViewPerformanceHelper.create(context, attrs, this, true);
     67         mRecycledViews = new HashSet<View>();
     68     }
     69 
     70     public void setMinSwipeAlpha(float minAlpha) {
     71         mSwipeHelper.setMinAlpha(minAlpha);
     72     }
     73 
     74     private int scrollPositionOfMostRecent() {
     75         return mLinearLayout.getHeight() - getHeight();
     76     }
     77 
     78     private void addToRecycledViews(View v) {
     79         if (mRecycledViews.size() < mNumItemsInOneScreenful) {
     80             mRecycledViews.add(v);
     81         }
     82     }
     83 
     84     private void update() {
     85         for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
     86             View v = mLinearLayout.getChildAt(i);
     87             addToRecycledViews(v);
     88             mAdapter.recycleView(v);
     89         }
     90         LayoutTransition transitioner = getLayoutTransition();
     91         setLayoutTransition(null);
     92 
     93         mLinearLayout.removeAllViews();
     94 
     95         // Once we can clear the data associated with individual item views,
     96         // we can get rid of the removeAllViews() and the code below will
     97         // recycle them.
     98         Iterator<View> recycledViews = mRecycledViews.iterator();
     99         for (int i = 0; i < mAdapter.getCount(); i++) {
    100             View old = null;
    101             if (recycledViews.hasNext()) {
    102                 old = recycledViews.next();
    103                 recycledViews.remove();
    104                 old.setVisibility(VISIBLE);
    105             }
    106             final View view = mAdapter.getView(i, old, mLinearLayout);
    107 
    108             if (mPerformanceHelper != null) {
    109                 mPerformanceHelper.addViewCallback(view);
    110             }
    111 
    112             OnTouchListener noOpListener = new OnTouchListener() {
    113                 @Override
    114                 public boolean onTouch(View v, MotionEvent event) {
    115                     return true;
    116                 }
    117             };
    118 
    119             view.setOnClickListener(new OnClickListener() {
    120                 public void onClick(View v) {
    121                     mCallback.dismiss();
    122                 }
    123             });
    124             // We don't want a click sound when we dimiss recents
    125             view.setSoundEffectsEnabled(false);
    126 
    127             OnClickListener launchAppListener = new OnClickListener() {
    128                 public void onClick(View v) {
    129                     mCallback.handleOnClick(view);
    130                 }
    131             };
    132 
    133             RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) view.getTag();
    134             final View thumbnailView = holder.thumbnailView;
    135             OnLongClickListener longClickListener = new OnLongClickListener() {
    136                 public boolean onLongClick(View v) {
    137                     final View anchorView = view.findViewById(R.id.app_description);
    138                     mCallback.handleLongPress(view, anchorView, thumbnailView);
    139                     return true;
    140                 }
    141             };
    142             thumbnailView.setClickable(true);
    143             thumbnailView.setOnClickListener(launchAppListener);
    144             thumbnailView.setOnLongClickListener(longClickListener);
    145 
    146             // We don't want to dismiss recents if a user clicks on the app title
    147             // (we also don't want to launch the app either, though, because the
    148             // app title is a small target and doesn't have great click feedback)
    149             final View appTitle = view.findViewById(R.id.app_label);
    150             appTitle.setContentDescription(" ");
    151             appTitle.setOnTouchListener(noOpListener);
    152             final View calloutLine = view.findViewById(R.id.recents_callout_line);
    153             calloutLine.setOnTouchListener(noOpListener);
    154 
    155             mLinearLayout.addView(view);
    156         }
    157         setLayoutTransition(transitioner);
    158 
    159         // Scroll to end after layout.
    160         final ViewTreeObserver observer = getViewTreeObserver();
    161 
    162         final OnGlobalLayoutListener updateScroll = new OnGlobalLayoutListener() {
    163                 public void onGlobalLayout() {
    164                     mLastScrollPosition = scrollPositionOfMostRecent();
    165                     scrollTo(0, mLastScrollPosition);
    166                     if (observer.isAlive()) {
    167                         observer.removeOnGlobalLayoutListener(this);
    168                     }
    169                 }
    170             };
    171         observer.addOnGlobalLayoutListener(updateScroll);
    172     }
    173 
    174     @Override
    175     public void removeViewInLayout(final View view) {
    176         dismissChild(view);
    177     }
    178 
    179     public boolean onInterceptTouchEvent(MotionEvent ev) {
    180         if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
    181         return mSwipeHelper.onInterceptTouchEvent(ev) ||
    182             super.onInterceptTouchEvent(ev);
    183     }
    184 
    185     @Override
    186     public boolean onTouchEvent(MotionEvent ev) {
    187         return mSwipeHelper.onTouchEvent(ev) ||
    188             super.onTouchEvent(ev);
    189     }
    190 
    191     public boolean canChildBeDismissed(View v) {
    192         return true;
    193     }
    194 
    195     public void dismissChild(View v) {
    196         mSwipeHelper.dismissChild(v, 0);
    197     }
    198 
    199     public void onChildDismissed(View v) {
    200         addToRecycledViews(v);
    201         mLinearLayout.removeView(v);
    202         mCallback.handleSwipe(v);
    203         // Restore the alpha/translation parameters to what they were before swiping
    204         // (for when these items are recycled)
    205         View contentView = getChildContentView(v);
    206         contentView.setAlpha(1f);
    207         contentView.setTranslationX(0);
    208     }
    209 
    210     public void onBeginDrag(View v) {
    211         // We do this so the underlying ScrollView knows that it won't get
    212         // the chance to intercept events anymore
    213         requestDisallowInterceptTouchEvent(true);
    214     }
    215 
    216     public void onDragCancelled(View v) {
    217     }
    218 
    219     public View getChildAtPosition(MotionEvent ev) {
    220         final float x = ev.getX() + getScrollX();
    221         final float y = ev.getY() + getScrollY();
    222         for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
    223             View item = mLinearLayout.getChildAt(i);
    224             if (item.getVisibility() == View.VISIBLE
    225                     && x >= item.getLeft() && x < item.getRight()
    226                     && y >= item.getTop() && y < item.getBottom()) {
    227                 return item;
    228             }
    229         }
    230         return null;
    231     }
    232 
    233     public View getChildContentView(View v) {
    234         return v.findViewById(R.id.recent_item);
    235     }
    236 
    237     @Override
    238     public void draw(Canvas canvas) {
    239         super.draw(canvas);
    240 
    241         if (mPerformanceHelper != null) {
    242             int paddingLeft = mPaddingLeft;
    243             final boolean offsetRequired = isPaddingOffsetRequired();
    244             if (offsetRequired) {
    245                 paddingLeft += getLeftPaddingOffset();
    246             }
    247 
    248             int left = mScrollX + paddingLeft;
    249             int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
    250             int top = mScrollY + getFadeTop(offsetRequired);
    251             int bottom = top + getFadeHeight(offsetRequired);
    252 
    253             if (offsetRequired) {
    254                 right += getRightPaddingOffset();
    255                 bottom += getBottomPaddingOffset();
    256             }
    257             mPerformanceHelper.drawCallback(canvas,
    258                     left, right, top, bottom, mScrollX, mScrollY,
    259                     getTopFadingEdgeStrength(), getBottomFadingEdgeStrength(),
    260                     0, 0);
    261         }
    262     }
    263 
    264     @Override
    265     public int getVerticalFadingEdgeLength() {
    266         if (mPerformanceHelper != null) {
    267             return mPerformanceHelper.getVerticalFadingEdgeLengthCallback();
    268         } else {
    269             return super.getVerticalFadingEdgeLength();
    270         }
    271     }
    272 
    273     @Override
    274     public int getHorizontalFadingEdgeLength() {
    275         if (mPerformanceHelper != null) {
    276             return mPerformanceHelper.getHorizontalFadingEdgeLengthCallback();
    277         } else {
    278             return super.getHorizontalFadingEdgeLength();
    279         }
    280     }
    281 
    282     @Override
    283     protected void onFinishInflate() {
    284         super.onFinishInflate();
    285         setScrollbarFadingEnabled(true);
    286         mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout);
    287         final int leftPadding = mContext.getResources()
    288             .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin);
    289         setOverScrollEffectPadding(leftPadding, 0);
    290     }
    291 
    292     @Override
    293     public void onAttachedToWindow() {
    294         if (mPerformanceHelper != null) {
    295             mPerformanceHelper.onAttachedToWindowCallback(
    296                     mCallback, mLinearLayout, isHardwareAccelerated());
    297         }
    298     }
    299 
    300     @Override
    301     protected void onConfigurationChanged(Configuration newConfig) {
    302         super.onConfigurationChanged(newConfig);
    303         float densityScale = getResources().getDisplayMetrics().density;
    304         mSwipeHelper.setDensityScale(densityScale);
    305         float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
    306         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
    307     }
    308 
    309     private void setOverScrollEffectPadding(int leftPadding, int i) {
    310         // TODO Add to (Vertical)ScrollView
    311     }
    312 
    313     @Override
    314     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    315         super.onSizeChanged(w, h, oldw, oldh);
    316 
    317         // Skip this work if a transition is running; it sets the scroll values independently
    318         // and should not have those animated values clobbered by this logic
    319         LayoutTransition transition = mLinearLayout.getLayoutTransition();
    320         if (transition != null && transition.isRunning()) {
    321             return;
    322         }
    323         // Keep track of the last visible item in the list so we can restore it
    324         // to the bottom when the orientation changes.
    325         mLastScrollPosition = scrollPositionOfMostRecent();
    326 
    327         // This has to happen post-layout, so run it "in the future"
    328         post(new Runnable() {
    329             public void run() {
    330                 // Make sure we're still not clobbering the transition-set values, since this
    331                 // runnable launches asynchronously
    332                 LayoutTransition transition = mLinearLayout.getLayoutTransition();
    333                 if (transition == null || !transition.isRunning()) {
    334                     scrollTo(0, mLastScrollPosition);
    335                 }
    336             }
    337         });
    338     }
    339 
    340     @Override
    341     protected void onVisibilityChanged(View changedView, int visibility) {
    342         super.onVisibilityChanged(changedView, visibility);
    343         // scroll to bottom after reloading
    344         if (visibility == View.VISIBLE && changedView == this) {
    345             post(new Runnable() {
    346                 public void run() {
    347                     update();
    348                 }
    349             });
    350         }
    351     }
    352 
    353     public void setAdapter(TaskDescriptionAdapter adapter) {
    354         mAdapter = adapter;
    355         mAdapter.registerDataSetObserver(new DataSetObserver() {
    356             public void onChanged() {
    357                 update();
    358             }
    359 
    360             public void onInvalidated() {
    361                 update();
    362             }
    363         });
    364 
    365         DisplayMetrics dm = getResources().getDisplayMetrics();
    366         int childWidthMeasureSpec =
    367                 MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.AT_MOST);
    368         int childheightMeasureSpec =
    369                 MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.AT_MOST);
    370         View child = mAdapter.createView(mLinearLayout);
    371         child.measure(childWidthMeasureSpec, childheightMeasureSpec);
    372         mNumItemsInOneScreenful =
    373                 (int) FloatMath.ceil(dm.heightPixels / (float) child.getMeasuredHeight());
    374         addToRecycledViews(child);
    375 
    376         for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) {
    377             addToRecycledViews(mAdapter.createView(mLinearLayout));
    378         }
    379     }
    380 
    381     public int numItemsInOneScreenful() {
    382         return mNumItemsInOneScreenful;
    383     }
    384 
    385     @Override
    386     public void setLayoutTransition(LayoutTransition transition) {
    387         // The layout transition applies to our embedded LinearLayout
    388         mLinearLayout.setLayoutTransition(transition);
    389     }
    390 
    391     public void setCallback(RecentsCallback callback) {
    392         mCallback = callback;
    393     }
    394 }
    395