RecentsHorizontalScrollView.java revision ab48b681401628a191a4a90d4906fa88edde95ba
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
17package com.android.systemui.recent;
18
19import android.animation.LayoutTransition;
20import android.content.Context;
21import android.content.res.Configuration;
22import android.database.DataSetObserver;
23import android.graphics.Canvas;
24import android.util.AttributeSet;
25import android.util.Log;
26import android.view.MotionEvent;
27import android.view.View;
28import android.view.ViewConfiguration;
29import android.view.View.OnTouchListener;
30import android.widget.HorizontalScrollView;
31import android.widget.LinearLayout;
32
33import com.android.systemui.R;
34import com.android.systemui.SwipeHelper;
35import com.android.systemui.recent.RecentsPanelView.TaskDescriptionAdapter;
36
37public class RecentsHorizontalScrollView extends HorizontalScrollView
38    implements SwipeHelper.Callback {
39    private static final String TAG = RecentsPanelView.TAG;
40    private static final boolean DEBUG = RecentsPanelView.DEBUG;
41    private LinearLayout mLinearLayout;
42    private TaskDescriptionAdapter mAdapter;
43    private RecentsCallback mCallback;
44    protected int mLastScrollPosition;
45    private SwipeHelper mSwipeHelper;
46    private RecentsScrollViewPerformanceHelper mPerformanceHelper;
47
48    public RecentsHorizontalScrollView(Context context, AttributeSet attrs) {
49        super(context, attrs, 0);
50        float densityScale = getResources().getDisplayMetrics().density;
51        float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
52        mSwipeHelper = new SwipeHelper(SwipeHelper.Y, this, densityScale, pagingTouchSlop);
53        mPerformanceHelper = RecentsScrollViewPerformanceHelper.create(context, attrs, this, false);
54    }
55
56    private int scrollPositionOfMostRecent() {
57        return mLinearLayout.getWidth() - getWidth();
58    }
59
60    private void update() {
61        mLinearLayout.removeAllViews();
62        for (int i = 0; i < mAdapter.getCount(); i++) {
63            final View view = mAdapter.getView(i, null, mLinearLayout);
64
65            if (mPerformanceHelper != null) {
66                mPerformanceHelper.addViewCallback(view);
67            }
68
69            OnTouchListener noOpListener = new OnTouchListener() {
70                @Override
71                public boolean onTouch(View v, MotionEvent event) {
72                    return true;
73                }
74            };
75
76            view.setOnClickListener(new OnClickListener() {
77                public void onClick(View v) {
78                    mCallback.dismiss();
79                }
80            });
81            // We don't want a click sound when we dimiss recents
82            view.setSoundEffectsEnabled(false);
83
84            OnClickListener launchAppListener = new OnClickListener() {
85                public void onClick(View v) {
86                    mCallback.handleOnClick(view);
87                }
88            };
89
90            final View thumbnailView = view.findViewById(R.id.app_thumbnail);
91            OnLongClickListener longClickListener = new OnLongClickListener() {
92                public boolean onLongClick(View v) {
93                    final View anchorView = view.findViewById(R.id.app_description);
94                    mCallback.handleLongPress(view, anchorView, thumbnailView);
95                    return true;
96                }
97            };
98            thumbnailView.setClickable(true);
99            thumbnailView.setOnClickListener(launchAppListener);
100            thumbnailView.setOnLongClickListener(longClickListener);
101
102            // We don't want to dismiss recents if a user clicks on the app title
103            // (we also don't want to launch the app either, though, because the
104            // app title is a small target and doesn't have great click feedback)
105            final View appTitle = view.findViewById(R.id.app_label);
106            appTitle.setContentDescription(" ");
107            appTitle.setOnTouchListener(noOpListener);
108            mLinearLayout.addView(view);
109        }
110        // Scroll to end after layout.
111        post(new Runnable() {
112            public void run() {
113                mLastScrollPosition = scrollPositionOfMostRecent();
114                scrollTo(mLastScrollPosition, 0);
115            }
116        });
117    }
118
119    @Override
120    public void removeViewInLayout(final View view) {
121        dismissChild(view);
122    }
123
124    public boolean onInterceptTouchEvent(MotionEvent ev) {
125        if (DEBUG) Log.v(TAG, "onInterceptTouchEvent()");
126        return mSwipeHelper.onInterceptTouchEvent(ev) ||
127            super.onInterceptTouchEvent(ev);
128    }
129
130    @Override
131    public boolean onTouchEvent(MotionEvent ev) {
132        return mSwipeHelper.onTouchEvent(ev) ||
133            super.onTouchEvent(ev);
134    }
135
136    public boolean canChildBeDismissed(View v) {
137        return true;
138    }
139
140    public void dismissChild(View v) {
141        mSwipeHelper.dismissChild(v, 0);
142    }
143
144    public void onChildDismissed(View v) {
145        mLinearLayout.removeView(v);
146        mCallback.handleSwipe(v);
147    }
148
149    public void onBeginDrag(View v) {
150        // We do this so the underlying ScrollView knows that it won't get
151        // the chance to intercept events anymore
152        requestDisallowInterceptTouchEvent(true);
153        v.setActivated(true);
154    }
155
156    public void onDragCancelled(View v) {
157        v.setActivated(false);
158    }
159
160    public View getChildAtPosition(MotionEvent ev) {
161        final float x = ev.getX() + getScrollX();
162        final float y = ev.getY() + getScrollY();
163        for (int i = 0; i < mLinearLayout.getChildCount(); i++) {
164            View item = mLinearLayout.getChildAt(i);
165            if (x >= item.getLeft() && x < item.getRight()
166                && y >= item.getTop() && y < item.getBottom()) {
167                return item;
168            }
169        }
170        return null;
171    }
172
173    public View getChildContentView(View v) {
174        return v.findViewById(R.id.recent_item);
175    }
176
177    @Override
178    protected void onLayout (boolean changed, int left, int top, int right, int bottom) {
179        super.onLayout(changed, left, top, right, bottom);
180        if (mPerformanceHelper != null) {
181            mPerformanceHelper.onLayoutCallback();
182        }
183    }
184
185    @Override
186    public void draw(Canvas canvas) {
187        super.draw(canvas);
188
189        if (mPerformanceHelper != null) {
190            int paddingLeft = mPaddingLeft;
191            final boolean offsetRequired = isPaddingOffsetRequired();
192            if (offsetRequired) {
193                paddingLeft += getLeftPaddingOffset();
194            }
195
196            int left = mScrollX + paddingLeft;
197            int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
198            int top = mScrollY + getFadeTop(offsetRequired);
199            int bottom = top + getFadeHeight(offsetRequired);
200
201            if (offsetRequired) {
202                right += getRightPaddingOffset();
203                bottom += getBottomPaddingOffset();
204            }
205            mPerformanceHelper.drawCallback(canvas,
206                    left, right, top, bottom, mScrollX, mScrollY,
207                    0, 0,
208                    getLeftFadingEdgeStrength(), getRightFadingEdgeStrength());
209        }
210    }
211
212    @Override
213    public int getVerticalFadingEdgeLength() {
214        if (mPerformanceHelper != null) {
215            return mPerformanceHelper.getVerticalFadingEdgeLengthCallback();
216        } else {
217            return super.getVerticalFadingEdgeLength();
218        }
219    }
220
221    @Override
222    public int getHorizontalFadingEdgeLength() {
223        if (mPerformanceHelper != null) {
224            return mPerformanceHelper.getHorizontalFadingEdgeLengthCallback();
225        } else {
226            return super.getHorizontalFadingEdgeLength();
227        }
228    }
229
230    @Override
231    protected void onFinishInflate() {
232        super.onFinishInflate();
233        setScrollbarFadingEnabled(true);
234        mLinearLayout = (LinearLayout) findViewById(R.id.recents_linear_layout);
235        final int leftPadding = mContext.getResources()
236            .getDimensionPixelOffset(R.dimen.status_bar_recents_thumbnail_left_margin);
237        setOverScrollEffectPadding(leftPadding, 0);
238    }
239
240    @Override
241    public void onAttachedToWindow() {
242        if (mPerformanceHelper != null) {
243            mPerformanceHelper.onAttachedToWindowCallback(
244                    mCallback, mLinearLayout, isHardwareAccelerated());
245        }
246    }
247
248    @Override
249    protected void onConfigurationChanged(Configuration newConfig) {
250        super.onConfigurationChanged(newConfig);
251        float densityScale = getResources().getDisplayMetrics().density;
252        mSwipeHelper.setDensityScale(densityScale);
253        float pagingTouchSlop = ViewConfiguration.get(mContext).getScaledPagingTouchSlop();
254        mSwipeHelper.setPagingTouchSlop(pagingTouchSlop);
255    }
256
257    private void setOverScrollEffectPadding(int leftPadding, int i) {
258        // TODO Add to (Vertical)ScrollView
259    }
260
261    @Override
262    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
263        super.onSizeChanged(w, h, oldw, oldh);
264
265        // Skip this work if a transition is running; it sets the scroll values independently
266        // and should not have those animated values clobbered by this logic
267        LayoutTransition transition = mLinearLayout.getLayoutTransition();
268        if (transition != null && transition.isRunning()) {
269            return;
270        }
271        // Keep track of the last visible item in the list so we can restore it
272        // to the bottom when the orientation changes.
273        mLastScrollPosition = scrollPositionOfMostRecent();
274
275        // This has to happen post-layout, so run it "in the future"
276        post(new Runnable() {
277            public void run() {
278                // Make sure we're still not clobbering the transition-set values, since this
279                // runnable launches asynchronously
280                LayoutTransition transition = mLinearLayout.getLayoutTransition();
281                if (transition == null || !transition.isRunning()) {
282                    scrollTo(mLastScrollPosition, 0);
283                }
284            }
285        });
286    }
287
288    public void onRecentsVisibilityChanged() {
289        if (mPerformanceHelper != null) {
290            mPerformanceHelper.updateShowBackground();
291        }
292    }
293
294    @Override
295    protected void onVisibilityChanged(View changedView, int visibility) {
296        super.onVisibilityChanged(changedView, visibility);
297        // scroll to bottom after reloading
298        if (visibility == View.VISIBLE && changedView == this) {
299            post(new Runnable() {
300                public void run() {
301                    update();
302                }
303            });
304        }
305    }
306
307    public void setAdapter(TaskDescriptionAdapter adapter) {
308        mAdapter = adapter;
309        mAdapter.registerDataSetObserver(new DataSetObserver() {
310            public void onChanged() {
311                update();
312            }
313
314            public void onInvalidated() {
315                update();
316            }
317        });
318    }
319
320    @Override
321    public void setLayoutTransition(LayoutTransition transition) {
322        if (mPerformanceHelper != null) {
323            mPerformanceHelper.setLayoutTransitionCallback(transition);
324        }
325        // The layout transition applies to our embedded LinearLayout
326        mLinearLayout.setLayoutTransition(transition);
327    }
328
329    public void setCallback(RecentsCallback callback) {
330        mCallback = callback;
331    }
332}
333