Fixing issue with wrong transition when tasks are offscreen.
- Fixing issue with wrong animation specs being created for tasks that
are offscreen
- Removing unused code and simplifying the logic for task launching, also
pulling this out into another file so that it is easier to read and
understand
- Removing old incorrect call to moveTaskToFront() instead of
startActivityFromRecents()
Bug: 25590404
Change-Id: I25d9530d089a7984fb8c94954a34dd124420755a
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
index a58bc58..bf5417d 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/Recents.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/Recents.java
@@ -386,8 +386,8 @@
* Handle screen pinning request.
*/
public final void onBusEvent(final ScreenPinningRequestEvent event) {
- int processUser = event.systemServicesProxy.getProcessUser();
- if (event.systemServicesProxy.isSystemUser(processUser)) {
+ int processUser = sSystemServicesProxy.getProcessUser();
+ if (sSystemServicesProxy.isSystemUser(processUser)) {
mImpl.onStartScreenPinning(event.applicationContext);
} else {
postToSystemUser(new Runnable() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
index 6874247..58f7124 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java
@@ -46,6 +46,8 @@
import com.android.systemui.recents.events.activity.EnterRecentsWindowLastAnimationFrameEvent;
import com.android.systemui.recents.events.activity.HideRecentsEvent;
import com.android.systemui.recents.events.activity.IterateRecentsEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
import com.android.systemui.recents.events.activity.ToggleRecentsEvent;
import com.android.systemui.recents.events.component.RecentsVisibilityChangedEvent;
import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
@@ -108,7 +110,7 @@
DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
@Override
public void run() {
- boolean dismissed = dismissRecentsToFocusedTask(false);
+ dismissRecentsToFocusedTask(false);
}
});
@@ -569,12 +571,6 @@
/**** RecentsView.RecentsViewCallbacks Implementation ****/
@Override
- public void onTaskLaunchFailed() {
- // Return to Home
- dismissRecentsToHome(true);
- }
-
- @Override
public void onAllTaskViewsDismissed() {
mFinishLaunchHomeRunnable.run();
}
@@ -701,6 +697,17 @@
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_BEHIND);
}
+ public final void onBusEvent(LaunchTaskSucceededEvent event) {
+ MetricsLogger.histogram(this, "overview_task_launch_index", event.taskIndexFromStackFront);
+ }
+
+ public final void onBusEvent(LaunchTaskFailedEvent event) {
+ // Return to Home
+ dismissRecentsToHome(true);
+
+ MetricsLogger.count(this, "overview_task_launch_failed", 1);
+ }
+
public final void onBusEvent(ScreenPinningRequestEvent event) {
MetricsLogger.count(this, "overview_screen_pinned", 1);
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
index db65e00..4059543 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java
@@ -503,12 +503,7 @@
MetricsLogger.count(mContext, "overview_affiliated_task_launch", 1);
// Launch the task
- if (toTask.isActive) {
- // Bring an active task to the foreground
- ssp.moveTaskToFront(toTask.key.id, launchOpts);
- } else {
- ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts);
- }
+ ssp.startActivityFromRecents(mContext, toTask.key.id, toTask.activityLabel, launchOpts);
}
public void showNextAffiliatedTask() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskFailedEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskFailedEvent.java
new file mode 100644
index 0000000..3a2d58c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskFailedEvent.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when we fail to launch a task.
+ */
+public class LaunchTaskFailedEvent extends EventBus.Event {
+ // Simple event
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskSucceededEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskSucceededEvent.java
new file mode 100644
index 0000000..ec5089f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/activity/LaunchTaskSucceededEvent.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.events.activity;
+
+import com.android.systemui.recents.events.EventBus;
+
+/**
+ * This is sent when we successfully launch a task.
+ */
+public class LaunchTaskSucceededEvent extends EventBus.Event {
+
+ public final int taskIndexFromStackFront;
+
+ public LaunchTaskSucceededEvent(int taskIndexFromStackFront) {
+ this.taskIndexFromStackFront = taskIndexFromStackFront;
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
index 5cb4ccf..9d96d8e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/events/component/ScreenPinningRequestEvent.java
@@ -26,10 +26,8 @@
public class ScreenPinningRequestEvent extends EventBus.Event {
public final Context applicationContext;
- public final SystemServicesProxy systemServicesProxy;
- public ScreenPinningRequestEvent(Context context, SystemServicesProxy systemServicesProxy) {
+ public ScreenPinningRequestEvent(Context context) {
this.applicationContext = context.getApplicationContext();
- this.systemServicesProxy = systemServicesProxy;
}
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java b/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
index 8dc2983..784ac4e 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/misc/ForegroundThread.java
@@ -28,7 +28,7 @@
private static Handler sHandler;
private ForegroundThread() {
- super("recents.fg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+ super("recents.fg");
}
private static void ensureThreadLocked() {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
index 62493d6..7b04493 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/RecentsTaskLoadPlan.java
@@ -130,10 +130,9 @@
? t.taskDescription.getIconFilename() : null;
// Add the task to the stack
- Task task = new Task(taskKey, (t.id != INVALID_TASK_ID), t.affiliatedTaskId,
- t.affiliatedTaskColor, activityLabel, contentDescription, activityIcon,
- activityColor, (i == (taskCount - 1)), config.lockToAppEnabled, icon,
- iconFilename, t.bounds);
+ Task task = new Task(taskKey, t.affiliatedTaskId, t.affiliatedTaskColor, activityLabel,
+ contentDescription, activityIcon, activityColor, (i == (taskCount - 1)),
+ config.lockToAppEnabled, icon, iconFilename, t.bounds);
task.thumbnail = loader.getAndUpdateThumbnail(taskKey, ssp, false);
if (DEBUG) {
Log.d(TAG, activityLabel + " bounds: " + t.bounds);
diff --git a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
index 12bd556b..67e18f3 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/model/Task.java
@@ -103,7 +103,6 @@
public int colorPrimary;
public boolean useLightOnPrimaryColor;
public Bitmap thumbnail;
- public boolean isActive;
public boolean lockToThisTask;
public boolean lockToTaskEnabled;
public Bitmap icon;
@@ -116,7 +115,7 @@
// Do nothing
}
- public Task(TaskKey key, boolean isActive, int taskAffiliation, int taskAffiliationColor,
+ public Task(TaskKey key, int taskAffiliation, int taskAffiliationColor,
String activityTitle, String contentDescription, Drawable activityIcon,
int colorPrimary, boolean lockToThisTask, boolean lockToTaskEnabled, Bitmap icon,
String iconFilename, Rect bounds) {
@@ -131,7 +130,6 @@
this.colorPrimary = hasAffiliationGroupColor ? taskAffiliationColor : colorPrimary;
this.useLightOnPrimaryColor = Utilities.computeContrastBetweenColors(this.colorPrimary,
Color.WHITE) > 3f;
- this.isActive = isActive;
this.lockToThisTask = lockToTaskEnabled && lockToThisTask;
this.lockToTaskEnabled = lockToTaskEnabled;
this.icon = icon;
@@ -149,7 +147,6 @@
this.activityIcon = o.activityIcon;
this.colorPrimary = o.colorPrimary;
this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
- this.isActive = o.isActive;
this.lockToThisTask = o.lockToThisTask;
this.lockToTaskEnabled = o.lockToTaskEnabled;
}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
new file mode 100644
index 0000000..a28601b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsTransitionHelper.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recents.views;
+
+import android.annotation.Nullable;
+import android.app.ActivityOptions;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.SparseArray;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.WindowManagerGlobal;
+import com.android.internal.annotations.GuardedBy;
+import com.android.systemui.recents.Constants;
+import com.android.systemui.recents.Recents;
+import com.android.systemui.recents.events.EventBus;
+import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskFailedEvent;
+import com.android.systemui.recents.events.activity.LaunchTaskSucceededEvent;
+import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
+import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
+import com.android.systemui.recents.misc.SystemServicesProxy;
+import com.android.systemui.recents.model.Task;
+import com.android.systemui.recents.model.TaskStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
+import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
+
+/**
+ * A helper class to create transitions to/from Recents
+ */
+public class RecentsTransitionHelper {
+
+ private static final String TAG = "RecentsTransitionHelper";
+ private static final boolean DEBUG = false;
+
+ /**
+ * Special value for {@link #mAppTransitionAnimationSpecs}: Indicate that we are currently
+ * waiting for the specs to be retrieved.
+ */
+ private static final List<AppTransitionAnimationSpec> SPECS_WAITING = new ArrayList<>();
+
+ @GuardedBy("this")
+ private List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs = SPECS_WAITING;
+
+ private Context mContext;
+ private Handler mHandler;
+ private TaskViewTransform mTmpTransform = new TaskViewTransform();
+
+ private Runnable mStartScreenPinningRunnable = new Runnable() {
+ @Override
+ public void run() {
+ EventBus.getDefault().send(new ScreenPinningRequestEvent(mContext));
+ }
+ };
+
+ public RecentsTransitionHelper(Context context, Handler handler) {
+ mContext = context;
+ mHandler = handler;
+ }
+
+ /**
+ * Launches the specified {@link Task}.
+ */
+ public void launchTaskFromRecents(final TaskStack stack, @Nullable final Task task,
+ final TaskStackView stackView, final TaskView taskView,
+ final boolean lockToTask, final Rect bounds, int destinationStack) {
+ final ActivityOptions opts = ActivityOptions.makeBasic();
+ if (bounds != null) {
+ opts.setBounds(bounds.isEmpty() ? null : bounds);
+ }
+
+ final ActivityOptions.OnAnimationStartedListener animStartedListener;
+ final IAppTransitionAnimationSpecsFuture transitionFuture;
+ if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
+ task.thumbnail.getHeight() > 0) {
+ transitionFuture = getAppTransitionFuture(task, stackView, destinationStack);
+ animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
+ @Override
+ public void onAnimationStarted() {
+ // If we are launching into another task, cancel the previous task's
+ // window transition
+ EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
+
+ if (lockToTask) {
+ // Request screen pinning after the animation runs
+ mHandler.postDelayed(mStartScreenPinningRunnable, 350);
+ }
+ }
+ };
+ } else {
+ // This is only the case if the task is not on screen (scrolled offscreen for example)
+ transitionFuture = null;
+ animStartedListener = null;
+ }
+
+ if (taskView == null) {
+ // If there is no task view, then we do not need to worry about animating out occluding
+ // task views, and we can launch immediately
+ startTaskActivity(stack, task, taskView, opts, transitionFuture, animStartedListener);
+ } else {
+ if (task.group != null && !task.group.isFrontMostTask(task)) {
+ stackView.startLaunchTaskAnimation(taskView, new Runnable() {
+ @Override
+ public void run() {
+ startTaskActivity(stack, task, taskView, opts, transitionFuture,
+ animStartedListener);
+ }
+ }, lockToTask);
+ } else {
+ stackView.startLaunchTaskAnimation(taskView, null, lockToTask);
+ startTaskActivity(stack, task, taskView, opts, transitionFuture,
+ animStartedListener);
+ }
+ }
+ }
+
+ /**
+ * Starts the activity for the launch task.
+ *
+ * @param taskView this is the {@link TaskView} that we are launching from. This can be null if
+ * we are toggling recents and the launch-to task is now offscreen.
+ */
+ private void startTaskActivity(TaskStack stack, Task task, @Nullable TaskView taskView,
+ ActivityOptions opts, IAppTransitionAnimationSpecsFuture transitionFuture,
+ final ActivityOptions.OnAnimationStartedListener animStartedListener) {
+ SystemServicesProxy ssp = Recents.getSystemServices();
+ if (ssp.startActivityFromRecents(mContext, task.key.id, task.activityLabel, opts)) {
+ // Keep track of the index of the task launch
+ int taskIndexFromFront = 0;
+ int taskIndex = stack.indexOfTask(task);
+ if (taskIndex > -1) {
+ taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
+ }
+ EventBus.getDefault().send(new LaunchTaskSucceededEvent(taskIndexFromFront));
+ } else {
+ // Dismiss the task if we fail to launch it
+ EventBus.getDefault().send(new DismissTaskViewEvent(task, taskView));
+
+ // Keep track of failed launches
+ EventBus.getDefault().send(new LaunchTaskFailedEvent());
+ }
+ if (transitionFuture != null) {
+ IRemoteCallback.Stub callback = null;
+ if (animStartedListener != null) {
+ callback = new IRemoteCallback.Stub() {
+ @Override
+ public void sendResult(Bundle data) throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ if (animStartedListener != null) {
+ animStartedListener.onAnimationStarted();
+ }
+ }
+ });
+ }
+ };
+ }
+ try {
+ WindowManagerGlobal.getWindowManagerService()
+ .overridePendingAppTransitionMultiThumbFuture(transitionFuture,
+ callback, true /* scaleUp */);
+ } catch (RemoteException e) {
+ Log.w(TAG, "Failed to override transition: " + e);
+ }
+ }
+ }
+
+ /**
+ * Creates a future which will later be queried for animation specs for this current transition.
+ */
+ private IAppTransitionAnimationSpecsFuture getAppTransitionFuture(final Task task,
+ final TaskStackView stackView, final int destinationStack) {
+ return new IAppTransitionAnimationSpecsFuture.Stub() {
+ @Override
+ public AppTransitionAnimationSpec[] get() throws RemoteException {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ synchronized (RecentsTransitionHelper.this) {
+ mAppTransitionAnimationSpecs = composeAnimationSpecs(task, stackView,
+ destinationStack);
+ RecentsTransitionHelper.this.notifyAll();
+ }
+ }
+ });
+ synchronized (RecentsTransitionHelper.this) {
+ while (mAppTransitionAnimationSpecs == SPECS_WAITING) {
+ try {
+ RecentsTransitionHelper.this.wait();
+ } catch (InterruptedException e) {}
+ }
+ if (mAppTransitionAnimationSpecs == null) {
+ return null;
+ }
+ AppTransitionAnimationSpec[] specs
+ = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()];
+ mAppTransitionAnimationSpecs.toArray(specs);
+ mAppTransitionAnimationSpecs = SPECS_WAITING;
+ return specs;
+ }
+ }
+ };
+ }
+
+ /**
+ * Composes the animation specs for all the tasks in the target stack.
+ */
+ private List<AppTransitionAnimationSpec> composeAnimationSpecs(final Task task,
+ final TaskStackView stackView, final int destinationStack) {
+ // Ensure we have a valid target stack id
+ final int targetStackId = destinationStack != INVALID_STACK_ID ?
+ destinationStack : task.key.stackId;
+ if (targetStackId != FREEFORM_WORKSPACE_STACK_ID
+ && targetStackId != FULLSCREEN_WORKSPACE_STACK_ID) {
+ return null;
+ }
+
+ // Calculate the offscreen task rect (for tasks that are not backed by views)
+ float stackScroll = stackView.getScroller().getStackScroll();
+ TaskView taskView = stackView.getChildViewForTask(task);
+ TaskStackLayoutAlgorithm layoutAlgorithm = stackView.getStackAlgorithm();
+ Rect offscreenTaskRect = new Rect(layoutAlgorithm.mTaskRect);
+ offscreenTaskRect.offsetTo(offscreenTaskRect.left,
+ layoutAlgorithm.mCurrentStackRect.bottom);
+
+ // If this is a full screen stack, the transition will be towards the single, full screen
+ // task. We only need the transition spec for this task.
+ List<AppTransitionAnimationSpec> specs = new ArrayList<>();
+ if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
+ if (taskView == null) {
+ specs.add(composeOffscreenAnimationSpec(task, offscreenTaskRect));
+ } else {
+ layoutAlgorithm.getStackTransform(task, stackScroll, mTmpTransform, null);
+ specs.add(composeAnimationSpec(taskView, mTmpTransform, true /* addHeaderBitmap */));
+ }
+ return specs;
+ }
+
+ // Otherwise, for freeform tasks, create a new animation spec for each task we have to
+ // launch
+ TaskStack stack = stackView.getStack();
+ ArrayList<Task> tasks = stack.getTasks();
+ int taskCount = tasks.size();
+ for (int i = taskCount - 1; i >= 0; i--) {
+ Task t = tasks.get(i);
+ if (t.isFreeformTask()) {
+ TaskView tv = stackView.getChildViewForTask(t);
+ if (tv == null) {
+ // TODO: Create a different animation task rect for this case (though it should
+ // never happen)
+ specs.add(composeOffscreenAnimationSpec(t, offscreenTaskRect));
+ } else {
+ layoutAlgorithm.getStackTransform(task, stackScroll, mTmpTransform, null);
+ specs.add(composeAnimationSpec(tv, mTmpTransform, true /* addHeaderBitmap */));
+ }
+ }
+ }
+
+ return specs;
+ }
+
+ /**
+ * Composes a single animation spec for the given {@link Task}
+ */
+ private static AppTransitionAnimationSpec composeOffscreenAnimationSpec(Task task,
+ Rect taskRect) {
+ return new AppTransitionAnimationSpec(task.key.id, null, taskRect);
+ }
+
+ /**
+ * Composes a single animation spec for the given {@link TaskView}
+ */
+ private static AppTransitionAnimationSpec composeAnimationSpec(TaskView taskView,
+ TaskViewTransform transform, boolean addHeaderBitmap) {
+ // Disable any focused state before we draw the header
+ // Upfront the processing of the thumbnail
+ if (taskView.isFocusedTask()) {
+ taskView.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
+ }
+
+ Bitmap b = null;
+ if (addHeaderBitmap) {
+ float scale = transform.scale;
+ int fromHeaderWidth = (int) (taskView.mHeaderView.getMeasuredWidth() * scale);
+ int fromHeaderHeight = (int) (taskView.mHeaderView.getMeasuredHeight() * scale);
+ b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
+ Bitmap.Config.ARGB_8888);
+
+ if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
+ b.eraseColor(0xFFff0000);
+ } else {
+ Canvas c = new Canvas(b);
+ c.scale(scale, scale);
+ taskView.mHeaderView.draw(c);
+ c.setBitmap(null);
+ }
+ b = b.createAshmemBitmap();
+ }
+
+ Rect taskRect = new Rect();
+ transform.rect.round(taskRect);
+ return new AppTransitionAnimationSpec(taskView.getTask().key.id, b, taskRect);
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
index af30268..e37b7dc 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java
@@ -16,45 +16,31 @@
package com.android.systemui.recents.views;
-import android.app.ActivityOptions;
-import android.app.ActivityOptions.OnAnimationStartedListener;
import android.content.Context;
-import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Bundle;
-import android.os.IRemoteCallback;
-import android.os.RemoteException;
+import android.os.Handler;
import android.util.ArraySet;
import android.util.AttributeSet;
-import android.util.Log;
-import android.util.SparseArray;
import android.view.AppTransitionAnimationSpec;
-import android.view.IAppTransitionAnimationSpecsFuture;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowInsets;
-import android.view.WindowManagerGlobal;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.FrameLayout;
-
import com.android.internal.annotations.GuardedBy;
import com.android.internal.logging.MetricsLogger;
import com.android.systemui.R;
-import com.android.systemui.recents.Constants;
import com.android.systemui.recents.Recents;
import com.android.systemui.recents.RecentsActivity;
-import com.android.systemui.recents.RecentsActivityLaunchState;
import com.android.systemui.recents.RecentsAppWidgetHostView;
import com.android.systemui.recents.RecentsConfiguration;
import com.android.systemui.recents.events.EventBus;
import com.android.systemui.recents.events.activity.CancelEnterRecentsWindowAnimationEvent;
import com.android.systemui.recents.events.activity.DismissRecentsToHomeAnimationStarted;
-import com.android.systemui.recents.events.component.ScreenPinningRequestEvent;
-import com.android.systemui.recents.events.ui.DismissTaskViewEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragDropTargetChangedEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragEndEvent;
import com.android.systemui.recents.events.ui.dragndrop.DragStartEvent;
@@ -65,8 +51,6 @@
import java.util.ArrayList;
import java.util.List;
-import static android.app.ActivityManager.StackId.FREEFORM_WORKSPACE_STACK_ID;
-import static android.app.ActivityManager.StackId.FULLSCREEN_WORKSPACE_STACK_ID;
import static android.app.ActivityManager.StackId.INVALID_STACK_ID;
/**
@@ -78,29 +62,22 @@
private static final String TAG = "RecentsView";
private static final boolean DEBUG = false;
- private static final boolean ADD_HEADER_BITMAP = true;
-
- /**
- * Special value for {@link #mAppTransitionAnimationSpecs}: Indicate that we are currently
- * waiting for the specs to be retrieved.
- */
- private static final List<AppTransitionAnimationSpec> SPECS_WAITING = new ArrayList<>();
-
private int mStackViewVisibility = View.VISIBLE;
/** The RecentsView callbacks */
public interface RecentsViewCallbacks {
- public void onTaskLaunchFailed();
public void onAllTaskViewsDismissed();
}
LayoutInflater mInflater;
+ Handler mHandler;
ArrayList<TaskStack> mStacks;
TaskStackView mTaskStackView;
RecentsAppWidgetHostView mSearchBar;
RecentsViewCallbacks mCb;
+ RecentsTransitionHelper mTransitionHelper;
RecentsViewTouchHandler mTouchHandler;
DragView mDragView;
TaskStack.DockState[] mVisibleDockStates = {
@@ -114,10 +91,6 @@
Rect mSystemInsets = new Rect();
-
- @GuardedBy("this")
- List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs = SPECS_WAITING;
-
public RecentsView(Context context) {
super(context);
}
@@ -134,6 +107,8 @@
super(context, attrs, defStyleAttr, defStyleRes);
setWillNotDraw(false);
mInflater = LayoutInflater.from(context);
+ mHandler = new Handler();
+ mTransitionHelper = new RecentsTransitionHelper(getContext(), mHandler);
mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
com.android.internal.R.interpolator.fast_out_slow_in);
mTouchHandler = new RecentsViewTouchHandler(this);
@@ -201,7 +176,7 @@
Task task = mTaskStackView.getFocusedTask();
if (task != null) {
TaskView taskView = mTaskStackView.getChildViewForTask(task);
- onTaskViewClicked(mTaskStackView, taskView, stack, task, false, false, null,
+ onTaskViewClicked(mTaskStackView, taskView, stack, task, false, null,
INVALID_STACK_ID);
return true;
}
@@ -219,7 +194,7 @@
for (int j = 0; j < taskViewCount; j++) {
TaskView tv = taskViews.get(j);
if (tv.getTask() == task) {
- onTaskViewClicked(mTaskStackView, tv, stack, task, false, taskBounds != null,
+ onTaskViewClicked(mTaskStackView, tv, stack, task, false,
taskBounds, destinationStack);
return true;
}
@@ -424,282 +399,14 @@
}
}
- private IAppTransitionAnimationSpecsFuture getAppTransitionFuture(final TaskStackView stackView,
- final TaskView clickedTask, final int offsetX, final int offsetY,
- final float stackScroll, final int destinationStack) {
- return new IAppTransitionAnimationSpecsFuture.Stub() {
- @Override
- public AppTransitionAnimationSpec[] get() throws RemoteException {
- post(new Runnable() {
- @Override
- public void run() {
- synchronized (RecentsView.this) {
- mAppTransitionAnimationSpecs = getAppTransitionAnimationSpecs(stackView,
- clickedTask, offsetX, offsetY, stackScroll, destinationStack);
- RecentsView.this.notifyAll();
- }
- }
- });
- synchronized (RecentsView.this) {
- while (mAppTransitionAnimationSpecs == SPECS_WAITING) {
- try {
- RecentsView.this.wait();
- } catch (InterruptedException e) {}
- }
- if (mAppTransitionAnimationSpecs == null) {
- return null;
- }
- AppTransitionAnimationSpec[] specs
- = new AppTransitionAnimationSpec[mAppTransitionAnimationSpecs.size()];
- mAppTransitionAnimationSpecs.toArray(specs);
- mAppTransitionAnimationSpecs = SPECS_WAITING;
- return specs;
- }
- }
- };
- }
-
- private List<AppTransitionAnimationSpec> getAppTransitionAnimationSpecs(TaskStackView stackView,
- TaskView clickedTask, int offsetX, int offsetY, float stackScroll,
- int destinationStack) {
- final int targetStackId = destinationStack != INVALID_STACK_ID ?
- destinationStack : clickedTask.getTask().key.stackId;
- if (targetStackId != FREEFORM_WORKSPACE_STACK_ID
- && targetStackId != FULLSCREEN_WORKSPACE_STACK_ID) {
- return null;
- }
- // If this is a full screen stack, the transition will be towards the single, full screen
- // task. We only need the transition spec for this task.
- List<AppTransitionAnimationSpec> specs = new ArrayList<>();
- if (targetStackId == FULLSCREEN_WORKSPACE_STACK_ID) {
- specs.add(createThumbnailHeaderAnimationSpec(
- stackView, offsetX, offsetY, stackScroll, clickedTask,
- clickedTask.getTask().key.id, ADD_HEADER_BITMAP));
- return specs;
- }
- // This is a free form stack or full screen stack, so there will be multiple windows
- // animating from thumbnails. We need transition animation specs for all of them.
-
- // We will use top and bottom task views as a base for tasks, that aren't visible on the
- // screen. This is necessary for cascade recents list, where some of the tasks might be
- // hidden.
- List<TaskView> taskViews = stackView.getTaskViews();
- int childCount = taskViews.size();
- TaskView topChild = taskViews.get(0);
- TaskView bottomChild = taskViews.get(childCount - 1);
- SparseArray<TaskView> taskViewsByTaskId = new SparseArray<>();
- for (int i = 0; i < childCount; i++) {
- TaskView taskView = taskViews.get(i);
- taskViewsByTaskId.put(taskView.getTask().key.id, taskView);
- }
-
- TaskStack stack = stackView.getStack();
- // We go through all tasks now and for each generate transition animation spec. If there is
- // a view associated with a task, we use that view as a base for the animation. If there
- // isn't, we use bottom or top view, depending on which one would be closer to the task
- // view if it existed.
- ArrayList<Task> tasks = stack.getTasks();
- boolean passedClickedTask = false;
- for (int i = 0, n = tasks.size(); i < n; i++) {
- Task task = tasks.get(i);
- TaskView taskView = taskViewsByTaskId.get(task.key.id);
- if (taskView != null) {
- specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
- stackScroll, taskView, taskView.getTask().key.id, ADD_HEADER_BITMAP));
- if (taskView == clickedTask) {
- passedClickedTask = true;
- }
- } else {
- taskView = passedClickedTask ? bottomChild : topChild;
- specs.add(createThumbnailHeaderAnimationSpec(stackView, offsetX, offsetY,
- stackScroll, taskView, task.key.id, !ADD_HEADER_BITMAP));
- }
- }
-
- return specs;
- }
-
- private AppTransitionAnimationSpec createThumbnailHeaderAnimationSpec(TaskStackView stackView,
- int offsetX, int offsetY, float stackScroll, TaskView tv, int taskId,
- boolean addHeaderBitmap) {
- // Disable any focused state before we draw the header
- // Upfront the processing of the thumbnail
- if (tv.isFocusedTask()) {
- tv.setFocusedState(false, false /* animated */, false /* requestViewFocus */);
- }
- TaskViewTransform transform = new TaskViewTransform();
- transform = stackView.getStackAlgorithm().getStackTransform(tv.mTask, stackScroll,
- transform, null);
-
- float scale = tv.getScaleX();
- int fromHeaderWidth = (int) (tv.mHeaderView.getMeasuredWidth() * scale);
- int fromHeaderHeight = (int) (tv.mHeaderView.getMeasuredHeight() * scale);
-
- Bitmap b = null;
- if (addHeaderBitmap) {
- b = Bitmap.createBitmap(fromHeaderWidth, fromHeaderHeight,
- Bitmap.Config.ARGB_8888);
-
- if (Constants.DebugFlags.App.EnableTransitionThumbnailDebugMode) {
- b.eraseColor(0xFFff0000);
- } else {
- Canvas c = new Canvas(b);
- c.scale(tv.getScaleX(), tv.getScaleY());
- tv.mHeaderView.draw(c);
- c.setBitmap(null);
-
- }
- b = b.createAshmemBitmap();
- }
-
- int[] pts = new int[2];
- tv.getLocationOnScreen(pts);
-
- final int left = pts[0] + offsetX;
- final int top = pts[1] + offsetY;
- final Rect rect = new Rect(left, top, left + (int) transform.rect.width(),
- top + (int) transform.rect.height());
-
- return new AppTransitionAnimationSpec(taskId, b, rect);
- }
-
/**** TaskStackView.TaskStackCallbacks Implementation ****/
@Override
public void onTaskViewClicked(final TaskStackView stackView, final TaskView tv,
final TaskStack stack, final Task task, final boolean lockToTask,
- final boolean boundsValid, final Rect bounds, int destinationStack) {
-
- // Upfront the processing of the thumbnail
- TaskViewTransform transform = new TaskViewTransform();
- View sourceView;
- int offsetX = 0;
- int offsetY = 0;
- float stackScroll = stackView.getScroller().getStackScroll();
- if (tv == null) {
- // If there is no actual task view, then use the stack view as the source view
- // and then offset to the expected transform rect, but bound this to just
- // outside the display rect (to ensure we don't animate from too far away)
- sourceView = stackView;
- offsetX = (int) transform.rect.left;
- offsetY = getMeasuredHeight();
- } else {
- sourceView = tv.mThumbnailView;
- }
-
- // Compute the thumbnail to scale up from
- final SystemServicesProxy ssp = Recents.getSystemServices();
- boolean screenPinningRequested = false;
- ActivityOptions opts = ActivityOptions.makeBasic();
- ActivityOptions.OnAnimationStartedListener animStartedListener = null;
- final IAppTransitionAnimationSpecsFuture transitionFuture;
- if (task.thumbnail != null && task.thumbnail.getWidth() > 0 &&
- task.thumbnail.getHeight() > 0) {
- animStartedListener = new ActivityOptions.OnAnimationStartedListener() {
- @Override
- public void onAnimationStarted() {
- // If we are launching into another task, cancel the previous task's
- // window transition
- EventBus.getDefault().send(new CancelEnterRecentsWindowAnimationEvent(task));
-
- if (lockToTask) {
- // Request screen pinning after the animation runs
- postDelayed(new Runnable() {
- @Override
- public void run() {
- EventBus.getDefault().send(new ScreenPinningRequestEvent(
- getContext(), ssp));
- }
- }, 350);
- }
- }
- };
- transitionFuture = getAppTransitionFuture(stackView, tv, offsetX, offsetY, stackScroll,
- destinationStack);
- screenPinningRequested = true;
- } else {
- transitionFuture = null;
- }
- if (boundsValid) {
- opts.setBounds(bounds.isEmpty() ? null : bounds);
- }
- final ActivityOptions launchOpts = opts;
- final boolean finalScreenPinningRequested = screenPinningRequested;
- final OnAnimationStartedListener finalAnimStartedListener = animStartedListener;
- final Runnable launchRunnable = new Runnable() {
- @Override
- public void run() {
- if (task.isActive) {
- // Bring an active task to the foreground
- ssp.moveTaskToFront(task.key.id, launchOpts);
- } else {
- if (ssp.startActivityFromRecents(getContext(), task.key.id, task.activityLabel,
- launchOpts)) {
- if (!finalScreenPinningRequested) {
- // If we have not requested this already to be run after the window
- // transition, then just run it now
- EventBus.getDefault().send(new ScreenPinningRequestEvent(
- getContext(), ssp));
- }
- } else {
- // Dismiss the task and return the user to home if we fail to
- // launch the task
- EventBus.getDefault().send(new DismissTaskViewEvent(task, tv));
- if (mCb != null) {
- mCb.onTaskLaunchFailed();
- }
-
- // Keep track of failed launches
- MetricsLogger.count(getContext(), "overview_task_launch_failed", 1);
- }
- }
- if (transitionFuture != null) {
- IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
- @Override
- public void sendResult(Bundle data) throws RemoteException {
- post(new Runnable() {
- @Override
- public void run() {
- if (finalAnimStartedListener != null) {
- finalAnimStartedListener.onAnimationStarted();
- }
- }
- });
- }
- };
- try {
- WindowManagerGlobal.getWindowManagerService()
- .overridePendingAppTransitionMultiThumbFuture(transitionFuture,
- callback, true /* scaleUp */);
- } catch (RemoteException e) {
- Log.w(TAG, "Failed to override transition: " + e);
- }
- }
- }
- };
-
- // Keep track of the index of the task launch
- int taskIndexFromFront = 0;
- int taskIndex = stack.indexOfTask(task);
- if (taskIndex > -1) {
- taskIndexFromFront = stack.getTaskCount() - taskIndex - 1;
- }
- MetricsLogger.histogram(getContext(), "overview_task_launch_index", taskIndexFromFront);
-
- // Launch the app right away if there is no task view, otherwise, animate the icon out first
- if (tv == null) {
- launchRunnable.run();
- } else {
- if (task.group != null && !task.group.isFrontMostTask(task)) {
- // For affiliated tasks that are behind other tasks, we must animate the front cards
- // out of view before starting the task transition
- stackView.startLaunchTaskAnimation(tv, launchRunnable, lockToTask);
- } else {
- // Otherwise, we can start the task transition immediately
- stackView.startLaunchTaskAnimation(tv, null, lockToTask);
- launchRunnable.run();
- }
- }
+ final Rect bounds, int destinationStack) {
+ mTransitionHelper.launchTaskFromRecents(stack, task, stackView, tv, lockToTask, bounds,
+ destinationStack);
}
@Override
diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
index 4a11b93..9c8829f 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/views/TaskStackView.java
@@ -79,7 +79,7 @@
/** The TaskView callbacks */
interface TaskStackViewCallbacks {
public void onTaskViewClicked(TaskStackView stackView, TaskView tv, TaskStack stack, Task t,
- boolean lockToTask, boolean boundsValid, Rect bounds, int destinationStack);
+ boolean lockToTask, Rect bounds, int destinationStack);
public void onAllTaskViewsDismissed(ArrayList<Task> removedTasks);
public void onTaskStackFilterTriggered();
public void onTaskStackUnfilterTriggered();
@@ -1346,8 +1346,7 @@
mUIDozeTrigger.stopDozing();
if (mCb != null) {
- mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask, false, null,
- INVALID_STACK_ID);
+ mCb.onTaskViewClicked(this, tv, mStack, task, lockToTask, null, INVALID_STACK_ID);
}
}