diff options
12 files changed, 423 insertions, 333 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/recents/Recents.java b/packages/SystemUI/src/com/android/systemui/recents/Recents.java index a58bc5882f2a..bf5417dac5d5 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 @@ 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() { diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsActivity.java index 6874247e5fa3..58f71242bec0 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.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); } }); @@ -569,12 +571,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); } diff --git a/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/RecentsImpl.java index db65e003f32f..4059543d4658 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 @@ 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() { 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 000000000000..3a2d58c80d88 --- /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 000000000000..ec5089feabad --- /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 5cb4ccf173d2..9d96d8e92c14 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 @@ 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; } } 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 8dc298340e47..784ac4e16838 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 @@ 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() { 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 62493d63ab87..7b0449330ce1 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 @@ 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); 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 12bd556b8655..67e18f34f3dd 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 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; } 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 000000000000..a28601be03fa --- /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 af3026892725..e37b7dc7cd96 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 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 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 4a11b9327b1f..9c8829fcefb9 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 @@ 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); } } |