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
This commit is contained in:
@@ -386,8 +386,8 @@ public class Recents extends SystemUI
|
||||
* 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() {
|
||||
|
||||
@@ -46,6 +46,8 @@ import com.android.systemui.recents.events.activity.EnterRecentsWindowAnimationS
|
||||
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 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
|
||||
DozeTrigger mIterateTrigger = new DozeTrigger(500, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
boolean dismissed = dismissRecentsToFocusedTask(false);
|
||||
dismissRecentsToFocusedTask(false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -568,12 +570,6 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
|
||||
|
||||
/**** RecentsView.RecentsViewCallbacks Implementation ****/
|
||||
|
||||
@Override
|
||||
public void onTaskLaunchFailed() {
|
||||
// Return to Home
|
||||
dismissRecentsToHome(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAllTaskViewsDismissed() {
|
||||
mFinishLaunchHomeRunnable.run();
|
||||
@@ -701,6 +697,17 @@ public class RecentsActivity extends Activity implements RecentsView.RecentsView
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -503,12 +503,7 @@ public class RecentsImpl extends IRecentsNonSystemUserCallbacks.Stub
|
||||
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() {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -26,10 +26,8 @@ import com.android.systemui.recents.misc.SystemServicesProxy;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ public final class ForegroundThread extends HandlerThread {
|
||||
private static Handler sHandler;
|
||||
|
||||
private ForegroundThread() {
|
||||
super("recents.fg", android.os.Process.THREAD_PRIORITY_BACKGROUND);
|
||||
super("recents.fg");
|
||||
}
|
||||
|
||||
private static void ensureThreadLocked() {
|
||||
|
||||
@@ -130,10 +130,9 @@ public class RecentsTaskLoadPlan {
|
||||
? 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);
|
||||
|
||||
@@ -103,7 +103,6 @@ public class Task {
|
||||
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 @@ public class Task {
|
||||
// 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 @@ public class Task {
|
||||
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 @@ public class Task {
|
||||
this.activityIcon = o.activityIcon;
|
||||
this.colorPrimary = o.colorPrimary;
|
||||
this.useLightOnPrimaryColor = o.useLightOnPrimaryColor;
|
||||
this.isActive = o.isActive;
|
||||
this.lockToThisTask = o.lockToThisTask;
|
||||
this.lockToTaskEnabled = o.lockToTaskEnabled;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 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;
|
||||
|
||||
/**
|
||||
@@ -78,29 +62,22 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
|
||||
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 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
|
||||
|
||||
Rect mSystemInsets = new Rect();
|
||||
|
||||
|
||||
@GuardedBy("this")
|
||||
List<AppTransitionAnimationSpec> mAppTransitionAnimationSpecs = SPECS_WAITING;
|
||||
|
||||
public RecentsView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
@@ -134,6 +107,8 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
|
||||
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 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
|
||||
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 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
|
||||
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 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -79,7 +79,7 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
/** 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 @@ public class TaskStackView extends FrameLayout implements TaskStack.TaskStackCal
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user