TaskView.java revision 54e297a5bb143e60e29fd7dfe87e04a8cc96c72c
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.Outline;
24import android.graphics.Path;
25import android.graphics.Point;
26import android.graphics.Rect;
27import android.graphics.RectF;
28import android.util.AttributeSet;
29import android.view.MotionEvent;
30import android.view.View;
31import android.view.animation.AccelerateInterpolator;
32import android.widget.FrameLayout;
33import com.android.systemui.R;
34import com.android.systemui.recents.Constants;
35import com.android.systemui.recents.RecentsConfiguration;
36import com.android.systemui.recents.model.Task;
37
38
39/* A task view */
40public class TaskView extends FrameLayout implements View.OnClickListener,
41        Task.TaskCallbacks {
42    /** The TaskView callbacks */
43    interface TaskViewCallbacks {
44        public void onTaskIconClicked(TaskView tv);
45        public void onTaskInfoPanelShown(TaskView tv);
46        public void onTaskInfoPanelHidden(TaskView tv);
47        public void onTaskAppInfoClicked(TaskView tv);
48        public void onTaskDismissed(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        // Update the outline
113        Outline o = new Outline();
114        o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), radius);
115        setOutline(o);
116    }
117
118    @Override
119    public boolean onInterceptTouchEvent(MotionEvent ev) {
120        switch (ev.getAction()) {
121            case MotionEvent.ACTION_DOWN:
122            case MotionEvent.ACTION_MOVE:
123                mLastTouchDown.set((int) ev.getX(), (int) ev.getY());
124                break;
125        }
126        return super.onInterceptTouchEvent(ev);
127    }
128
129    /** Set callback */
130    void setCallbacks(TaskViewCallbacks cb) {
131        mCb = cb;
132    }
133
134    /** Gets the task */
135    Task getTask() {
136        return mTask;
137    }
138
139    /** Synchronizes this view's properties with the task's transform */
140    void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform,
141                                             TaskViewTransform toTransform, int duration) {
142        RecentsConfiguration config = RecentsConfiguration.getInstance();
143        int minZ = config.taskViewTranslationZMinPx;
144        int incZ = config.taskViewTranslationZIncrementPx;
145
146        // Update the bar view
147        mBarView.updateViewPropertiesToTaskTransform(animateFromTransform, toTransform, duration);
148
149        // Update this task view
150        if (duration > 0) {
151            if (animateFromTransform != null) {
152                setTranslationY(animateFromTransform.translationY);
153                if (Constants.DebugFlags.App.EnableShadows) {
154                    setTranslationZ(Math.max(minZ, minZ + (animateFromTransform.t * incZ)));
155                }
156                setScaleX(animateFromTransform.scale);
157                setScaleY(animateFromTransform.scale);
158                setAlpha(animateFromTransform.alpha);
159            }
160            if (Constants.DebugFlags.App.EnableShadows) {
161                animate().translationZ(Math.max(minZ, minZ + (toTransform.t * incZ)));
162            }
163            animate().translationY(toTransform.translationY)
164                    .scaleX(toTransform.scale)
165                    .scaleY(toTransform.scale)
166                    .alpha(toTransform.alpha)
167                    .setDuration(duration)
168                    .setInterpolator(config.defaultBezierInterpolator)
169                    .withLayer()
170                    .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
171                        @Override
172                        public void onAnimationUpdate(ValueAnimator animation) {
173                            updateDimOverlayFromScale();
174                        }
175                    })
176                    .start();
177        } else {
178            setTranslationY(toTransform.translationY);
179            if (Constants.DebugFlags.App.EnableShadows) {
180                setTranslationZ(Math.max(minZ, minZ + (toTransform.t * incZ)));
181            }
182            setScaleX(toTransform.scale);
183            setScaleY(toTransform.scale);
184            setAlpha(toTransform.alpha);
185        }
186        updateDimOverlayFromScale();
187        invalidate();
188    }
189
190    /** Resets this view's properties */
191    void resetViewProperties() {
192        setTranslationX(0f);
193        setTranslationY(0f);
194        if (Constants.DebugFlags.App.EnableShadows) {
195            setTranslationZ(0f);
196        }
197        setScaleX(1f);
198        setScaleY(1f);
199        setAlpha(1f);
200        invalidate();
201    }
202
203    /**
204     * When we are un/filtering, this method will set up the transform that we are animating to,
205     * in order to hide the task.
206     */
207    void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
208        // Fade the view out and slide it away
209        toTransform.alpha = 0f;
210        toTransform.translationY += 200;
211    }
212
213    /**
214     * When we are un/filtering, this method will setup the transform that we are animating from,
215     * in order to show the task.
216     */
217    void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
218        // Fade the view in
219        fromTransform.alpha = 0f;
220    }
221
222    /** Animates this task view as it enters recents */
223    public void animateOnEnterRecents() {
224        RecentsConfiguration config = RecentsConfiguration.getInstance();
225        mBarView.setAlpha(0f);
226        mBarView.animate()
227                .alpha(1f)
228                .setStartDelay(250)
229                .setInterpolator(config.defaultBezierInterpolator)
230                .setDuration(config.taskBarEnterAnimDuration)
231                .withLayer()
232                .start();
233    }
234
235    /** Animates this task view as it exits recents */
236    public void animateOnLeavingRecents(final Runnable r) {
237        RecentsConfiguration config = RecentsConfiguration.getInstance();
238        mBarView.animate()
239            .alpha(0f)
240            .setStartDelay(0)
241            .setInterpolator(config.defaultBezierInterpolator)
242            .setDuration(config.taskBarExitAnimDuration)
243            .withLayer()
244            .withEndAction(new Runnable() {
245                @Override
246                public void run() {
247                    post(r);
248                }
249            })
250            .start();
251    }
252
253    /** Animates the deletion of this task view */
254    public void animateRemoval(final Runnable r) {
255        RecentsConfiguration config = RecentsConfiguration.getInstance();
256        animate().translationX(config.taskViewRemoveAnimTranslationXPx)
257            .alpha(0f)
258            .setStartDelay(0)
259            .setInterpolator(config.defaultBezierInterpolator)
260            .setDuration(config.taskViewRemoveAnimDuration)
261            .withLayer()
262            .withEndAction(new Runnable() {
263                @Override
264                public void run() {
265                    post(r);
266                }
267            })
268            .start();
269    }
270
271    /** Returns the rect we want to clip (it may not be the full rect) */
272    Rect getClippingRect(Rect outRect) {
273        getHitRect(outRect);
274        // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
275        outRect.right = outRect.left + mThumbnailView.getRight();
276        outRect.bottom = outRect.top + mThumbnailView.getBottom();
277        return outRect;
278    }
279
280    /** Returns whether this task has an info pane visible */
281    boolean isInfoPaneVisible() {
282        return mTaskInfoPaneVisible;
283    }
284
285    /** Shows the info pane if it is not visible. */
286    void showInfoPane(Rect taskVisibleRect) {
287        if (mTaskInfoPaneVisible) return;
288
289        // Remove the bar view from the visible rect and update the info pane contents
290        taskVisibleRect.top += mBarView.getMeasuredHeight();
291        mInfoView.updateContents(taskVisibleRect);
292
293        // Show the info pane and animate it into view
294        mInfoView.setVisibility(View.VISIBLE);
295        mInfoView.animateCircularClip(mLastTouchDown, 0f, 1f, null, true);
296        mInfoView.setOnClickListener(this);
297        mTaskInfoPaneVisible = true;
298
299        // Notify any callbacks
300        if (mCb != null) {
301            mCb.onTaskInfoPanelShown(this);
302        }
303    }
304
305    /** Hides the info pane if it is visible. */
306    void hideInfoPane() {
307        if (!mTaskInfoPaneVisible) return;
308        RecentsConfiguration config = RecentsConfiguration.getInstance();
309
310        // Cancel any circular clip animation
311        mInfoView.cancelCircularClipAnimation();
312
313        // Animate the info pane out
314        mInfoView.animate()
315                .alpha(0f)
316                .setDuration(config.taskViewInfoPaneAnimDuration)
317                .setInterpolator(config.defaultBezierInterpolator)
318                .withLayer()
319                .withEndAction(new Runnable() {
320                    @Override
321                    public void run() {
322                        mInfoView.setVisibility(View.INVISIBLE);
323                        mInfoView.setOnClickListener(null);
324
325                        mInfoView.setAlpha(1f);
326                    }
327                })
328                .start();
329        mTaskInfoPaneVisible = false;
330
331        // Notify any callbacks
332        if (mCb != null) {
333            mCb.onTaskInfoPanelHidden(this);
334        }
335    }
336
337    /** Enable the hw layers on this task view */
338    void enableHwLayers() {
339        mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
340    }
341
342    /** Disable the hw layers on this task view */
343    void disableHwLayers() {
344        mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
345    }
346
347    /** Update the dim as a function of the scale of this view. */
348    void updateDimOverlayFromScale() {
349        float minScale = Constants.Values.TaskStackView.StackPeekMinScale;
350        float scaleRange = 1f - minScale;
351        float dim = (1f - getScaleX()) / scaleRange;
352        dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f));
353        mDim = Math.max(0, Math.min(mMaxDim, (int) (dim * 255)));
354        invalidate();
355    }
356
357    @Override
358    public void draw(Canvas canvas) {
359        // Apply the rounded rect clip path on the whole view
360        canvas.clipPath(mRoundedRectClipPath);
361
362        super.draw(canvas);
363
364        // Apply the dim if necessary
365        if (mDim > 0) {
366            canvas.drawColor(mDim << 24);
367        }
368    }
369
370    /**** TaskCallbacks Implementation ****/
371
372    /** Binds this task view to the task */
373    public void onTaskBound(Task t) {
374        mTask = t;
375        mTask.setCallbacks(this);
376    }
377
378    @Override
379    public void onTaskDataLoaded(boolean reloadingTaskData) {
380        if (mThumbnailView != null && mBarView != null && mInfoView != null) {
381            // Bind each of the views to the new task data
382            mThumbnailView.rebindToTask(mTask, reloadingTaskData);
383            mBarView.rebindToTask(mTask, reloadingTaskData);
384            mInfoView.rebindToTask(mTask, reloadingTaskData);
385            // Rebind any listeners
386            mBarView.mApplicationIcon.setOnClickListener(this);
387            mBarView.mDismissButton.setOnClickListener(this);
388            mInfoView.mAppInfoButton.setOnClickListener(this);
389        }
390        mTaskDataLoaded = true;
391    }
392
393    @Override
394    public void onTaskDataUnloaded() {
395        if (mThumbnailView != null && mBarView != null && mInfoView != null) {
396            // Unbind each of the views from the task data and remove the task callback
397            mTask.setCallbacks(null);
398            mThumbnailView.unbindFromTask();
399            mBarView.unbindFromTask();
400            // Unbind any listeners
401            mBarView.mApplicationIcon.setOnClickListener(null);
402            mInfoView.mAppInfoButton.setOnClickListener(null);
403        }
404        mTaskDataLoaded = false;
405    }
406
407    @Override
408    public void onClick(View v) {
409        if (v == mInfoView) {
410            hideInfoPane();
411        } else if (v == mBarView.mApplicationIcon) {
412            mCb.onTaskIconClicked(this);
413        } else if (v == mBarView.mDismissButton) {
414            // Animate out the view and call the callback
415            final TaskView tv = this;
416            animateRemoval(new Runnable() {
417                @Override
418                public void run() {
419                    mCb.onTaskDismissed(tv);
420                }
421            });
422        } else if (v == mInfoView.mAppInfoButton) {
423            mCb.onTaskAppInfoClicked(this);
424        }
425    }
426}