Merge changes I710805c8,Ie26d634b into sc-v2-dev

* changes:
  Create a LegacyTransitions to manage combined WCT + remoteanimation
  Aggregate stage-split operations into WCT
This commit is contained in:
Evan Rosky
2021-07-19 15:31:15 +00:00
committed by Android (Google) Code Review
15 changed files with 547 additions and 96 deletions

View File

@@ -646,4 +646,9 @@ public abstract class ActivityManagerInternal {
*/
@Nullable
public abstract List<Integer> getIsolatedProcesses(int uid);
/** @see ActivityManagerService#sendIntentSender */
public abstract int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code,
Intent intent, String resolvedType,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options);
}

View File

@@ -19,6 +19,7 @@ package android.window;
import android.view.SurfaceControl;
import android.os.IBinder;
import android.view.RemoteAnimationAdapter;
import android.window.IDisplayAreaOrganizerController;
import android.window.ITaskFragmentOrganizerController;
import android.window.ITaskOrganizerController;
@@ -60,6 +61,17 @@ interface IWindowOrganizerController {
IBinder startTransition(int type, in @nullable IBinder transitionToken,
in @nullable WindowContainerTransaction t);
/**
* Starts a legacy transition.
* @param type The transition type.
* @param adapter The animation to use.
* @param syncCallback A sync callback for the contents of `t`
* @param t Operations that are part of the transition.
* @return sync-id or -1 if this no-op'd because a transition is already running.
*/
int startLegacyTransition(int type, in RemoteAnimationAdapter adapter,
in IWindowContainerTransactionCallback syncCallback, in WindowContainerTransaction t);
/**
* Finishes a transition. This must be called for all created transitions.
* @param transitionToken Which transition to finish

View File

@@ -19,6 +19,7 @@ package android.window;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.TestApi;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -388,6 +389,24 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
/**
* Sends a pending intent in sync.
* @param sender The PendingIntent sender.
* @param intent The fillIn intent to patch over the sender's base intent.
* @param options bundle containing ActivityOptions for the task's top activity.
* @hide
*/
@NonNull
public WindowContainerTransaction sendPendingIntent(PendingIntent sender, Intent intent,
@Nullable Bundle options) {
mHierarchyOps.add(new HierarchyOp.Builder(HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT)
.setLaunchOptions(options)
.setPendingIntent(sender)
.setActivityIntent(intent)
.build());
return this;
}
/**
* Creates a new TaskFragment with the given options.
* @param taskFragmentOptions the options used to create the TaskFragment.
@@ -886,6 +905,7 @@ public final class WindowContainerTransaction implements Parcelable {
public static final int HIERARCHY_OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT = 9;
public static final int HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT = 10;
public static final int HIERARCHY_OP_TYPE_REPARENT_CHILDREN = 11;
public static final int HIERARCHY_OP_TYPE_PENDING_INTENT = 12;
// The following key(s) are for use with mLaunchOptions:
// When launching a task (eg. from recents), this is the taskId to be launched.
@@ -920,6 +940,9 @@ public final class WindowContainerTransaction implements Parcelable {
@Nullable
private TaskFragmentCreationParams mTaskFragmentCreationOptions;
@Nullable
private PendingIntent mPendingIntent;
public static HierarchyOp createForReparent(
@NonNull IBinder container, @Nullable IBinder reparent, boolean toTop) {
return new HierarchyOp.Builder(HIERARCHY_OP_TYPE_REPARENT)
@@ -998,6 +1021,7 @@ public final class WindowContainerTransaction implements Parcelable {
mLaunchOptions = copy.mLaunchOptions;
mActivityIntent = copy.mActivityIntent;
mTaskFragmentCreationOptions = copy.mTaskFragmentCreationOptions;
mPendingIntent = copy.mPendingIntent;
}
protected HierarchyOp(Parcel in) {
@@ -1010,6 +1034,7 @@ public final class WindowContainerTransaction implements Parcelable {
mLaunchOptions = in.readBundle();
mActivityIntent = in.readTypedObject(Intent.CREATOR);
mTaskFragmentCreationOptions = in.readTypedObject(TaskFragmentCreationParams.CREATOR);
mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
}
public int getType() {
@@ -1062,6 +1087,11 @@ public final class WindowContainerTransaction implements Parcelable {
return mTaskFragmentCreationOptions;
}
@Nullable
public PendingIntent getPendingIntent() {
return mPendingIntent;
}
@Override
public String toString() {
switch (mType) {
@@ -1117,6 +1147,7 @@ public final class WindowContainerTransaction implements Parcelable {
dest.writeBundle(mLaunchOptions);
dest.writeTypedObject(mActivityIntent, flags);
dest.writeTypedObject(mTaskFragmentCreationOptions, flags);
dest.writeTypedObject(mPendingIntent, flags);
}
@Override
@@ -1163,6 +1194,9 @@ public final class WindowContainerTransaction implements Parcelable {
@Nullable
private TaskFragmentCreationParams mTaskFragmentCreationOptions;
@Nullable
private PendingIntent mPendingIntent;
Builder(int type) {
mType = type;
}
@@ -1202,6 +1236,11 @@ public final class WindowContainerTransaction implements Parcelable {
return this;
}
Builder setPendingIntent(@Nullable PendingIntent sender) {
mPendingIntent = sender;
return this;
}
Builder setTaskFragmentCreationOptions(
@Nullable TaskFragmentCreationParams taskFragmentCreationOptions) {
mTaskFragmentCreationOptions = taskFragmentCreationOptions;
@@ -1221,6 +1260,7 @@ public final class WindowContainerTransaction implements Parcelable {
hierarchyOp.mToTop = mToTop;
hierarchyOp.mLaunchOptions = mLaunchOptions;
hierarchyOp.mActivityIntent = mActivityIntent;
hierarchyOp.mPendingIntent = mPendingIntent;
hierarchyOp.mTaskFragmentCreationOptions = mTaskFragmentCreationOptions;
return hierarchyOp;

View File

@@ -25,6 +25,7 @@ import android.app.ActivityTaskManager;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Singleton;
import android.view.RemoteAnimationAdapter;
/**
* Base class for organizing specific types of windows like Tasks and DisplayAreas
@@ -124,6 +125,27 @@ public class WindowOrganizer {
}
}
/**
* Start a legacy transition.
* @param type The type of the transition. This is ignored if a transitionToken is provided.
* @param adapter An existing transition to start. If null, a new transition is created.
* @param t The set of window operations that are part of this transition.
* @return true on success, false if a transition was already running.
* @hide
*/
@RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_TASKS)
@NonNull
public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
@NonNull WindowContainerTransactionCallback syncCallback,
@NonNull WindowContainerTransaction t) {
try {
return getWindowOrganizerController().startLegacyTransition(
type, adapter, syncCallback.mInterface, t);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Register an ITransitionPlayer to handle transition animations.
* @hide

View File

@@ -187,6 +187,7 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou
.setPosition(mTaskLeash2, mTaskInfo2.positionInParent.x,
mTaskInfo2.positionInParent.y)
.setPosition(dividerLeash, dividerBounds.left, dividerBounds.top)
.show(dividerLeash)
.show(mRootTaskLeash)
.show(mTaskLeash1)
.show(mTaskLeash2);
@@ -212,9 +213,12 @@ class AppPair implements ShellTaskOrganizer.TaskListener, SplitLayout.SplitLayou
}
mRootTaskInfo = taskInfo;
if (mSplitLayout != null
&& mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
onBoundsChanged(mSplitLayout);
if (mSplitLayout != null) {
if (mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)) {
onBoundsChanged(mSplitLayout);
}
// updateConfiguration re-inits the dividerbar, so show it now
mSyncQueue.runInSync(t -> t.show(mSplitLayout.getDividerLeash()));
}
} else if (taskInfo.taskId == getTaskId1()) {
mTaskInfo1 = taskInfo;

View File

@@ -18,13 +18,15 @@ package com.android.wm.shell.common;
import android.annotation.BinderThread;
import android.annotation.NonNull;
import android.os.RemoteException;
import android.util.Slog;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.WindowContainerTransaction;
import android.window.WindowContainerTransactionCallback;
import android.window.WindowOrganizer;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.transition.LegacyTransitions;
import java.util.ArrayList;
@@ -76,6 +78,21 @@ public final class SyncTransactionQueue {
}
}
/**
* Queues a legacy transition to be sent serially to WM
*/
public void queue(LegacyTransitions.ILegacyTransition transition,
@WindowManager.TransitionType int type, WindowContainerTransaction wct) {
SyncCallback cb = new SyncCallback(transition, type, wct);
synchronized (mQueue) {
if (DEBUG) Slog.d(TAG, "Queueing up legacy transition " + wct);
mQueue.add(cb);
if (mQueue.size() == 1) {
cb.send();
}
}
}
/**
* Queues a sync transaction only if there are already sync transaction(s) queued or in flight.
* Otherwise just returns without queueing.
@@ -118,12 +135,12 @@ public final class SyncTransactionQueue {
// Synchronized on mQueue
private void onTransactionReceived(@NonNull SurfaceControl.Transaction t) {
if (DEBUG) Slog.d(TAG, " Running " + mRunnables.size() + " sync runnables");
for (int i = 0, n = mRunnables.size(); i < n; ++i) {
final int n = mRunnables.size();
for (int i = 0; i < n; ++i) {
mRunnables.get(i).runWithTransaction(t);
}
mRunnables.clear();
t.apply();
t.close();
// More runnables may have been added, so only remove the ones that ran.
mRunnables.subList(0, n).clear();
}
/** Task to run with transaction. */
@@ -135,20 +152,38 @@ public final class SyncTransactionQueue {
private class SyncCallback extends WindowContainerTransactionCallback {
int mId = -1;
final WindowContainerTransaction mWCT;
final LegacyTransitions.LegacyTransition mLegacyTransition;
SyncCallback(WindowContainerTransaction wct) {
mWCT = wct;
mLegacyTransition = null;
}
SyncCallback(LegacyTransitions.ILegacyTransition legacyTransition,
@WindowManager.TransitionType int type, WindowContainerTransaction wct) {
mWCT = wct;
mLegacyTransition = new LegacyTransitions.LegacyTransition(type, legacyTransition);
}
// Must be sychronized on mQueue
void send() {
if (mInFlight == this) {
// This was probably queued up and sent during a sync runnable of the last callback.
// Don't queue it again.
return;
}
if (mInFlight != null) {
throw new IllegalStateException("Sync Transactions must be serialized. In Flight: "
+ mInFlight.mId + " - " + mInFlight.mWCT);
}
mInFlight = this;
if (DEBUG) Slog.d(TAG, "Sending sync transaction: " + mWCT);
mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
if (mLegacyTransition != null) {
mId = new WindowOrganizer().startLegacyTransition(mLegacyTransition.getType(),
mLegacyTransition.getAdapter(), this, mWCT);
} else {
mId = new WindowOrganizer().applySyncTransaction(mWCT, this);
}
if (DEBUG) Slog.d(TAG, " Sent sync transaction. Got id=" + mId);
mMainExecutor.executeDelayed(mOnReplyTimeout, REPLY_TIMEOUT);
}
@@ -169,6 +204,16 @@ public final class SyncTransactionQueue {
if (DEBUG) Slog.d(TAG, "onTransactionReady id=" + mId);
mQueue.remove(this);
onTransactionReceived(t);
if (mLegacyTransition != null) {
try {
mLegacyTransition.getSyncCallback().onTransactionReady(mId, t);
} catch (RemoteException e) {
Slog.e(TAG, "Error sending callback to legacy transition: " + mId, e);
}
} else {
t.apply();
t.close();
}
if (!mQueue.isEmpty()) {
mQueue.get(0).send();
}

View File

@@ -95,7 +95,7 @@ public final class SplitWindowManager extends WindowlessWindowManager {
final SurfaceControl.Builder builder = new SurfaceControl.Builder(new SurfaceSession())
.setContainerLayer()
.setName(TAG)
.setHidden(false)
.setHidden(true)
.setCallsite("SplitWindowManager#attachToParentSurface");
mParentContainerCallbacks.attachToParentSurface(builder);
mLeash = builder.build();

View File

@@ -17,14 +17,12 @@
package com.android.wm.shell.splitscreen;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static com.android.wm.shell.common.ExecutorUtils.executeRemoteCallWithTaskPermission;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_BOTTOM_OR_RIGHT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_TOP_OR_LEFT;
import static com.android.wm.shell.common.split.SplitLayout.SPLIT_POSITION_UNDEFINED;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
import android.app.ActivityManager;
import android.app.ActivityTaskManager;
@@ -39,9 +37,13 @@ import android.os.IBinder;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.Slog;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IRemoteTransition;
import android.window.WindowContainerTransaction;
import androidx.annotation.BinderThread;
import androidx.annotation.NonNull;
@@ -58,6 +60,7 @@ import com.android.wm.shell.common.annotations.ExternalThread;
import com.android.wm.shell.common.split.SplitLayout.SplitPosition;
import com.android.wm.shell.draganddrop.DragAndDropPolicy;
import com.android.wm.shell.splitscreen.ISplitScreenListener;
import com.android.wm.shell.transition.LegacyTransitions;
import com.android.wm.shell.transition.Transitions;
import java.io.PrintWriter;
@@ -147,7 +150,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
}
public void setSideStagePosition(@SplitPosition int sideStagePosition) {
mStageCoordinator.setSideStagePosition(sideStagePosition);
mStageCoordinator.setSideStagePosition(sideStagePosition, null /* wct */);
}
public void setSideStageVisibility(boolean visible) {
@@ -185,7 +188,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startTask(int taskId, @SplitScreen.StageType int stage,
@SplitPosition int position, @Nullable Bundle options) {
options = resolveStartStage(stage, position, options);
options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
try {
ActivityTaskManager.getService().startActivityFromRecents(taskId, options);
@@ -197,7 +200,7 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startShortcut(String packageName, String shortcutId,
@SplitScreen.StageType int stage, @SplitPosition int position,
@Nullable Bundle options, UserHandle user) {
options = resolveStartStage(stage, position, options);
options = mStageCoordinator.resolveStartStage(stage, position, options, null /* wct */);
try {
LauncherApps launcherApps =
@@ -212,64 +215,61 @@ public class SplitScreenController implements DragAndDropPolicy.Starter,
public void startIntent(PendingIntent intent, Intent fillInIntent,
@SplitScreen.StageType int stage, @SplitPosition int position,
@Nullable Bundle options) {
options = resolveStartStage(stage, position, options);
try {
intent.send(mContext, 0, fillInIntent, null, null, null, options);
} catch (PendingIntent.CanceledException e) {
Slog.e(TAG, "Failed to launch activity", e);
if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
startIntentLegacy(intent, fillInIntent, stage, position, options);
return;
}
mStageCoordinator.startIntent(intent, fillInIntent, stage, position, options,
null /* remote */);
}
private Bundle resolveStartStage(@SplitScreen.StageType int stage,
@SplitPosition int position, @Nullable Bundle options) {
switch (stage) {
case STAGE_TYPE_UNDEFINED: {
// Use the stage of the specified position is valid.
if (position != SPLIT_POSITION_UNDEFINED) {
if (position == mStageCoordinator.getSideStagePosition()) {
options = resolveStartStage(STAGE_TYPE_SIDE, position, options);
} else {
options = resolveStartStage(STAGE_TYPE_MAIN, position, options);
}
} else {
// Exit split-screen and launch fullscreen since stage wasn't specified.
mStageCoordinator.exitSplitScreen();
}
break;
}
case STAGE_TYPE_SIDE: {
if (position != SPLIT_POSITION_UNDEFINED) {
mStageCoordinator.setSideStagePosition(position);
} else {
position = mStageCoordinator.getSideStagePosition();
}
if (options == null) {
options = new Bundle();
}
mStageCoordinator.updateActivityOptions(options, position);
break;
}
case STAGE_TYPE_MAIN: {
if (position != SPLIT_POSITION_UNDEFINED) {
// Set the side stage opposite of what we want to the main stage.
final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
mStageCoordinator.setSideStagePosition(sideStagePosition);
} else {
position = mStageCoordinator.getMainStagePosition();
}
if (options == null) {
options = new Bundle();
}
mStageCoordinator.updateActivityOptions(options, position);
break;
}
default:
throw new IllegalArgumentException("Unknown stage=" + stage);
}
private void startIntentLegacy(PendingIntent intent, Intent fillInIntent,
@SplitScreen.StageType int stage, @SplitPosition int position,
@Nullable Bundle options) {
final boolean wasInSplit = isSplitScreenVisible();
return options;
LegacyTransitions.ILegacyTransition transition = new LegacyTransitions.ILegacyTransition() {
@Override
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback,
SurfaceControl.Transaction t) {
boolean cancelled = apps == null || apps.length == 0;
mStageCoordinator.updateSurfaceBounds(null /* layout */, t);
if (cancelled) {
if (!wasInSplit) {
final WindowContainerTransaction undoWct = new WindowContainerTransaction();
mStageCoordinator.prepareExitSplitScreen(STAGE_TYPE_MAIN, undoWct);
mSyncQueue.queue(undoWct);
mSyncQueue.runInSync(undoT -> {
// looks weird, but we want undoT to execute after t but still want the
// rest of the syncQueue runnables to aggregate.
t.merge(undoT);
undoT.merge(t);
});
return;
}
}
for (int i = 0; i < apps.length; ++i) {
if (apps[i].mode == MODE_OPENING) {
t.show(apps[i].leash);
}
}
RemoteAnimationTarget divider = mStageCoordinator.getDividerBarLegacyTarget();
t.show(divider.leash);
t.apply();
if (cancelled) return;
try {
finishedCallback.onAnimationFinished();
} catch (RemoteException e) {
Slog.e(TAG, "Error finishing legacy transition: ", e);
}
}
};
WindowContainerTransaction wct = new WindowContainerTransaction();
options = mStageCoordinator.resolveStartStage(stage, position, options, wct);
wct.sendPendingIntent(intent, fillInIntent, options);
mSyncQueue.queue(transition, WindowManager.TRANSIT_OPEN, wct);
}
RemoteAnimationTarget[] onGoingToRecentsLegacy(boolean cancel) {

View File

@@ -36,6 +36,7 @@ import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
import static com.android.wm.shell.splitscreen.SplitScreenTransitions.FLAG_IS_DIVIDER_BAR;
import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS_SNAP;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE;
import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
import static com.android.wm.shell.transition.Transitions.isClosingType;
import static com.android.wm.shell.transition.Transitions.isOpeningType;
@@ -45,8 +46,10 @@ import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.ActivityTaskManager;
import android.app.PendingIntent;
import android.app.WindowConfiguration;
import android.content.Context;
import android.content.Intent;
import android.graphics.Rect;
import android.hardware.devicestate.DeviceStateManager;
import android.os.Bundle;
@@ -211,7 +214,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
boolean moveToSideStage(ActivityManager.RunningTaskInfo task,
@SplitPosition int sideStagePosition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
setSideStagePosition(sideStagePosition);
setSideStagePosition(sideStagePosition, wct);
mMainStage.activate(getMainStageBounds(), wct);
mSideStage.addTask(task, getSideStageBounds(), wct);
mTaskOrganizer.applyTransaction(wct);
@@ -243,7 +246,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
final WindowContainerTransaction wct = new WindowContainerTransaction();
mainOptions = mainOptions != null ? mainOptions : new Bundle();
sideOptions = sideOptions != null ? sideOptions : new Bundle();
setSideStagePosition(sidePosition);
setSideStagePosition(sidePosition, wct);
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
@@ -312,7 +315,7 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
}
sideOptions = sideOptions != null ? sideOptions : new Bundle();
setSideStagePosition(sidePosition);
setSideStagePosition(sidePosition, wct);
// Build a request WCT that will launch both apps such that task 0 is on the main stage
// while task 1 is on the side stage.
@@ -331,6 +334,69 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mTaskOrganizer.applyTransaction(wct);
}
public void startIntent(PendingIntent intent, Intent fillInIntent,
@SplitScreen.StageType int stage, @SplitPosition int position,
@androidx.annotation.Nullable Bundle options,
@Nullable IRemoteTransition remoteTransition) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
options = resolveStartStage(stage, position, options, wct);
wct.sendPendingIntent(intent, fillInIntent, options);
mSplitTransitions.startEnterTransition(
TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE, wct, remoteTransition, this);
}
Bundle resolveStartStage(@SplitScreen.StageType int stage,
@SplitPosition int position, @androidx.annotation.Nullable Bundle options,
@androidx.annotation.Nullable WindowContainerTransaction wct) {
switch (stage) {
case STAGE_TYPE_UNDEFINED: {
// Use the stage of the specified position is valid.
if (position != SPLIT_POSITION_UNDEFINED) {
if (position == getSideStagePosition()) {
options = resolveStartStage(STAGE_TYPE_SIDE, position, options, wct);
} else {
options = resolveStartStage(STAGE_TYPE_MAIN, position, options, wct);
}
} else {
// Exit split-screen and launch fullscreen since stage wasn't specified.
prepareExitSplitScreen(STAGE_TYPE_UNDEFINED, wct);
}
break;
}
case STAGE_TYPE_SIDE: {
if (position != SPLIT_POSITION_UNDEFINED) {
setSideStagePosition(position, wct);
} else {
position = getSideStagePosition();
}
if (options == null) {
options = new Bundle();
}
updateActivityOptions(options, position, wct);
break;
}
case STAGE_TYPE_MAIN: {
if (position != SPLIT_POSITION_UNDEFINED) {
// Set the side stage opposite of what we want to the main stage.
final int sideStagePosition = position == SPLIT_POSITION_TOP_OR_LEFT
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
setSideStagePosition(sideStagePosition, wct);
} else {
position = getMainStagePosition();
}
if (options == null) {
options = new Bundle();
}
updateActivityOptions(options, position, wct);
break;
}
default:
throw new IllegalArgumentException("Unknown stage=" + stage);
}
return options;
}
@SplitLayout.SplitPosition
int getSideStagePosition() {
return mSideStagePosition;
@@ -342,18 +408,24 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT;
}
void setSideStagePosition(@SplitPosition int sideStagePosition) {
setSideStagePosition(sideStagePosition, true /* updateBounds */);
void setSideStagePosition(@SplitPosition int sideStagePosition,
@Nullable WindowContainerTransaction wct) {
setSideStagePosition(sideStagePosition, true /* updateBounds */, wct);
}
private void setSideStagePosition(@SplitPosition int sideStagePosition,
boolean updateBounds) {
boolean updateBounds, @Nullable WindowContainerTransaction wct) {
if (mSideStagePosition == sideStagePosition) return;
mSideStagePosition = sideStagePosition;
sendOnStagePositionChanged();
if (mSideStageListener.mVisible && updateBounds) {
onBoundsChanged(mSplitLayout);
if (wct == null) {
// onBoundsChanged builds/applies a wct with the contents of updateWindowBounds.
onBoundsChanged(mSplitLayout);
} else {
updateWindowBounds(mSplitLayout, wct);
}
}
}
@@ -388,7 +460,12 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
mSplitLayout.resetDividerPosition();
}
private void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
/**
* Unlike exitSplitScreen, this takes a stagetype vs an actual stage-reference and populates
* an existing WindowContainerTransaction (rather than applying immediately). This is intended
* to be used when exiting split might be bundled with other window operations.
*/
void prepareExitSplitScreen(@SplitScreen.StageType int stageToTop,
@NonNull WindowContainerTransaction wct) {
mSideStage.removeAllTasks(wct, stageToTop == STAGE_TYPE_SIDE);
mMainStage.deactivate(wct, stageToTop == STAGE_TYPE_MAIN);
@@ -403,15 +480,21 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
opts.putParcelable(KEY_LAUNCH_ROOT_TASK_TOKEN, stage.mRootTaskInfo.token);
}
void updateActivityOptions(Bundle opts, @SplitPosition int position) {
void updateActivityOptions(Bundle opts, @SplitPosition int position,
@Nullable WindowContainerTransaction wct) {
addActivityOptions(opts, position == mSideStagePosition ? mSideStage : mMainStage);
if (!mMainStage.isActive()) {
// Activate the main stage in anticipation of an app launch.
final WindowContainerTransaction wct = new WindowContainerTransaction();
boolean needsApply = wct == null;
if (needsApply) {
wct = new WindowContainerTransaction();
}
mMainStage.activate(getMainStageBounds(), wct);
mSideStage.setBounds(getSideStageBounds(), wct);
mTaskOrganizer.applyTransaction(wct);
if (needsApply) {
mTaskOrganizer.applyTransaction(wct);
}
}
}
@@ -624,32 +707,41 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
@Override
public void onDoubleTappedDivider() {
setSideStagePosition(mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT);
? SPLIT_POSITION_BOTTOM_OR_RIGHT : SPLIT_POSITION_TOP_OR_LEFT, null /* wct */);
}
@Override
public void onBoundsChanging(SplitLayout layout) {
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
mSyncQueue.runInSync(t -> layout.applySurfaceChanges(t, topLeftStage.mRootLeash,
bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer));
mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
}
@Override
public void onBoundsChanged(SplitLayout layout) {
final WindowContainerTransaction wct = new WindowContainerTransaction();
updateWindowBounds(layout, wct);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> updateSurfaceBounds(layout, t));
}
/**
* Populates `wct` with operations that match the split windows to the current layout.
* To match relevant surfaces, make sure to call updateSurfaceBounds after `wct` is applied
*/
private void updateWindowBounds(SplitLayout layout, WindowContainerTransaction wct) {
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
final WindowContainerTransaction wct = new WindowContainerTransaction();
layout.applyTaskChanges(wct, topLeftStage.mRootTaskInfo, bottomRightStage.mRootTaskInfo);
mSyncQueue.queue(wct);
mSyncQueue.runInSync(t -> layout.applySurfaceChanges(t, topLeftStage.mRootLeash,
bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer));
}
void updateSurfaceBounds(@Nullable SplitLayout layout, @NonNull SurfaceControl.Transaction t) {
final StageTaskListener topLeftStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
final StageTaskListener bottomRightStage =
mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
(layout != null ? layout : mSplitLayout).applySurfaceChanges(t, topLeftStage.mRootLeash,
bottomRightStage.mRootLeash, topLeftStage.mDimLayer, bottomRightStage.mDimLayer);
}
@Override
@@ -869,7 +961,8 @@ class StageCoordinator implements SplitLayout.SplitLayoutHandler,
// Update local states (before animating).
setDividerVisibility(true);
setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */);
setSideStagePosition(SPLIT_POSITION_BOTTOM_OR_RIGHT, false /* updateBounds */,
null /* wct */);
setSplitsVisible(true);
addDividerBarToTransition(info, t, true /* show */);

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2021 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.wm.shell.transition;
import android.annotation.NonNull;
import android.os.RemoteException;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.IWindowContainerTransactionCallback;
/**
* Utilities and interfaces for transition-like usage on top of the legacy app-transition and
* synctransaction tools.
*/
public class LegacyTransitions {
/**
* Interface for a "legacy" transition. Effectively wraps a sync callback + remoteAnimation
* into one callback.
*/
public interface ILegacyTransition {
/**
* Called when both the associated sync transaction finishes and the remote animation is
* ready.
*/
void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback, SurfaceControl.Transaction t);
}
/**
* Makes sure that a remote animation and corresponding sync callback are called together
* such that the sync callback is called first. This assumes that both the callback receiver
* and the remoteanimation are in the same process so that order is preserved on both ends.
*/
public static class LegacyTransition {
private final ILegacyTransition mLegacyTransition;
private int mSyncId = -1;
private SurfaceControl.Transaction mTransaction;
private int mTransit;
private RemoteAnimationTarget[] mApps;
private RemoteAnimationTarget[] mWallpapers;
private RemoteAnimationTarget[] mNonApps;
private IRemoteAnimationFinishedCallback mFinishCallback = null;
private boolean mCancelled = false;
private final SyncCallback mSyncCallback = new SyncCallback();
private final RemoteAnimationAdapter mAdapter =
new RemoteAnimationAdapter(new RemoteAnimationWrapper(), 0, 0);
public LegacyTransition(@WindowManager.TransitionType int type,
@NonNull ILegacyTransition legacyTransition) {
mLegacyTransition = legacyTransition;
mTransit = type;
}
public @WindowManager.TransitionType int getType() {
return mTransit;
}
public IWindowContainerTransactionCallback getSyncCallback() {
return mSyncCallback;
}
public RemoteAnimationAdapter getAdapter() {
return mAdapter;
}
private class SyncCallback extends IWindowContainerTransactionCallback.Stub {
@Override
public void onTransactionReady(int id, SurfaceControl.Transaction t)
throws RemoteException {
mSyncId = id;
mTransaction = t;
checkApply();
}
}
private class RemoteAnimationWrapper extends IRemoteAnimationRunner.Stub {
@Override
public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
mTransit = transit;
mApps = apps;
mWallpapers = wallpapers;
mNonApps = nonApps;
mFinishCallback = finishedCallback;
checkApply();
}
@Override
public void onAnimationCancelled() throws RemoteException {
mCancelled = true;
mApps = mWallpapers = mNonApps = null;
checkApply();
}
}
private void checkApply() throws RemoteException {
if (mSyncId < 0 || (mFinishCallback == null && !mCancelled)) return;
mLegacyTransition.onAnimationStart(mTransit, mApps, mWallpapers,
mNonApps, mFinishCallback, mTransaction);
}
}
}

View File

@@ -83,6 +83,9 @@ public class Transitions implements RemoteCallable<Transitions> {
/** Transition type for removing PIP via the Shell, either via Dismiss bubble or Close. */
public static final int TRANSIT_REMOVE_PIP = TRANSIT_FIRST_CUSTOM + 4;
/** Transition type for entering split by opening an app into side-stage. */
public static final int TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE = TRANSIT_FIRST_CUSTOM + 5;
private final WindowOrganizer mOrganizer;
private final Context mContext;
private final ShellExecutor mMainExecutor;

View File

@@ -16348,6 +16348,15 @@ public class ActivityManagerService extends IActivityManager.Stub
return mProcessList.getIsolatedProcessesLocked(uid);
}
}
/** @see ActivityManagerService#sendIntentSender */
@Override
public int sendIntentSender(IIntentSender target, IBinder allowlistToken, int code,
Intent intent, String resolvedType,
IIntentReceiver finishedReceiver, String requiredPermission, Bundle options) {
return ActivityManagerService.this.sendIntentSender(target, allowlistToken, code,
intent, resolvedType, finishedReceiver, requiredPermission, options);
}
}
long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) {

View File

@@ -213,6 +213,7 @@ public class AppTransition implements Dump {
private int mNextAppTransitionEnter;
private int mNextAppTransitionExit;
private int mNextAppTransitionInPlace;
private boolean mNextAppTransitionIsSync;
// Keyed by WindowContainer hashCode.
private final SparseArray<AppTransitionAnimationSpec> mNextAppTransitionAnimationsSpecs
@@ -348,6 +349,13 @@ public class AppTransition implements Dump {
fetchAppTransitionSpecsFromFuture();
}
void abort() {
if (mRemoteAnimationController != null) {
mRemoteAnimationController.cancelAnimation("aborted");
}
clear();
}
boolean isRunning() {
return mAppTransitionState == APP_STATE_RUNNING;
}
@@ -466,6 +474,7 @@ public class AppTransition implements Dump {
mNextAppTransitionAnimationsSpecsFuture = null;
mDefaultNextAppTransitionAnimationSpec = null;
mAnimationFinishedCallback = null;
mNextAppTransitionIsSync = false;
}
void freeze() {
@@ -1146,13 +1155,19 @@ public class AppTransition implements Dump {
}
void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter) {
overridePendingAppTransitionRemote(remoteAnimationAdapter, false /* sync */);
}
void overridePendingAppTransitionRemote(RemoteAnimationAdapter remoteAnimationAdapter,
boolean sync) {
ProtoLog.i(WM_DEBUG_APP_TRANSITIONS, "Override pending remote transitionSet=%b adapter=%s",
isTransitionSet(), remoteAnimationAdapter);
if (isTransitionSet()) {
if (isTransitionSet() && !mNextAppTransitionIsSync) {
clear();
mNextAppTransitionType = NEXT_TRANSIT_TYPE_REMOTE;
mRemoteAnimationController = new RemoteAnimationController(mService, mDisplayContent,
remoteAnimationAdapter, mHandler);
mNextAppTransitionIsSync = sync;
}
}

View File

@@ -5014,6 +5014,11 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
}
void cancelAppTransition() {
if (!mAppTransition.isTransitionSet() || mAppTransition.isRunning()) return;
mAppTransition.abort();
}
/**
* Update pendingLayoutChanges after app transition has finished.
*/

View File

@@ -17,10 +17,12 @@
package com.android.server.wm;
import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CHILDREN_TASKS_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_DELETE_TASK_FRAGMENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_LAUNCH_TASK;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_PENDING_INTENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT;
import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
@@ -39,6 +41,8 @@ import static com.android.server.wm.WindowContainer.POSITION_TOP;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.WindowConfiguration;
import android.content.Intent;
import android.content.pm.ActivityInfo;
@@ -52,6 +56,7 @@ import android.os.RemoteException;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Slog;
import android.view.RemoteAnimationAdapter;
import android.view.SurfaceControl;
import android.window.IDisplayAreaOrganizerController;
import android.window.ITaskFragmentOrganizer;
@@ -237,6 +242,44 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
}
@Override
public int startLegacyTransition(int type, @NonNull RemoteAnimationAdapter adapter,
@NonNull IWindowContainerTransactionCallback callback,
@NonNull WindowContainerTransaction t) {
enforceTaskPermission("startLegacyTransition()");
final CallerInfo caller = new CallerInfo();
final long ident = Binder.clearCallingIdentity();
int syncId;
try {
synchronized (mGlobalLock) {
if (type < 0) {
throw new IllegalArgumentException("Can't create transition with no type");
}
if (mTransitionController.getTransitionPlayer() != null) {
throw new IllegalArgumentException("Can't use legacy transitions in"
+ " when shell transitions are enabled.");
}
final DisplayContent dc =
mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
if (dc.mAppTransition.isTransitionSet()) {
// a transition already exists, so the callback probably won't be called.
return -1;
}
adapter.setCallingPidUid(caller.mPid, caller.mUid);
dc.prepareAppTransition(type);
dc.mAppTransition.overridePendingAppTransitionRemote(adapter, true /* sync */);
syncId = startSyncWithOrganizer(callback);
applyTransaction(t, syncId, null /* transition */, caller);
setSyncReady(syncId);
mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY)
.executeAppTransition();
}
} finally {
Binder.restoreCallingIdentity(ident);
}
return syncId;
}
@Override
public int finishTransition(@NonNull IBinder transitionToken,
@Nullable WindowContainerTransaction t,
@@ -578,6 +621,37 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
mService.mTaskSupervisor.startActivityFromRecents(caller.mPid, caller.mUid,
taskId, safeOptions);
break;
case HIERARCHY_OP_TYPE_PENDING_INTENT:
String resolvedType = hop.getActivityIntent() != null
? hop.getActivityIntent().resolveTypeIfNeeded(
mService.mContext.getContentResolver())
: null;
Bundle options = null;
if (hop.getPendingIntent().isActivity()) {
// Set the context display id as preferred for this activity launches, so that
// it can land on caller's display. Or just brought the task to front at the
// display where it was on since it has higher preference.
ActivityOptions activityOptions = hop.getLaunchOptions() != null
? new ActivityOptions(hop.getLaunchOptions())
: ActivityOptions.makeBasic();
activityOptions.setCallerDisplayId(DEFAULT_DISPLAY);
options = activityOptions.toBundle();
}
int res = mService.mAmInternal.sendIntentSender(hop.getPendingIntent().getTarget(),
hop.getPendingIntent().getWhitelistToken(), 0 /* code */,
hop.getActivityIntent(), resolvedType, null /* finishReceiver */,
null /* requiredPermission */, options);
if (res != ActivityManager.START_SUCCESS
&& res != ActivityManager.START_TASK_TO_FRONT) {
if (!mTransitionController.isShellTransitionsEnabled()) {
final DisplayContent dc =
mService.mRootWindowContainer.getDisplayContent(DEFAULT_DISPLAY);
dc.cancelAppTransition();
}
}
break;
case HIERARCHY_OP_TYPE_CREATE_TASK_FRAGMENT:
final TaskFragmentCreationParams taskFragmentCreationOptions =
hop.getTaskFragmentCreationOptions();