blob: fdb0d324a081f1b68e3b0aa79f747aaf33c0884f [file] [log] [blame]
Winson83c1b072015-11-09 10:48:04 -08001/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.systemui.recents.views;
18
19import android.annotation.Nullable;
Filip Gruszczynski96daf322015-11-18 18:01:27 -080020import android.app.ActivityManager.StackId;
Winson83c1b072015-11-09 10:48:04 -080021import android.app.ActivityOptions;
22import android.content.Context;
23import android.graphics.Bitmap;
24import android.graphics.Canvas;
25import android.graphics.Rect;
26import android.os.Bundle;
27import android.os.Handler;
28import android.os.IRemoteCallback;
29import android.os.RemoteException;
30import android.util.Log;
Winson83c1b072015-11-09 10:48:04 -080031import android.view.AppTransitionAnimationSpec;
32import android.view.IAppTransitionAnimationSpecsFuture;
33import android.view.WindowManagerGlobal;
34import com.android.internal.annotations.GuardedBy;
Winson83c1b072015-11-09 10:48:04 -080035import com.android.systemui.recents.Recents;
Winsonc742f972015-11-12 11:32:21 -080036import com.android.systemui.recents.RecentsDebugFlags;
Winson83c1b072015-11-09 10:48:04 -080037import com.android.systemui.recents.events.EventBus;
38import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
Winsondc8de842016-01-06 15:21:41 -080039import com.android.systemui.recents.events.activity.ExitRecentsWindowFirstAnimationFrameEvent;
Winson83c1b072015-11-09 10:48:04 -080040import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
Winsonef064132016-01-05 12:11:31 -080041import com.android.systemui.recents.events.activity.LaunchTaskStartedEvent;
Winson83c1b072015-11-09 10:48:04 -080042import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
43import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
Winson83c1b072015-11-09 10:48:04 -080044import com.android.systemui.recents.misc.SystemServicesProxy;
45import com.android.systemui.recents.model.Task;
46import com.android.systemui.recents.model.TaskStack;
47
48import java.util.ArrayList;
49import java.util.List;
50
51import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
52import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
53import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
54
55/**
56 * A helper class to create transitions to/from Recents
57 */
58public class RecentsTransitionHelper {
59
60 private static final String TAG = "RecentsTransitionHelper";
61 private static final boolean DEBUG = false;
62
63 /**
64 * Special value for {@link #mAppTransitionAnimationSpecs}: Indicate that we are currently
65 * waiting for the specs to be retrieved.
66 */
67 private static final List<AppTransitionAnimationSpec> SPECS_WAITING = new ArrayList<>();
68
69 @GuardedBy("this")
70 private List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs = SPECS_WAITING;
71
72 private Context mContext;
73 private Handler mHandler;
74 private TaskViewTransform mTmpTransform = new TaskViewTransform();
75
76 private Runnable mStartScreenPinningRunnable = new Runnable() {
77 @Override
78 public void run() {
79 EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext));
80 }
81 };
82
83 public RecentsTransitionHelper(Context context, Handler handler) {
84 mContext = context;
85 mHandler = handler;
86 }
87
88 /**
89 * Launches the specified {@link Task}.
90 */
91 public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
92 final TaskStackView stackView, final TaskView taskView,
Winsonef064132016-01-05 12:11:31 -080093 final boolean screenPinningRequested, final Rect bounds, int destinationStack) {
Winson83c1b072015-11-09 10:48:04 -080094 final ActivityOptions opts = ActivityOptions.makeBasic();
95 if (bounds != null) {
Wale Ogunwale7a8fa602015-11-18 15:56:57 -080096 opts.setLaunchBounds(bounds.isEmpty() ? null : bounds);
Winson83c1b072015-11-09 10:48:04 -080097 }
98
99 final ActivityOptions.OnAnimationStartedListener animStartedListener;
100 final IAppTransitionAnimationSpecsFuture transitionFuture;
101 if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
102 task.thumbnail.getHeight() > 0) {
103 transitionFuture = getAppTransitionFuture(task, stackView, destinationStack);
104 animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
105 @Override
106 public void onAnimationStarted() {
107 // If we are launching into another task, cancel the previous task's
108 // window transition
109 EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
Filip Gruszczynski1a4dfe52015-11-15 10:58:57 -0800110 EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
Winson83c1b072015-11-09 10:48:04 -0800111
Winsonef064132016-01-05 12:11:31 -0800112 if (screenPinningRequested) {
Winson83c1b072015-11-09 10:48:04 -0800113 // Request screen pinning after the animation runs
114 mHandler.postDelayed(mStartScreenPinningRunnable, 350);
115 }
116 }
117 };
118 } else {
119 // This is only the case if the task is not on screen (scrolled offscreen for example)
120 transitionFuture = null;
Filip Gruszczynski1a4dfe52015-11-15 10:58:57 -0800121 animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
122 @Override
123 public void onAnimationStarted() {
124 EventBus.getDefault().send(new ExitRecentsWindowFirstAnimationFrameEvent());
125 }
126 };
Winson83c1b072015-11-09 10:48:04 -0800127 }
128
129 if (taskView == null) {
130 // If there is no task view, then we do not need to worry about animating out occluding
131 // task views, and we can launch immediately
132 startTaskActivity(stack, task, taskView, opts, transitionFuture, animStartedListener);
133 } else {
Winsonef064132016-01-05 12:11:31 -0800134 LaunchTaskStartedEvent launchStartedEvent = new LaunchTaskStartedEvent(taskView,
135 screenPinningRequested);
Winson83c1b072015-11-09 10:48:04 -0800136 if (task.group != null && !task.group.isFrontMostTask(task)) {
Winsonef064132016-01-05 12:11:31 -0800137 launchStartedEvent.addPostAnimationCallback(new Runnable() {
Winson83c1b072015-11-09 10:48:04 -0800138 @Override
139 public void run() {
140 startTaskActivity(stack, task, taskView, opts, transitionFuture,
141 animStartedListener);
142 }
Winsonef064132016-01-05 12:11:31 -0800143 });
144 EventBus.getDefault().send(launchStartedEvent);
Winson83c1b072015-11-09 10:48:04 -0800145 } else {
Winsonef064132016-01-05 12:11:31 -0800146 EventBus.getDefault().send(launchStartedEvent);
Winson83c1b072015-11-09 10:48:04 -0800147 startTaskActivity(stack, task, taskView, opts, transitionFuture,
148 animStartedListener);
149 }
150 }
151 }
152
153 /**
154 * Starts the activity for the launch task.
155 *
156 * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
157 * we are toggling recents and the launch-to task is now offscreen.
158 */
159 private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
160 ActivityOptions opts, IAppTransitionAnimationSpecsFuture transitionFuture,
161 final ActivityOptions.OnAnimationStartedListener animStartedListener) {
162 SystemServicesProxy ssp = Recents.getSystemServices();
Winson Chung296278a2015-12-17 12:09:02 -0500163 if (ssp.startActivityFromRecents(mContext, task.key.id, task.title, opts)) {
Winson83c1b072015-11-09 10:48:04 -0800164 // Keep track of the index of the task launch
165 int taskIndexFromFront = 0;
Winson250608a2015-11-24 15:00:31 -0800166 int taskIndex = stack.indexOfStackTask(task);
Winson83c1b072015-11-09 10:48:04 -0800167 if (taskIndex > -1) {
Winson250608a2015-11-24 15:00:31 -0800168 taskIndexFromFront = stack.getStackTaskCount() - taskIndex - 1;
Winson83c1b072015-11-09 10:48:04 -0800169 }
170 EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
171 } else {
172 // Dismiss the task if we fail to launch it
Winsonef064132016-01-05 12:11:31 -0800173 taskView.dismissTask();
Winson83c1b072015-11-09 10:48:04 -0800174
175 // Keep track of failed launches
176 EventBus.getDefault().send(new LaunchTaskFailedEvent());
177 }
178 if (transitionFuture != null) {
179 IRemoteCallback.Stub callback = null;
180 if (animStartedListener != null) {
181 callback = new IRemoteCallback.Stub() {
182 @Override
183 public void sendResult(Bundle data) throws RemoteException {
184 mHandler.post(new Runnable() {
185 @Override
186 public void run() {
187 if (animStartedListener != null) {
188 animStartedListener.onAnimationStarted();
189 }
190 }
191 });
192 }
193 };
194 }
195 try {
Jorim Jaggic4d49cd2015-11-24 13:48:26 -0800196 synchronized (this) {
197 mAppTransitionAnimationSpecs = SPECS_WAITING;
198 }
Winson83c1b072015-11-09 10:48:04 -0800199 WindowManagerGlobal.getWindowManagerService()
200 .overridePendingAppTransitionMultiThumbFuture(transitionFuture,
201 callback, true /* scaleUp */);
202 } catch (RemoteException e) {
203 Log.w(TAG, "Failed to override transition: " + e);
204 }
205 }
206 }
207
208 /**
209 * Creates a future which will later be queried for animation specs for this current transition.
210 */
211 private IAppTransitionAnimationSpecsFuture getAppTransitionFuture(final Task task,
212 final TaskStackView stackView, final int destinationStack) {
213 return new IAppTransitionAnimationSpecsFuture.Stub() {
214 @Override
215 public AppTransitionAnimationSpec[] get() throws RemoteException {
216 mHandler.post(new Runnable() {
217 @Override
218 public void run() {
219 synchronized (RecentsTransitionHelper.this) {
220 mAppTransitionAnimationSpecs = composeAnimationSpecs(task, stackView,
221 destinationStack);
222 RecentsTransitionHelper.this.notifyAll();
223 }
224 }
225 });
226 synchronized (RecentsTransitionHelper.this) {
227 while (mAppTransitionAnimationSpecs == SPECS_WAITING) {
228 try {
229 RecentsTransitionHelper.this.wait();
230 } catch (InterruptedException e) {}
231 }
232 if (mAppTransitionAnimationSpecs == null) {
233 return null;
234 }
235 AppTransitionAnimationSpec[] specs
236 = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()];
237 mAppTransitionAnimationSpecs.toArray(specs);
238 mAppTransitionAnimationSpecs = SPECS_WAITING;
239 return specs;
240 }
241 }
242 };
243 }
244
245 /**
246 * Composes the animation specs for all the tasks in the target stack.
247 */
248 private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
249 final TaskStackView stackView, final int destinationStack) {
250 // Ensure we have a valid target stack id
251 final int targetStackId = destinationStack != INVALID_STACK_ID ?
252 destinationStack : task.key.stackId;
Filip Gruszczynski96daf322015-11-18 18:01:27 -0800253 if (!StackId.useAnimationSpecForAppTransition(targetStackId)) {
Winson83c1b072015-11-09 10:48:04 -0800254 return null;
255 }
256
257 // Calculate the offscreen task rect (for tasks that are not backed by views)
258 float stackScroll = stackView.getScroller().getStackScroll();
259 TaskView taskView = stackView.getChildViewForTask(task);
260 TaskStackLayoutAlgorithm layoutAlgorithm = stackView.getStackAlgorithm();
261 Rect offscreenTaskRect = new Rect(layoutAlgorithm.mTaskRect);
262 offscreenTaskRect.offsetTo(offscreenTaskRect.left,
Winsonf0d1c442015-12-01 11:04:45 -0800263 layoutAlgorithm.mStackRect.bottom);
Winson83c1b072015-11-09 10:48:04 -0800264
265 // If this is a full screen stack, the transition will be towards the single, full screen
266 // task. We only need the transition spec for this task.
267 List<AppTransitionAnimationSpec> specs = new ArrayList<>();
268 if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
269 if (taskView == null) {
270 specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
271 } else {
272 layoutAlgorithm.getStackTransform(task, stackScroll, mTmpTransform, null);
273 specs.add(composeAnimationSpec(taskView, mTmpTransform, true /* addHeaderBitmap */));
274 }
275 return specs;
276 }
277
278 // Otherwise, for freeform tasks, create a new animation spec for each task we have to
279 // launch
280 TaskStack stack = stackView.getStack();
Winson250608a2015-11-24 15:00:31 -0800281 ArrayList<Task> tasks = stack.getStackTasks();
Winson83c1b072015-11-09 10:48:04 -0800282 int taskCount = tasks.size();
283 for (int i = taskCount - 1; i >= 0; i--) {
284 Task t = tasks.get(i);
Filip Gruszczynskife9823f2015-11-12 14:09:26 -0800285 if (t.isFreeformTask() || targetStackId == FREEFORM_WORKSPACE_STACK_ID) {
Winson83c1b072015-11-09 10:48:04 -0800286 TaskView tv = stackView.getChildViewForTask(t);
287 if (tv == null) {
288 // TODO: Create a different animation task rect for this case (though it should
289 // never happen)
290 specs.add(composeOffscreenAnimationSpec(t, offscreenTaskRect));
291 } else {
Filip Gruszczynskib4cc67a2016-01-12 10:23:58 -0800292 layoutAlgorithm.getStackTransform(t, stackScroll, mTmpTransform, null);
Winson83c1b072015-11-09 10:48:04 -0800293 specs.add(composeAnimationSpec(tv, mTmpTransform, true /* addHeaderBitmap */));
294 }
295 }
296 }
297
298 return specs;
299 }
300
301 /**
302 * Composes a single animation spec for the given {@link Task}
303 */
304 private static AppTransitionAnimationSpec composeOffscreenAnimationSpec(Task task,
305 Rect taskRect) {
306 return new AppTransitionAnimationSpec(task.key.id, null, taskRect);
307 }
308
309 /**
310 * Composes a single animation spec for the given {@link TaskView}
311 */
312 private static AppTransitionAnimationSpec composeAnimationSpec(TaskView taskView,
313 TaskViewTransform transform, boolean addHeaderBitmap) {
Winson83c1b072015-11-09 10:48:04 -0800314 Bitmap b = null;
315 if (addHeaderBitmap) {
316 float scale = transform.scale;
Winson Chung509d0d02015-12-16 15:43:12 -0500317 int fromHeaderWidth = (int) (transform.rect.width());
Winson83c1b072015-11-09 10:48:04 -0800318 int fromHeaderHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
319 b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
320 Bitmap.Config.ARGB_8888);
321
Winsonc742f972015-11-12 11:32:21 -0800322 if (RecentsDebugFlags.Static.EnableTransitionThumbnailDebugMode) {
Winson83c1b072015-11-09 10:48:04 -0800323 b.eraseColor(0xFFff0000);
324 } else {
325 Canvas c = new Canvas(b);
326 c.scale(scale, scale);
327 taskView.mHeaderView.draw(c);
328 c.setBitmap(null);
329 }
330 b = b.createAshmemBitmap();
331 }
332
333 Rect taskRect = new Rect();
334 transform.rect.round(taskRect);
335 return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
336 }
337}