TaskView.java revision f5e22e71cb5f8699a4312c797af068f655cbe629
1/*
2 * Copyright (C) 2014 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.recents.views;
18
19import android.animation.TimeInterpolator;
20import android.animation.ValueAnimator;
21import android.content.Context;
22import android.graphics.Canvas;
23import android.graphics.Path;
24import android.graphics.Point;
25import android.graphics.Rect;
26import android.graphics.RectF;
27import android.util.AttributeSet;
28import android.view.MotionEvent;
29import android.view.View;
30import android.view.animation.AccelerateInterpolator;
31import android.widget.FrameLayout;
32import com.android.systemui.R;
33import com.android.systemui.recents.BakedBezierInterpolator;
34import com.android.systemui.recents.Constants;
35import com.android.systemui.recents.RecentsConfiguration;
36import com.android.systemui.recents.Utilities;
37import com.android.systemui.recents.model.Task;
38
39
40/* A task view */
41public class TaskView extends FrameLayout implements View.OnClickListener,
42        Task.TaskCallbacks {
43    /** The TaskView callbacks */
44    interface TaskViewCallbacks {
45        public void onTaskIconClicked(TaskView tv);
46        public void onTaskInfoPanelShown(TaskView tv);
47        public void onTaskInfoPanelHidden(TaskView tv);
48        public void onTaskAppInfoClicked(TaskView tv);
49
50        // public void onTaskViewReboundToTask(TaskView tv, Task t);
51    }
52
53    int mDim;
54    int mMaxDim;
55    TimeInterpolator mDimInterpolator = new AccelerateInterpolator();
56
57    Task mTask;
58    boolean mTaskDataLoaded;
59    boolean mTaskInfoPaneVisible;
60    Point mLastTouchDown = new Point();
61    Path mRoundedRectClipPath = new Path();
62
63    TaskThumbnailView mThumbnailView;
64    TaskBarView mBarView;
65    TaskInfoView mInfoView;
66    TaskViewCallbacks mCb;
67
68
69    public TaskView(Context context) {
70        this(context, null);
71    }
72
73    public TaskView(Context context, AttributeSet attrs) {
74        this(context, attrs, 0);
75    }
76
77    public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
78        this(context, attrs, defStyleAttr, 0);
79    }
80
81    public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
82        super(context, attrs, defStyleAttr, defStyleRes);
83        setWillNotDraw(false);
84    }
85
86    @Override
87    protected void onFinishInflate() {
88        RecentsConfiguration config = RecentsConfiguration.getInstance();
89        mMaxDim = config.taskStackMaxDim;
90
91        // Bind the views
92        mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
93        mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
94        mInfoView = (TaskInfoView) findViewById(R.id.task_view_info_pane);
95
96        if (mTaskDataLoaded) {
97            onTaskDataLoaded(false);
98        }
99    }
100
101    @Override
102    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
103        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
104
105        // Update the rounded rect clip path
106        RecentsConfiguration config = RecentsConfiguration.getInstance();
107        float radius = config.taskViewRoundedCornerRadiusPx;
108        mRoundedRectClipPath.reset();
109        mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
110                radius, radius, Path.Direction.CW);
111    }
112
113    @Override
114    public boolean onInterceptTouchEvent(MotionEvent ev) {
115        switch (ev.getAction()) {
116            case MotionEvent.ACTION_DOWN:
117            case MotionEvent.ACTION_MOVE:
118                mLastTouchDown.set((int) ev.getX(), (int) ev.getY());
119                break;
120        }
121        return super.onInterceptTouchEvent(ev);
122    }
123
124    /** Set callback */
125    void setCallbacks(TaskViewCallbacks cb) {
126        mCb = cb;
127    }
128
129    /** Gets the task */
130    Task getTask() {
131        return mTask;
132    }
133
134    /** Synchronizes this view's properties with the task's transform */
135    void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform,
136                                             TaskViewTransform toTransform, int duration) {
137        if (duration > 0) {
138            if (animateFromTransform != null) {
139                setTranslationY(animateFromTransform.translationY);
140                setScaleX(animateFromTransform.scale);
141                setScaleY(animateFromTransform.scale);
142                setAlpha(animateFromTransform.alpha);
143            }
144            animate().translationY(toTransform.translationY)
145                    .scaleX(toTransform.scale)
146                    .scaleY(toTransform.scale)
147                    .alpha(toTransform.alpha)
148                    .setDuration(duration)
149                    .setInterpolator(BakedBezierInterpolator.INSTANCE)
150                    .withLayer()
151                    .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
152                        @Override
153                        public void onAnimationUpdate(ValueAnimator animation) {
154                            updateDimOverlayFromScale();
155                        }
156                    })
157                    .start();
158        } else {
159            setTranslationY(toTransform.translationY);
160            setScaleX(toTransform.scale);
161            setScaleY(toTransform.scale);
162            setAlpha(toTransform.alpha);
163        }
164        updateDimOverlayFromScale();
165        invalidate();
166    }
167
168    /** Resets this view's properties */
169    void resetViewProperties() {
170        setTranslationX(0f);
171        setTranslationY(0f);
172        setScaleX(1f);
173        setScaleY(1f);
174        setAlpha(1f);
175        invalidate();
176    }
177
178    /**
179     * When we are un/filtering, this method will set up the transform that we are animating to,
180     * in order to hide the task.
181     */
182    void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
183        // Fade the view out and slide it away
184        toTransform.alpha = 0f;
185        toTransform.translationY += 200;
186    }
187
188    /**
189     * When we are un/filtering, this method will setup the transform that we are animating from,
190     * in order to show the task.
191     */
192    void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
193        // Fade the view in
194        fromTransform.alpha = 0f;
195    }
196
197    /** Animates this task view as it enters recents */
198    public void animateOnEnterRecents() {
199        RecentsConfiguration config = RecentsConfiguration.getInstance();
200        mBarView.setAlpha(0f);
201        mBarView.animate()
202                .alpha(1f)
203                .setStartDelay(235)
204                .setInterpolator(BakedBezierInterpolator.INSTANCE)
205                .setDuration(config.taskBarEnterAnimDuration)
206                .withLayer()
207                .start();
208    }
209
210    /** Animates this task view as it exits recents */
211    public void animateOnLeavingRecents(final Runnable r) {
212        RecentsConfiguration config = RecentsConfiguration.getInstance();
213        mBarView.animate()
214            .alpha(0f)
215            .setStartDelay(0)
216            .setInterpolator(BakedBezierInterpolator.INSTANCE)
217            .setDuration(config.taskBarExitAnimDuration)
218            .withLayer()
219            .withEndAction(new Runnable() {
220                @Override
221                public void run() {
222                    post(r);
223                }
224            })
225            .start();
226    }
227
228    /** Returns the rect we want to clip (it may not be the full rect) */
229    Rect getClippingRect(Rect outRect) {
230        getHitRect(outRect);
231        // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
232        outRect.right = outRect.left + mThumbnailView.getRight();
233        outRect.bottom = outRect.top + mThumbnailView.getBottom();
234        return outRect;
235    }
236
237    /** Returns whether this task has an info pane visible */
238    boolean isInfoPaneVisible() {
239        return mTaskInfoPaneVisible;
240    }
241
242    /** Shows the info pane if it is not visible. */
243    void showInfoPane(Rect taskVisibleRect) {
244        if (mTaskInfoPaneVisible) return;
245
246        // Remove the bar view from the visible rect and update the info pane contents
247        taskVisibleRect.top += mBarView.getMeasuredHeight();
248        mInfoView.updateContents(taskVisibleRect);
249
250        // Show the info pane and animate it into view
251        mInfoView.setVisibility(View.VISIBLE);
252        mInfoView.animateCircularClip(mLastTouchDown, 0f, 1f, null, true);
253        mInfoView.setOnClickListener(this);
254        mTaskInfoPaneVisible = true;
255
256        // Notify any callbacks
257        if (mCb != null) {
258            mCb.onTaskInfoPanelShown(this);
259        }
260    }
261
262    /** Hides the info pane if it is visible. */
263    void hideInfoPane() {
264        if (!mTaskInfoPaneVisible) return;
265        RecentsConfiguration config = RecentsConfiguration.getInstance();
266
267        // Cancel any circular clip animation
268        mInfoView.cancelCircularClipAnimation();
269
270        // Animate the info pane out
271        mInfoView.animate()
272                .alpha(0f)
273                .setDuration(config.taskViewInfoPaneAnimDuration)
274                .setInterpolator(BakedBezierInterpolator.INSTANCE)
275                .withLayer()
276                .withEndAction(new Runnable() {
277                    @Override
278                    public void run() {
279                        mInfoView.setVisibility(View.INVISIBLE);
280                        mInfoView.setOnClickListener(null);
281
282                        mInfoView.setAlpha(1f);
283                    }
284                })
285                .start();
286        mTaskInfoPaneVisible = false;
287
288        // Notify any callbacks
289        if (mCb != null) {
290            mCb.onTaskInfoPanelHidden(this);
291        }
292    }
293
294    /** Enable the hw layers on this task view */
295    void enableHwLayers() {
296        mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
297    }
298
299    /** Disable the hw layers on this task view */
300    void disableHwLayers() {
301        mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
302    }
303
304    /** Update the dim as a function of the scale of this view. */
305    void updateDimOverlayFromScale() {
306        float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
307        float scaleRange = 1f - minScale;
308        float dim = (1f - getScaleX()) / scaleRange;
309        dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f));
310        mDim = Math.max(0, Math.min(mMaxDim, (int) (dim * 255)));
311        invalidate();
312    }
313
314    @Override
315    public void draw(Canvas canvas) {
316        // Apply the rounded rect clip path on the whole view
317        canvas.clipPath(mRoundedRectClipPath);
318
319        super.draw(canvas);
320
321        // Apply the dim if necessary
322        if (mDim > 0) {
323            canvas.drawColor(mDim << 24);
324        }
325    }
326
327    /**** TaskCallbacks Implementation ****/
328
329    /** Binds this task view to the task */
330    public void onTaskBound(Task t) {
331        mTask = t;
332        mTask.setCallbacks(this);
333    }
334
335    @Override
336    public void onTaskDataLoaded(boolean reloadingTaskData) {
337        if (mThumbnailView != null && mBarView != null && mInfoView != null) {
338            // Bind each of the views to the new task data
339            mThumbnailView.rebindToTask(mTask, reloadingTaskData);
340            mBarView.rebindToTask(mTask, reloadingTaskData);
341            mInfoView.rebindToTask(mTask, reloadingTaskData);
342            // Rebind any listeners
343            mBarView.mApplicationIcon.setOnClickListener(this);
344            mInfoView.mAppInfoButton.setOnClickListener(this);
345        }
346        mTaskDataLoaded = true;
347    }
348
349    @Override
350    public void onTaskDataUnloaded() {
351        if (mThumbnailView != null && mBarView != null && mInfoView != null) {
352            // Unbind each of the views from the task data and remove the task callback
353            mTask.setCallbacks(null);
354            mThumbnailView.unbindFromTask();
355            mBarView.unbindFromTask();
356            // Unbind any listeners
357            mBarView.mApplicationIcon.setOnClickListener(null);
358            mInfoView.mAppInfoButton.setOnClickListener(null);
359        }
360        mTaskDataLoaded = false;
361    }
362
363    @Override
364    public void onClick(View v) {
365        if (v == mInfoView) {
366            // Do nothing
367        } else if (v == mBarView.mApplicationIcon) {
368            mCb.onTaskIconClicked(this);
369        } else if (v == mInfoView.mAppInfoButton) {
370            mCb.onTaskAppInfoClicked(this);
371        }
372    }
373}