blob: 16a3f4520a64cec6d6826fbdd8d32a37a63a116b [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
Winson Chung14926462014-04-14 18:57:14 -070019import android.animation.TimeInterpolator;
20import android.animation.ValueAnimator;
Winson Chung6cb485f2014-05-19 10:30:43 -070021import android.content.ContentResolver;
Winson Chung303e1ff2014-03-07 15:06:19 -080022import android.content.Context;
Winson Chung14926462014-04-14 18:57:14 -070023import android.graphics.Canvas;
Winson Chung96e3bc12014-05-06 16:44:12 -070024import android.graphics.Outline;
Winson Chung14926462014-04-14 18:57:14 -070025import android.graphics.Path;
Winson Chung9f9679d2014-04-11 16:49:09 -070026import android.graphics.Point;
Winson Chung303e1ff2014-03-07 15:06:19 -080027import android.graphics.Rect;
Winson Chung14926462014-04-14 18:57:14 -070028import android.graphics.RectF;
Winson Chung6cb485f2014-05-19 10:30:43 -070029import android.provider.Settings;
Winson Chung37c8d8e2014-03-24 14:53:07 -070030import android.util.AttributeSet;
Winson Chung9f9679d2014-04-11 16:49:09 -070031import android.view.MotionEvent;
Winson Chung303e1ff2014-03-07 15:06:19 -080032import android.view.View;
Winson Chung14926462014-04-14 18:57:14 -070033import android.view.animation.AccelerateInterpolator;
Winson Chung303e1ff2014-03-07 15:06:19 -080034import android.widget.FrameLayout;
Winson Chung37c8d8e2014-03-24 14:53:07 -070035import com.android.systemui.R;
Winson Chung14926462014-04-14 18:57:14 -070036import com.android.systemui.recents.Constants;
Winson Chung303e1ff2014-03-07 15:06:19 -080037import com.android.systemui.recents.RecentsConfiguration;
38import com.android.systemui.recents.model.Task;
Winson Chung303e1ff2014-03-07 15:06:19 -080039
Winson Chung303e1ff2014-03-07 15:06:19 -080040
Winson Chung37c8d8e2014-03-24 14:53:07 -070041/* A task view */
Winson Chung6cb485f2014-05-19 10:30:43 -070042public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.OnClickListener,
43 View.OnLongClickListener {
Winson Chung37c8d8e2014-03-24 14:53:07 -070044 /** The TaskView callbacks */
45 interface TaskViewCallbacks {
46 public void onTaskIconClicked(TaskView tv);
Winson Chung9f9679d2014-04-11 16:49:09 -070047 public void onTaskAppInfoClicked(TaskView tv);
Winson Chung54e297a2014-05-09 17:15:32 -070048 public void onTaskDismissed(TaskView tv);
Winson Chung9f9679d2014-04-11 16:49:09 -070049
Winson Chung37c8d8e2014-03-24 14:53:07 -070050 // public void onTaskViewReboundToTask(TaskView tv, Task t);
51 }
52
Winson Chung14926462014-04-14 18:57:14 -070053 int mDim;
54 int mMaxDim;
55 TimeInterpolator mDimInterpolator = new AccelerateInterpolator();
56
Winson Chung303e1ff2014-03-07 15:06:19 -080057 Task mTask;
Winson Chung37c8d8e2014-03-24 14:53:07 -070058 boolean mTaskDataLoaded;
Winson Chung1e8d71b2014-05-16 17:05:22 -070059 boolean mIsFocused;
Winson Chung9f9679d2014-04-11 16:49:09 -070060 Point mLastTouchDown = new Point();
Winson Chung14926462014-04-14 18:57:14 -070061 Path mRoundedRectClipPath = new Path();
Winson Chung37c8d8e2014-03-24 14:53:07 -070062
63 TaskThumbnailView mThumbnailView;
64 TaskBarView mBarView;
65 TaskViewCallbacks mCb;
Winson Chung303e1ff2014-03-07 15:06:19 -080066
Winson Chung37c8d8e2014-03-24 14:53:07 -070067
68 public TaskView(Context context) {
69 this(context, null);
Winson Chung303e1ff2014-03-07 15:06:19 -080070 }
71
Winson Chung37c8d8e2014-03-24 14:53:07 -070072 public TaskView(Context context, AttributeSet attrs) {
73 this(context, attrs, 0);
74 }
Winson Chung303e1ff2014-03-07 15:06:19 -080075
Winson Chung37c8d8e2014-03-24 14:53:07 -070076 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);
Winson Chung14926462014-04-14 18:57:14 -070082 setWillNotDraw(false);
Winson Chung37c8d8e2014-03-24 14:53:07 -070083 }
84
85 @Override
86 protected void onFinishInflate() {
Winson Chung14926462014-04-14 18:57:14 -070087 RecentsConfiguration config = RecentsConfiguration.getInstance();
88 mMaxDim = config.taskStackMaxDim;
89
Winson Chung37c8d8e2014-03-24 14:53:07 -070090 // Bind the views
91 mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
92 mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
Winson Chung9f9679d2014-04-11 16:49:09 -070093
Winson Chung37c8d8e2014-03-24 14:53:07 -070094 if (mTaskDataLoaded) {
95 onTaskDataLoaded(false);
Winson Chung303e1ff2014-03-07 15:06:19 -080096 }
97 }
98
Winson Chung9f9679d2014-04-11 16:49:09 -070099 @Override
Winson Chung14926462014-04-14 18:57:14 -0700100 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);
Winson Chung96e3bc12014-05-06 16:44:12 -0700109
110 // Update the outline
111 Outline o = new Outline();
112 o.setRoundRect(0, 0, getMeasuredWidth(), getMeasuredHeight(), radius);
113 setOutline(o);
Winson Chung14926462014-04-14 18:57:14 -0700114 }
115
116 @Override
Winson Chung9f9679d2014-04-11 16:49:09 -0700117 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
Winson Chung04dfe0d2014-03-14 14:06:29 -0700127 /** Set callback */
128 void setCallbacks(TaskViewCallbacks cb) {
Winson Chung303e1ff2014-03-07 15:06:19 -0800129 mCb = cb;
130 }
131
Winson Chung303e1ff2014-03-07 15:06:19 -0800132 /** Gets the task */
133 Task getTask() {
134 return mTask;
135 }
136
137 /** Synchronizes this view's properties with the task's transform */
Winson Chungc6a16232014-04-01 14:04:48 -0700138 void updateViewPropertiesToTaskTransform(TaskViewTransform animateFromTransform,
139 TaskViewTransform toTransform, int duration) {
Winson Chung96e3bc12014-05-06 16:44:12 -0700140 RecentsConfiguration config = RecentsConfiguration.getInstance();
141 int minZ = config.taskViewTranslationZMinPx;
142 int incZ = config.taskViewTranslationZIncrementPx;
143
Winson Chung54e297a2014-05-09 17:15:32 -0700144 // Update the bar view
145 mBarView.updateViewPropertiesToTaskTransform(animateFromTransform, toTransform, duration);
146
147 // Update this task view
Winson Chung303e1ff2014-03-07 15:06:19 -0800148 if (duration > 0) {
149 if (animateFromTransform != null) {
150 setTranslationY(animateFromTransform.translationY);
Winson Chung814086d2014-05-07 15:01:14 -0700151 if (Constants.DebugFlags.App.EnableShadows) {
152 setTranslationZ(Math.max(minZ, minZ + (animateFromTransform.t * incZ)));
153 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800154 setScaleX(animateFromTransform.scale);
155 setScaleY(animateFromTransform.scale);
Winson Chungc6a16232014-04-01 14:04:48 -0700156 setAlpha(animateFromTransform.alpha);
Winson Chung303e1ff2014-03-07 15:06:19 -0800157 }
Winson Chung814086d2014-05-07 15:01:14 -0700158 if (Constants.DebugFlags.App.EnableShadows) {
159 animate().translationZ(Math.max(minZ, minZ + (toTransform.t * incZ)));
160 }
Winson Chungc6a16232014-04-01 14:04:48 -0700161 animate().translationY(toTransform.translationY)
162 .scaleX(toTransform.scale)
163 .scaleY(toTransform.scale)
164 .alpha(toTransform.alpha)
Winson Chung303e1ff2014-03-07 15:06:19 -0800165 .setDuration(duration)
Winson Chung918c0722014-05-08 15:04:23 -0700166 .setInterpolator(config.defaultBezierInterpolator)
Winson Chungc6a16232014-04-01 14:04:48 -0700167 .withLayer()
Winson Chung14926462014-04-14 18:57:14 -0700168 .setUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
169 @Override
170 public void onAnimationUpdate(ValueAnimator animation) {
171 updateDimOverlayFromScale();
172 }
173 })
Winson Chung303e1ff2014-03-07 15:06:19 -0800174 .start();
175 } else {
Winson Chungc6a16232014-04-01 14:04:48 -0700176 setTranslationY(toTransform.translationY);
Winson Chung814086d2014-05-07 15:01:14 -0700177 if (Constants.DebugFlags.App.EnableShadows) {
178 setTranslationZ(Math.max(minZ, minZ + (toTransform.t * incZ)));
179 }
Winson Chungc6a16232014-04-01 14:04:48 -0700180 setScaleX(toTransform.scale);
181 setScaleY(toTransform.scale);
182 setAlpha(toTransform.alpha);
Winson Chung303e1ff2014-03-07 15:06:19 -0800183 }
Winson Chung14926462014-04-14 18:57:14 -0700184 updateDimOverlayFromScale();
185 invalidate();
Winson Chung303e1ff2014-03-07 15:06:19 -0800186 }
187
188 /** Resets this view's properties */
189 void resetViewProperties() {
190 setTranslationX(0f);
191 setTranslationY(0f);
Winson Chung814086d2014-05-07 15:01:14 -0700192 if (Constants.DebugFlags.App.EnableShadows) {
193 setTranslationZ(0f);
194 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800195 setScaleX(1f);
196 setScaleY(1f);
197 setAlpha(1f);
Winson Chung14926462014-04-14 18:57:14 -0700198 invalidate();
Winson Chung303e1ff2014-03-07 15:06:19 -0800199 }
200
Winson Chung11ca76a52014-04-10 17:29:13 -0700201 /**
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 */
Winson Chungc6a16232014-04-01 14:04:48 -0700205 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
206 // Fade the view out and slide it away
207 toTransform.alpha = 0f;
208 toTransform.translationY += 200;
209 }
210
Winson Chung11ca76a52014-04-10 17:29:13 -0700211 /**
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 */
Winson Chungc6a16232014-04-01 14:04:48 -0700215 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
216 // Fade the view in
217 fromTransform.alpha = 0f;
218 }
219
Winson Chung303e1ff2014-03-07 15:06:19 -0800220 /** Animates this task view as it enters recents */
221 public void animateOnEnterRecents() {
Winson Chung37c8d8e2014-03-24 14:53:07 -0700222 RecentsConfiguration config = RecentsConfiguration.getInstance();
Winson Chung37c8d8e2014-03-24 14:53:07 -0700223 mBarView.setAlpha(0f);
Winson Chung37c8d8e2014-03-24 14:53:07 -0700224 mBarView.animate()
225 .alpha(1f)
Winson Chung918c0722014-05-08 15:04:23 -0700226 .setStartDelay(250)
227 .setInterpolator(config.defaultBezierInterpolator)
Winson Chung0d767552014-04-09 14:33:23 -0700228 .setDuration(config.taskBarEnterAnimDuration)
Winson Chung37c8d8e2014-03-24 14:53:07 -0700229 .withLayer()
230 .start();
Winson Chung303e1ff2014-03-07 15:06:19 -0800231 }
232
233 /** Animates this task view as it exits recents */
234 public void animateOnLeavingRecents(final Runnable r) {
Winson Chung37c8d8e2014-03-24 14:53:07 -0700235 RecentsConfiguration config = RecentsConfiguration.getInstance();
Winson Chung37c8d8e2014-03-24 14:53:07 -0700236 mBarView.animate()
237 .alpha(0f)
Winson Chung37c8d8e2014-03-24 14:53:07 -0700238 .setStartDelay(0)
Winson Chung918c0722014-05-08 15:04:23 -0700239 .setInterpolator(config.defaultBezierInterpolator)
Winson Chungf5e22e72014-05-02 18:35:35 -0700240 .setDuration(config.taskBarExitAnimDuration)
Winson Chung37c8d8e2014-03-24 14:53:07 -0700241 .withLayer()
Winson Chung0d767552014-04-09 14:33:23 -0700242 .withEndAction(new Runnable() {
243 @Override
244 public void run() {
245 post(r);
246 }
247 })
Winson Chung37c8d8e2014-03-24 14:53:07 -0700248 .start();
Winson Chung303e1ff2014-03-07 15:06:19 -0800249 }
250
Winson Chung9f49df92014-05-07 18:08:34 -0700251 /** 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)
Winson Chung918c0722014-05-08 15:04:23 -0700257 .setInterpolator(config.defaultBezierInterpolator)
Winson Chung9f49df92014-05-07 18:08:34 -0700258 .setDuration(config.taskViewRemoveAnimDuration)
259 .withLayer()
260 .withEndAction(new Runnable() {
261 @Override
262 public void run() {
263 post(r);
264 }
265 })
266 .start();
267 }
268
Winson Chung303e1ff2014-03-07 15:06:19 -0800269 /** Returns the rect we want to clip (it may not be the full rect) */
Winson Chung0d767552014-04-09 14:33:23 -0700270 Rect getClippingRect(Rect outRect) {
Winson Chung303e1ff2014-03-07 15:06:19 -0800271 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();
Winson Chung303e1ff2014-03-07 15:06:19 -0800275 return outRect;
276 }
277
278 /** Enable the hw layers on this task view */
279 void enableHwLayers() {
Winson Chung303e1ff2014-03-07 15:06:19 -0800280 mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
281 }
282
283 /** Disable the hw layers on this task view */
284 void disableHwLayers() {
Winson Chung303e1ff2014-03-07 15:06:19 -0800285 mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, null);
286 }
287
Winson Chung14926462014-04-14 18:57:14 -0700288 /** 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)));
Winson Chung14926462014-04-14 18:57:14 -0700295 }
296
297 @Override
298 public void draw(Canvas canvas) {
299 // Apply the rounded rect clip path on the whole view
300 canvas.clipPath(mRoundedRectClipPath);
301
302 super.draw(canvas);
303
304 // Apply the dim if necessary
305 if (mDim > 0) {
306 canvas.drawColor(mDim << 24);
307 }
308 }
309
Winson Chung1e8d71b2014-05-16 17:05:22 -0700310 /**
311 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
312 * if the view is not currently visible, or we are in touch state (where we still want to keep
313 * track of focus).
314 */
315 public void setFocusedTask() {
316 mIsFocused = true;
317 requestFocus();
318 }
319
320 /**
321 * Updates the explicitly focused state when the view focus changes.
322 */
323 @Override
324 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
325 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
326 if (!gainFocus) {
327 mIsFocused = false;
328 }
329 }
330
331 /**
332 * Returns whether we have explicitly been focused.
333 */
334 public boolean isFocusedTask() {
335 return mIsFocused || isFocused();
336 }
337
Winson Chung4d7b0922014-03-13 17:14:17 -0700338 /**** TaskCallbacks Implementation ****/
339
Winson Chung04dfe0d2014-03-14 14:06:29 -0700340 /** Binds this task view to the task */
341 public void onTaskBound(Task t) {
342 mTask = t;
343 mTask.setCallbacks(this);
Winson Chung303e1ff2014-03-07 15:06:19 -0800344 }
345
346 @Override
Winson Chung4c71aef2014-03-21 15:15:11 -0700347 public void onTaskDataLoaded(boolean reloadingTaskData) {
Winson Chung863db8a2014-05-20 14:27:39 -0700348 if (mThumbnailView != null && mBarView != null) {
Winson Chung37c8d8e2014-03-24 14:53:07 -0700349 // Bind each of the views to the new task data
350 mThumbnailView.rebindToTask(mTask, reloadingTaskData);
351 mBarView.rebindToTask(mTask, reloadingTaskData);
Winson Chung9f9679d2014-04-11 16:49:09 -0700352 // Rebind any listeners
353 mBarView.mApplicationIcon.setOnClickListener(this);
Winson Chung54e297a2014-05-09 17:15:32 -0700354 mBarView.mDismissButton.setOnClickListener(this);
Winson Chung6cb485f2014-05-19 10:30:43 -0700355 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
356 ContentResolver cr = getContext().getContentResolver();
357 boolean devOptsEnabled = Settings.Global.getInt(cr,
358 Settings.Global.DEVELOPMENT_SETTINGS_ENABLED, 0) != 0;
359 if (devOptsEnabled) {
360 mBarView.mApplicationIcon.setOnLongClickListener(this);
361 }
362 }
Winson Chung37c8d8e2014-03-24 14:53:07 -0700363 }
364 mTaskDataLoaded = true;
Winson Chung4d7b0922014-03-13 17:14:17 -0700365 }
366
367 @Override
Winson Chung04dfe0d2014-03-14 14:06:29 -0700368 public void onTaskDataUnloaded() {
Winson Chung863db8a2014-05-20 14:27:39 -0700369 if (mThumbnailView != null && mBarView != null) {
Winson Chung37c8d8e2014-03-24 14:53:07 -0700370 // Unbind each of the views from the task data and remove the task callback
371 mTask.setCallbacks(null);
372 mThumbnailView.unbindFromTask();
373 mBarView.unbindFromTask();
Winson Chung9f9679d2014-04-11 16:49:09 -0700374 // Unbind any listeners
375 mBarView.mApplicationIcon.setOnClickListener(null);
Winson Chung6cb485f2014-05-19 10:30:43 -0700376 mBarView.mDismissButton.setOnClickListener(null);
377 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
378 mBarView.mApplicationIcon.setOnLongClickListener(null);
379 }
Winson Chung37c8d8e2014-03-24 14:53:07 -0700380 }
381 mTaskDataLoaded = false;
Winson Chung4d7b0922014-03-13 17:14:17 -0700382 }
383
384 @Override
Winson Chung303e1ff2014-03-07 15:06:19 -0800385 public void onClick(View v) {
Winson Chung863db8a2014-05-20 14:27:39 -0700386 if (v == mBarView.mApplicationIcon) {
Winson Chung9f9679d2014-04-11 16:49:09 -0700387 mCb.onTaskIconClicked(this);
Winson Chung54e297a2014-05-09 17:15:32 -0700388 } else if (v == mBarView.mDismissButton) {
389 // Animate out the view and call the callback
390 final TaskView tv = this;
391 animateRemoval(new Runnable() {
392 @Override
393 public void run() {
394 mCb.onTaskDismissed(tv);
395 }
396 });
Winson Chung9f9679d2014-04-11 16:49:09 -0700397 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800398 }
Winson Chung6cb485f2014-05-19 10:30:43 -0700399
400 @Override
401 public boolean onLongClick(View v) {
402 if (v == mBarView.mApplicationIcon) {
403 mCb.onTaskAppInfoClicked(this);
404 return true;
405 }
406 return false;
407 }
Winson Chung7bb18852014-05-20 23:25:41 +0000408}