Merge "Fixing issue with wrong transition when tasks are offscreen."

This commit is contained in:
Winson Chung
2015-11-11 17:30:24 +00:00
committed by Android (Google) Code Review
12 changed files with 423 additions and 333 deletions

View File

@@ -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() {

View File

@@ -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);
}

View File

@@ -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() {

View File

@@ -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
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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);
}
}