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