TaskView.java revision 4d7b092a866d2fce3e11b5a12cda2b87a83af52d
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.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.TimeInterpolator;
23import android.content.Context;
24import android.graphics.Canvas;
25import android.graphics.Paint;
26import android.graphics.Path;
27import android.graphics.Point;
28import android.graphics.Rect;
29import android.graphics.RectF;
30import android.graphics.Typeface;
31import android.view.Gravity;
32import android.view.View;
33import android.view.animation.AccelerateDecelerateInterpolator;
34import android.view.animation.AccelerateInterpolator;
35import android.view.animation.DecelerateInterpolator;
36import android.widget.FrameLayout;
37import android.widget.ImageView;
38import com.android.systemui.recents.Console;
39import com.android.systemui.recents.Constants;
40import com.android.systemui.recents.RecentsConfiguration;
41import com.android.systemui.recents.model.Task;
42import com.android.systemui.recents.model.TaskCallbacks;
43
44/** The TaskView callbacks */
45interface TaskViewCallbacks {
46    public void onTaskIconClicked(TaskView tv);
47    // public void onTaskViewReboundToTask(TaskView tv, Task t);
48}
49
50/** The task thumbnail view */
51class TaskThumbnailView extends ImageView {
52    Task mTask;
53    int mBarColor;
54
55    Path mRoundedRectClipPath = new Path();
56
57    public TaskThumbnailView(Context context) {
58        super(context);
59        setScaleType(ScaleType.FIT_XY);
60    }
61
62    /** Binds the thumbnail view to the task */
63    void rebindToTask(Task t, boolean animate) {
64        mTask = t;
65        if (t.thumbnail != null) {
66            // Update the bar color
67            if (Constants.Values.TaskView.DrawColoredTaskBars) {
68                int[] colors = {0xFFCC0C39, 0xFFE6781E, 0xFFC8CF02, 0xFF1693A7};
69                mBarColor = colors[mTask.intent.getComponent().getPackageName().length() % colors.length];
70            }
71
72            setImageBitmap(t.thumbnail);
73            if (animate) {
74                setAlpha(0f);
75                animate().alpha(1f)
76                        .setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration)
77                        .start();
78            }
79        }
80    }
81
82    /** Unbinds the thumbnail view from the task */
83    void unbindFromTask() {
84        mTask = null;
85        setImageDrawable(null);
86    }
87
88    @Override
89    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
90        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
91
92        // Update the rounded rect clip path
93        RecentsConfiguration config = RecentsConfiguration.getInstance();
94        float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps);
95        mRoundedRectClipPath.reset();
96        mRoundedRectClipPath.addRoundRect(new RectF(0, 0, getMeasuredWidth(), getMeasuredHeight()),
97                radius, radius, Path.Direction.CW);
98    }
99
100    @Override
101    protected void onDraw(Canvas canvas) {
102        if (Constants.Values.TaskView.UseRoundedCorners) {
103            canvas.clipPath(mRoundedRectClipPath);
104        }
105
106        super.onDraw(canvas);
107
108        if (Constants.Values.TaskView.DrawColoredTaskBars) {
109            RecentsConfiguration config = RecentsConfiguration.getInstance();
110            int taskBarHeight = config.pxFromDp(Constants.Values.TaskView.TaskBarHeightDps);
111            // XXX: If we actually use this, this should be pulled out into a TextView that we
112            // inflate
113
114            // Draw the task bar
115            Rect r = new Rect();
116            Paint p = new Paint();
117            p.setAntiAlias(true);
118            p.setSubpixelText(true);
119            p.setColor(mBarColor);
120            p.setTypeface(Typeface.create("sans-serif-light", Typeface.NORMAL));
121            canvas.drawRect(0, 0, getMeasuredWidth(), taskBarHeight, p);
122            p.setColor(0xFFffffff);
123            p.setTextSize(68);
124            p.getTextBounds("X", 0, 1, r);
125            int offset = (int) (taskBarHeight - r.height()) / 2;
126            canvas.drawText(mTask.title, offset, offset + r.height(), p);
127        }
128    }
129}
130
131/* The task icon view */
132class TaskIconView extends ImageView {
133    Task mTask;
134
135    Path mClipPath = new Path();
136    float mClipRadius;
137    Point mClipOrigin = new Point();
138    ObjectAnimator mCircularClipAnimator;
139
140    public TaskIconView(Context context) {
141        super(context);
142        mClipPath = new Path();
143        mClipRadius = 1f;
144    }
145
146    /** Binds the icon view to the task */
147    void rebindToTask(Task t, boolean animate) {
148        mTask = t;
149        if (t.icon != null) {
150            setImageDrawable(t.icon);
151            if (animate) {
152                setAlpha(0f);
153                animate().alpha(1f)
154                        .setDuration(Constants.Values.TaskView.Animation.TaskDataUpdatedFadeDuration)
155                        .start();
156            }
157        }
158    }
159
160    /** Unbinds the icon view from the task */
161    void unbindFromTask() {
162        mTask = null;
163        setImageDrawable(null);
164    }
165
166    /** Sets the circular clip radius on the icon */
167    public void setCircularClipRadius(float r) {
168        Console.log(Constants.DebugFlags.UI.Clipping, "[TaskView|setCircularClip]", "" + r);
169        mClipRadius = r;
170        invalidate();
171    }
172
173    /** Gets the circular clip radius on the icon */
174    public float getCircularClipRadius() {
175        return mClipRadius;
176    }
177
178    /** Animates the circular clip radius on the icon */
179    void animateCircularClip(boolean brNotTl, float newRadius, int duration, int startDelay,
180                             TimeInterpolator interpolator,
181                             AnimatorListenerAdapter listener) {
182        if (mCircularClipAnimator != null) {
183            mCircularClipAnimator.cancel();
184            mCircularClipAnimator.removeAllListeners();
185        }
186        if (brNotTl) {
187            mClipOrigin.set(0, 0);
188        } else {
189            mClipOrigin.set(getMeasuredWidth(), getMeasuredHeight());
190        }
191        mCircularClipAnimator = ObjectAnimator.ofFloat(this, "circularClipRadius", newRadius);
192        mCircularClipAnimator.setStartDelay(startDelay);
193        mCircularClipAnimator.setDuration(duration);
194        mCircularClipAnimator.setInterpolator(interpolator);
195        if (listener != null) {
196            mCircularClipAnimator.addListener(listener);
197        }
198        mCircularClipAnimator.start();
199    }
200
201    @Override
202    protected void onDraw(Canvas canvas) {
203        int saveCount = canvas.save(Canvas.CLIP_SAVE_FLAG);
204        int width = getMeasuredWidth();
205        int height = getMeasuredHeight();
206        int maxSize = (int) Math.ceil(Math.sqrt(width * width + height * height));
207        mClipPath.reset();
208        mClipPath.addCircle(mClipOrigin.x, mClipOrigin.y, mClipRadius * maxSize, Path.Direction.CW);
209        canvas.clipPath(mClipPath);
210        super.onDraw(canvas);
211        canvas.restoreToCount(saveCount);
212    }
213}
214
215/* A task view */
216public class TaskView extends FrameLayout implements View.OnClickListener, TaskCallbacks {
217    Task mTask;
218    TaskThumbnailView mThumbnailView;
219    TaskIconView mIconView;
220    TaskViewCallbacks mCb;
221
222    public TaskView(Context context) {
223        super(context);
224        mThumbnailView = new TaskThumbnailView(context);
225        mIconView = new TaskIconView(context);
226        mIconView.setOnClickListener(this);
227        addView(mThumbnailView);
228        addView(mIconView);
229
230        RecentsConfiguration config = RecentsConfiguration.getInstance();
231        int barHeight = config.pxFromDp(Constants.Values.TaskView.TaskBarHeightDps);
232        int iconSize = config.pxFromDp(Constants.Values.TaskView.TaskIconSizeDps);
233        int offset = barHeight - (iconSize / 2);
234
235        // XXX: Lets keep the icon in the corner for the time being
236        offset = iconSize / 4;
237
238        /*
239        ((LayoutParams) mThumbnailView.getLayoutParams()).leftMargin = barHeight / 2;
240        ((LayoutParams) mThumbnailView.getLayoutParams()).rightMargin = barHeight / 2;
241        ((LayoutParams) mThumbnailView.getLayoutParams()).bottomMargin = barHeight;
242        */
243        ((LayoutParams) mIconView.getLayoutParams()).gravity = Gravity.END;
244        ((LayoutParams) mIconView.getLayoutParams()).width = iconSize;
245        ((LayoutParams) mIconView.getLayoutParams()).height = iconSize;
246        ((LayoutParams) mIconView.getLayoutParams()).topMargin = offset;
247        ((LayoutParams) mIconView.getLayoutParams()).rightMargin = offset;
248    }
249
250    /** Set the task and callback */
251    void bindToTask(Task t, TaskViewCallbacks cb) {
252        mTask = t;
253        mTask.setCallbacks(this);
254        mCb = cb;
255    }
256
257    /** Actually synchronizes the model data into the views */
258    private void syncToTask() {
259        mThumbnailView.rebindToTask(mTask, false);
260        mIconView.rebindToTask(mTask, false);
261    }
262
263    /** Unset the task and callback */
264    private void unbindFromTask() {
265        mTask.setCallbacks(null);
266        mThumbnailView.unbindFromTask();
267        mIconView.unbindFromTask();
268    }
269
270    /** Gets the task */
271    Task getTask() {
272        return mTask;
273    }
274
275    /** Synchronizes this view's properties with the task's transform */
276    void updateViewPropertiesFromTask(TaskViewTransform animateFromTransform,
277                                      TaskViewTransform transform, int duration) {
278        if (duration > 0) {
279            if (animateFromTransform != null) {
280                setTranslationY(animateFromTransform.translationY);
281                setScaleX(animateFromTransform.scale);
282                setScaleY(animateFromTransform.scale);
283            }
284            animate().translationY(transform.translationY)
285                    .scaleX(transform.scale)
286                    .scaleY(transform.scale)
287                    .setDuration(duration)
288                    .setInterpolator(new AccelerateDecelerateInterpolator())
289                    .start();
290        } else {
291            setTranslationY(transform.translationY);
292            setScaleX(transform.scale);
293            setScaleY(transform.scale);
294        }
295    }
296
297    /** Resets this view's properties */
298    void resetViewProperties() {
299        setTranslationX(0f);
300        setTranslationY(0f);
301        setScaleX(1f);
302        setScaleY(1f);
303        setAlpha(1f);
304    }
305
306    /** Animates this task view as it enters recents */
307    public void animateOnEnterRecents() {
308        mIconView.setCircularClipRadius(0f);
309        mIconView.animateCircularClip(true, 1f,
310            Constants.Values.TaskView.Animation.TaskIconCircularClipInDuration,
311            300, new AccelerateInterpolator(), null);
312    }
313
314    /** Animates this task view as it exits recents */
315    public void animateOnLeavingRecents(final Runnable r) {
316        if (Constants.Values.TaskView.AnimateFrontTaskIconOnLeavingUseClip) {
317            mIconView.animateCircularClip(false, 0f,
318                Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration, 0,
319                new DecelerateInterpolator(),
320                new AnimatorListenerAdapter() {
321                    @Override
322                    public void onAnimationEnd(Animator animation) {
323                        r.run();
324                    }
325                });
326        } else {
327            mIconView.animate()
328                .alpha(0f)
329                .setDuration(Constants.Values.TaskView.Animation.TaskIconCircularClipOutDuration)
330                .setInterpolator(new DecelerateInterpolator())
331                .setListener(
332                    new AnimatorListenerAdapter() {
333                        @Override
334                        public void onAnimationEnd(Animator animation) {
335                            r.run();
336                        }
337                    })
338                .start();
339        }
340    }
341
342    /** Returns the rect we want to clip (it may not be the full rect) */
343    Rect getClippingRect(Rect outRect, boolean accountForRoundedRects) {
344        getHitRect(outRect);
345        // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
346        outRect.right = outRect.left + mThumbnailView.getRight();
347        outRect.bottom = outRect.top + mThumbnailView.getBottom();
348        // We need to shrink the next rect by the rounded corners since those are draw on
349        // top of the current view
350        if (accountForRoundedRects) {
351            RecentsConfiguration config = RecentsConfiguration.getInstance();
352            float radius = config.pxFromDp(Constants.Values.TaskView.RoundedCornerRadiusDps);
353            outRect.inset((int) radius, (int) radius);
354        }
355        return outRect;
356    }
357
358    /** Enable the hw layers on this task view */
359    void enableHwLayers() {
360        mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
361    }
362
363    /** Disable the hw layers on this task view */
364    void disableHwLayers() {
365        mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
366    }
367
368    /**** TaskCallbacks Implementation ****/
369
370    @Override
371    public void onTaskDataChanged(Task task) {
372        Console.log(Constants.DebugFlags.App.EnableBackgroundTaskLoading,
373                "[TaskView|onTaskDataChanged]", task);
374
375        // Only update this task view if the changed task is the same as the task for this view
376        if (mTask == task) {
377            mThumbnailView.rebindToTask(mTask, true);
378            mIconView.rebindToTask(mTask, true);
379        }
380    }
381
382    @Override
383    public void onTaskBound() {
384        syncToTask();
385    }
386
387    @Override
388    public void onTaskUnbound() {
389        unbindFromTask();
390    }
391
392    @Override
393    public void onClick(View v) {
394        mCb.onTaskIconClicked(this);
395    }
396}