• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.LinearLayout;
34 import android.widget.ScrollView;
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 RecentsVerticalScrollView extends ScrollView
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 
RecentsVerticalScrollView(Context context, AttributeSet attrs)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         mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, true);
64         mRecycledViews = new HashSet<View>();
65     }
66 
setMinSwipeAlpha(float minAlpha)67     public void setMinSwipeAlpha(float minAlpha) {
68         mSwipeHelper.setMinAlpha(minAlpha);
69     }
70 
scrollPositionOfMostRecent()71     private int scrollPositionOfMostRecent() {
72         return mLinearLayout.getHeight() - getHeight() + mPaddingTop;
73     }
74 
addToRecycledViews(View v)75     private void addToRecycledViews(View v) {
76         if (mRecycledViews.size() < mNumItemsInOneScreenful) {
77             mRecycledViews.add(v);
78         }
79     }
80 
findViewForTask(int persistentTaskId)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 
update()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 (mFadedEdgeDrawHelper != null) {
117                 mFadedEdgeDrawHelper.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
removeViewInLayout(final View view)184     public void removeViewInLayout(final View view) {
185         dismissChild(view);
186     }
187 
onInterceptTouchEvent(MotionEvent ev)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
onTouchEvent(MotionEvent ev)195     public boolean onTouchEvent(MotionEvent ev) {
196         return mSwipeHelper.onTouchEvent(ev) ||
197             super.onTouchEvent(ev);
198     }
199 
canChildBeDismissed(View v)200     public boolean canChildBeDismissed(View v) {
201         return true;
202     }
203 
dismissChild(View v)204     public void dismissChild(View v) {
205         mSwipeHelper.dismissChild(v, 0);
206     }
207 
onChildDismissed(View v)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 
onBeginDrag(View v)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 
onDragCancelled(View v)225     public void onDragCancelled(View v) {
226     }
227 
getChildAtPosition(MotionEvent ev)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 
getChildContentView(View v)242     public View getChildContentView(View v) {
243         return v.findViewById(R.id.recent_item);
244     }
245 
246     @Override
drawFadedEdges(Canvas canvas, int left, int right, int top, int bottom)247     public void drawFadedEdges(Canvas canvas, int left, int right, int top, int bottom) {
248         if (mFadedEdgeDrawHelper != null) {
249             final boolean offsetRequired = isPaddingOffsetRequired();
250             mFadedEdgeDrawHelper.drawCallback(canvas,
251                     left, right, top + getFadeTop(offsetRequired), bottom, mScrollX, mScrollY,
252                     getTopFadingEdgeStrength(), getBottomFadingEdgeStrength(),
253                     0, 0, mPaddingTop);
254         }
255     }
256 
257     @Override
onScrollChanged(int l, int t, int oldl, int oldt)258     protected void onScrollChanged(int l, int t, int oldl, int oldt) {
259        super.onScrollChanged(l, t, oldl, oldt);
260        if (mOnScrollListener != null) {
261            mOnScrollListener.run();
262        }
263     }
264 
setOnScrollListener(Runnable listener)265     public void setOnScrollListener(Runnable listener) {
266         mOnScrollListener = listener;
267     }
268 
269     @Override
getVerticalFadingEdgeLength()270     public int getVerticalFadingEdgeLength() {
271         if (mFadedEdgeDrawHelper != null) {
272             return mFadedEdgeDrawHelper.getVerticalFadingEdgeLength();
273         } else {
274             return super.getVerticalFadingEdgeLength();
275         }
276     }
277 
278     @Override
getHorizontalFadingEdgeLength()279     public int getHorizontalFadingEdgeLength() {
280         if (mFadedEdgeDrawHelper != null) {
281             return mFadedEdgeDrawHelper.getHorizontalFadingEdgeLength();
282         } else {
283             return super.getHorizontalFadingEdgeLength();
284         }
285     }
286 
287     @Override
onFinishInflate()288     protected void onFinishInflate() {
289         super.onFinishInflate();
290         setScrollbarFadingEnabled(true);
291         mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout);
292         final int leftPadding = mContext.getResources()
293             .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin);
294         setOverScrollEffectPadding(leftPadding, 0);
295     }
296 
297     @Override
onAttachedToWindow()298     public void onAttachedToWindow() {
299         if (mFadedEdgeDrawHelper != null) {
300             mFadedEdgeDrawHelper.onAttachedToWindowCallback(mLinearLayout, isHardwareAccelerated());
301         }
302     }
303 
304     @Override
onConfigurationChanged(Configuration newConfig)305     protected void onConfigurationChanged(Configuration newConfig) {
306         super.onConfigurationChanged(newConfig);
307         float densityScale = getResources().getDisplayMetrics().density;
308         mSwipeHelper.setDensityScale(densityScale);
309         float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
310         mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
311     }
312 
setOverScrollEffectPadding(int leftPadding, int i)313     private void setOverScrollEffectPadding(int leftPadding, int i) {
314         // TODO Add to (Vertical)ScrollView
315     }
316 
317     @Override
onSizeChanged(int w, int h, int oldw, int oldh)318     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
319         super.onSizeChanged(w, h, oldw, oldh);
320 
321         // Skip this work if a transition is running; it sets the scroll values independently
322         // and should not have those animated values clobbered by this logic
323         LayoutTransition transition = mLinearLayout.getLayoutTransition();
324         if (transition != null && transition.isRunning()) {
325             return;
326         }
327         // Keep track of the last visible item in the list so we can restore it
328         // to the bottom when the orientation changes.
329         mLastScrollPosition = scrollPositionOfMostRecent();
330 
331         // This has to happen post-layout, so run it "in the future"
332         post(new Runnable() {
333             public void run() {
334                 // Make sure we're still not clobbering the transition-set values, since this
335                 // runnable launches asynchronously
336                 LayoutTransition transition = mLinearLayout.getLayoutTransition();
337                 if (transition == null || !transition.isRunning()) {
338                     scrollTo(0, mLastScrollPosition);
339                 }
340             }
341         });
342     }
343 
setAdapter(TaskDescriptionAdapter adapter)344     public void setAdapter(TaskDescriptionAdapter adapter) {
345         mAdapter = adapter;
346         mAdapter.registerDataSetObserver(new DataSetObserver() {
347             public void onChanged() {
348                 update();
349             }
350 
351             public void onInvalidated() {
352                 update();
353             }
354         });
355 
356         DisplayMetrics dm = getResources().getDisplayMetrics();
357         int childWidthMeasureSpec =
358                 MeasureSpec.makeMeasureSpec(dm.widthPixels, MeasureSpec.AT_MOST);
359         int childheightMeasureSpec =
360                 MeasureSpec.makeMeasureSpec(dm.heightPixels, MeasureSpec.AT_MOST);
361         View child = mAdapter.createView(mLinearLayout);
362         child.measure(childWidthMeasureSpec, childheightMeasureSpec);
363         mNumItemsInOneScreenful =
364                 (int) FloatMath.ceil(dm.heightPixels / (float) child.getMeasuredHeight());
365         addToRecycledViews(child);
366 
367         for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) {
368             addToRecycledViews(mAdapter.createView(mLinearLayout));
369         }
370     }
371 
numItemsInOneScreenful()372     public int numItemsInOneScreenful() {
373         return mNumItemsInOneScreenful;
374     }
375 
376     @Override
setLayoutTransition(LayoutTransition transition)377     public void setLayoutTransition(LayoutTransition transition) {
378         // The layout transition applies to our embedded LinearLayout
379         mLinearLayout.setLayoutTransition(transition);
380     }
381 
setCallback(RecentsCallback callback)382     public void setCallback(RecentsCallback callback) {
383         mCallback = callback;
384     }
385 }
386