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