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