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