blob: 125b01825fc09792e764d10d0abe7b4b9568858a [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;
Winson Chungb5ddfc32014-06-13 17:23:12 -070030import android.view.ViewPropertyAnimator;
Winson Chung14926462014-04-14 18:57:14 -070031import android.view.animation.AccelerateInterpolator;
Winson Chung303e1ff2014-03-07 15:06:19 -080032import android.widget.FrameLayout;
Winson Chung37c8d8e2014-03-24 14:53:07 -070033import com.android.systemui.R;
Winson Chungffa2ec62014-07-03 15:54:42 -070034import com.android.systemui.recents.misc.Console;
Winson Chung14926462014-04-14 18:57:14 -070035import com.android.systemui.recents.Constants;
Winson Chung303e1ff2014-03-07 15:06:19 -080036import com.android.systemui.recents.RecentsConfiguration;
37import com.android.systemui.recents.model.Task;
Winson Chung1f24c7e2014-07-11 17:06:48 -070038import com.android.systemui.recents.model.TaskStack;
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 {
Winson Chung7aceb9a2014-07-03 13:38:01 -070046 public void onTaskViewAppIconClicked(TaskView tv);
47 public void onTaskViewAppInfoClicked(TaskView tv);
Winson Chung1f24c7e2014-07-11 17:06:48 -070048 public void onTaskViewClicked(TaskView tv, Task t, boolean lockToTask);
Winson Chung7aceb9a2014-07-03 13:38:01 -070049 public void onTaskViewDismissed(TaskView tv);
Winson Chung37c8d8e2014-03-24 14:53:07 -070050 }
51
Winson Chungd42a6cf2014-06-03 16:24:04 -070052 RecentsConfiguration mConfig;
53
Winson Chung1f24c7e2014-07-11 17:06:48 -070054 int mFooterHeight;
55 int mMaxFooterHeight;
56 ObjectAnimator mFooterAnimator;
57
Winson Chung14926462014-04-14 18:57:14 -070058 int mDim;
59 int mMaxDim;
Winson Chung6057c912014-07-08 14:39:08 -070060 AccelerateInterpolator mDimInterpolator = new AccelerateInterpolator();
Winson Chung14926462014-04-14 18:57:14 -070061
Winson Chung303e1ff2014-03-07 15:06:19 -080062 Task mTask;
Winson Chung37c8d8e2014-03-24 14:53:07 -070063 boolean mTaskDataLoaded;
Winson Chung1e8d71b2014-05-16 17:05:22 -070064 boolean mIsFocused;
Winson Chungffa2ec62014-07-03 15:54:42 -070065 boolean mIsStub;
Winson Chung5a9b0b02014-05-20 17:32:03 -070066 boolean mClipViewInStack;
Winson Chungd42a6cf2014-06-03 16:24:04 -070067 Rect mTmpRect = new Rect();
Winson Chungc9567c02014-06-16 20:25:51 -070068 Paint mLayerPaint = new Paint();
Winson Chung1f24c7e2014-07-11 17:06:48 -070069 Outline mOutline = new Outline();
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());
Winson Chung37c8d8e2014-03-24 14:53:07 -0700118 }
119
120 @Override
121 protected void onFinishInflate() {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700122 mMaxDim = mConfig.taskStackMaxDim;
Winson Chung14926462014-04-14 18:57:14 -0700123
Winson Chung5a9b0b02014-05-20 17:32:03 -0700124 // By default, all views are clipped to other views in their stack
125 mClipViewInStack = true;
126
Winson Chung37c8d8e2014-03-24 14:53:07 -0700127 // Bind the views
Winson Chung37c8d8e2014-03-24 14:53:07 -0700128 mBarView = (TaskBarView) findViewById(R.id.task_view_bar);
Winson Chung743d5c92014-06-13 10:14:53 -0700129 mThumbnailView = (TaskThumbnailView) findViewById(R.id.task_view_thumbnail);
Winson Chung1f24c7e2014-07-11 17:06:48 -0700130 mLockToAppButtonView = findViewById(R.id.lock_to_app);
Winson Chung9f9679d2014-04-11 16:49:09 -0700131
Winson Chung37c8d8e2014-03-24 14:53:07 -0700132 if (mTaskDataLoaded) {
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700133 onTaskDataLoaded();
Winson Chung303e1ff2014-03-07 15:06:19 -0800134 }
135 }
136
Winson Chung9f9679d2014-04-11 16:49:09 -0700137 @Override
Winson Chung14926462014-04-14 18:57:14 -0700138 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
Winson Chung1f24c7e2014-07-11 17:06:48 -0700139 int width = MeasureSpec.getSize(widthMeasureSpec);
140 int height = MeasureSpec.getSize(heightMeasureSpec);
Winson Chung14926462014-04-14 18:57:14 -0700141
Winson Chung1f24c7e2014-07-11 17:06:48 -0700142 // Measure the bar view, thumbnail, and lock-to-app buttons
143 mBarView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
144 MeasureSpec.makeMeasureSpec(mConfig.taskBarHeight, MeasureSpec.EXACTLY));
145 mLockToAppButtonView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
146 MeasureSpec.makeMeasureSpec(mConfig.taskViewLockToAppButtonHeight,
147 MeasureSpec.EXACTLY));
148 // Measure the thumbnail height to be the same as the width
149 mThumbnailView.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
150 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY));
151 setMeasuredDimension(width, height);
152 updateOutline();
153 }
154
155 /** Updates the outline to match whether the lock-to-app button is visible or not. */
156 void updateOutline() {
157 int height = getMeasuredHeight();
158 if (height == 0) return;
159
160 // Account for the current footer height
161 height = height - mMaxFooterHeight + mFooterHeight;
162
163 mOutline.setRoundRect(0, 0, getMeasuredWidth(), height,
Winson Chungffa2ec62014-07-03 15:54:42 -0700164 mConfig.taskViewRoundedCornerRadiusPx);
Winson Chung1f24c7e2014-07-11 17:06:48 -0700165 setOutline(mOutline);
Winson Chung14926462014-04-14 18:57:14 -0700166 }
167
Winson Chung04dfe0d2014-03-14 14:06:29 -0700168 /** Set callback */
169 void setCallbacks(TaskViewCallbacks cb) {
Winson Chung303e1ff2014-03-07 15:06:19 -0800170 mCb = cb;
171 }
172
Winson Chung303e1ff2014-03-07 15:06:19 -0800173 /** Gets the task */
174 Task getTask() {
175 return mTask;
176 }
177
178 /** Synchronizes this view's properties with the task's transform */
Winson Chungb5ddfc32014-06-13 17:23:12 -0700179 void updateViewPropertiesToTaskTransform(TaskViewTransform toTransform, int duration) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700180 if (Console.Enabled) {
181 Console.log(Constants.Log.UI.Draw, "[TaskView|updateViewPropertiesToTaskTransform]",
182 "duration: " + duration, Console.AnsiPurple);
183 }
Winson Chung96e3bc12014-05-06 16:44:12 -0700184
Winson Chung54e297a2014-05-09 17:15:32 -0700185 // Update the bar view
Winson Chungb5ddfc32014-06-13 17:23:12 -0700186 mBarView.updateViewPropertiesToTaskTransform(toTransform, duration);
Winson Chung54e297a2014-05-09 17:15:32 -0700187
Winson Chungb5ddfc32014-06-13 17:23:12 -0700188 // Check to see if any properties have changed, and update the task view
Winson Chung303e1ff2014-03-07 15:06:19 -0800189 if (duration > 0) {
Winson Chungb5ddfc32014-06-13 17:23:12 -0700190 ViewPropertyAnimator anim = animate();
191 boolean useLayers = false;
192
193 // Animate to the final state
194 if (toTransform.hasTranslationYChangedFrom(getTranslationY())) {
195 anim.translationY(toTransform.translationY);
Winson Chung303e1ff2014-03-07 15:06:19 -0800196 }
Winson Chungb5ddfc32014-06-13 17:23:12 -0700197 if (Constants.DebugFlags.App.EnableShadows &&
198 toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
199 anim.translationZ(toTransform.translationZ);
Winson Chung814086d2014-05-07 15:01:14 -0700200 }
Winson Chungb5ddfc32014-06-13 17:23:12 -0700201 if (toTransform.hasScaleChangedFrom(getScaleX())) {
202 anim.scaleX(toTransform.scale)
Winson Chungc6a16232014-04-01 14:04:48 -0700203 .scaleY(toTransform.scale)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700204 .setUpdateListener(mUpdateDimListener);
205 useLayers = true;
206 }
207 if (toTransform.hasAlphaChangedFrom(getAlpha())) {
208 // Use layers if we animate alpha
209 anim.alpha(toTransform.alpha);
210 useLayers = true;
211 }
212 if (useLayers) {
213 anim.withLayer();
214 }
Winson Chung7ab650c2014-06-18 14:25:34 -0700215 anim.setStartDelay(toTransform.startDelay)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700216 .setDuration(duration)
217 .setInterpolator(mConfig.fastOutSlowInInterpolator)
218 .start();
Winson Chung303e1ff2014-03-07 15:06:19 -0800219 } else {
Winson Chungb5ddfc32014-06-13 17:23:12 -0700220 // Set the changed properties
221 if (toTransform.hasTranslationYChangedFrom(getTranslationY())) {
222 setTranslationY(toTransform.translationY);
223 }
224 if (Constants.DebugFlags.App.EnableShadows &&
225 toTransform.hasTranslationZChangedFrom(getTranslationZ())) {
Winson Chungb13c46e2014-06-09 18:33:08 -0700226 setTranslationZ(toTransform.translationZ);
Winson Chung814086d2014-05-07 15:01:14 -0700227 }
Winson Chungb5ddfc32014-06-13 17:23:12 -0700228 if (toTransform.hasScaleChangedFrom(getScaleX())) {
229 setScaleX(toTransform.scale);
230 setScaleY(toTransform.scale);
231 updateDimOverlayFromScale();
232 }
233 if (toTransform.hasAlphaChangedFrom(getAlpha())) {
234 setAlpha(toTransform.alpha);
235 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800236 }
237 }
238
239 /** Resets this view's properties */
240 void resetViewProperties() {
241 setTranslationX(0f);
242 setTranslationY(0f);
Winson Chung814086d2014-05-07 15:01:14 -0700243 if (Constants.DebugFlags.App.EnableShadows) {
244 setTranslationZ(0f);
245 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800246 setScaleX(1f);
247 setScaleY(1f);
248 setAlpha(1f);
Winson Chunga26fb782014-06-12 17:52:39 -0700249 setDim(0);
Winson Chung14926462014-04-14 18:57:14 -0700250 invalidate();
Winson Chung303e1ff2014-03-07 15:06:19 -0800251 }
252
Winson Chung11ca76a52014-04-10 17:29:13 -0700253 /**
254 * When we are un/filtering, this method will set up the transform that we are animating to,
255 * in order to hide the task.
256 */
Winson Chungc6a16232014-04-01 14:04:48 -0700257 void prepareTaskTransformForFilterTaskHidden(TaskViewTransform toTransform) {
258 // Fade the view out and slide it away
259 toTransform.alpha = 0f;
260 toTransform.translationY += 200;
Winson Chung7ab650c2014-06-18 14:25:34 -0700261 toTransform.translationZ = 0;
Winson Chungc6a16232014-04-01 14:04:48 -0700262 }
263
Winson Chung11ca76a52014-04-10 17:29:13 -0700264 /**
265 * When we are un/filtering, this method will setup the transform that we are animating from,
266 * in order to show the task.
267 */
Winson Chungc6a16232014-04-01 14:04:48 -0700268 void prepareTaskTransformForFilterTaskVisible(TaskViewTransform fromTransform) {
269 // Fade the view in
270 fromTransform.alpha = 0f;
271 }
272
Winson Chung24cf1522014-05-29 12:03:33 -0700273 /** Prepares this task view for the enter-recents animations. This is called earlier in the
274 * first layout because the actual animation into recents may take a long time. */
Winson Chung083baf92014-07-11 10:32:42 -0700275 public void prepareEnterRecentsAnimation(boolean isTaskViewLaunchTargetTask, int offsetY,
276 int offscreenY) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700277 if (mConfig.launchedFromAppWithScreenshot) {
Winson Chung083baf92014-07-11 10:32:42 -0700278 if (isTaskViewLaunchTargetTask) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700279 // Hide the task view as we are going to animate the full screenshot into view
280 // and then replace it with this view once we are done
281 setVisibility(View.INVISIBLE);
282 // Also hide the front most task bar view so we can animate it in
Winson Chung969f5862014-06-16 17:08:24 -0700283 mBarView.prepareEnterRecentsAnimation();
Winson Chungd42a6cf2014-06-03 16:24:04 -0700284 } else {
285 // Top align the task views
286 setTranslationY(offsetY);
287 setScaleX(1f);
288 setScaleY(1f);
289 }
290
291 } else if (mConfig.launchedFromAppWithThumbnail) {
Winson Chung083baf92014-07-11 10:32:42 -0700292 if (isTaskViewLaunchTargetTask) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700293 // Hide the front most task bar view so we can animate it in
Winson Chung969f5862014-06-16 17:08:24 -0700294 mBarView.prepareEnterRecentsAnimation();
Winson Chunga26fb782014-06-12 17:52:39 -0700295 // Set the dim to 0 so we can animate it in
296 setDim(0);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700297 }
298
299 } else if (mConfig.launchedFromHome) {
300 // Move the task view off screen (below) so we can animate it in
301 setTranslationY(offscreenY);
Winson Chung7aceb9a2014-07-03 13:38:01 -0700302 if (Constants.DebugFlags.App.EnableShadows) {
303 setTranslationZ(0);
304 }
Winson Chungd42a6cf2014-06-03 16:24:04 -0700305 setScaleX(1f);
306 setScaleY(1f);
307 }
Winson Chung24cf1522014-05-29 12:03:33 -0700308 }
309
Winson Chung303e1ff2014-03-07 15:06:19 -0800310 /** Animates this task view as it enters recents */
Winson Chungc6011de2014-06-30 18:04:55 -0700311 public void startEnterRecentsAnimation(final ViewAnimation.TaskViewEnterContext ctx) {
Winson Chung6057c912014-07-08 14:39:08 -0700312 TaskViewTransform transform = ctx.currentTaskTransform;
Winson Chungd42a6cf2014-06-03 16:24:04 -0700313
314 if (mConfig.launchedFromAppWithScreenshot) {
Winson Chung083baf92014-07-11 10:32:42 -0700315 if (ctx.isCurrentTaskLaunchTarget) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700316 // Animate the full screenshot down first, before swapping with this task view
Winson Chung6057c912014-07-08 14:39:08 -0700317 ctx.fullScreenshotView.animateOnEnterRecents(ctx, new Runnable() {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700318 @Override
319 public void run() {
320 // Animate the task bar of the first task view
Winson Chung969f5862014-06-16 17:08:24 -0700321 mBarView.startEnterRecentsAnimation(0, mEnableThumbnailClip);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700322 setVisibility(View.VISIBLE);
Winson Chung1f24c7e2014-07-11 17:06:48 -0700323 // Animate the footer into view
324 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration, 0);
Winson Chungc6011de2014-06-30 18:04:55 -0700325 // Decrement the post animation trigger
326 ctx.postAnimationTrigger.decrement();
Winson Chungd42a6cf2014-06-03 16:24:04 -0700327 }
328 });
329 } else {
330 // Animate the tasks down behind the full screenshot
331 animate()
332 .scaleX(transform.scale)
333 .scaleY(transform.scale)
334 .translationY(transform.translationY)
335 .setStartDelay(0)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700336 .setUpdateListener(null)
Winson Chungd42a6cf2014-06-03 16:24:04 -0700337 .setInterpolator(mConfig.linearOutSlowInInterpolator)
338 .setDuration(475)
339 .withLayer()
Winson Chungc6011de2014-06-30 18:04:55 -0700340 .withEndAction(new Runnable() {
341 @Override
342 public void run() {
343 mEnableThumbnailClip.run();
344 // Decrement the post animation trigger
345 ctx.postAnimationTrigger.decrement();
346 }
347 })
Winson Chungd42a6cf2014-06-03 16:24:04 -0700348 .start();
349 }
Winson Chungc6011de2014-06-30 18:04:55 -0700350 ctx.postAnimationTrigger.increment();
Winson Chungd42a6cf2014-06-03 16:24:04 -0700351
352 } else if (mConfig.launchedFromAppWithThumbnail) {
Winson Chung083baf92014-07-11 10:32:42 -0700353 if (ctx.isCurrentTaskLaunchTarget) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700354 // Animate the task bar of the first task view
Winson Chung969f5862014-06-16 17:08:24 -0700355 mBarView.startEnterRecentsAnimation(mConfig.taskBarEnterAnimDelay, mEnableThumbnailClip);
Winson Chung743d5c92014-06-13 10:14:53 -0700356
Winson Chunga26fb782014-06-12 17:52:39 -0700357 // Animate the dim into view as well
358 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", getDimOverlayFromScale());
359 anim.setStartDelay(mConfig.taskBarEnterAnimDelay);
360 anim.setDuration(mConfig.taskBarEnterAnimDuration);
361 anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
Winson Chungc6011de2014-06-30 18:04:55 -0700362 anim.addListener(new AnimatorListenerAdapter() {
363 @Override
364 public void onAnimationEnd(Animator animation) {
365 // Decrement the post animation trigger
366 ctx.postAnimationTrigger.decrement();
367 }
368 });
Winson Chunga26fb782014-06-12 17:52:39 -0700369 anim.start();
Winson Chungc6011de2014-06-30 18:04:55 -0700370 ctx.postAnimationTrigger.increment();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700371
372 // Animate the footer into view
373 animateFooterVisibility(true, mConfig.taskBarEnterAnimDuration,
374 mConfig.taskBarEnterAnimDelay);
Winson Chung743d5c92014-06-13 10:14:53 -0700375 } else {
376 mEnableThumbnailClip.run();
Winson Chungd42a6cf2014-06-03 16:24:04 -0700377 }
378
379 } else if (mConfig.launchedFromHome) {
380 // Animate the tasks up
Winson Chung6057c912014-07-08 14:39:08 -0700381 int frontIndex = (ctx.currentStackViewCount - ctx.currentStackViewIndex - 1);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700382 int delay = mConfig.taskBarEnterAnimDelay +
383 frontIndex * mConfig.taskViewEnterFromHomeDelay;
Winson Chung7aceb9a2014-07-03 13:38:01 -0700384 if (Constants.DebugFlags.App.EnableShadows) {
385 animate().translationZ(transform.translationZ);
386 }
Winson Chungd42a6cf2014-06-03 16:24:04 -0700387 animate()
388 .scaleX(transform.scale)
389 .scaleY(transform.scale)
390 .translationY(transform.translationY)
391 .setStartDelay(delay)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700392 .setUpdateListener(null)
Winson Chungd42a6cf2014-06-03 16:24:04 -0700393 .setInterpolator(mConfig.quintOutInterpolator)
394 .setDuration(mConfig.taskViewEnterFromHomeDuration)
395 .withLayer()
Winson Chungc6011de2014-06-30 18:04:55 -0700396 .withEndAction(new Runnable() {
397 @Override
398 public void run() {
399 mEnableThumbnailClip.run();
400 // Decrement the post animation trigger
401 ctx.postAnimationTrigger.decrement();
402 }
403 })
Winson Chungd42a6cf2014-06-03 16:24:04 -0700404 .start();
Winson Chungc6011de2014-06-30 18:04:55 -0700405 ctx.postAnimationTrigger.increment();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700406
407 // Animate the footer into view
408 animateFooterVisibility(true, mConfig.taskViewEnterFromHomeDuration,
409 mConfig.taskBarEnterAnimDelay);
Winson Chung7aceb9a2014-07-03 13:38:01 -0700410 } else {
411 // Otherwise, just enable the thumbnail clip
412 mEnableThumbnailClip.run();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700413
414 // Animate the footer into view
415 animateFooterVisibility(true, 0, 0);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700416 }
417 }
418
Winson Chung969f5862014-06-16 17:08:24 -0700419 /** Animates this task view as it leaves recents by pressing home. */
420 public void startExitToHomeAnimation(ViewAnimation.TaskViewExitContext ctx) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700421 animate()
422 .translationY(ctx.offscreenTranslationY)
423 .setStartDelay(0)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700424 .setUpdateListener(null)
Winson Chungad6f2762014-06-13 09:41:09 -0700425 .setInterpolator(mConfig.fastOutLinearInInterpolator)
426 .setDuration(mConfig.taskViewExitToHomeDuration)
Winson Chung37c8d8e2014-03-24 14:53:07 -0700427 .withLayer()
Winson Chungd42a6cf2014-06-03 16:24:04 -0700428 .withEndAction(ctx.postAnimationTrigger.decrementAsRunnable())
Winson Chung37c8d8e2014-03-24 14:53:07 -0700429 .start();
Winson Chungd42a6cf2014-06-03 16:24:04 -0700430 ctx.postAnimationTrigger.increment();
Winson Chung303e1ff2014-03-07 15:06:19 -0800431 }
432
433 /** Animates this task view as it exits recents */
Winson Chung969f5862014-06-16 17:08:24 -0700434 public void startLaunchTaskAnimation(final Runnable r, boolean isLaunchingTask) {
435 if (isLaunchingTask) {
436 // Disable the thumbnail clip and animate the bar out
437 mBarView.startLaunchTaskAnimation(mDisableThumbnailClip, r);
Winson Chunga26fb782014-06-12 17:52:39 -0700438
Winson Chung969f5862014-06-16 17:08:24 -0700439 // Animate the dim
440 if (mDim > 0) {
441 ObjectAnimator anim = ObjectAnimator.ofInt(this, "dim", 0);
442 anim.setDuration(mConfig.taskBarExitAnimDuration);
443 anim.setInterpolator(mConfig.fastOutLinearInInterpolator);
444 anim.start();
445 }
446 } else {
447 // Hide the dismiss button
448 mBarView.startLaunchTaskDismissAnimation();
Winson Chunga26fb782014-06-12 17:52:39 -0700449 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800450 }
451
Winson Chung9f49df92014-05-07 18:08:34 -0700452 /** Animates the deletion of this task view */
Winson Chung969f5862014-06-16 17:08:24 -0700453 public void startDeleteTaskAnimation(final Runnable r) {
Winson Chung5a9b0b02014-05-20 17:32:03 -0700454 // Disabling clipping with the stack while the view is animating away
455 setClipViewInStack(false);
456
Winson Chungd42a6cf2014-06-03 16:24:04 -0700457 animate().translationX(mConfig.taskViewRemoveAnimTranslationXPx)
Winson Chung9f49df92014-05-07 18:08:34 -0700458 .alpha(0f)
459 .setStartDelay(0)
Winson Chungb5ddfc32014-06-13 17:23:12 -0700460 .setUpdateListener(null)
Winson Chungd42a6cf2014-06-03 16:24:04 -0700461 .setInterpolator(mConfig.fastOutSlowInInterpolator)
462 .setDuration(mConfig.taskViewRemoveAnimDuration)
Winson Chung9f49df92014-05-07 18:08:34 -0700463 .withLayer()
464 .withEndAction(new Runnable() {
465 @Override
466 public void run() {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700467 // We just throw this into a runnable because starting a view property
468 // animation using layers can cause inconsisten results if we try and
469 // update the layers while the animation is running. In some cases,
470 // the runnabled passed in may start an animation which also uses layers
471 // so we defer all this by posting this.
472 r.run();
Winson Chung5a9b0b02014-05-20 17:32:03 -0700473
474 // Re-enable clipping with the stack (we will reuse this view)
Winson Chungd42a6cf2014-06-03 16:24:04 -0700475 setClipViewInStack(true);
Winson Chung9f49df92014-05-07 18:08:34 -0700476 }
477 })
478 .start();
479 }
480
Winson Chung969f5862014-06-16 17:08:24 -0700481 /** Animates this task view if the user does not interact with the stack after a certain time. */
482 public void startNoUserInteractionAnimation() {
483 mBarView.startNoUserInteractionAnimation();
484 }
485
486 /** Mark this task view that the user does has not interacted with the stack after a certain time. */
487 public void setNoUserInteractionState() {
488 mBarView.setNoUserInteractionState();
489 }
490
Winson Chung303e1ff2014-03-07 15:06:19 -0800491 /** Returns the rect we want to clip (it may not be the full rect) */
Winson Chung0d767552014-04-09 14:33:23 -0700492 Rect getClippingRect(Rect outRect) {
Winson Chung303e1ff2014-03-07 15:06:19 -0800493 getHitRect(outRect);
494 // XXX: We should get the hit rect of the thumbnail view and intersect, but this is faster
495 outRect.right = outRect.left + mThumbnailView.getRight();
496 outRect.bottom = outRect.top + mThumbnailView.getBottom();
Winson Chung303e1ff2014-03-07 15:06:19 -0800497 return outRect;
498 }
499
500 /** Enable the hw layers on this task view */
501 void enableHwLayers() {
Winson Chungc9567c02014-06-16 20:25:51 -0700502 mThumbnailView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700503 mBarView.enableHwLayers();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700504 mLockToAppButtonView.setLayerType(View.LAYER_TYPE_HARDWARE, mLayerPaint);
Winson Chung303e1ff2014-03-07 15:06:19 -0800505 }
506
507 /** Disable the hw layers on this task view */
508 void disableHwLayers() {
Winson Chungc9567c02014-06-16 20:25:51 -0700509 mThumbnailView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
Winson Chungd42a6cf2014-06-03 16:24:04 -0700510 mBarView.disableHwLayers();
Winson Chung1f24c7e2014-07-11 17:06:48 -0700511 mLockToAppButtonView.setLayerType(View.LAYER_TYPE_NONE, mLayerPaint);
Winson Chung303e1ff2014-03-07 15:06:19 -0800512 }
513
Winson Chungffa2ec62014-07-03 15:54:42 -0700514 /** Sets the stubbed state of this task view. */
515 void setStubState(boolean isStub) {
516 if (!mIsStub && isStub) {
517 // This is now a stub task view, so clip to the bar height, hide the thumbnail
518 setClipBounds(new Rect(0, 0, getMeasuredWidth(), mBarView.getMeasuredHeight()));
519 mThumbnailView.setVisibility(View.INVISIBLE);
520 // Temporary
521 mBarView.mActivityDescription.setText("Stub");
522 } else if (mIsStub && !isStub) {
523 setClipBounds(null);
524 mThumbnailView.setVisibility(View.VISIBLE);
525 }
526 mIsStub = isStub;
527 }
528
Winson Chung5a9b0b02014-05-20 17:32:03 -0700529 /**
530 * Returns whether this view should be clipped, or any views below should clip against this
531 * view.
532 */
533 boolean shouldClipViewInStack() {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700534 return mClipViewInStack && (getVisibility() == View.VISIBLE);
Winson Chung5a9b0b02014-05-20 17:32:03 -0700535 }
536
537 /** Sets whether this view should be clipped, or clipped against. */
538 void setClipViewInStack(boolean clip) {
539 if (clip != mClipViewInStack) {
540 mClipViewInStack = clip;
541 if (getParent() instanceof View) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700542 getHitRect(mTmpRect);
543 ((View) getParent()).invalidate(mTmpRect);
Winson Chung5a9b0b02014-05-20 17:32:03 -0700544 }
545 }
546 }
547
Winson Chung1f24c7e2014-07-11 17:06:48 -0700548 /** Sets the footer height. */
549 public void setFooterHeight(int height) {
550 mFooterHeight = height;
551 updateOutline();
552 invalidate(0, getMeasuredHeight() - mMaxFooterHeight, getMeasuredWidth(),
553 getMeasuredHeight());
554 }
555
556 /** Gets the footer height. */
557 public int getFooterHeight() {
558 return mFooterHeight;
559 }
560
561 /** Animates the footer into and out of view. */
562 public void animateFooterVisibility(boolean visible, int duration, int delay) {
563 if (!mTask.canLockToTask) return;
564 if (mMaxFooterHeight <= 0) return;
565
566 if (mFooterAnimator != null) {
567 mFooterAnimator.removeAllListeners();
568 mFooterAnimator.cancel();
569 }
570 int height = visible ? mMaxFooterHeight : 0;
571 if (visible && mLockToAppButtonView.getVisibility() != View.VISIBLE) {
572 if (duration > 0) {
573 setFooterHeight(0);
574 } else {
575 setFooterHeight(mMaxFooterHeight);
576 }
577 mLockToAppButtonView.setVisibility(View.VISIBLE);
578 }
579 if (duration > 0) {
580 mFooterAnimator = ObjectAnimator.ofInt(this, "footerHeight", height);
581 mFooterAnimator.setDuration(duration);
582 mFooterAnimator.setInterpolator(mConfig.fastOutSlowInInterpolator);
583 if (!visible) {
584 mFooterAnimator.addListener(new AnimatorListenerAdapter() {
585 @Override
586 public void onAnimationEnd(Animator animation) {
587 mLockToAppButtonView.setVisibility(View.INVISIBLE);
588 }
589 });
590 }
591 mFooterAnimator.start();
592 } else {
593 if (!visible) {
594 mLockToAppButtonView.setVisibility(View.INVISIBLE);
595 }
596 }
597 }
598
Winson Chunga26fb782014-06-12 17:52:39 -0700599 /** Returns the current dim. */
600 public void setDim(int dim) {
601 mDim = dim;
602 postInvalidateOnAnimation();
603 }
604
605 /** Returns the current dim. */
606 public int getDim() {
607 return mDim;
608 }
609
610 /** Compute the dim as a function of the scale of this view. */
611 int getDimOverlayFromScale() {
Winson Chungd7b2cb12014-06-26 15:08:50 -0700612 float minScale = TaskStackViewLayoutAlgorithm.StackPeekMinScale;
Winson Chung14926462014-04-14 18:57:14 -0700613 float scaleRange = 1f - minScale;
614 float dim = (1f - getScaleX()) / scaleRange;
615 dim = mDimInterpolator.getInterpolation(Math.min(dim, 1f));
Winson Chunga26fb782014-06-12 17:52:39 -0700616 return Math.max(0, Math.min(mMaxDim, (int) (dim * 255)));
617 }
618
619 /** Update the dim as a function of the scale of this view. */
620 void updateDimOverlayFromScale() {
621 setDim(getDimOverlayFromScale());
Winson Chung14926462014-04-14 18:57:14 -0700622 }
623
624 @Override
625 public void draw(Canvas canvas) {
Winson Chung14926462014-04-14 18:57:14 -0700626 super.draw(canvas);
627
628 // Apply the dim if necessary
629 if (mDim > 0) {
630 canvas.drawColor(mDim << 24);
631 }
632 }
633
Winson Chungbdbb87d2014-07-09 14:29:13 -0700634 @Override
635 protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
636 if (mIsStub && (child == mThumbnailView)) {
637 // Skip the thumbnail view if we are in stub mode
638 return false;
639 }
640 return super.drawChild(canvas, child, drawingTime);
641 }
642
Winson Chung1e8d71b2014-05-16 17:05:22 -0700643 /**
644 * Sets the focused task explicitly. We need a separate flag because requestFocus() won't happen
645 * if the view is not currently visible, or we are in touch state (where we still want to keep
646 * track of focus).
647 */
648 public void setFocusedTask() {
649 mIsFocused = true;
Winson Chungc6011de2014-06-30 18:04:55 -0700650 // Workaround, we don't always want it focusable in touch mode, but we want the first task
651 // to be focused after the enter-recents animation, which can be triggered from either touch
652 // or keyboard
653 setFocusableInTouchMode(true);
Winson Chung1e8d71b2014-05-16 17:05:22 -0700654 requestFocus();
Winson Chungc6011de2014-06-30 18:04:55 -0700655 setFocusableInTouchMode(false);
Winson Chung47a3e652014-05-21 16:03:42 -0700656 invalidate();
Winson Chung1e8d71b2014-05-16 17:05:22 -0700657 }
658
659 /**
660 * Updates the explicitly focused state when the view focus changes.
661 */
662 @Override
663 protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
664 super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
665 if (!gainFocus) {
666 mIsFocused = false;
Winson Chung47a3e652014-05-21 16:03:42 -0700667 invalidate();
Winson Chung1e8d71b2014-05-16 17:05:22 -0700668 }
669 }
670
671 /**
672 * Returns whether we have explicitly been focused.
673 */
674 public boolean isFocusedTask() {
675 return mIsFocused || isFocused();
676 }
677
Winson Chung4d7b0922014-03-13 17:14:17 -0700678 /**** TaskCallbacks Implementation ****/
679
Winson Chung04dfe0d2014-03-14 14:06:29 -0700680 /** Binds this task view to the task */
681 public void onTaskBound(Task t) {
682 mTask = t;
683 mTask.setCallbacks(this);
Winson Chung1f24c7e2014-07-11 17:06:48 -0700684 if (getMeasuredWidth() == 0) {
685 animateFooterVisibility(t.canLockToTask, 0, 0);
686 } else {
687 animateFooterVisibility(t.canLockToTask, mConfig.taskViewLockToAppLongAnimDuration, 0);
688 }
Winson Chung303e1ff2014-03-07 15:06:19 -0800689 }
690
691 @Override
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700692 public void onTaskDataLoaded() {
Winson Chung863db8a2014-05-20 14:27:39 -0700693 if (mThumbnailView != null && mBarView != null) {
Winson Chung37c8d8e2014-03-24 14:53:07 -0700694 // Bind each of the views to the new task data
Winson Chung8eaeb7d2014-06-25 15:10:59 -0700695 mThumbnailView.rebindToTask(mTask);
696 mBarView.rebindToTask(mTask);
Winson Chung9f9679d2014-04-11 16:49:09 -0700697 // Rebind any listeners
Winson Chungffa2ec62014-07-03 15:54:42 -0700698 if (Constants.DebugFlags.App.EnableTaskFiltering) {
699 mBarView.mApplicationIcon.setOnClickListener(this);
700 }
Winson Chung54e297a2014-05-09 17:15:32 -0700701 mBarView.mDismissButton.setOnClickListener(this);
Winson Chung1f24c7e2014-07-11 17:06:48 -0700702 mLockToAppButtonView.setOnClickListener(this);
Winson Chung6cb485f2014-05-19 10:30:43 -0700703 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
Winson Chungd42a6cf2014-06-03 16:24:04 -0700704 if (mConfig.developerOptionsEnabled) {
Winson Chung6cb485f2014-05-19 10:30:43 -0700705 mBarView.mApplicationIcon.setOnLongClickListener(this);
706 }
707 }
Winson Chung37c8d8e2014-03-24 14:53:07 -0700708 }
709 mTaskDataLoaded = true;
Winson Chung4d7b0922014-03-13 17:14:17 -0700710 }
711
712 @Override
Winson Chung04dfe0d2014-03-14 14:06:29 -0700713 public void onTaskDataUnloaded() {
Winson Chung863db8a2014-05-20 14:27:39 -0700714 if (mThumbnailView != null && mBarView != null) {
Winson Chung37c8d8e2014-03-24 14:53:07 -0700715 // Unbind each of the views from the task data and remove the task callback
716 mTask.setCallbacks(null);
717 mThumbnailView.unbindFromTask();
718 mBarView.unbindFromTask();
Winson Chung9f9679d2014-04-11 16:49:09 -0700719 // Unbind any listeners
Winson Chungffa2ec62014-07-03 15:54:42 -0700720 if (Constants.DebugFlags.App.EnableTaskFiltering) {
721 mBarView.mApplicationIcon.setOnClickListener(null);
722 }
Winson Chung6cb485f2014-05-19 10:30:43 -0700723 mBarView.mDismissButton.setOnClickListener(null);
Winson Chung1f24c7e2014-07-11 17:06:48 -0700724 mLockToAppButtonView.setOnClickListener(null);
Winson Chung6cb485f2014-05-19 10:30:43 -0700725 if (Constants.DebugFlags.App.EnableDevAppInfoOnLongPress) {
726 mBarView.mApplicationIcon.setOnLongClickListener(null);
727 }
Winson Chung37c8d8e2014-03-24 14:53:07 -0700728 }
729 mTaskDataLoaded = false;
Winson Chung4d7b0922014-03-13 17:14:17 -0700730 }
731
Winson Chung1f24c7e2014-07-11 17:06:48 -0700732 /** Enables/disables handling touch on this task view. */
733 void setTouchEnabled(boolean enabled) {
734 setOnClickListener(enabled ? this : null);
735 }
736
Winson Chung4d7b0922014-03-13 17:14:17 -0700737 @Override
Winson Chung7ab650c2014-06-18 14:25:34 -0700738 public void onClick(final View v) {
739 // We purposely post the handler delayed to allow for the touch feedback to draw
740 final TaskView tv = this;
741 postDelayed(new Runnable() {
742 @Override
743 public void run() {
744 if (v == mBarView.mApplicationIcon) {
Winson Chung7aceb9a2014-07-03 13:38:01 -0700745 mCb.onTaskViewAppIconClicked(tv);
Winson Chung7ab650c2014-06-18 14:25:34 -0700746 } else if (v == mBarView.mDismissButton) {
747 // Animate out the view and call the callback
748 startDeleteTaskAnimation(new Runnable() {
749 @Override
750 public void run() {
Winson Chung7aceb9a2014-07-03 13:38:01 -0700751 mCb.onTaskViewDismissed(tv);
Winson Chung7ab650c2014-06-18 14:25:34 -0700752 }
753 });
Winson Chung1f24c7e2014-07-11 17:06:48 -0700754 // Hide the footer
755 tv.animateFooterVisibility(false, mConfig.taskViewRemoveAnimDuration, 0);
756 } else if (v == tv || v == mLockToAppButtonView) {
757 mCb.onTaskViewClicked(tv, tv.getTask(), (v == mLockToAppButtonView));
Winson Chung54e297a2014-05-09 17:15:32 -0700758 }
Winson Chung7ab650c2014-06-18 14:25:34 -0700759 }
760 }, 125);
Winson Chung303e1ff2014-03-07 15:06:19 -0800761 }
Winson Chung6cb485f2014-05-19 10:30:43 -0700762
763 @Override
764 public boolean onLongClick(View v) {
765 if (v == mBarView.mApplicationIcon) {
Winson Chung7aceb9a2014-07-03 13:38:01 -0700766 mCb.onTaskViewAppInfoClicked(this);
Winson Chung6cb485f2014-05-19 10:30:43 -0700767 return true;
768 }
769 return false;
770 }
Winson Chung7bb18852014-05-20 23:25:41 +0000771}