blob: 5524e155b283c8753096326f8ab50851ed8988bc [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 Chungc6011de2014-06-30 18:04:55 -070019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
Winson Chunga26fb782014-06-12 17:52:39 -070021import android.animation.ObjectAnimator;
Winson Chung14926462014-04-14 18:57:14 -070022import android.animation.ValueAnimator;
Winson Chung303e1ff2014-03-07 15:06:19 -080023import android.content.Context;
Winson Chung14926462014-04-14 18:57:14 -070024import android.graphics.Canvas;
Winson Chung96e3bc12014-05-06 16:44:12 -070025import android.graphics.Outline;
Winson Chungc9567c02014-06-16 20:25:51 -070026import android.graphics.Paint;
Winson Chung303e1ff2014-03-07 15:06:19 -080027import android.graphics.Rect;
Winson Chung37c8d8e2014-03-24 14:53:07 -070028import android.util.AttributeSet;
Winson Chung303e1ff2014-03-07 15:06:19 -080029import android.view.View;
Chris Craik7b7ca3c2014-07-11 13:32:19 -070030import android.view.ViewOutlineProvider;
Winson Chungb5ddfc32014-06-13 17:23:12 -070031import android.view.ViewPropertyAnimator;
Winson Chung14926462014-04-14 18:57:14 -070032import android.view.animation.AccelerateInterpolator;
Winson Chung303e1ff2014-03-07 15:06:19 -080033import android.widget.FrameLayout;
Winson Chung37c8d8e2014-03-24 14:53:07 -070034import com.android.systemui.R;
Winson Chungffa2ec62014-07-03 15:54:42 -070035import com.android.systemui.recents.misc.Console;
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 Chung1f24c7e2014-07-11 17:06:48 -070039import com.android.systemui.recents.model.TaskStack;
Winson Chung303e1ff2014-03-07 15:06:19 -080040
Winson Chung303e1ff2014-03-07 15:06:19 -080041
Winson Chung37c8d8e2014-03-24 14:53:07 -070042/* A task view */
Winson Chung6cb485f2014-05-19 10:30:43 -070043public class TaskView extends FrameLayout implements Task.TaskCallbacks, View.OnClickListener,
44 View.OnLongClickListener {
Winson Chung37c8d8e2014-03-24 14:53:07 -070045 /** The TaskView callbacks */
46 interface TaskViewCallbacks {
Winson Chung7aceb9a2014-07-03 13:38:01 -070047 public void onTaskViewAppIconClicked(TaskView tv);
48 public void onTaskViewAppInfoClicked(TaskView tv);
Winson Chung1f24c7e2014-07-11 17:06:48 -070049 public void onTaskViewClicked(TaskView tv, Task t, boolean lockToTask);
Winson Chung7aceb9a2014-07-03 13:38:01 -070050 public void onTaskViewDismissed(TaskView tv);
Winson Chung37c8d8e2014-03-24 14:53:07 -070051 }
52
Winson Chungd42a6cf2014-06-03 16:24:04 -070053 RecentsConfiguration mConfig;
54
Winson Chung1f24c7e2014-07-11 17:06:48 -070055 int mFooterHeight;
56 int mMaxFooterHeight;
57 ObjectAnimator mFooterAnimator;
58
Winson Chung14926462014-04-14 18:57:14 -070059 int mDim;
60 int mMaxDim;
Winson Chung6057c912014-07-08 14:39:08 -070061 AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
Winson Chung14926462014-04-14 18:57:14 -070062
Winson Chung303e1ff2014-03-07 15:06:19 -080063 Task mTask;
Winson Chung37c8d8e2014-03-24 14:53:07 -070064 boolean mTaskDataLoaded;
Winson Chung1e8d71b2014-05-16 17:05:22 -070065 boolean mIsFocused;
Winson Chungffa2ec62014-07-03 15:54:42 -070066 boolean mIsStub;
Winson Chung5a9b0b02014-05-20 17:32:03 -070067 boolean mClipViewInStack;
Winson Chungd42a6cf2014-06-03 16:24:04 -070068 Rect mTmpRect = new Rect();
Winson Chungc9567c02014-06-16 20:25:51 -070069 Paint mLayerPaint = new Paint();
Winson Chung37c8d8e2014-03-24 14:53:07 -070070
71 TaskThumbnailView mThumbnailView;
72 TaskBarView mBarView;
Winson Chung1f24c7e2014-07-11 17:06:48 -070073 View mLockToAppButtonView;
Winson Chung37c8d8e2014-03-24 14:53:07 -070074 TaskViewCallbacks mCb;
Winson Chung303e1ff2014-03-07 15:06:19 -080075
Winson Chungd42a6cf2014-06-03 16:24:04 -070076 // Optimizations
77 ValueAnimator.AnimatorUpdateListener mUpdateDimListener =
78 new ValueAnimator.AnimatorUpdateListener() {
79 @Override
80 public void onAnimationUpdate(ValueAnimator animation) {
81 updateDimOverlayFromScale();
82 }
83 };
Winson Chung743d5c92014-06-13 10:14:53 -070084 Runnable mEnableThumbnailClip = new Runnable() {
85 @Override
86 public void run() {
87 mThumbnailView.updateTaskBarClip(mBarView);
88 }
89 };
90 Runnable mDisableThumbnailClip = new Runnable() {
91 @Override
92 public void run() {
93 mThumbnailView.disableClipTaskBarView();
94 }
95 };
Winson Chungd42a6cf2014-06-03 16:24:04 -070096
Winson Chung37c8d8e2014-03-24 14:53:07 -070097
98 public TaskView(Context context) {
99 this(context, null);
Winson Chung303e1ff2014-03-07 15:06:19 -0800100 }
101
Winson Chung37c8d8e2014-03-24 14:53:07 -0700102 public TaskView(Context context, AttributeSet attrs) {
103 this(context, attrs, 0);
104 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800105
Winson Chung37c8d8e2014-03-24 14:53:07 -0700106 public TaskView(Context context, AttributeSet attrs, int defStyleAttr) {
107 this(context, attrs, defStyleAttr, 0);
108 }
109
110 public TaskView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
111 super(context, attrs, defStyleAttr, defStyleRes);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700112 mConfig = RecentsConfiguration.getInstance();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700113 mMaxFooterHeight = mConfig.taskViewLockToAppButtonHeight;
Winson Chung14926462014-04-14 18:57:14 -0700114 setWillNotDraw(false);
Winson Chung7aceb9a2014-07-03 13:38:01 -0700115 setClipToOutline(true);
Winson Chunga26fb782014-06-12 17:52:39 -0700116 setDim(getDim());
Winson Chung1f24c7e2014-07-11 17:06:48 -0700117 setFooterHeight(getFooterHeight());
Chris Craik7b7ca3c2014-07-11 13:32:19 -0700118 setOutlineProvider(new ViewOutlineProvider() {
119 @Override
120 public boolean getOutline(View view, Outline outline) {
121 int height = getHeight() - mMaxFooterHeight + mFooterHeight;
122 outline.setRoundRect(0, 0, getWidth(), height,
123 mConfig.taskViewRoundedCornerRadiusPx);
124 return true;
125 }
126 });
Winson Chung37c8d8e2014-03-24 14:53:07 -0700127 }
128
129 @Override
130 protected void onFinishInflate() {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700131 mMaxDim = mConfig.taskStackMaxDim;
Winson Chung14926462014-04-14 18:57:14 -0700132
Winson Chung5a9b0b02014-05-20 17:32:03 -0700133 // By default, all views are clipped to other views in their stack
134 mClipViewInStack = true;
135
Winson Chung37c8d8e2014-03-24 14:53:07 -0700136 // Bind the views
Winson Chung37c8d8e2014-03-24 14:53:07 -0700137 mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
Winson Chung743d5c92014-06-13 10:14:53 -0700138 mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
Winson Chung1f24c7e2014-07-11 17:06:48 -0700139 mLockToAppButtonView = findViewById(R.id.lock_to_app);
Winson Chung9f9679d2014-04-11 16:49:09 -0700140
Winson Chung37c8d8e2014-03-24 14:53:07 -0700141 if (mTaskDataLoaded) {
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700142 onTaskDataLoaded();
Winson Chung303e1ff2014-03-07 15:06:19 -0800143 }
144 }
145
Winson Chung9f9679d2014-04-11 16:49:09 -0700146 @Override
Winson Chung14926462014-04-14 18:57:14 -0700147 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Winson Chung1f24c7e2014-07-11 17:06:48 -0700148 int width = MeasureSpec.getSize(widthMeasureSpec);
149 int height = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung14926462014-04-14 18:57:14 -0700150
Winson Chung1f24c7e2014-07-11 17:06:48 -0700151 // Measure the bar view, thumbnail, and lock-to-app buttons
152 mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
153 MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
154 mLockToAppButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
155 MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
156 MeasureSpec.EXACTLY));
157 // Measure the thumbnail height to be the same as the width
158 mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
159 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
160 setMeasuredDimension(width, height);
Winson Chung14926462014-04-14 18:57:14 -0700161 }
162
Winson Chung04dfe0d2014-03-14 14:06:29 -0700163 /** Set callback */
164 void setCallbacks(TaskViewCallbacks cb) {
Winson Chung303e1ff2014-03-07 15:06:19 -0800165 mCb = cb;
166 }
167
Winson Chung303e1ff2014-03-07 15:06:19 -0800168 /** Gets the task */
169 Task getTask() {
170 return mTask;
171 }
172
173 /** Synchronizes this view's properties with the task's transform */
Winson Chungb5ddfc32014-06-13 17:23:12 -0700174 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700175 if (Console.Enabled) {
176 Console.log(Constants.Log.UI.Draw, "[TaskView|updateViewPropertiesToTaskTransform]",
177 "duration: " + duration, Console.AnsiPurple);
178 }
Winson Chung96e3bc12014-05-06 16:44:12 -0700179
Winson Chung54e297a2014-05-09 17:15:32 -0700180 // Update the bar view
Winson Chungb5ddfc32014-06-13 17:23:12 -0700181 mBarView.updateViewPropertiesToTaskTransform(toTransform, duration);
Winson Chung54e297a2014-05-09 17:15:32 -0700182
Winson Chungb5ddfc32014-06-13 17:23:12 -0700183 // Check to see if any properties have changed, and update the task view
Winson Chung303e1ff2014-03-07 15:06:19 -0800184 if (duration > 0) {
Winson Chungb5ddfc32014-06-13 17:23:12 -0700185 ViewPropertyAnimator anim = animate();
186 boolean useLayers = false;
187
188 // Animate to the final state
189 if (toTransform.hasTranslationYChangedFrom(getTranslationY())) {
190 anim.translationY(toTransform.translationY);
Winson Chung303e1ff2014-03-07 15:06:19 -0800191 }
Winson Chungb5ddfc32014-06-13 17:23:12 -0700192 if (Constants.DebugFlags.App.EnableShadows &&
193 toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
194 anim.translationZ(toTransform.translationZ);
Winson Chung814086d2014-05-07 15:01:14 -0700195 }
Winson Chungb5ddfc32014-06-13 17:23:12 -0700196 if (toTransform.hasScaleChangedFrom(getScaleX())) {
197 anim.scaleX(toTransform.scale)
Winson Chungc6a16232014-04-01 14:04:48 -0700198 .scaleY(toTransform.scale)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700199 .setUpdateListener(mUpdateDimListener);
200 useLayers = true;
201 }
202 if (toTransform.hasAlphaChangedFrom(getAlpha())) {
203 // Use layers if we animate alpha
204 anim.alpha(toTransform.alpha);
205 useLayers = true;
206 }
207 if (useLayers) {
208 anim.withLayer();
209 }
Winson Chung7ab650c2014-06-18 14:25:34 -0700210 anim.setStartDelay(toTransform.startDelay)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700211 .setDuration(duration)
212 .setInterpolator(mConfig.fastOutSlowInInterpolator)
213 .start();
Winson Chung303e1ff2014-03-07 15:06:19 -0800214 } else {
Winson Chungb5ddfc32014-06-13 17:23:12 -0700215 // Set the changed properties
216 if (toTransform.hasTranslationYChangedFrom(getTranslationY())) {
217 setTranslationY(toTransform.translationY);
218 }
219 if (Constants.DebugFlags.App.EnableShadows &&
220 toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
Winson Chungb13c46e2014-06-09 18:33:08 -0700221 setTranslationZ(toTransform.translationZ);
Winson Chung814086d2014-05-07 15:01:14 -0700222 }
Winson Chungb5ddfc32014-06-13 17:23:12 -0700223 if (toTransform.hasScaleChangedFrom(getScaleX())) {
224 setScaleX(toTransform.scale);
225 setScaleY(toTransform.scale);
226 updateDimOverlayFromScale();
227 }
228 if (toTransform.hasAlphaChangedFrom(getAlpha())) {
229 setAlpha(toTransform.alpha);
230 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800231 }
232 }
233
234 /** Resets this view's properties */
235 void resetViewProperties() {
236 setTranslationX(0f);
237 setTranslationY(0f);
Winson Chung814086d2014-05-07 15:01:14 -0700238 if (Constants.DebugFlags.App.EnableShadows) {
239 setTranslationZ(0f);
240 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800241 setScaleX(1f);
242 setScaleY(1f);
243 setAlpha(1f);
Winson Chunga26fb782014-06-12 17:52:39 -0700244 setDim(0);
Winson Chung14926462014-04-14 18:57:14 -0700245 invalidate();
Winson Chung303e1ff2014-03-07 15:06:19 -0800246 }
247
Winson Chung11ca76a52014-04-10 17:29:13 -0700248 /**
249 * When we are un/filtering, this method will set up the transform that we are animating to,
250 * in order to hide the task.
251 */
Winson Chungc6a16232014-04-01 14:04:48 -0700252 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
253 // Fade the view out and slide it away
254 toTransform.alpha = 0f;
255 toTransform.translationY += 200;
Winson Chung7ab650c2014-06-18 14:25:34 -0700256 toTransform.translationZ = 0;
Winson Chungc6a16232014-04-01 14:04:48 -0700257 }
258
Winson Chung11ca76a52014-04-10 17:29:13 -0700259 /**
260 * When we are un/filtering, this method will setup the transform that we are animating from,
261 * in order to show the task.
262 */
Winson Chungc6a16232014-04-01 14:04:48 -0700263 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
264 // Fade the view in
265 fromTransform.alpha = 0f;
266 }
267
Winson Chung24cf1522014-05-29 12:03:33 -0700268 /** Prepares this task view for the enter-recents animations. This is called earlier in the
269 * first layout because the actual animation into recents may take a long time. */
Winson Chung083baf92014-07-11 10:32:42 -0700270 public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, int offsetY,
271 int offscreenY) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700272 if (mConfig.launchedFromAppWithScreenshot) {
Winson Chung083baf92014-07-11 10:32:42 -0700273 if (isTaskViewLaunchTargetTask) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700274 // Hide the task view as we are going to animate the full screenshot into view
275 // and then replace it with this view once we are done
276 setVisibility(View.INVISIBLE);
277 // Also hide the front most task bar view so we can animate it in
Winson Chung969f5862014-06-16 17:08:24 -0700278 mBarView.prepareEnterRecentsAnimation();
Winson Chungd42a6cf2014-06-03 16:24:04 -0700279 } else {
280 // Top align the task views
281 setTranslationY(offsetY);
282 setScaleX(1f);
283 setScaleY(1f);
284 }
285
286 } else if (mConfig.launchedFromAppWithThumbnail) {
Winson Chung083baf92014-07-11 10:32:42 -0700287 if (isTaskViewLaunchTargetTask) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700288 // Hide the front most task bar view so we can animate it in
Winson Chung969f5862014-06-16 17:08:24 -0700289 mBarView.prepareEnterRecentsAnimation();
Winson Chunga26fb782014-06-12 17:52:39 -0700290 // Set the dim to 0 so we can animate it in
291 setDim(0);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700292 }
293
294 } else if (mConfig.launchedFromHome) {
295 // Move the task view off screen (below) so we can animate it in
296 setTranslationY(offscreenY);
Winson Chung7aceb9a2014-07-03 13:38:01 -0700297 if (Constants.DebugFlags.App.EnableShadows) {
298 setTranslationZ(0);
299 }
Winson Chungd42a6cf2014-06-03 16:24:04 -0700300 setScaleX(1f);
301 setScaleY(1f);
302 }
Winson Chung24cf1522014-05-29 12:03:33 -0700303 }
304
Winson Chung303e1ff2014-03-07 15:06:19 -0800305 /** Animates this task view as it enters recents */
Winson Chungc6011de2014-06-30 18:04:55 -0700306 public void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
Winson Chung6057c912014-07-08 14:39:08 -0700307 TaskViewTransform transform = ctx.currentTaskTransform;
Winson Chungd42a6cf2014-06-03 16:24:04 -0700308
309 if (mConfig.launchedFromAppWithScreenshot) {
Winson Chung083baf92014-07-11 10:32:42 -0700310 if (ctx.isCurrentTaskLaunchTarget) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700311 // Animate the full screenshot down first, before swapping with this task view
Winson Chung6057c912014-07-08 14:39:08 -0700312 ctx.fullScreenshotView.animateOnEnterRecents(ctx, new Runnable() {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700313 @Override
314 public void run() {
315 // Animate the task bar of the first task view
Winson Chung969f5862014-06-16 17:08:24 -0700316 mBarView.startEnterRecentsAnimation(0, mEnableThumbnailClip);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700317 setVisibility(View.VISIBLE);
Winson Chung1f24c7e2014-07-11 17:06:48 -0700318 // Animate the footer into view
319 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration, 0);
Winson Chungc6011de2014-06-30 18:04:55 -0700320 // Decrement the post animation trigger
321 ctx.postAnimationTrigger.decrement();
Winson Chungd42a6cf2014-06-03 16:24:04 -0700322 }
323 });
324 } else {
325 // Animate the tasks down behind the full screenshot
326 animate()
327 .scaleX(transform.scale)
328 .scaleY(transform.scale)
329 .translationY(transform.translationY)
330 .setStartDelay(0)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700331 .setUpdateListener(null)
Winson Chungd42a6cf2014-06-03 16:24:04 -0700332 .setInterpolator(mConfig.linearOutSlowInInterpolator)
333 .setDuration(475)
334 .withLayer()
Winson Chungc6011de2014-06-30 18:04:55 -0700335 .withEndAction(new Runnable() {
336 @Override
337 public void run() {
338 mEnableThumbnailClip.run();
339 // Decrement the post animation trigger
340 ctx.postAnimationTrigger.decrement();
341 }
342 })
Winson Chungd42a6cf2014-06-03 16:24:04 -0700343 .start();
344 }
Winson Chungc6011de2014-06-30 18:04:55 -0700345 ctx.postAnimationTrigger.increment();
Winson Chungd42a6cf2014-06-03 16:24:04 -0700346
347 } else if (mConfig.launchedFromAppWithThumbnail) {
Winson Chung083baf92014-07-11 10:32:42 -0700348 if (ctx.isCurrentTaskLaunchTarget) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700349 // Animate the task bar of the first task view
Winson Chung969f5862014-06-16 17:08:24 -0700350 mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, mEnableThumbnailClip);
Winson Chung743d5c92014-06-13 10:14:53 -0700351
Winson Chunga26fb782014-06-12 17:52:39 -0700352 // Animate the dim into view as well
353 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimOverlayFromScale());
354 anim.setStartDelay(mConfig.taskBarEnterAnimDelay);
355 anim.setDuration(mConfig.taskBarEnterAnimDuration);
356 anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
Winson Chungc6011de2014-06-30 18:04:55 -0700357 anim.addListener(new AnimatorListenerAdapter() {
358 @Override
359 public void onAnimationEnd(Animator animation) {
360 // Decrement the post animation trigger
361 ctx.postAnimationTrigger.decrement();
362 }
363 });
Winson Chunga26fb782014-06-12 17:52:39 -0700364 anim.start();
Winson Chungc6011de2014-06-30 18:04:55 -0700365 ctx.postAnimationTrigger.increment();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700366
367 // Animate the footer into view
368 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration,
369 mConfig.taskBarEnterAnimDelay);
Winson Chung743d5c92014-06-13 10:14:53 -0700370 } else {
371 mEnableThumbnailClip.run();
Winson Chungd42a6cf2014-06-03 16:24:04 -0700372 }
373
374 } else if (mConfig.launchedFromHome) {
375 // Animate the tasks up
Winson Chung6057c912014-07-08 14:39:08 -0700376 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700377 int delay = mConfig.taskBarEnterAnimDelay +
378 frontIndex * mConfig.taskViewEnterFromHomeDelay;
Winson Chung7aceb9a2014-07-03 13:38:01 -0700379 if (Constants.DebugFlags.App.EnableShadows) {
380 animate().translationZ(transform.translationZ);
381 }
Winson Chungd42a6cf2014-06-03 16:24:04 -0700382 animate()
383 .scaleX(transform.scale)
384 .scaleY(transform.scale)
385 .translationY(transform.translationY)
386 .setStartDelay(delay)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700387 .setUpdateListener(null)
Winson Chungd42a6cf2014-06-03 16:24:04 -0700388 .setInterpolator(mConfig.quintOutInterpolator)
389 .setDuration(mConfig.taskViewEnterFromHomeDuration)
390 .withLayer()
Winson Chungc6011de2014-06-30 18:04:55 -0700391 .withEndAction(new Runnable() {
392 @Override
393 public void run() {
394 mEnableThumbnailClip.run();
395 // Decrement the post animation trigger
396 ctx.postAnimationTrigger.decrement();
397 }
398 })
Winson Chungd42a6cf2014-06-03 16:24:04 -0700399 .start();
Winson Chungc6011de2014-06-30 18:04:55 -0700400 ctx.postAnimationTrigger.increment();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700401
402 // Animate the footer into view
403 animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration,
404 mConfig.taskBarEnterAnimDelay);
Winson Chung7aceb9a2014-07-03 13:38:01 -0700405 } else {
406 // Otherwise, just enable the thumbnail clip
407 mEnableThumbnailClip.run();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700408
409 // Animate the footer into view
410 animateFooterVisibility(true, 0, 0);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700411 }
412 }
413
Winson Chung969f5862014-06-16 17:08:24 -0700414 /** Animates this task view as it leaves recents by pressing home. */
415 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700416 animate()
417 .translationY(ctx.offscreenTranslationY)
418 .setStartDelay(0)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700419 .setUpdateListener(null)
Winson Chungad6f2762014-06-13 09:41:09 -0700420 .setInterpolator(mConfig.fastOutLinearInInterpolator)
421 .setDuration(mConfig.taskViewExitToHomeDuration)
Winson Chung37c8d8e2014-03-24 14:53:07 -0700422 .withLayer()
Winson Chungd42a6cf2014-06-03 16:24:04 -0700423 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
Winson Chung37c8d8e2014-03-24 14:53:07 -0700424 .start();
Winson Chungd42a6cf2014-06-03 16:24:04 -0700425 ctx.postAnimationTrigger.increment();
Winson Chung303e1ff2014-03-07 15:06:19 -0800426 }
427
428 /** Animates this task view as it exits recents */
Winson Chung969f5862014-06-16 17:08:24 -0700429 public void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask) {
430 if (isLaunchingTask) {
431 // Disable the thumbnail clip and animate the bar out
432 mBarView.startLaunchTaskAnimation(mDisableThumbnailClip, r);
Winson Chunga26fb782014-06-12 17:52:39 -0700433
Winson Chung969f5862014-06-16 17:08:24 -0700434 // Animate the dim
435 if (mDim > 0) {
436 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
437 anim.setDuration(mConfig.taskBarExitAnimDuration);
438 anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
439 anim.start();
440 }
441 } else {
442 // Hide the dismiss button
443 mBarView.startLaunchTaskDismissAnimation();
Winson Chunga26fb782014-06-12 17:52:39 -0700444 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800445 }
446
Winson Chung9f49df92014-05-07 18:08:34 -0700447 /** Animates the deletion of this task view */
Winson Chung969f5862014-06-16 17:08:24 -0700448 public void startDeleteTaskAnimation(final Runnable r) {
Winson Chung5a9b0b02014-05-20 17:32:03 -0700449 // Disabling clipping with the stack while the view is animating away
450 setClipViewInStack(false);
451
Winson Chungd42a6cf2014-06-03 16:24:04 -0700452 animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
Winson Chung9f49df92014-05-07 18:08:34 -0700453 .alpha(0f)
454 .setStartDelay(0)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700455 .setUpdateListener(null)
Winson Chungd42a6cf2014-06-03 16:24:04 -0700456 .setInterpolator(mConfig.fastOutSlowInInterpolator)
457 .setDuration(mConfig.taskViewRemoveAnimDuration)
Winson Chung9f49df92014-05-07 18:08:34 -0700458 .withLayer()
459 .withEndAction(new Runnable() {
460 @Override
461 public void run() {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700462 // We just throw this into a runnable because starting a view property
463 // animation using layers can cause inconsisten results if we try and
464 // update the layers while the animation is running. In some cases,
465 // the runnabled passed in may start an animation which also uses layers
466 // so we defer all this by posting this.
467 r.run();
Winson Chung5a9b0b02014-05-20 17:32:03 -0700468
469 // Re-enable clipping with the stack (we will reuse this view)
Winson Chungd42a6cf2014-06-03 16:24:04 -0700470 setClipViewInStack(true);
Winson Chung9f49df92014-05-07 18:08:34 -0700471 }
472 })
473 .start();
474 }
475
Winson Chung969f5862014-06-16 17:08:24 -0700476 /** Animates this task view if the user does not interact with the stack after a certain time. */
477 public void startNoUserInteractionAnimation() {
478 mBarView.startNoUserInteractionAnimation();
479 }
480
481 /** Mark this task view that the user does has not interacted with the stack after a certain time. */
482 public void setNoUserInteractionState() {
483 mBarView.setNoUserInteractionState();
484 }
485
Winson Chung303e1ff2014-03-07 15:06:19 -0800486 /** Returns the rect we want to clip (it may not be the full rect) */
Winson Chung0d767552014-04-09 14:33:23 -0700487 Rect getClippingRect(Rect outRect) {
Winson Chung303e1ff2014-03-07 15:06:19 -0800488 getHitRect(outRect);
489 // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
490 outRect.right = outRect.left + mThumbnailView.getRight();
491 outRect.bottom = outRect.top + mThumbnailView.getBottom();
Winson Chung303e1ff2014-03-07 15:06:19 -0800492 return outRect;
493 }
494
495 /** Enable the hw layers on this task view */
496 void enableHwLayers() {
Winson Chungc9567c02014-06-16 20:25:51 -0700497 mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700498 mBarView.enableHwLayers();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700499 mLockToAppButtonView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
Winson Chung303e1ff2014-03-07 15:06:19 -0800500 }
501
502 /** Disable the hw layers on this task view */
503 void disableHwLayers() {
Winson Chungc9567c02014-06-16 20:25:51 -0700504 mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700505 mBarView.disableHwLayers();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700506 mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
Winson Chung303e1ff2014-03-07 15:06:19 -0800507 }
508
Winson Chungffa2ec62014-07-03 15:54:42 -0700509 /** Sets the stubbed state of this task view. */
510 void setStubState(boolean isStub) {
511 if (!mIsStub && isStub) {
512 // This is now a stub task view, so clip to the bar height, hide the thumbnail
513 setClipBounds(new Rect(0, 0, getMeasuredWidth(), mBarView.getMeasuredHeight()));
514 mThumbnailView.setVisibility(View.INVISIBLE);
515 // Temporary
516 mBarView.mActivityDescription.setText("Stub");
517 } else if (mIsStub && !isStub) {
518 setClipBounds(null);
519 mThumbnailView.setVisibility(View.VISIBLE);
520 }
521 mIsStub = isStub;
522 }
523
Winson Chung5a9b0b02014-05-20 17:32:03 -0700524 /**
525 * Returns whether this view should be clipped, or any views below should clip against this
526 * view.
527 */
528 boolean shouldClipViewInStack() {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700529 return mClipViewInStack && (getVisibility() == View.VISIBLE);
Winson Chung5a9b0b02014-05-20 17:32:03 -0700530 }
531
532 /** Sets whether this view should be clipped, or clipped against. */
533 void setClipViewInStack(boolean clip) {
534 if (clip != mClipViewInStack) {
535 mClipViewInStack = clip;
536 if (getParent() instanceof View) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700537 getHitRect(mTmpRect);
538 ((View) getParent()).invalidate(mTmpRect);
Winson Chung5a9b0b02014-05-20 17:32:03 -0700539 }
540 }
541 }
542
Winson Chung1f24c7e2014-07-11 17:06:48 -0700543 /** Sets the footer height. */
544 public void setFooterHeight(int height) {
545 mFooterHeight = height;
Chris Craik7b7ca3c2014-07-11 13:32:19 -0700546 invalidateOutline();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700547 invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
548 getMeasuredHeight());
549 }
550
551 /** Gets the footer height. */
552 public int getFooterHeight() {
553 return mFooterHeight;
554 }
555
556 /** Animates the footer into and out of view. */
557 public void animateFooterVisibility(boolean visible, int duration, int delay) {
558 if (!mTask.canLockToTask) return;
559 if (mMaxFooterHeight <= 0) return;
560
561 if (mFooterAnimator != null) {
562 mFooterAnimator.removeAllListeners();
563 mFooterAnimator.cancel();
564 }
565 int height = visible ? mMaxFooterHeight : 0;
566 if (visible && mLockToAppButtonView.getVisibility() != View.VISIBLE) {
567 if (duration > 0) {
568 setFooterHeight(0);
569 } else {
570 setFooterHeight(mMaxFooterHeight);
571 }
572 mLockToAppButtonView.setVisibility(View.VISIBLE);
573 }
574 if (duration > 0) {
575 mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", height);
576 mFooterAnimator.setDuration(duration);
577 mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
578 if (!visible) {
579 mFooterAnimator.addListener(new AnimatorListenerAdapter() {
580 @Override
581 public void onAnimationEnd(Animator animation) {
582 mLockToAppButtonView.setVisibility(View.INVISIBLE);
583 }
584 });
585 }
586 mFooterAnimator.start();
587 } else {
588 if (!visible) {
589 mLockToAppButtonView.setVisibility(View.INVISIBLE);
590 }
591 }
592 }
593
Winson Chunga26fb782014-06-12 17:52:39 -0700594 /** Returns the current dim. */
595 public void setDim(int dim) {
596 mDim = dim;
597 postInvalidateOnAnimation();
598 }
599
600 /** Returns the current dim. */
601 public int getDim() {
602 return mDim;
603 }
604
605 /** Compute the dim as a function of the scale of this view. */
606 int getDimOverlayFromScale() {
Winson Chungd7b2cb12014-06-26 15:08:50 -0700607 float minScale = TaskStackViewLayoutAlgorithm.StackPeekMinScale;
Winson Chung14926462014-04-14 18:57:14 -0700608 float scaleRange = 1f - minScale;
609 float dim = (1f - getScaleX()) / scaleRange;
610 dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f));
Winson Chunga26fb782014-06-12 17:52:39 -0700611 return Math.max(0, Math.min(mMaxDim, (int) (dim * 255)));
612 }
613
614 /** Update the dim as a function of the scale of this view. */
615 void updateDimOverlayFromScale() {
616 setDim(getDimOverlayFromScale());
Winson Chung14926462014-04-14 18:57:14 -0700617 }
618
619 @Override
620 public void draw(Canvas canvas) {
Winson Chung14926462014-04-14 18:57:14 -0700621 super.draw(canvas);
622
623 // Apply the dim if necessary
624 if (mDim > 0) {
625 canvas.drawColor(mDim << 24);
626 }
627 }
628
Winson Chungbdbb87d2014-07-09 14:29:13 -0700629 @Override
630 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
631 if (mIsStub && (child == mThumbnailView)) {
632 // Skip the thumbnail view if we are in stub mode
633 return false;
634 }
635 return super.drawChild(canvas, child, drawingTime);
636 }
637
Winson Chung1e8d71b2014-05-16 17:05:22 -0700638 /**
639 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
640 * if the view is not currently visible, or we are in touch state (where we still want to keep
641 * track of focus).
642 */
643 public void setFocusedTask() {
644 mIsFocused = true;
Winson Chungc6011de2014-06-30 18:04:55 -0700645 // Workaround, we don't always want it focusable in touch mode, but we want the first task
646 // to be focused after the enter-recents animation, which can be triggered from either touch
647 // or keyboard
648 setFocusableInTouchMode(true);
Winson Chung1e8d71b2014-05-16 17:05:22 -0700649 requestFocus();
Winson Chungc6011de2014-06-30 18:04:55 -0700650 setFocusableInTouchMode(false);
Winson Chung47a3e652014-05-21 16:03:42 -0700651 invalidate();
Winson Chung1e8d71b2014-05-16 17:05:22 -0700652 }
653
654 /**
655 * Updates the explicitly focused state when the view focus changes.
656 */
657 @Override
658 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
659 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
660 if (!gainFocus) {
661 mIsFocused = false;
Winson Chung47a3e652014-05-21 16:03:42 -0700662 invalidate();
Winson Chung1e8d71b2014-05-16 17:05:22 -0700663 }
664 }
665
666 /**
667 * Returns whether we have explicitly been focused.
668 */
669 public boolean isFocusedTask() {
670 return mIsFocused || isFocused();
671 }
672
Winson Chung4d7b0922014-03-13 17:14:17 -0700673 /**** TaskCallbacks Implementation ****/
674
Winson Chung04dfe0d2014-03-14 14:06:29 -0700675 /** Binds this task view to the task */
676 public void onTaskBound(Task t) {
677 mTask = t;
678 mTask.setCallbacks(this);
Winson Chung1f24c7e2014-07-11 17:06:48 -0700679 if (getMeasuredWidth() == 0) {
680 animateFooterVisibility(t.canLockToTask, 0, 0);
681 } else {
682 animateFooterVisibility(t.canLockToTask, mConfig.taskViewLockToAppLongAnimDuration, 0);
683 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800684 }
685
686 @Override
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700687 public void onTaskDataLoaded() {
Winson Chung863db8a2014-05-20 14:27:39 -0700688 if (mThumbnailView != null && mBarView != null) {
Winson Chung37c8d8e2014-03-24 14:53:07 -0700689 // Bind each of the views to the new task data
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700690 mThumbnailView.rebindToTask(mTask);
691 mBarView.rebindToTask(mTask);
Winson Chung9f9679d2014-04-11 16:49:09 -0700692 // Rebind any listeners
Winson Chungffa2ec62014-07-03 15:54:42 -0700693 if (Constants.DebugFlags.App.EnableTaskFiltering) {
694 mBarView.mApplicationIcon.setOnClickListener(this);
695 }
Winson Chung54e297a2014-05-09 17:15:32 -0700696 mBarView.mDismissButton.setOnClickListener(this);
Winson Chung1f24c7e2014-07-11 17:06:48 -0700697 mLockToAppButtonView.setOnClickListener(this);
Winson Chung6cb485f2014-05-19 10:30:43 -0700698 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700699 if (mConfig.developerOptionsEnabled) {
Winson Chung6cb485f2014-05-19 10:30:43 -0700700 mBarView.mApplicationIcon.setOnLongClickListener(this);
701 }
702 }
Winson Chung37c8d8e2014-03-24 14:53:07 -0700703 }
704 mTaskDataLoaded = true;
Winson Chung4d7b0922014-03-13 17:14:17 -0700705 }
706
707 @Override
Winson Chung04dfe0d2014-03-14 14:06:29 -0700708 public void onTaskDataUnloaded() {
Winson Chung863db8a2014-05-20 14:27:39 -0700709 if (mThumbnailView != null && mBarView != null) {
Winson Chung37c8d8e2014-03-24 14:53:07 -0700710 // Unbind each of the views from the task data and remove the task callback
711 mTask.setCallbacks(null);
712 mThumbnailView.unbindFromTask();
713 mBarView.unbindFromTask();
Winson Chung9f9679d2014-04-11 16:49:09 -0700714 // Unbind any listeners
Winson Chungffa2ec62014-07-03 15:54:42 -0700715 if (Constants.DebugFlags.App.EnableTaskFiltering) {
716 mBarView.mApplicationIcon.setOnClickListener(null);
717 }
Winson Chung6cb485f2014-05-19 10:30:43 -0700718 mBarView.mDismissButton.setOnClickListener(null);
Winson Chung1f24c7e2014-07-11 17:06:48 -0700719 mLockToAppButtonView.setOnClickListener(null);
Winson Chung6cb485f2014-05-19 10:30:43 -0700720 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
721 mBarView.mApplicationIcon.setOnLongClickListener(null);
722 }
Winson Chung37c8d8e2014-03-24 14:53:07 -0700723 }
724 mTaskDataLoaded = false;
Winson Chung4d7b0922014-03-13 17:14:17 -0700725 }
726
Winson Chung1f24c7e2014-07-11 17:06:48 -0700727 /** Enables/disables handling touch on this task view. */
728 void setTouchEnabled(boolean enabled) {
729 setOnClickListener(enabled ? this : null);
730 }
731
Winson Chung4d7b0922014-03-13 17:14:17 -0700732 @Override
Winson Chung7ab650c2014-06-18 14:25:34 -0700733 public void onClick(final View v) {
734 // We purposely post the handler delayed to allow for the touch feedback to draw
735 final TaskView tv = this;
736 postDelayed(new Runnable() {
737 @Override
738 public void run() {
739 if (v == mBarView.mApplicationIcon) {
Winson Chung7aceb9a2014-07-03 13:38:01 -0700740 mCb.onTaskViewAppIconClicked(tv);
Winson Chung7ab650c2014-06-18 14:25:34 -0700741 } else if (v == mBarView.mDismissButton) {
742 // Animate out the view and call the callback
743 startDeleteTaskAnimation(new Runnable() {
744 @Override
745 public void run() {
Winson Chung7aceb9a2014-07-03 13:38:01 -0700746 mCb.onTaskViewDismissed(tv);
Winson Chung7ab650c2014-06-18 14:25:34 -0700747 }
748 });
Winson Chung1f24c7e2014-07-11 17:06:48 -0700749 // Hide the footer
750 tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration, 0);
751 } else if (v == tv || v == mLockToAppButtonView) {
752 mCb.onTaskViewClicked(tv, tv.getTask(), (v == mLockToAppButtonView));
Winson Chung54e297a2014-05-09 17:15:32 -0700753 }
Winson Chung7ab650c2014-06-18 14:25:34 -0700754 }
755 }, 125);
Winson Chung303e1ff2014-03-07 15:06:19 -0800756 }
Winson Chung6cb485f2014-05-19 10:30:43 -0700757
758 @Override
759 public boolean onLongClick(View v) {
760 if (v == mBarView.mApplicationIcon) {
Winson Chung7aceb9a2014-07-03 13:38:01 -0700761 mCb.onTaskViewAppInfoClicked(this);
Winson Chung6cb485f2014-05-19 10:30:43 -0700762 return true;
763 }
764 return false;
765 }
Winson Chung7bb18852014-05-20 23:25:41 +0000766}