blob: 9ef74ca0c0817737a721201a5a1cd29ab39b1182 [file] [log] [blame]
Winson Chung303e1ff2014-03-07 15:06:19 -08001/*
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 */
Winson Chung4d7b0922014-03-13 17:14:17 -0700258 private void syncToTask() {
Winson Chung303e1ff2014-03-07 15:06:19 -0800259 mThumbnailView.rebindToTask(mTask, false);
260 mIconView.rebindToTask(mTask, false);
261 }
262
263 /** Unset the task and callback */
Winson Chung4d7b0922014-03-13 17:14:17 -0700264 private void unbindFromTask() {
Winson Chung303e1ff2014-03-07 15:06:19 -0800265 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() {
Winson Chung303e1ff2014-03-07 15:06:19 -0800360 mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
361 }
362
363 /** Disable the hw layers on this task view */
364 void disableHwLayers() {
Winson Chung303e1ff2014-03-07 15:06:19 -0800365 mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
366 }
367
Winson Chung4d7b0922014-03-13 17:14:17 -0700368 /**** TaskCallbacks Implementation ****/
369
Winson Chung303e1ff2014-03-07 15:06:19 -0800370 @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
Winson Chung4d7b0922014-03-13 17:14:17 -0700383 public void onTaskBound() {
384 syncToTask();
385 }
386
387 @Override
388 public void onTaskUnbound() {
389 unbindFromTask();
390 }
391
392 @Override
Winson Chung303e1ff2014-03-07 15:06:19 -0800393 public void onClick(View v) {
394 mCb.onTaskIconClicked(this);
395 }
396}