Refactor back navigation animtion (1/2)

In previous design, it would create all necessary leashes when starting
back navigation, and they would be carried by `BackNavigationInfo` and
`BackEvent` and would finally deliver to the shell and animator side.

In this CL, we will use the adapter that wraps a back animation runner
to deliver all leashes in next surface placement after back navigation
has started.

In shell side, every animator should be registered by type, so the
adapter could deliver leashes via IRemoteAnimationRunner to the
target animator, and invoke callback when it finished.

This also eliminated all unecessary fields from `BackNavigationInfo` and
`BackEvent`.

Bug: 241808055
Test: atest BackNavigationControllerTests BackAnimationControllerTest
      BackNavigationTest
Change-Id: I8bbec0d8d9631110c3d2788d958b50ae487520a7
This commit is contained in:
Arthur Hung
2022-08-16 08:17:08 +00:00
parent bb25387f0f
commit 8fcc92bb34
16 changed files with 792 additions and 545 deletions

View File

@@ -73,6 +73,7 @@ import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationDefinition;
import android.view.RemoteAnimationAdapter;
import android.window.IWindowOrganizerController;
import android.window.BackAnimationAdapter;
import android.window.BackNavigationInfo;
import android.window.SplashScreenView;
import com.android.internal.app.IVoiceInteractor;
@@ -352,9 +353,10 @@ interface IActivityTaskManager {
/**
* Prepare the back navigation in the server. This setups the leashed for sysui to animate
* the back gesture and returns the data needed for the animation.
* @param requestAnimation true if the caller wishes to animate the back navigation
* @param focusObserver a remote callback to nofify shell when the focused window lost focus.
* @param adaptor a remote animation to be run for the back navigation plays the animation.
* @return Returns the back navigation info.
*/
android.window.BackNavigationInfo startBackNavigation(in boolean requestAnimation,
in IWindowFocusObserver focusObserver);
android.window.BackNavigationInfo startBackNavigation(
in IWindowFocusObserver focusObserver, in BackAnimationAdapter adaptor);
}

View File

@@ -0,0 +1,22 @@
/*
* Copyright (C) 2022 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 android.window;
/**
* @hide
*/
parcelable BackAnimationAdapter;

View File

@@ -0,0 +1,62 @@
/*
* Copyright (C) 2022 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 android.window;
import android.os.Parcel;
import android.os.Parcelable;
/**
* Object that describes how to run a remote back animation.
*
* @hide
*/
public class BackAnimationAdapter implements Parcelable {
private final IBackAnimationRunner mRunner;
public BackAnimationAdapter(IBackAnimationRunner runner) {
mRunner = runner;
}
public BackAnimationAdapter(Parcel in) {
mRunner = IBackAnimationRunner.Stub.asInterface(in.readStrongBinder());
}
public IBackAnimationRunner getRunner() {
return mRunner;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeStrongInterface(mRunner);
}
public static final @android.annotation.NonNull Creator<BackAnimationAdapter> CREATOR =
new Creator<BackAnimationAdapter>() {
public BackAnimationAdapter createFromParcel(Parcel in) {
return new BackAnimationAdapter(in);
}
public BackAnimationAdapter[] newArray(int size) {
return new BackAnimationAdapter[size];
}
};
}

View File

@@ -18,10 +18,8 @@ package android.window;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Parcel;
import android.os.Parcelable;
import android.view.RemoteAnimationTarget;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -52,8 +50,6 @@ public class BackEvent implements Parcelable {
@SwipeEdge
private final int mSwipeEdge;
@Nullable
private final RemoteAnimationTarget mDepartingAnimationTarget;
/**
* Creates a new {@link BackEvent} instance.
@@ -62,16 +58,12 @@ public class BackEvent implements Parcelable {
* @param touchY Absolute Y location of the touch point of this event.
* @param progress Value between 0 and 1 on how far along the back gesture is.
* @param swipeEdge Indicates which edge the swipe starts from.
* @param departingAnimationTarget The remote animation target of the departing application
* window.
*/
public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge,
@Nullable RemoteAnimationTarget departingAnimationTarget) {
public BackEvent(float touchX, float touchY, float progress, @SwipeEdge int swipeEdge) {
mTouchX = touchX;
mTouchY = touchY;
mProgress = progress;
mSwipeEdge = swipeEdge;
mDepartingAnimationTarget = departingAnimationTarget;
}
private BackEvent(@NonNull Parcel in) {
@@ -79,7 +71,6 @@ public class BackEvent implements Parcelable {
mTouchY = in.readFloat();
mProgress = in.readFloat();
mSwipeEdge = in.readInt();
mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
}
public static final Creator<BackEvent> CREATOR = new Creator<BackEvent>() {
@@ -105,7 +96,6 @@ public class BackEvent implements Parcelable {
dest.writeFloat(mTouchY);
dest.writeFloat(mProgress);
dest.writeInt(mSwipeEdge);
dest.writeTypedObject(mDepartingAnimationTarget, flags);
}
/**
@@ -136,16 +126,6 @@ public class BackEvent implements Parcelable {
return mSwipeEdge;
}
/**
* Returns the {@link RemoteAnimationTarget} of the top departing application window,
* or {@code null} if the top window should not be moved for the current type of back
* destination.
*/
@Nullable
public RemoteAnimationTarget getDepartingAnimationTarget() {
return mDepartingAnimationTarget;
}
@Override
public String toString() {
return "BackEvent{"
@@ -153,7 +133,6 @@ public class BackEvent implements Parcelable {
+ ", mTouchY=" + mTouchY
+ ", mProgress=" + mProgress
+ ", mSwipeEdge" + mSwipeEdge
+ ", mDepartingAnimationTarget" + mDepartingAnimationTarget
+ "}";
}
}

View File

@@ -19,14 +19,10 @@ package android.window;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.hardware.HardwareBuffer;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.RemoteCallback;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
/**
* Information to be sent to SysUI about a back event.
@@ -84,75 +80,50 @@ public final class BackNavigationInfo implements Parcelable {
TYPE_CROSS_TASK,
TYPE_CALLBACK
})
@interface BackTargetType {
public @interface BackTargetType {
}
private final int mType;
@Nullable
private final RemoteAnimationTarget mDepartingAnimationTarget;
@Nullable
private final SurfaceControl mScreenshotSurface;
@Nullable
private final HardwareBuffer mScreenshotBuffer;
@Nullable
private final RemoteCallback mOnBackNavigationDone;
@Nullable
private final WindowConfiguration mTaskWindowConfiguration;
@Nullable
private final IOnBackInvokedCallback mOnBackInvokedCallback;
private final boolean mPrepareRemoteAnimation;
/**
* Create a new {@link BackNavigationInfo} instance.
*
* @param type The {@link BackTargetType} of the destination (what will be
* displayed after the back action).
* @param departingAnimationTarget The remote animation target, containing a leash to animate
* away the departing window. The consumer of the leash is
* responsible for removing it.
* @param screenshotSurface The screenshot of the previous activity to be displayed.
* @param screenshotBuffer A buffer containing a screenshot used to display the activity.
* See {@link #getScreenshotHardwareBuffer()} for information
* about nullity.
* @param taskWindowConfiguration The window configuration of the Task being animated beneath.
* @param onBackNavigationDone The callback to be called once the client is done with the
* back preview.
* @param onBackInvokedCallback The back callback registered by the current top level window.
* @param isPrepareRemoteAnimation Return whether the core is preparing a back gesture
* animation, if true, the caller of startBackNavigation should
* be expected to receive an animation start callback.
*/
private BackNavigationInfo(@BackTargetType int type,
@Nullable RemoteAnimationTarget departingAnimationTarget,
@Nullable SurfaceControl screenshotSurface,
@Nullable HardwareBuffer screenshotBuffer,
@Nullable WindowConfiguration taskWindowConfiguration,
@Nullable RemoteCallback onBackNavigationDone,
@Nullable IOnBackInvokedCallback onBackInvokedCallback) {
@Nullable IOnBackInvokedCallback onBackInvokedCallback,
boolean isPrepareRemoteAnimation) {
mType = type;
mDepartingAnimationTarget = departingAnimationTarget;
mScreenshotSurface = screenshotSurface;
mScreenshotBuffer = screenshotBuffer;
mTaskWindowConfiguration = taskWindowConfiguration;
mOnBackNavigationDone = onBackNavigationDone;
mOnBackInvokedCallback = onBackInvokedCallback;
mPrepareRemoteAnimation = isPrepareRemoteAnimation;
}
private BackNavigationInfo(@NonNull Parcel in) {
mType = in.readInt();
mDepartingAnimationTarget = in.readTypedObject(RemoteAnimationTarget.CREATOR);
mScreenshotSurface = in.readTypedObject(SurfaceControl.CREATOR);
mScreenshotBuffer = in.readTypedObject(HardwareBuffer.CREATOR);
mTaskWindowConfiguration = in.readTypedObject(WindowConfiguration.CREATOR);
mOnBackNavigationDone = in.readTypedObject(RemoteCallback.CREATOR);
mOnBackInvokedCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder());
mPrepareRemoteAnimation = in.readBoolean();
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeInt(mType);
dest.writeTypedObject(mDepartingAnimationTarget, flags);
dest.writeTypedObject(mScreenshotSurface, flags);
dest.writeTypedObject(mScreenshotBuffer, flags);
dest.writeTypedObject(mTaskWindowConfiguration, flags);
dest.writeTypedObject(mOnBackNavigationDone, flags);
dest.writeStrongInterface(mOnBackInvokedCallback);
dest.writeBoolean(mPrepareRemoteAnimation);
}
/**
@@ -164,49 +135,6 @@ public final class BackNavigationInfo implements Parcelable {
return mType;
}
/**
* Returns a {@link RemoteAnimationTarget}, containing a leash to the top window container
* that needs to be animated. This can be null if the back animation is controlled by
* the application.
*/
@Nullable
public RemoteAnimationTarget getDepartingAnimationTarget() {
return mDepartingAnimationTarget;
}
/**
* Returns the {@link SurfaceControl} that should be used to display a screenshot of the
* previous activity.
*/
@Nullable
public SurfaceControl getScreenshotSurface() {
return mScreenshotSurface;
}
/**
* Returns the {@link HardwareBuffer} containing the screenshot the activity about to be
* shown. This can be null if one of the following conditions is met:
* <ul>
* <li>The screenshot is not available
* <li> The previous activity is the home screen ( {@link #TYPE_RETURN_TO_HOME}
* <li> The current window is a dialog ({@link #TYPE_DIALOG_CLOSE}
* <li> The back animation is controlled by the application
* </ul>
*/
@Nullable
public HardwareBuffer getScreenshotHardwareBuffer() {
return mScreenshotBuffer;
}
/**
* Returns the {@link WindowConfiguration} of the current task. This is null when the top
* application is controlling the back animation.
*/
@Nullable
public WindowConfiguration getTaskWindowConfiguration() {
return mTaskWindowConfiguration;
}
/**
* Returns the {@link OnBackInvokedCallback} of the top level window or null if
* the client didn't register a callback.
@@ -221,6 +149,13 @@ public final class BackNavigationInfo implements Parcelable {
return mOnBackInvokedCallback;
}
/**
* Return true if the core is preparing a back gesture nimation.
*/
public boolean isPrepareRemoteAnimation() {
return mPrepareRemoteAnimation;
}
/**
* Callback to be called when the back preview is finished in order to notify the server that
* it can clean up the resources created for the animation.
@@ -256,10 +191,6 @@ public final class BackNavigationInfo implements Parcelable {
public String toString() {
return "BackNavigationInfo{"
+ "mType=" + typeToString(mType) + " (" + mType + ")"
+ ", mDepartingAnimationTarget=" + mDepartingAnimationTarget
+ ", mScreenshotSurface=" + mScreenshotSurface
+ ", mTaskWindowConfiguration= " + mTaskWindowConfiguration
+ ", mScreenshotBuffer=" + mScreenshotBuffer
+ ", mOnBackNavigationDone=" + mOnBackNavigationDone
+ ", mOnBackInvokedCallback=" + mOnBackInvokedCallback
+ '}';
@@ -291,21 +222,12 @@ public final class BackNavigationInfo implements Parcelable {
*/
@SuppressWarnings("UnusedReturnValue") // Builder pattern
public static class Builder {
private int mType = TYPE_UNDEFINED;
@Nullable
private RemoteAnimationTarget mDepartingAnimationTarget = null;
@Nullable
private SurfaceControl mScreenshotSurface = null;
@Nullable
private HardwareBuffer mScreenshotBuffer = null;
@Nullable
private WindowConfiguration mTaskWindowConfiguration = null;
@Nullable
private RemoteCallback mOnBackNavigationDone = null;
@Nullable
private IOnBackInvokedCallback mOnBackInvokedCallback = null;
private boolean mPrepareRemoteAnimation;
/**
* @see BackNavigationInfo#getType()
*/
@@ -314,40 +236,6 @@ public final class BackNavigationInfo implements Parcelable {
return this;
}
/**
* @see BackNavigationInfo#getDepartingAnimationTarget
*/
public Builder setDepartingAnimationTarget(
@Nullable RemoteAnimationTarget departingAnimationTarget) {
mDepartingAnimationTarget = departingAnimationTarget;
return this;
}
/**
* @see BackNavigationInfo#getScreenshotSurface
*/
public Builder setScreenshotSurface(@Nullable SurfaceControl screenshotSurface) {
mScreenshotSurface = screenshotSurface;
return this;
}
/**
* @see BackNavigationInfo#getScreenshotHardwareBuffer()
*/
public Builder setScreenshotBuffer(@Nullable HardwareBuffer screenshotBuffer) {
mScreenshotBuffer = screenshotBuffer;
return this;
}
/**
* @see BackNavigationInfo#getTaskWindowConfiguration
*/
public Builder setTaskWindowConfiguration(
@Nullable WindowConfiguration taskWindowConfiguration) {
mTaskWindowConfiguration = taskWindowConfiguration;
return this;
}
/**
* @see BackNavigationInfo#onBackNavigationFinished(boolean)
*/
@@ -365,13 +253,20 @@ public final class BackNavigationInfo implements Parcelable {
return this;
}
/**
* @param prepareRemoteAnimation Whether core prepare animation for shell.
*/
public Builder setPrepareRemoteAnimation(boolean prepareRemoteAnimation) {
mPrepareRemoteAnimation = prepareRemoteAnimation;
return this;
}
/**
* Builds and returns an instance of {@link BackNavigationInfo}
*/
public BackNavigationInfo build() {
return new BackNavigationInfo(mType, mDepartingAnimationTarget, mScreenshotSurface,
mScreenshotBuffer, mTaskWindowConfiguration, mOnBackNavigationDone,
mOnBackInvokedCallback);
return new BackNavigationInfo(mType, mOnBackNavigationDone,
mOnBackInvokedCallback, mPrepareRemoteAnimation);
}
}
}

View File

@@ -0,0 +1,27 @@
/*
* Copyright (C) 2022 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 android.window;
/**
* Interface to be invoked by the controlling process when a back animation has finished.
*
* @param trigger Whether the back gesture has passed the triggering threshold.
* {@hide}
*/
oneway interface IBackAnimationFinishedCallback {
void onAnimationFinished(in boolean triggerBack);
}

View File

@@ -0,0 +1,52 @@
/*
* Copyright (C) 2022 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 android.window;
import android.view.RemoteAnimationTarget;
import android.window.IBackAnimationFinishedCallback;
/**
* Interface that is used to callback from window manager to the process that runs a back gesture
* animation to start or cancel it.
*
* {@hide}
*/
oneway interface IBackAnimationRunner {
/**
* Called when the system needs to cancel the current animation. This can be due to the
* wallpaper not drawing in time, or the handler not finishing the animation within a predefined
* amount of time.
*
*/
void onAnimationCancelled() = 1;
/**
* Called when the system is ready for the handler to start animating all the visible tasks.
* @param type The back navigation type.
* @param apps The list of departing (type=MODE_CLOSING) and entering (type=MODE_OPENING)
windows to animate,
* @param wallpapers The list of wallpapers to animate.
* @param nonApps The list of non-app windows such as Bubbles to animate.
* @param finishedCallback The callback to invoke when the animation is finished.
*/
void onAnimationStart(in int type,
in RemoteAnimationTarget[] apps,
in RemoteAnimationTarget[] wallpapers,
in RemoteAnimationTarget[] nonApps,
in IBackAnimationFinishedCallback finishedCallback) = 2;
}

View File

@@ -92,7 +92,7 @@ public class BackNavigationTest {
try {
mInstrumentation.getUiAutomation().waitForIdle(500, 1000);
BackNavigationInfo info = ActivityTaskManager.getService()
.startBackNavigation(true, null);
.startBackNavigation(null, null);
assertNotNull("BackNavigationInfo is null", info);
assertNotNull("OnBackInvokedCallback is null", info.getOnBackInvokedCallback());
info.getOnBackInvokedCallback().onBackInvoked();

View File

@@ -1111,6 +1111,12 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
"-1033630971": {
"message": "onBackNavigationDone backType=%s, triggerBack=%b",
"level": "DEBUG",
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
"-1022146708": {
"message": "Skipping %s: mismatch activity type",
"level": "DEBUG",
@@ -3991,12 +3997,6 @@
"group": "WM_ERROR",
"at": "com\/android\/server\/wm\/WindowManagerService.java"
},
"1778919449": {
"message": "onBackNavigationDone backType=%s, task=%s, prevActivity=%s",
"level": "DEBUG",
"group": "WM_DEBUG_BACK_PREVIEW",
"at": "com\/android\/server\/wm\/BackNavigationController.java"
},
"1781673113": {
"message": "onAnimationFinished(): targetRootTask=%s targetActivity=%s mRestoreTargetBehindRootTask=%s",
"level": "DEBUG",

View File

@@ -23,13 +23,9 @@ import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityTaskManager;
import android.app.IActivityTaskManager;
import android.app.WindowConfiguration;
import android.content.ContentResolver;
import android.content.Context;
import android.database.ContentObserver;
import android.graphics.Point;
import android.graphics.PointF;
import android.hardware.HardwareBuffer;
import android.hardware.input.InputManager;
import android.net.Uri;
import android.os.Handler;
@@ -40,15 +36,19 @@ import android.os.SystemProperties;
import android.os.UserHandle;
import android.provider.Settings.Global;
import android.util.Log;
import android.util.SparseArray;
import android.view.IRemoteAnimationRunner;
import android.view.IWindowFocusObserver;
import android.view.InputDevice;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackAnimationAdapter;
import android.window.BackEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
import com.android.internal.annotations.VisibleForTesting;
@@ -83,16 +83,6 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
/**
* Location of the initial touch event of the back gesture.
*/
private final PointF mInitTouchLocation = new PointF();
/**
* Raw delta between {@link #mInitTouchLocation} and the last touch location.
*/
private final Point mTouchEventDelta = new Point();
/** True when a back gesture is ongoing */
private boolean mBackGestureStarted = false;
@@ -105,23 +95,25 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@Nullable
private BackNavigationInfo mBackNavigationInfo;
private final SurfaceControl.Transaction mTransaction;
private final IActivityTaskManager mActivityTaskManager;
private final Context mContext;
private final ContentResolver mContentResolver;
private final ShellExecutor mShellExecutor;
private final Handler mBgHandler;
@Nullable
private IOnBackInvokedCallback mBackToLauncherCallback;
private float mTriggerThreshold;
private float mProgressThreshold;
private final Runnable mResetTransitionRunnable = () -> {
finishAnimation();
mTransitionInProgress = false;
ProtoLog.w(WM_SHELL_BACK_PREVIEW, "Transition didn't finish in %d ms. Resetting...",
MAX_TRANSITION_DURATION);
onBackAnimationFinished();
};
private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
@VisibleForTesting
BackAnimationAdapter mBackAnimationAdapter;
private final TouchTracker mTouchTracker = new TouchTracker();
private final SparseArray<BackAnimationRunner> mAnimationDefinition = new SparseArray<>();
@VisibleForTesting
final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
@Override
@@ -134,18 +126,66 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
// this due to the transition may cause focus lost. (alpha = 0)
return;
}
ProtoLog.i(WM_SHELL_BACK_PREVIEW, "Target window lost focus.");
setTriggerBack(false);
onGestureFinished(false);
});
}
};
/**
* Helper class to record the touch location for gesture start and latest.
*/
private static class TouchTracker {
/**
* Location of the latest touch event
*/
private float mLatestTouchX;
private float mLatestTouchY;
private int mSwipeEdge;
private float mProgressThreshold;
/**
* Location of the initial touch event of the back gesture.
*/
private float mInitTouchX;
private float mInitTouchY;
void update(float touchX, float touchY, int swipeEdge) {
mLatestTouchX = touchX;
mLatestTouchY = touchY;
mSwipeEdge = swipeEdge;
}
void setGestureStartLocation(float touchX, float touchY) {
mInitTouchX = touchX;
mInitTouchY = touchY;
}
void setProgressThreshold(float progressThreshold) {
mProgressThreshold = progressThreshold;
}
float getProgress(float touchX) {
int deltaX = Math.round(touchX - mInitTouchX);
float progressThreshold = PROGRESS_THRESHOLD >= 0
? PROGRESS_THRESHOLD : mProgressThreshold;
return Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
}
void reset() {
mInitTouchX = 0;
mInitTouchY = 0;
mSwipeEdge = -1;
}
}
public BackAnimationController(
@NonNull ShellInit shellInit,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler backgroundHandler,
Context context) {
this(shellInit, shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
this(shellInit, shellExecutor, backgroundHandler,
ActivityTaskManager.getService(), context, context.getContentResolver());
}
@@ -154,11 +194,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
@NonNull ShellInit shellInit,
@NonNull @ShellMainThread ShellExecutor shellExecutor,
@NonNull @ShellBackgroundThread Handler bgHandler,
@NonNull SurfaceControl.Transaction transaction,
@NonNull IActivityTaskManager activityTaskManager,
Context context, ContentResolver contentResolver) {
mShellExecutor = shellExecutor;
mTransaction = transaction;
mActivityTaskManager = activityTaskManager;
mContext = context;
mContentResolver = contentResolver;
@@ -168,6 +206,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
createAdapter();
}
private void setupAnimationDeveloperSettingsObserver(
@@ -250,9 +289,10 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
@Override
public void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
public void setBackToLauncherCallback(IOnBackInvokedCallback callback,
IRemoteAnimationRunner runner) {
executeRemoteCallWithTaskPermission(mController, "setBackToLauncherCallback",
(controller) -> controller.setBackToLauncherCallback(callback));
(controller) -> controller.setBackToLauncherCallback(callback, runner));
}
@Override
@@ -261,28 +301,30 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
(controller) -> controller.clearBackToLauncherCallback());
}
@Override
public void onBackToLauncherAnimationFinished() {
executeRemoteCallWithTaskPermission(mController, "onBackToLauncherAnimationFinished",
(controller) -> controller.onBackToLauncherAnimationFinished());
}
void invalidate() {
mController = null;
}
}
@VisibleForTesting
void setBackToLauncherCallback(IOnBackInvokedCallback callback) {
mBackToLauncherCallback = callback;
void setBackToLauncherCallback(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
mAnimationDefinition.set(BackNavigationInfo.TYPE_RETURN_TO_HOME,
new BackAnimationRunner(callback, runner));
}
private void clearBackToLauncherCallback() {
mBackToLauncherCallback = null;
mAnimationDefinition.remove(BackNavigationInfo.TYPE_RETURN_TO_HOME);
}
@VisibleForTesting
void onBackToLauncherAnimationFinished() {
void onBackAnimationFinished() {
if (!mTransitionInProgress) {
return;
}
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: onBackAnimationFinished()");
// Trigger real back.
if (mBackNavigationInfo != null) {
IOnBackInvokedCallback callback = mBackNavigationInfo.getOnBackInvokedCallback();
if (mTriggerBack) {
@@ -303,6 +345,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (mTransitionInProgress) {
return;
}
mTouchTracker.update(touchX, touchY, swipeEdge);
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true;
@@ -333,13 +376,12 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
finishAnimation();
}
mInitTouchLocation.set(touchX, touchY);
mTouchTracker.setGestureStartLocation(touchX, touchY);
mBackGestureStarted = true;
try {
boolean requestAnimation = mEnableAnimations.get();
mBackNavigationInfo =
mActivityTaskManager.startBackNavigation(requestAnimation, mFocusObserver);
mBackNavigationInfo = mActivityTaskManager.startBackNavigation(
mFocusObserver, mEnableAnimations.get() ? mBackAnimationAdapter : null);
onBackNavigationInfoReceived(mBackNavigationInfo);
} catch (RemoteException remoteException) {
Log.e(TAG, "Failed to initAnimation", remoteException);
@@ -353,74 +395,32 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
Log.e(TAG, "Received BackNavigationInfo is null.");
return;
}
int backType = backNavigationInfo.getType();
IOnBackInvokedCallback targetCallback = null;
if (backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
HardwareBuffer hardwareBuffer = backNavigationInfo.getScreenshotHardwareBuffer();
if (hardwareBuffer != null) {
displayTargetScreenshot(hardwareBuffer,
backNavigationInfo.getTaskWindowConfiguration());
}
mTransaction.apply();
} else if (shouldDispatchToLauncher(backType)) {
targetCallback = mBackToLauncherCallback;
} else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
final int backType = backNavigationInfo.getType();
final IOnBackInvokedCallback targetCallback;
final boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
if (shouldDispatchToAnimator) {
targetCallback = mAnimationDefinition.get(backType).getGestureStartedCallback();
} else {
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
}
dispatchOnBackStarted(targetCallback);
}
/**
* Display the screenshot of the activity beneath.
*
* @param hardwareBuffer The buffer containing the screenshot.
*/
private void displayTargetScreenshot(@NonNull HardwareBuffer hardwareBuffer,
WindowConfiguration taskWindowConfiguration) {
SurfaceControl screenshotSurface =
mBackNavigationInfo == null ? null : mBackNavigationInfo.getScreenshotSurface();
if (screenshotSurface == null) {
Log.e(TAG, "BackNavigationInfo doesn't contain a surface for the screenshot. ");
return;
if (shouldDispatchToAnimator) {
dispatchOnBackStarted(targetCallback);
}
// Scale the buffer to fill the whole Task
float sx = 1;
float sy = 1;
float w = taskWindowConfiguration.getBounds().width();
float h = taskWindowConfiguration.getBounds().height();
if (w != hardwareBuffer.getWidth()) {
sx = w / hardwareBuffer.getWidth();
}
if (h != hardwareBuffer.getHeight()) {
sy = h / hardwareBuffer.getHeight();
}
mTransaction.setScale(screenshotSurface, sx, sy);
mTransaction.setBuffer(screenshotSurface, hardwareBuffer);
mTransaction.setVisibility(screenshotSurface, true);
}
private void onMove(float touchX, float touchY, @BackEvent.SwipeEdge int swipeEdge) {
if (!mBackGestureStarted || mBackNavigationInfo == null) {
if (!mBackGestureStarted || mBackNavigationInfo == null || !mEnableAnimations.get()) {
return;
}
int deltaX = Math.round(touchX - mInitTouchLocation.x);
float progressThreshold = PROGRESS_THRESHOLD >= 0 ? PROGRESS_THRESHOLD : mProgressThreshold;
float progress = Math.min(Math.max(Math.abs(deltaX) / progressThreshold, 0), 1);
mTouchTracker.update(touchX, touchY, swipeEdge);
float progress = mTouchTracker.getProgress(touchX);
int backType = mBackNavigationInfo.getType();
RemoteAnimationTarget animationTarget = mBackNavigationInfo.getDepartingAnimationTarget();
BackEvent backEvent = new BackEvent(
touchX, touchY, progress, swipeEdge, animationTarget);
BackEvent backEvent = new BackEvent(touchX, touchY, progress, swipeEdge);
IOnBackInvokedCallback targetCallback = null;
if (shouldDispatchToLauncher(backType)) {
targetCallback = mBackToLauncherCallback;
} else if (backType == BackNavigationInfo.TYPE_CROSS_TASK
|| backType == BackNavigationInfo.TYPE_CROSS_ACTIVITY) {
// TODO(208427216) Run the actual animation
} else if (backType == BackNavigationInfo.TYPE_CALLBACK) {
if (shouldDispatchToAnimator(backType)) {
targetCallback = mAnimationDefinition.get(backType).getCallback();
} else {
targetCallback = mBackNavigationInfo.getOnBackInvokedCallback();
}
dispatchOnBackProgressed(targetCallback, backEvent);
@@ -473,11 +473,16 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
int backType = mBackNavigationInfo.getType();
boolean shouldDispatchToLauncher = shouldDispatchToLauncher(backType);
IOnBackInvokedCallback targetCallback = shouldDispatchToLauncher
? mBackToLauncherCallback
: mBackNavigationInfo.getOnBackInvokedCallback();
if (shouldDispatchToLauncher) {
boolean shouldDispatchToAnimator = shouldDispatchToAnimator(backType);
final BackAnimationRunner runner = mAnimationDefinition.get(backType);
IOnBackInvokedCallback targetCallback = shouldDispatchToAnimator
? runner.getCallback() : mBackNavigationInfo.getOnBackInvokedCallback();
if (shouldDispatchToAnimator) {
if (runner.onGestureFinished(mTriggerBack)) {
Log.w(TAG, "Gesture released, but animation didn't ready.");
return;
}
startTransition();
}
if (mTriggerBack) {
@@ -485,18 +490,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
} else {
dispatchOnBackCancelled(targetCallback);
}
if (backType != BackNavigationInfo.TYPE_RETURN_TO_HOME || !shouldDispatchToLauncher) {
// Launcher callback missing. Simply finish animation.
if (!shouldDispatchToAnimator) {
// Animation callback missing. Simply finish animation.
finishAnimation();
}
}
private boolean shouldDispatchToLauncher(int backType) {
return backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
&& mBackToLauncherCallback != null
&& mEnableAnimations.get()
private boolean shouldDispatchToAnimator(int backType) {
return mEnableAnimations.get()
&& mBackNavigationInfo != null
&& mBackNavigationInfo.getDepartingAnimationTarget() != null;
&& mBackNavigationInfo.isPrepareRemoteAnimation()
&& mAnimationDefinition.contains(backType);
}
private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
@@ -555,35 +559,29 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
}
private void setSwipeThresholds(float triggerThreshold, float progressThreshold) {
mProgressThreshold = progressThreshold;
mTriggerThreshold = triggerThreshold;
mTouchTracker.setProgressThreshold(progressThreshold);
}
private void finishAnimation() {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishAnimation()");
mTouchEventDelta.set(0, 0);
mInitTouchLocation.set(0, 0);
BackNavigationInfo backNavigationInfo = mBackNavigationInfo;
boolean triggerBack = mTriggerBack;
mBackNavigationInfo = null;
mTriggerBack = false;
mShouldStartOnNextMoveEvent = false;
mTouchTracker.reset();
if (backNavigationInfo == null) {
return;
}
RemoteAnimationTarget animationTarget = backNavigationInfo.getDepartingAnimationTarget();
if (animationTarget != null) {
if (animationTarget.leash != null && animationTarget.leash.isValid()) {
mTransaction.remove(animationTarget.leash);
}
}
SurfaceControl screenshotSurface = backNavigationInfo.getScreenshotSurface();
if (screenshotSurface != null && screenshotSurface.isValid()) {
mTransaction.remove(screenshotSurface);
}
mTransaction.apply();
stopTransition();
if (mBackAnimationFinishedCallback != null) {
try {
mBackAnimationFinishedCallback.onAnimationFinished(triggerBack);
} catch (RemoteException e) {
Log.e(TAG, "Failed call IBackAnimationFinishedCallback", e);
}
mBackAnimationFinishedCallback = null;
}
backNavigationInfo.onBackNavigationFinished(triggerBack);
}
@@ -602,4 +600,48 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
mShellExecutor.removeCallbacks(mResetTransitionRunnable);
mTransitionInProgress = false;
}
private void createAdapter() {
IBackAnimationRunner runner = new IBackAnimationRunner.Stub() {
@Override
public void onAnimationStart(int type, RemoteAnimationTarget[] apps,
RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
IBackAnimationFinishedCallback finishedCallback) {
mShellExecutor.execute(() -> {
final BackAnimationRunner runner = mAnimationDefinition.get(type);
if (runner == null) {
Log.e(TAG, "Animation didn't be defined for type "
+ BackNavigationInfo.typeToString(type));
if (finishedCallback != null) {
try {
finishedCallback.onAnimationFinished(false);
} catch (RemoteException e) {
Log.w(TAG, "Failed call IBackNaviAnimationController", e);
}
}
return;
}
mBackAnimationFinishedCallback = finishedCallback;
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: startAnimation()");
runner.startAnimation(apps, wallpapers, nonApps,
BackAnimationController.this::onBackAnimationFinished);
if (!mBackGestureStarted) {
// if the down -> up gesture happened before animation start, we have to
// trigger the uninterruptible transition to finish the back animation.
final BackEvent backFinish = new BackEvent(
mTouchTracker.mLatestTouchX, mTouchTracker.mLatestTouchY, 1,
mTouchTracker.mSwipeEdge);
startTransition();
runner.consumeIfGestureFinished(backFinish);
}
});
}
@Override
public void onAnimationCancelled() { }
};
mBackAnimationAdapter = new BackAnimationAdapter(runner);
}
}

View File

@@ -0,0 +1,108 @@
/*
* Copyright (C) 2022 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.back;
import static android.view.WindowManager.TRANSIT_OLD_UNSET;
import android.os.RemoteException;
import android.util.Log;
import android.view.IRemoteAnimationFinishedCallback;
import android.view.IRemoteAnimationRunner;
import android.view.RemoteAnimationTarget;
import android.window.BackEvent;
import android.window.IBackAnimationRunner;
import android.window.IOnBackInvokedCallback;
/**
* Used to register the animation callback and runner, it will trigger result if gesture was finish
* before it received IBackAnimationRunner#onAnimationStart, so the controller could continue
* trigger the real back behavior.
*/
class BackAnimationRunner {
private static final String TAG = "ShellBackPreview";
private final IOnBackInvokedCallback mCallback;
private final IRemoteAnimationRunner mRunner;
private boolean mTriggerBack;
// Whether we are waiting to receive onAnimationStart
private boolean mWaitingAnimation;
BackAnimationRunner(IOnBackInvokedCallback callback, IRemoteAnimationRunner runner) {
mCallback = callback;
mRunner = runner;
}
/** Returns the registered animation runner */
IRemoteAnimationRunner getRunner() {
return mRunner;
}
/** Returns the registered animation callback */
IOnBackInvokedCallback getCallback() {
return mCallback;
}
/**
* Called from {@link IBackAnimationRunner}, it will deliver these
* {@link RemoteAnimationTarget}s to the corresponding runner.
*/
void startAnimation(RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
RemoteAnimationTarget[] nonApps, Runnable finishedCallback) {
final IRemoteAnimationFinishedCallback callback =
new IRemoteAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished() {
finishedCallback.run();
}
};
mWaitingAnimation = false;
try {
mRunner.onAnimationStart(TRANSIT_OLD_UNSET, apps, wallpapers,
nonApps, callback);
} catch (RemoteException e) {
Log.w(TAG, "Failed call onAnimationStart", e);
}
}
IOnBackInvokedCallback getGestureStartedCallback() {
mWaitingAnimation = true;
return mCallback;
}
boolean onGestureFinished(boolean triggerBack) {
if (mWaitingAnimation) {
mTriggerBack = triggerBack;
return true;
}
return false;
}
void consumeIfGestureFinished(final BackEvent backFinish) {
Log.d(TAG, "Start transition due to gesture is finished");
try {
mCallback.onBackProgressed(backFinish);
if (mTriggerBack) {
mCallback.onBackInvoked();
} else {
mCallback.onBackCancelled();
}
} catch (RemoteException e) {
Log.e(TAG, "dispatch error: ", e);
}
}
}

View File

@@ -17,29 +17,21 @@
package com.android.wm.shell.back;
import android.window.IOnBackInvokedCallback;
import android.view.IRemoteAnimationRunner;
/**
* Interface for Launcher process to register back invocation callbacks.
*/
interface IBackAnimation {
/**
* Sets a {@link IOnBackInvokedCallback} to be invoked when
* Sets a {@link IOnBackInvokedCallback} and a {@link IRemoteAnimationRunner} to be invoked when
* back navigation has type {@link BackNavigationInfo#TYPE_RETURN_TO_HOME}.
*/
void setBackToLauncherCallback(in IOnBackInvokedCallback callback);
void setBackToLauncherCallback(in IOnBackInvokedCallback callback,
in IRemoteAnimationRunner runner);
/**
* Clears the previously registered {@link IOnBackInvokedCallback}.
*/
void clearBackToLauncherCallback();
/**
* Notifies Shell that the back to launcher animation has fully finished
* (including the transition animation that runs after the finger is lifted).
*
* At this point the top window leash (if one was created) should be ready to be released.
* //TODO: Remove once we play the transition animation through shell transitions.
*/
void onBackToLauncherAnimationFinished();
}

View File

@@ -18,12 +18,9 @@ package com.android.wm.shell.back;
import static android.window.BackNavigationInfo.KEY_TRIGGER_BACK;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
@@ -39,7 +36,6 @@ import android.app.WindowConfiguration;
import android.content.pm.ApplicationInfo;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.HardwareBuffer;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteCallback;
@@ -49,11 +45,13 @@ import android.testing.AndroidTestingRunner;
import android.testing.TestableContentResolver;
import android.testing.TestableContext;
import android.testing.TestableLooper;
import android.view.IRemoteAnimationRunner;
import android.view.MotionEvent;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackEvent;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.IOnBackInvokedCallback;
import androidx.test.filters.SmallTest;
@@ -65,7 +63,6 @@ import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.sysui.ShellInit;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,15 +86,18 @@ public class BackAnimationControllerTest extends ShellTestCase {
public TestableContext mContext =
new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
@Mock
private SurfaceControl.Transaction mTransaction;
@Mock
private IActivityTaskManager mActivityTaskManager;
@Mock
private IOnBackInvokedCallback mIOnBackInvokedCallback;
@Mock
private IBackAnimationFinishedCallback mBackAnimationFinishedCallback;
@Mock
private IRemoteAnimationRunner mBackAnimationRunner;
private BackAnimationController mController;
private int mEventTime = 0;
@@ -115,7 +115,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
mTestableLooper = TestableLooper.get(this);
mShellInit = spy(new ShellInit(mShellExecutor));
mController = new BackAnimationController(mShellInit,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
mContentResolver);
mShellInit.init();
@@ -123,19 +123,12 @@ public class BackAnimationControllerTest extends ShellTestCase {
mShellExecutor.flushAll();
}
private void createNavigationInfo(RemoteAnimationTarget topAnimationTarget,
SurfaceControl screenshotSurface,
HardwareBuffer hardwareBuffer,
int backType,
IOnBackInvokedCallback onBackInvokedCallback) {
private void createNavigationInfo(int backType, IOnBackInvokedCallback onBackInvokedCallback) {
BackNavigationInfo.Builder builder = new BackNavigationInfo.Builder()
.setType(backType)
.setDepartingAnimationTarget(topAnimationTarget)
.setScreenshotSurface(screenshotSurface)
.setScreenshotBuffer(hardwareBuffer)
.setTaskWindowConfiguration(new WindowConfiguration())
.setOnBackNavigationDone(new RemoteCallback((bundle) -> {}))
.setOnBackInvokedCallback(onBackInvokedCallback);
.setOnBackInvokedCallback(onBackInvokedCallback)
.setPrepareRemoteAnimation(true);
createNavigationInfo(builder);
}
@@ -143,7 +136,7 @@ public class BackAnimationControllerTest extends ShellTestCase {
private void createNavigationInfo(BackNavigationInfo.Builder builder) {
try {
doReturn(builder.build()).when(mActivityTaskManager)
.startBackNavigation(anyBoolean(), any());
.startBackNavigation(any(), any());
} catch (RemoteException ex) {
ex.rethrowFromSystemServer();
}
@@ -169,43 +162,12 @@ public class BackAnimationControllerTest extends ShellTestCase {
verify(mShellInit, times(1)).addInitCallback(any(), any());
}
@Test
@Ignore("b/207481538")
public void crossActivity_screenshotAttachedAndVisible() {
SurfaceControl screenshotSurface = new SurfaceControl();
HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
createNavigationInfo(createAnimationTarget(), screenshotSurface, hardwareBuffer,
BackNavigationInfo.TYPE_CROSS_ACTIVITY, null);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer);
verify(mTransaction).setVisibility(screenshotSurface, true);
verify(mTransaction).apply();
}
@Test
public void crossActivity_surfaceMovesWithGesture() {
SurfaceControl screenshotSurface = new SurfaceControl();
HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
RemoteAnimationTarget animationTarget = createAnimationTarget();
createNavigationInfo(animationTarget, screenshotSurface, hardwareBuffer,
BackNavigationInfo.TYPE_CROSS_ACTIVITY, null);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
// b/207481538, we check that the surface is not moved for now, we can re-enable this once
// we implement the animation
verify(mTransaction, never()).setScale(eq(screenshotSurface), anyInt(), anyInt());
verify(mTransaction, never()).setPosition(
animationTarget.leash, 100, 100);
verify(mTransaction, atLeastOnce()).apply();
}
@Test
public void verifyAnimationFinishes() {
RemoteAnimationTarget animationTarget = createAnimationTarget();
boolean[] backNavigationDone = new boolean[]{false};
boolean[] triggerBack = new boolean[]{false};
createNavigationInfo(new BackNavigationInfo.Builder()
.setDepartingAnimationTarget(animationTarget)
.setType(BackNavigationInfo.TYPE_CROSS_ACTIVITY)
.setOnBackNavigationDone(
new RemoteCallback(result -> {
@@ -219,19 +181,19 @@ public class BackAnimationControllerTest extends ShellTestCase {
@Test
public void backToHome_dispatchesEvents() throws RemoteException {
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
RemoteAnimationTarget animationTarget = createAnimationTarget();
createNavigationInfo(animationTarget, null, null,
BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, mIOnBackInvokedCallback);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
verify(mIOnBackInvokedCallback).onBackStarted();
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture());
assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
verify(mIOnBackInvokedCallback, atLeastOnce()).onBackProgressed(backEventCaptor.capture());
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
@@ -245,17 +207,16 @@ public class BackAnimationControllerTest extends ShellTestCase {
Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
ShellInit shellInit = new ShellInit(mShellExecutor);
mController = new BackAnimationController(shellInit,
mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
mShellExecutor, new Handler(mTestableLooper.getLooper()),
mActivityTaskManager, mContext,
mContentResolver);
shellInit.init();
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
RemoteAnimationTarget animationTarget = createAnimationTarget();
IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
createNavigationInfo(animationTarget, null, null,
BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback);
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback);
triggerBackGesture();
@@ -266,25 +227,29 @@ public class BackAnimationControllerTest extends ShellTestCase {
verify(mIOnBackInvokedCallback, never()).onBackStarted();
verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
verify(mIOnBackInvokedCallback, never()).onBackInvoked();
verify(mBackAnimationRunner, never()).onAnimationStart(
anyInt(), any(), any(), any(), any());
}
@Test
public void ignoresGesture_transitionInProgress() throws RemoteException {
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
RemoteAnimationTarget animationTarget = createAnimationTarget();
createNavigationInfo(animationTarget, null, null,
BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
triggerBackGesture();
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
// Check that back invocation is dispatched.
verify(mIOnBackInvokedCallback).onBackInvoked();
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
reset(mIOnBackInvokedCallback);
reset(mBackAnimationRunner);
// Verify that we prevent animation from restarting if another gestures happens before
// the previous transition is finished.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
verifyNoMoreInteractions(mIOnBackInvokedCallback);
mController.onBackToLauncherAnimationFinished();
mController.onBackAnimationFinished();
// Verify that more events from a rejected swipe cannot start animation.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
@@ -294,39 +259,47 @@ public class BackAnimationControllerTest extends ShellTestCase {
// Verify that we start accepting gestures again once transition finishes.
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
verify(mIOnBackInvokedCallback).onBackStarted();
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
}
@Test
public void acceptsGesture_transitionTimeout() throws RemoteException {
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
RemoteAnimationTarget animationTarget = createAnimationTarget();
createNavigationInfo(animationTarget, null, null,
BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
triggerBackGesture();
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
reset(mIOnBackInvokedCallback);
// Simulate transition timeout.
mShellExecutor.flushAll();
mController.onBackAnimationFinished();
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
verify(mIOnBackInvokedCallback).onBackStarted();
}
@Test
public void cancelBackInvokeWhenLostFocus() throws RemoteException {
mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
RemoteAnimationTarget animationTarget = createAnimationTarget();
mController.setBackToLauncherCallback(mIOnBackInvokedCallback, mBackAnimationRunner);
createNavigationInfo(animationTarget, null, null,
BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
createNavigationInfo(BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
doMotionEvent(MotionEvent.ACTION_DOWN, 0);
// Check that back start and progress is dispatched when first move.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
simulateRemoteAnimationStart(BackNavigationInfo.TYPE_RETURN_TO_HOME);
verify(mIOnBackInvokedCallback).onBackStarted();
verify(mBackAnimationRunner).onAnimationStart(anyInt(), any(), any(), any(), any());
// Check that back invocation is dispatched.
mController.setTriggerBack(true); // Fake trigger back
@@ -349,4 +322,14 @@ public class BackAnimationControllerTest extends ShellTestCase {
BackEvent.EDGE_LEFT);
mEventTime += 10;
}
private void simulateRemoteAnimationStart(int type) throws RemoteException {
RemoteAnimationTarget animationTarget = createAnimationTarget();
RemoteAnimationTarget[] targets = new RemoteAnimationTarget[]{animationTarget};
if (mController.mBackAnimationAdapter != null) {
mController.mBackAnimationAdapter.getRunner().onAnimationStart(type,
targets, null, null, mBackAnimationFinishedCallback);
mShellExecutor.flushAll();
}
}
}

View File

@@ -227,6 +227,7 @@ import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationAdapter;
import android.view.RemoteAnimationDefinition;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
import android.window.BackNavigationInfo;
import android.window.IWindowOrganizerController;
import android.window.SplashScreenView.SplashScreenViewParcelable;
@@ -1846,14 +1847,15 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
}
@Override
public BackNavigationInfo startBackNavigation(boolean requestAnimation,
IWindowFocusObserver observer) {
public BackNavigationInfo startBackNavigation(
IWindowFocusObserver observer, BackAnimationAdapter adapter) {
mAmInternal.enforceCallingPermission(START_TASKS_FROM_RECENTS,
"startBackNavigation()");
if (mBackNavigationController == null) {
return null;
}
return mBackNavigationController.startBackNavigation(requestAnimation, observer);
return mBackNavigationController.startBackNavigation(observer, adapter);
}
/**

View File

@@ -16,11 +16,14 @@
package com.android.server.wm;
import static android.view.RemoteAnimationTarget.MODE_CLOSING;
import static android.view.RemoteAnimationTarget.MODE_OPENING;
import static android.view.WindowManager.LayoutParams.INVALID_WINDOW_TYPE;
import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BACK_PREVIEW;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.WindowConfiguration;
import android.content.ComponentName;
import android.graphics.Point;
import android.graphics.Rect;
@@ -34,7 +37,9 @@ import android.util.Slog;
import android.view.IWindowFocusObserver;
import android.view.RemoteAnimationTarget;
import android.view.SurfaceControl;
import android.window.BackAnimationAdapter;
import android.window.BackNavigationInfo;
import android.window.IBackAnimationFinishedCallback;
import android.window.OnBackInvokedCallbackInfo;
import android.window.ScreenCapture;
import android.window.TaskSnapshot;
@@ -43,6 +48,8 @@ import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.protolog.common.ProtoLog;
import com.android.server.LocalServices;
import java.util.ArrayList;
/**
* Controller to handle actions related to the back gesture on the server side.
*/
@@ -72,10 +79,9 @@ class BackNavigationController {
*/
@VisibleForTesting
@Nullable
BackNavigationInfo startBackNavigation(boolean requestAnimation,
IWindowFocusObserver observer) {
BackNavigationInfo startBackNavigation(
IWindowFocusObserver observer, BackAnimationAdapter adapter) {
final WindowManagerService wmService = mWindowManagerService;
final SurfaceControl.Transaction tx = wmService.mTransactionFactory.get();
mFocusObserver = observer;
int backType = BackNavigationInfo.TYPE_UNDEFINED;
@@ -95,18 +101,10 @@ class BackNavigationController {
// currentActivity is the last child of currentTask.
ActivityRecord prevActivity;
WindowContainer<?> removedWindowContainer = null;
SurfaceControl animationLeashParent = null;
HardwareBuffer screenshotBuffer = null;
RemoteAnimationTarget topAppTarget = null;
WindowState window;
int prevTaskId;
int prevUserId;
boolean prepareAnimation;
BackNavigationInfo.Builder infoBuilder = new BackNavigationInfo.Builder();
synchronized (wmService.mGlobalLock) {
WindowConfiguration taskWindowConfiguration;
WindowManagerInternal windowManagerInternal =
LocalServices.getService(WindowManagerInternal.class);
IBinder focusedWindowToken = windowManagerInternal.getFocusedWindowToken();
@@ -204,8 +202,8 @@ class BackNavigationController {
infoBuilder.setType(BackNavigationInfo.TYPE_CALLBACK);
final WindowState finalFocusedWindow = window;
infoBuilder.setOnBackNavigationDone(new RemoteCallback(result ->
onBackNavigationDone(result, finalFocusedWindow, finalFocusedWindow,
BackNavigationInfo.TYPE_CALLBACK, null, null, false)));
onBackNavigationDone(result, finalFocusedWindow,
BackNavigationInfo.TYPE_CALLBACK)));
return infoBuilder.setType(backType).build();
}
@@ -245,9 +243,6 @@ class BackNavigationController {
}
infoBuilder.setType(backType);
prevTaskId = prevTask != null ? prevTask.mTaskId : 0;
prevUserId = prevTask != null ? prevTask.mUserId : 0;
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Previous Destination is Activity:%s Task:%s "
+ "removedContainer:%s, backType=%s",
prevActivity != null ? prevActivity.mActivityComponent : null,
@@ -256,162 +251,218 @@ class BackNavigationController {
BackNavigationInfo.typeToString(backType));
// For now, we only animate when going home.
prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
&& requestAnimation
// Only create a new leash if no leash has been created.
// Otherwise return null for animation target to avoid conflict.
&& !removedWindowContainer.hasCommittedReparentToAnimationLeash();
boolean prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
&& adapter != null;
// Only prepare animation if no leash has been created (no animation is running).
// TODO(b/241808055): Cancel animation when preparing back animation.
if (prepareAnimation
&& removedWindowContainer.hasCommittedReparentToAnimationLeash()) {
Slog.w(TAG, "Can't prepare back animation due to another animation is running.");
prepareAnimation = false;
}
if (prepareAnimation) {
taskWindowConfiguration =
currentTask.getTaskInfo().configuration.windowConfiguration;
infoBuilder.setTaskWindowConfiguration(taskWindowConfiguration);
// Prepare a leash to animate the current top window
// TODO(b/220934562): Use surface animator to better manage animation conflicts.
SurfaceControl animLeash = removedWindowContainer.makeAnimationLeash()
.setName("BackPreview Leash for " + removedWindowContainer)
.setHidden(false)
.setBLASTLayer()
.build();
removedWindowContainer.reparentSurfaceControl(tx, animLeash);
animationLeashParent = removedWindowContainer.getAnimationLeashParent();
topAppTarget = createRemoteAnimationTargetLocked(removedWindowContainer,
currentActivity,
currentTask, animLeash);
infoBuilder.setDepartingAnimationTarget(topAppTarget);
}
//TODO(207481538) Remove once the infrastructure to support per-activity screenshot is
// implemented. For now we simply have the mBackScreenshots hash map that dumbly
// saves the screenshots.
if (needsScreenshot(backType) && prevActivity != null
&& prevActivity.mActivityComponent != null) {
screenshotBuffer =
getActivitySnapshot(currentTask, prevActivity.mActivityComponent);
}
// Special handling for back to home animation
if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation
&& prevTask != null) {
currentTask.mBackGestureStarted = true;
// Make launcher show from behind by marking its top activity as visible and
// launch-behind to bump its visibility for the duration of the back gesture.
prevActivity = prevTask.getTopNonFinishingActivity();
if (prevActivity != null) {
if (!prevActivity.mVisibleRequested) {
prevActivity.setVisibility(true);
}
prevActivity.mLaunchTaskBehind = true;
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Setting Activity.mLauncherTaskBehind to true. Activity=%s",
prevActivity);
prevActivity.mRootWindowContainer.ensureActivitiesVisible(
null /* starting */, 0 /* configChanges */,
false /* preserveWindows */);
}
prepareAnimationIfNeeded(currentTask, prevTask, prevActivity,
removedWindowContainer, backType, adapter);
}
infoBuilder.setPrepareRemoteAnimation(prepareAnimation);
} // Release wm Lock
// Find a screenshot of the previous activity if we actually have an animation
if (topAppTarget != null && needsScreenshot(backType) && prevTask != null
&& screenshotBuffer == null) {
SurfaceControl.Builder builder = new SurfaceControl.Builder()
.setName("BackPreview Screenshot for " + prevActivity)
.setParent(animationLeashParent)
.setHidden(false)
.setBLASTLayer();
infoBuilder.setScreenshotSurface(builder.build());
screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId);
infoBuilder.setScreenshotBuffer(screenshotBuffer);
// The Animation leash needs to be above the screenshot surface, but the animation leash
// needs to be added before to be in the synchronized block.
tx.setLayer(topAppTarget.leash, 1);
}
WindowContainer<?> finalRemovedWindowContainer = removedWindowContainer;
if (finalRemovedWindowContainer != null) {
try {
currentActivity.token.linkToDeath(
() -> resetSurfaces(finalRemovedWindowContainer), 0);
} catch (RemoteException e) {
Slog.e(TAG, "Failed to link to death", e);
resetSurfaces(removedWindowContainer);
return null;
}
int finalBackType = backType;
final ActivityRecord finalprevActivity = prevActivity;
final Task finalTask = currentTask;
final int finalBackType = backType;
final WindowState finalFocusedWindow = window;
RemoteCallback onBackNavigationDone = new RemoteCallback(result -> onBackNavigationDone(
result, finalFocusedWindow, finalRemovedWindowContainer, finalBackType,
finalTask, finalprevActivity, prepareAnimation));
result, finalFocusedWindow, finalBackType));
infoBuilder.setOnBackNavigationDone(onBackNavigationDone);
}
tx.apply();
return infoBuilder.build();
}
private void prepareAnimationIfNeeded(Task currentTask,
Task prevTask, ActivityRecord prevActivity, WindowContainer<?> removedWindowContainer,
int backType, BackAnimationAdapter adapter) {
final ArrayList<SurfaceControl> leashes = new ArrayList<>();
final SurfaceControl.Transaction startedTransaction = currentTask.getPendingTransaction();
final SurfaceControl.Transaction finishedTransaction = new SurfaceControl.Transaction();
// Prepare a leash to animate for the departing window
final SurfaceControl animLeash = currentTask.makeAnimationLeash()
.setName("BackPreview Leash for " + currentTask)
.setHidden(false)
.build();
removedWindowContainer.reparentSurfaceControl(startedTransaction, animLeash);
final RemoteAnimationTarget topAppTarget = createRemoteAnimationTargetLocked(
currentTask, animLeash, MODE_CLOSING);
// reset leash after animation finished.
leashes.add(animLeash);
removedWindowContainer.reparentSurfaceControl(finishedTransaction,
removedWindowContainer.getParentSurfaceControl());
// Prepare a leash to animate for the entering window.
RemoteAnimationTarget behindAppTarget = null;
if (needsScreenshot(backType)) {
HardwareBuffer screenshotBuffer = null;
switch(backType) {
case BackNavigationInfo.TYPE_CROSS_TASK:
int prevTaskId = prevTask != null ? prevTask.mTaskId : 0;
int prevUserId = prevTask != null ? prevTask.mUserId : 0;
screenshotBuffer = getTaskSnapshot(prevTaskId, prevUserId);
break;
case BackNavigationInfo.TYPE_CROSS_ACTIVITY:
//TODO(207481538) Remove once the infrastructure to support per-activity
// screenshot is implemented. For now we simply have the mBackScreenshots hash
// map that dumbly saves the screenshots.
if (prevActivity != null
&& prevActivity.mActivityComponent != null) {
screenshotBuffer =
getActivitySnapshot(currentTask, prevActivity.mActivityComponent);
}
break;
}
// Find a screenshot of the previous activity if we actually have an animation
SurfaceControl animationLeashParent = removedWindowContainer.getAnimationLeashParent();
if (screenshotBuffer != null) {
final SurfaceControl screenshotSurface = new SurfaceControl.Builder()
.setName("BackPreview Screenshot for " + prevActivity)
.setHidden(false)
.setParent(animationLeashParent)
.setBLASTLayer()
.build();
startedTransaction.setBuffer(screenshotSurface, screenshotBuffer);
// The Animation leash needs to be above the screenshot surface, but the animation
// leash needs to be added before to be in the synchronized block.
startedTransaction.setLayer(topAppTarget.leash, 1);
behindAppTarget = createRemoteAnimationTargetLocked(
prevTask, screenshotSurface, MODE_OPENING);
// reset leash after animation finished.
leashes.add(screenshotSurface);
}
} else if (prevTask != null) {
// Special handling for preventing next transition.
currentTask.mBackGestureStarted = true;
prevActivity = prevTask.getTopNonFinishingActivity();
if (prevActivity != null) {
// Make previous task show from behind by marking its top activity as visible
// and launch-behind to bump its visibility for the duration of the back gesture.
setLaunchBehind(prevActivity);
final SurfaceControl leash = prevActivity.makeAnimationLeash()
.setName("BackPreview Leash for " + prevActivity)
.setHidden(false)
.build();
prevActivity.reparentSurfaceControl(startedTransaction, leash);
behindAppTarget = createRemoteAnimationTargetLocked(
prevTask, leash, MODE_OPENING);
// reset leash after animation finished.
leashes.add(leash);
prevActivity.reparentSurfaceControl(finishedTransaction,
prevActivity.getParentSurfaceControl());
}
}
final RemoteAnimationTarget[] targets = (behindAppTarget == null)
? new RemoteAnimationTarget[] {topAppTarget}
: new RemoteAnimationTarget[] {topAppTarget, behindAppTarget};
final ActivityRecord finalPrevActivity = prevActivity;
final IBackAnimationFinishedCallback callback =
new IBackAnimationFinishedCallback.Stub() {
@Override
public void onAnimationFinished(boolean triggerBack) {
for (SurfaceControl sc: leashes) {
finishedTransaction.remove(sc);
}
synchronized (mWindowManagerService.mGlobalLock) {
if (triggerBack) {
final SurfaceControl surfaceControl =
removedWindowContainer.getSurfaceControl();
if (surfaceControl != null && surfaceControl.isValid()) {
// When going back to home, hide the task surface before it is
// re-parented to avoid flicker.
finishedTransaction.hide(surfaceControl);
}
} else if (!needsScreenshot(backType)) {
restoreLaunchBehind(finalPrevActivity);
}
}
finishedTransaction.apply();
}
};
startAnimation(backType, targets, adapter, callback);
}
@NonNull
private static RemoteAnimationTarget createRemoteAnimationTargetLocked(
WindowContainer<?> removedWindowContainer,
ActivityRecord activityRecord, Task task, SurfaceControl animLeash) {
Task task, SurfaceControl animLeash, int mode) {
ActivityRecord topApp = task.getTopRealVisibleActivity();
if (topApp == null) {
topApp = task.getTopNonFinishingActivity();
}
final WindowState mainWindow = topApp != null
? topApp.findMainWindow()
: null;
int windowType = INVALID_WINDOW_TYPE;
if (mainWindow != null) {
windowType = mainWindow.getWindowType();
}
Rect bounds = new Rect(task.getBounds());
Rect localBounds = new Rect(bounds);
Point tmpPos = new Point();
task.getRelativePosition(tmpPos);
localBounds.offsetTo(tmpPos.x, tmpPos.y);
return new RemoteAnimationTarget(
task.mTaskId,
RemoteAnimationTarget.MODE_CLOSING,
mode,
animLeash,
false /* isTransluscent */,
new Rect() /* clipRect */,
new Rect() /* contentInsets */,
activityRecord.getPrefixOrderIndex(),
new Point(0, 0) /* position */,
new Rect() /* localBounds */,
new Rect() /* screenSpaceBounds */,
removedWindowContainer.getWindowConfiguration(),
task.getPrefixOrderIndex(),
tmpPos /* position */,
localBounds /* localBounds */,
bounds /* screenSpaceBounds */,
task.getWindowConfiguration(),
true /* isNotInRecent */,
null,
null,
task.getTaskInfo(),
false,
activityRecord.windowType);
windowType);
}
private void onBackNavigationDone(
Bundle result, WindowState focusedWindow, WindowContainer<?> windowContainer,
int backType, @Nullable Task task, @Nullable ActivityRecord prevActivity,
boolean prepareAnimation) {
SurfaceControl surfaceControl = windowContainer.getSurfaceControl();
@VisibleForTesting
void startAnimation(@BackNavigationInfo.BackTargetType int type,
RemoteAnimationTarget[] targets, BackAnimationAdapter backAnimationAdapter,
IBackAnimationFinishedCallback callback) {
mWindowManagerService.mAnimator.addAfterPrepareSurfacesRunnable(() -> {
try {
backAnimationAdapter.getRunner().onAnimationStart(type,
targets, null, null, callback);
} catch (RemoteException e) {
e.printStackTrace();
}
});
}
private void onBackNavigationDone(Bundle result, WindowState focusedWindow, int backType) {
boolean triggerBack = result != null && result.getBoolean(
BackNavigationInfo.KEY_TRIGGER_BACK);
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "onBackNavigationDone backType=%s, "
+ "task=%s, prevActivity=%s", backType, task, prevActivity);
if (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME && prepareAnimation) {
if (triggerBack) {
if (surfaceControl != null && surfaceControl.isValid()) {
// When going back to home, hide the task surface before it is re-parented to
// avoid flicker.
SurfaceControl.Transaction t = windowContainer.getSyncTransaction();
t.hide(surfaceControl);
t.apply();
}
}
if (prevActivity != null && !triggerBack) {
// Restore the launch-behind state.
task.mTaskSupervisor.scheduleLaunchTaskBehindComplete(prevActivity.token);
prevActivity.mLaunchTaskBehind = false;
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Setting Activity.mLauncherTaskBehind to false. Activity=%s",
prevActivity);
}
} else if (task != null) {
task.mBackGestureStarted = false;
}
resetSurfaces(windowContainer);
+ "triggerBack=%b", backType, triggerBack);
if (mFocusObserver != null) {
focusedWindow.unregisterFocusObserver(mFocusObserver);
@@ -450,20 +501,47 @@ class BackNavigationController {
return true;
}
private void resetSurfaces(@NonNull WindowContainer<?> windowContainer) {
synchronized (windowContainer.mWmService.mGlobalLock) {
ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "Back: Reset surfaces");
SurfaceControl.Transaction tx = windowContainer.getSyncTransaction();
SurfaceControl surfaceControl = windowContainer.getSurfaceControl();
if (surfaceControl != null) {
tx.reparent(surfaceControl,
windowContainer.getParent().getSurfaceControl());
tx.apply();
}
}
}
void setWindowManager(WindowManagerService wm) {
mWindowManagerService = wm;
}
private void setLaunchBehind(ActivityRecord activity) {
if (activity == null) {
return;
}
if (!activity.mVisibleRequested) {
activity.setVisibility(true);
}
activity.mLaunchTaskBehind = true;
// Handle fixed rotation launching app.
final DisplayContent dc = activity.mDisplayContent;
dc.rotateInDifferentOrientationIfNeeded(activity);
if (activity.hasFixedRotationTransform()) {
// Set the record so we can recognize it to continue to update display orientation
// if the previous activity becomes the top later.
dc.setFixedRotationLaunchingApp(activity,
activity.getWindowConfiguration().getRotation());
}
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Setting Activity.mLauncherTaskBehind to true. Activity=%s", activity);
activity.getDisplayContent().ensureActivitiesVisible(null /* starting */,
0 /* configChanges */, false /* preserveWindows */, true);
}
private void restoreLaunchBehind(ActivityRecord activity) {
if (activity == null) {
return;
}
activity.mDisplayContent.continueUpdateOrientationForDiffOrienLaunchingApp();
// Restore the launch-behind state.
activity.mTaskSupervisor.scheduleLaunchTaskBehindComplete(activity.token);
activity.mLaunchTaskBehind = false;
ProtoLog.d(WM_DEBUG_BACK_PREVIEW,
"Setting Activity.mLauncherTaskBehind to false. Activity=%s",
activity);
}
}

View File

@@ -32,6 +32,7 @@ import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.annotation.NonNull;
@@ -40,6 +41,7 @@ import android.hardware.HardwareBuffer;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.view.WindowManager;
import android.window.BackAnimationAdapter;
import android.window.BackEvent;
import android.window.BackNavigationInfo;
import android.window.IOnBackInvokedCallback;
@@ -54,6 +56,7 @@ import com.android.server.LocalServices;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -61,17 +64,18 @@ import java.util.concurrent.TimeUnit;
@Presubmit
@RunWith(WindowTestRunner.class)
public class BackNavigationControllerTests extends WindowTestsBase {
private BackNavigationController mBackNavigationController;
private WindowManagerInternal mWindowManagerInternal;
private BackAnimationAdapter mBackAnimationAdapter;
@Before
public void setUp() throws Exception {
mBackNavigationController = new BackNavigationController();
mBackNavigationController = Mockito.spy(new BackNavigationController());
LocalServices.removeServiceForTest(WindowManagerInternal.class);
mWindowManagerInternal = mock(WindowManagerInternal.class);
LocalServices.addService(WindowManagerInternal.class, mWindowManagerInternal);
mBackNavigationController.setWindowManager(mWm);
mBackAnimationAdapter = mock(BackAnimationAdapter.class);
}
@Test
@@ -79,14 +83,16 @@ public class BackNavigationControllerTests extends WindowTestsBase {
Task task = createTopTaskWithActivity();
IOnBackInvokedCallback callback = withSystemCallback(task);
BackNavigationInfo backNavigationInfo =
mBackNavigationController.startBackNavigation(true, null);
BackNavigationInfo backNavigationInfo = startBackNavigation();
assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
assertThat(backNavigationInfo.getDepartingAnimationTarget()).isNotNull();
assertThat(backNavigationInfo.getTaskWindowConfiguration()).isNotNull();
assertThat(backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
assertThat(typeToString(backNavigationInfo.getType()))
.isEqualTo(typeToString(BackNavigationInfo.TYPE_RETURN_TO_HOME));
// verify if back animation would start.
verify(mBackNavigationController).startAnimation(
eq(BackNavigationInfo.TYPE_RETURN_TO_HOME), any(), eq(mBackAnimationAdapter),
any());
}
@Test
@@ -114,10 +120,6 @@ public class BackNavigationControllerTests extends WindowTestsBase {
.isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_ACTIVITY));
assertWithMessage("Activity callback").that(
backNavigationInfo.getOnBackInvokedCallback()).isEqualTo(callback);
// Until b/207481538 is implemented, this should be null
assertThat(backNavigationInfo.getScreenshotSurface()).isNull();
assertThat(backNavigationInfo.getScreenshotHardwareBuffer()).isNull();
}
@Test
@@ -233,7 +235,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
@Nullable
private BackNavigationInfo startBackNavigation() {
return mBackNavigationController.startBackNavigation(true, null);
return mBackNavigationController.startBackNavigation(null, mBackAnimationAdapter);
}
@NonNull
@@ -287,6 +289,7 @@ public class BackNavigationControllerTests extends WindowTestsBase {
PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK;
WindowState window = createWindow(null, FIRST_APPLICATION_WINDOW, record, "window");
when(record.mSurfaceControl.isValid()).thenReturn(true);
Mockito.doNothing().when(task).reparentSurfaceControl(any(), any());
mAtm.setFocusedTask(task.mTaskId, record);
addToWindowMap(window, true);
return task;