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