blob: be42bc0c9b32577a1abaa2f9625fb9f5c7cbd070 [file] [log] [blame]
Jim Miller9f0f0e02011-05-17 20:06:29 -07001/*
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
17package com.android.systemui.recent;
18
Jim Miller9f0f0e02011-05-17 20:06:29 -070019import android.animation.LayoutTransition;
Jim Miller9f0f0e02011-05-17 20:06:29 -070020import android.content.Context;
Jim Miller3c728fe2011-07-07 20:53:52 -070021import android.content.res.Configuration;
Jim Miller9f0f0e02011-05-17 20:06:29 -070022import android.database.DataSetObserver;
Michael Jurka3cd0a592011-08-16 12:40:30 -070023import android.graphics.Canvas;
Jim Miller9f0f0e02011-05-17 20:06:29 -070024import android.util.AttributeSet;
Michael Jurka99a96552012-01-27 17:23:38 -080025import android.util.DisplayMetrics;
26import android.util.FloatMath;
Jim Miller9f0f0e02011-05-17 20:06:29 -070027import android.util.Log;
Jim Miller9f0f0e02011-05-17 20:06:29 -070028import android.view.MotionEvent;
Jim Miller9f0f0e02011-05-17 20:06:29 -070029import android.view.View;
Michael Jurka99a96552012-01-27 17:23:38 -080030import android.view.ViewConfiguration;
31import android.view.ViewTreeObserver;
32import android.view.ViewTreeObserver.OnGlobalLayoutListener;
Jim Miller9f0f0e02011-05-17 20:06:29 -070033import android.widget.HorizontalScrollView;
34import android.widget.LinearLayout;
35
36import com.android.systemui.R;
Michael Jurka07d40462011-07-19 10:54:38 -070037import com.android.systemui.SwipeHelper;
Michael Jurkaab48b682011-09-12 15:39:45 -070038import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
Jim Miller9f0f0e02011-05-17 20:06:29 -070039
Michael Jurkad1a040c2012-06-06 10:05:46 -070040import java.util.HashSet;
41import java.util.Iterator;
Michael Jurka99a96552012-01-27 17:23:38 -080042
Michael Jurka07d40462011-07-19 10:54:38 -070043public class RecentsHorizontalScrollView extends HorizontalScrollView
Michael Jurka4eaa9832012-02-29 15:51:49 -080044 implements SwipeHelper.Callback, RecentsPanelView.RecentsScrollView {
Jim Miller3c728fe2011-07-07 20:53:52 -070045 private static final String TAG = RecentsPanelView.TAG;
46 private static final boolean DEBUG = RecentsPanelView.DEBUG;
Jim Miller9f0f0e02011-05-17 20:06:29 -070047 private LinearLayout mLinearLayout;
Michael Jurkaab48b682011-09-12 15:39:45 -070048 private TaskDescriptionAdapter mAdapter;
Jim Miller9f0f0e02011-05-17 20:06:29 -070049 private RecentsCallback mCallback;
50 protected int mLastScrollPosition;
Michael Jurka07d40462011-07-19 10:54:38 -070051 private SwipeHelper mSwipeHelper;
Michael Jurka4a6bc552013-09-17 17:42:54 +020052 private FadedEdgeDrawHelper mFadedEdgeDrawHelper;
Michael Jurkad1a040c2012-06-06 10:05:46 -070053 private HashSet<View> mRecycledViews;
Michael Jurka99a96552012-01-27 17:23:38 -080054 private int mNumItemsInOneScreenful;
Michael Jurka4a6bc552013-09-17 17:42:54 +020055 private Runnable mOnScrollListener;
Michael Jurka3cd0a592011-08-16 12:40:30 -070056
Jim Miller9f0f0e02011-05-17 20:06:29 -070057 public RecentsHorizontalScrollView(Context context, AttributeSet attrs) {
58 super(context, attrs, 0);
Michael Jurka07d40462011-07-19 10:54:38 -070059 float densityScale = getResources().getDisplayMetrics().density;
60 float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
61 mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, densityScale, pagingTouchSlop);
Michael Jurka4a6bc552013-09-17 17:42:54 +020062 mFadedEdgeDrawHelper = FadedEdgeDrawHelper.create(context, attrs, this, false);
Michael Jurkad1a040c2012-06-06 10:05:46 -070063 mRecycledViews = new HashSet<View>();
Jim Miller9f0f0e02011-05-17 20:06:29 -070064 }
65
Michael Jurka4eaa9832012-02-29 15:51:49 -080066 public void setMinSwipeAlpha(float minAlpha) {
67 mSwipeHelper.setMinAlpha(minAlpha);
68 }
69
Jim Miller9f0f0e02011-05-17 20:06:29 -070070 private int scrollPositionOfMostRecent() {
71 return mLinearLayout.getWidth() - getWidth();
72 }
73
Michael Jurka261277e2012-06-01 04:06:45 -070074 private void addToRecycledViews(View v) {
75 if (mRecycledViews.size() < mNumItemsInOneScreenful) {
76 mRecycledViews.add(v);
77 }
78 }
79
Michael Jurkae5923632012-10-03 15:29:36 +020080 public View findViewForTask(int persistentTaskId) {
Michael Jurkacb2522c2012-04-13 09:32:47 -070081 for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
82 View v = mLinearLayout.getChildAt(i);
83 RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) v.getTag();
Michael Jurkae5923632012-10-03 15:29:36 +020084 if (holder.taskDescription.persistentTaskId == persistentTaskId) {
Michael Jurkacb2522c2012-04-13 09:32:47 -070085 return v;
86 }
87 }
88 return null;
89 }
90
Jim Millerc0d27312011-07-14 18:54:01 -070091 private void update() {
Michael Jurka99a96552012-01-27 17:23:38 -080092 for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
93 View v = mLinearLayout.getChildAt(i);
Michael Jurka261277e2012-06-01 04:06:45 -070094 addToRecycledViews(v);
Michael Jurka99a96552012-01-27 17:23:38 -080095 mAdapter.recycleView(v);
96 }
97 LayoutTransition transitioner = getLayoutTransition();
98 setLayoutTransition(null);
99
Jim Miller9f0f0e02011-05-17 20:06:29 -0700100 mLinearLayout.removeAllViews();
Michael Jurkad1a040c2012-06-06 10:05:46 -0700101 Iterator<View> recycledViews = mRecycledViews.iterator();
Jim Miller9f0f0e02011-05-17 20:06:29 -0700102 for (int i = 0; i < mAdapter.getCount(); i++) {
Michael Jurka99a96552012-01-27 17:23:38 -0800103 View old = null;
Michael Jurkad1a040c2012-06-06 10:05:46 -0700104 if (recycledViews.hasNext()) {
105 old = recycledViews.next();
106 recycledViews.remove();
Michael Jurka99a96552012-01-27 17:23:38 -0800107 old.setVisibility(VISIBLE);
108 }
109
110 final View view = mAdapter.getView(i, old, mLinearLayout);
Michael Jurka07d40462011-07-19 10:54:38 -0700111
Michael Jurka4a6bc552013-09-17 17:42:54 +0200112 if (mFadedEdgeDrawHelper != null) {
113 mFadedEdgeDrawHelper.addViewCallback(view);
Michael Jurka3cd0a592011-08-16 12:40:30 -0700114 }
115
Michael Jurka7daf95d2011-09-30 11:07:30 -0700116 OnTouchListener noOpListener = new OnTouchListener() {
117 @Override
118 public boolean onTouch(View v, MotionEvent event) {
119 return true;
120 }
121 };
122
Michael Jurka7725a4e2011-09-02 15:55:27 -0700123 view.setOnClickListener(new OnClickListener() {
124 public void onClick(View v) {
125 mCallback.dismiss();
126 }
127 });
Michael Jurka0e8063a2011-09-09 15:31:55 -0700128 // We don't want a click sound when we dimiss recents
129 view.setSoundEffectsEnabled(false);
Michael Jurka7725a4e2011-09-02 15:55:27 -0700130
131 OnClickListener launchAppListener = new OnClickListener() {
Michael Jurka07d40462011-07-19 10:54:38 -0700132 public void onClick(View v) {
133 mCallback.handleOnClick(view);
134 }
Michael Jurka7725a4e2011-09-02 15:55:27 -0700135 };
Michael Jurka7daf95d2011-09-30 11:07:30 -0700136
Michael Jurka99a96552012-01-27 17:23:38 -0800137 RecentsPanelView.ViewHolder holder = (RecentsPanelView.ViewHolder) view.getTag();
138 final View thumbnailView = holder.thumbnailView;
Michael Jurka0e8063a2011-09-09 15:31:55 -0700139 OnLongClickListener longClickListener = new OnLongClickListener() {
140 public boolean onLongClick(View v) {
141 final View anchorView = view.findViewById(R.id.app_description);
Michael Jurka0e8063a2011-09-09 15:31:55 -0700142 mCallback.handleLongPress(view, anchorView, thumbnailView);
143 return true;
144 }
145 };
Michael Jurka7daf95d2011-09-30 11:07:30 -0700146 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)
Michael Jurka7725a4e2011-09-02 15:55:27 -0700153 final View appTitle = view.findViewById(R.id.app_label);
Michael Jurka7daf95d2011-09-30 11:07:30 -0700154 appTitle.setContentDescription(" ");
155 appTitle.setOnTouchListener(noOpListener);
Jim Miller9f0f0e02011-05-17 20:06:29 -0700156 mLinearLayout.addView(view);
157 }
Michael Jurka99a96552012-01-27 17:23:38 -0800158 setLayoutTransition(transitioner);
159
Michael Jurka841594b2012-10-02 14:42:53 +0200160 // Scroll to end after initial layout.
Michael Jurka99a96552012-01-27 17:23:38 -0800161
162 final OnGlobalLayoutListener updateScroll = new OnGlobalLayoutListener() {
163 public void onGlobalLayout() {
164 mLastScrollPosition = scrollPositionOfMostRecent();
165 scrollTo(mLastScrollPosition, 0);
Michael Jurka841594b2012-10-02 14:42:53 +0200166 final ViewTreeObserver observer = getViewTreeObserver();
Michael Jurka99a96552012-01-27 17:23:38 -0800167 if (observer.isAlive()) {
168 observer.removeOnGlobalLayoutListener(this);
169 }
170 }
171 };
Michael Jurka841594b2012-10-02 14:42:53 +0200172 getViewTreeObserver().addOnGlobalLayoutListener(updateScroll);
Jim Miller9f0f0e02011-05-17 20:06:29 -0700173 }
174
175 @Override
Jim Millerc0d27312011-07-14 18:54:01 -0700176 public void removeViewInLayout(final View view) {
Michael Jurka07d40462011-07-19 10:54:38 -0700177 dismissChild(view);
Jim Millerc0d27312011-07-14 18:54:01 -0700178 }
179
Jim Miller9f0f0e02011-05-17 20:06:29 -0700180 public boolean onInterceptTouchEvent(MotionEvent ev) {
Jim Millerc0d27312011-07-14 18:54:01 -0700181 if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
Michael Jurka07d40462011-07-19 10:54:38 -0700182 return mSwipeHelper.onInterceptTouchEvent(ev) ||
183 super.onInterceptTouchEvent(ev);
Jim Miller9f0f0e02011-05-17 20:06:29 -0700184 }
185
186 @Override
187 public boolean onTouchEvent(MotionEvent ev) {
Michael Jurka07d40462011-07-19 10:54:38 -0700188 return mSwipeHelper.onTouchEvent(ev) ||
189 super.onTouchEvent(ev);
190 }
Jim Miller9f0f0e02011-05-17 20:06:29 -0700191
Michael Jurka07d40462011-07-19 10:54:38 -0700192 public boolean canChildBeDismissed(View v) {
Jim Miller9f0f0e02011-05-17 20:06:29 -0700193 return true;
194 }
195
Michael Jurka07d40462011-07-19 10:54:38 -0700196 public void dismissChild(View v) {
197 mSwipeHelper.dismissChild(v, 0);
Jim Millerc0d27312011-07-14 18:54:01 -0700198 }
199
Michael Jurka07d40462011-07-19 10:54:38 -0700200 public void onChildDismissed(View v) {
Michael Jurka261277e2012-06-01 04:06:45 -0700201 addToRecycledViews(v);
Michael Jurka07d40462011-07-19 10:54:38 -0700202 mLinearLayout.removeView(v);
203 mCallback.handleSwipe(v);
Michael Jurka2db72fc2012-02-23 17:16:11 -0800204 // 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);
Michael Jurka07d40462011-07-19 10:54:38 -0700209 }
210
211 public void onBeginDrag(View v) {
Michael Jurka13451a42011-08-22 15:54:21 -0700212 // We do this so the underlying ScrollView knows that it won't get
213 // the chance to intercept events anymore
214 requestDisallowInterceptTouchEvent(true);
Peter Ng622a9762011-08-29 10:56:53 -0700215 }
216
217 public void onDragCancelled(View v) {
Michael Jurka07d40462011-07-19 10:54:38 -0700218 }
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;
Jim Miller9f0f0e02011-05-17 20:06:29 -0700228 }
229 }
Michael Jurka07d40462011-07-19 10:54:38 -0700230 return null;
231 }
232
233 public View getChildContentView(View v) {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700234 return v.findViewById(R.id.recent_item);
235 }
236
237 @Override
Michael Jurka4a6bc552013-09-17 17:42:54 +0200238 public void drawFadedEdges(Canvas canvas, int left, int right, int top, int bottom) {
239 if (mFadedEdgeDrawHelper != null) {
Michael Jurka3cd0a592011-08-16 12:40:30 -0700240
Michael Jurka4a6bc552013-09-17 17:42:54 +0200241 mFadedEdgeDrawHelper.drawCallback(canvas,
Michael Jurka3cd0a592011-08-16 12:40:30 -0700242 left, right, top, bottom, mScrollX, mScrollY,
243 0, 0,
Michael Jurka31a6fb32013-03-20 16:44:13 +0100244 getLeftFadingEdgeStrength(), getRightFadingEdgeStrength(), mPaddingTop);
Michael Jurka3cd0a592011-08-16 12:40:30 -0700245 }
246 }
247
248 @Override
Michael Jurka4a6bc552013-09-17 17:42:54 +0200249 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
Michael Jurka3cd0a592011-08-16 12:40:30 -0700261 public int getVerticalFadingEdgeLength() {
Michael Jurka4a6bc552013-09-17 17:42:54 +0200262 if (mFadedEdgeDrawHelper != null) {
263 return mFadedEdgeDrawHelper.getVerticalFadingEdgeLength();
Michael Jurka3cd0a592011-08-16 12:40:30 -0700264 } else {
265 return super.getVerticalFadingEdgeLength();
266 }
267 }
268
269 @Override
270 public int getHorizontalFadingEdgeLength() {
Michael Jurka4a6bc552013-09-17 17:42:54 +0200271 if (mFadedEdgeDrawHelper != null) {
272 return mFadedEdgeDrawHelper.getHorizontalFadingEdgeLength();
Michael Jurka3cd0a592011-08-16 12:40:30 -0700273 } else {
274 return super.getHorizontalFadingEdgeLength();
275 }
Jim Miller9f0f0e02011-05-17 20:06:29 -0700276 }
277
278 @Override
279 protected void onFinishInflate() {
280 super.onFinishInflate();
Jim Miller9f0f0e02011-05-17 20:06:29 -0700281 setScrollbarFadingEnabled(true);
Jim Miller9f0f0e02011-05-17 20:06:29 -0700282 mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout);
Jim Miller9f0f0e02011-05-17 20:06:29 -0700283 final int leftPadding = mContext.getResources()
284 .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin);
285 setOverScrollEffectPadding(leftPadding, 0);
286 }
287
Jim Miller3c728fe2011-07-07 20:53:52 -0700288 @Override
Michael Jurka3cd0a592011-08-16 12:40:30 -0700289 public void onAttachedToWindow() {
Michael Jurka4a6bc552013-09-17 17:42:54 +0200290 if (mFadedEdgeDrawHelper != null) {
291 mFadedEdgeDrawHelper.onAttachedToWindowCallback(mLinearLayout, isHardwareAccelerated());
Michael Jurka3cd0a592011-08-16 12:40:30 -0700292 }
293 }
294
295 @Override
Jim Miller3c728fe2011-07-07 20:53:52 -0700296 protected void onConfigurationChanged(Configuration newConfig) {
297 super.onConfigurationChanged(newConfig);
Michael Jurka07d40462011-07-19 10:54:38 -0700298 float densityScale = getResources().getDisplayMetrics().density;
299 mSwipeHelper.setDensityScale(densityScale);
300 float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
301 mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
Jim Miller3c728fe2011-07-07 20:53:52 -0700302 }
303
Jim Miller9f0f0e02011-05-17 20:06:29 -0700304 private void setOverScrollEffectPadding(int leftPadding, int i) {
Jim Miller3c728fe2011-07-07 20:53:52 -0700305 // TODO Add to (Vertical)ScrollView
Jim Miller9f0f0e02011-05-17 20:06:29 -0700306 }
307
308 @Override
309 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
310 super.onSizeChanged(w, h, oldw, oldh);
Chet Haase81abe872011-08-10 13:53:43 -0700311
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 }
Jim Miller9f0f0e02011-05-17 20:06:29 -0700318 // 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() {
Chet Haase81abe872011-08-10 13:53:43 -0700325 // 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 }
Jim Miller9f0f0e02011-05-17 20:06:29 -0700331 }
332 });
333 }
334
Michael Jurkaab48b682011-09-12 15:39:45 -0700335 public void setAdapter(TaskDescriptionAdapter adapter) {
Jim Miller9f0f0e02011-05-17 20:06:29 -0700336 mAdapter = adapter;
337 mAdapter.registerDataSetObserver(new DataSetObserver() {
338 public void onChanged() {
339 update();
340 }
341
342 public void onInvalidated() {
343 update();
344 }
345 });
Michael Jurka99a96552012-01-27 17:23:38 -0800346 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());
Michael Jurka261277e2012-06-01 04:06:45 -0700355 addToRecycledViews(child);
Michael Jurka99a96552012-01-27 17:23:38 -0800356
357 for (int i = 0; i < mNumItemsInOneScreenful - 1; i++) {
Michael Jurka261277e2012-06-01 04:06:45 -0700358 addToRecycledViews(mAdapter.createView(mLinearLayout));
Michael Jurka99a96552012-01-27 17:23:38 -0800359 }
360 }
361
362 public int numItemsInOneScreenful() {
363 return mNumItemsInOneScreenful;
Jim Miller9f0f0e02011-05-17 20:06:29 -0700364 }
365
366 @Override
367 public void setLayoutTransition(LayoutTransition transition) {
368 // The layout transition applies to our embedded LinearLayout
369 mLinearLayout.setLayoutTransition(transition);
370 }
371
Jim Miller9f0f0e02011-05-17 20:06:29 -0700372 public void setCallback(RecentsCallback callback) {
373 mCallback = callback;
374 }
Jim Miller9f0f0e02011-05-17 20:06:29 -0700375}