Merge changes from topic "refactor_back"

* changes:
  Migrate back animation to shell transition
  Add show wallpaper feature for back navigation
  Refactor back navigation animtion (1/2)
This commit is contained in:
Arthur Hung
2022-10-03 08:15:51 +00:00
committed by Android (Google) Code Review
20 changed files with 1057 additions and 565 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;
@@ -353,9 +354,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,57 @@ 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;
@Nullable
private WindowContainerToken mDepartingWindowContainerToken;
/**
* 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 departingWindowContainerToken The {@link WindowContainerToken} of departing 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,
@Nullable WindowContainerToken departingWindowContainerToken) {
mType = type;
mDepartingAnimationTarget = departingAnimationTarget;
mScreenshotSurface = screenshotSurface;
mScreenshotBuffer = screenshotBuffer;
mTaskWindowConfiguration = taskWindowConfiguration;
mOnBackNavigationDone = onBackNavigationDone;
mOnBackInvokedCallback = onBackInvokedCallback;
mPrepareRemoteAnimation = isPrepareRemoteAnimation;
mDepartingWindowContainerToken = departingWindowContainerToken;
}
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();
mDepartingWindowContainerToken = in.readTypedObject(WindowContainerToken.CREATOR);
}
@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);
dest.writeTypedObject(mDepartingWindowContainerToken, flags);
}
/**
@@ -164,49 +142,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 +156,25 @@ public final class BackNavigationInfo implements Parcelable {
return mOnBackInvokedCallback;
}
/**
* Return true if the core is preparing a back gesture nimation.
*/
public boolean isPrepareRemoteAnimation() {
return mPrepareRemoteAnimation;
}
/**
* Returns the {@link WindowContainerToken} of the highest container in the hierarchy being
* removed.
* <p>
* For example, if an Activity is the last one of its Task, the Task's token will be given.
* Otherwise, it will be the Activity's token.
*/
@Nullable
public WindowContainerToken getDepartingWindowContainerToken() {
return mDepartingWindowContainerToken;
}
/**
* 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,12 +210,9 @@ 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
+ ", mWindowContainerToken=" + mDepartingWindowContainerToken
+ '}';
}
@@ -291,20 +242,14 @@ 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;
@Nullable
private WindowContainerToken mDepartingWindowContainerToken = null;
/**
* @see BackNavigationInfo#getType()
@@ -314,40 +259,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 +276,29 @@ 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;
}
/**
* @see BackNavigationInfo#getDepartingWindowContainerToken()
*/
public void setDepartingWCT(@NonNull WindowContainerToken windowContainerToken) {
mDepartingWindowContainerToken = windowContainerToken;
}
/**
* 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,
mDepartingWindowContainerToken);
}
}
}

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;
@@ -58,6 +58,7 @@ import com.android.wm.shell.common.ShellExecutor;
import com.android.wm.shell.common.annotations.ShellBackgroundThread;
import com.android.wm.shell.common.annotations.ShellMainThread;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import java.util.concurrent.atomic.AtomicBoolean;
@@ -75,6 +76,9 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
SETTING_VALUE_ON) != SETTING_VALUE_OFF;
private static final int PROGRESS_THRESHOLD = SystemProperties
.getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
// TODO (b/241808055) Find a appropriate time to remove during refactor
private static final boolean ENABLE_SHELL_TRANSITIONS = Transitions.ENABLE_SHELL_TRANSITIONS;
/**
* Max duration to wait for a transition to finish before accepting another gesture start
* request.
@@ -83,16 +87,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 +99,27 @@ 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<>();
private final Transitions mTransitions;
private BackTransitionHandler mBackTransitionHandler;
@VisibleForTesting
final IWindowFocusObserver mFocusObserver = new IWindowFocusObserver.Stub() {
@Override
@@ -134,19 +132,69 @@ 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(),
ActivityTaskManager.getService(), context, context.getContentResolver());
Context context,
Transitions transitions) {
this(shellInit, shellExecutor, backgroundHandler,
ActivityTaskManager.getService(), context, context.getContentResolver(),
transitions);
}
@VisibleForTesting
@@ -154,20 +202,25 @@ 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) {
Context context, ContentResolver contentResolver,
Transitions transitions) {
mShellExecutor = shellExecutor;
mTransaction = transaction;
mActivityTaskManager = activityTaskManager;
mContext = context;
mContentResolver = contentResolver;
mBgHandler = bgHandler;
shellInit.addInitCallback(this::onInit, this);
mTransitions = transitions;
}
private void onInit() {
setupAnimationDeveloperSettingsObserver(mContentResolver, mBgHandler);
createAdapter();
if (ENABLE_SHELL_TRANSITIONS) {
mBackTransitionHandler = new BackTransitionHandler(this);
mTransitions.addHandler(mBackTransitionHandler);
}
}
private void setupAnimationDeveloperSettingsObserver(
@@ -250,9 +303,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 +315,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) {
@@ -291,7 +347,18 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
dispatchOnBackCancelled(callback);
}
}
finishAnimation();
// In legacy transition, it would use `Task.mBackGestureStarted` in core to handle the
// following transition when back callback is invoked.
// If the back callback is not invoked, we should reset the token and finish the whole back
// navigation without waiting the transition.
if (!ENABLE_SHELL_TRANSITIONS) {
finishBackNavigation();
} else if (!mTriggerBack) {
// reset the token to prevent it consume next transition.
mBackTransitionHandler.setDepartingWindowContainerToken(null);
finishBackNavigation();
}
}
/**
@@ -303,6 +370,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (mTransitionInProgress) {
return;
}
mTouchTracker.update(touchX, touchY, swipeEdge);
if (keyAction == MotionEvent.ACTION_DOWN) {
if (!mBackGestureStarted) {
mShouldStartOnNextMoveEvent = true;
@@ -330,20 +398,19 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "initAnimation mMotionStarted=%b", mBackGestureStarted);
if (mBackGestureStarted || mBackNavigationInfo != null) {
Log.e(TAG, "Animation is being initialized but is already started.");
finishAnimation();
finishBackNavigation();
}
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);
finishAnimation();
finishBackNavigation();
}
}
@@ -353,74 +420,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);
@@ -448,7 +473,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
private void onGestureFinished(boolean fromTouch) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "onGestureFinished() mTriggerBack == %s", mTriggerBack);
if (!mBackGestureStarted) {
finishAnimation();
finishBackNavigation();
return;
}
@@ -468,16 +493,21 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
if (mTriggerBack) {
injectBackKey();
}
finishAnimation();
finishBackNavigation();
return;
}
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 +515,17 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
} else {
dispatchOnBackCancelled(targetCallback);
}
if (backType != BackNavigationInfo.TYPE_RETURN_TO_HOME || !shouldDispatchToLauncher) {
// Launcher callback missing. Simply finish animation.
finishAnimation();
if (!shouldDispatchToAnimator) {
// Animation callback missing. Simply finish animation.
finishBackNavigation();
}
}
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 +584,30 @@ 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);
@VisibleForTesting
void finishBackNavigation() {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishBackNavigation()");
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);
}
@@ -592,14 +616,72 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
return;
}
mTransitionInProgress = true;
if (ENABLE_SHELL_TRANSITIONS) {
mBackTransitionHandler.setDepartingWindowContainerToken(
mBackNavigationInfo.getDepartingWindowContainerToken());
}
mShellExecutor.executeDelayed(mResetTransitionRunnable, MAX_TRANSITION_DURATION);
}
private void stopTransition() {
if (!mTransitionInProgress) {
return;
}
void stopTransition() {
mShellExecutor.removeCallbacks(mResetTransitionRunnable);
mTransitionInProgress = false;
}
/**
* This should be called from {@link BackTransitionHandler#startAnimation} when the following
* transition is triggered by the real back callback in {@link #onBackAnimationFinished}.
* Will consume the default transition and finish current back navigation.
*/
void finishTransition(Transitions.TransitionFinishCallback finishCallback) {
ProtoLog.d(WM_SHELL_BACK_PREVIEW, "BackAnimationController: finishTransition()");
mShellExecutor.execute(() -> {
finishBackNavigation();
finishCallback.onTransitionFinished(null, null);
});
}
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

@@ -0,0 +1,78 @@
/*
* 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 android.os.IBinder;
import android.view.SurfaceControl;
import android.window.TransitionInfo;
import android.window.TransitionRequestInfo;
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.android.wm.shell.transition.Transitions;
class BackTransitionHandler implements Transitions.TransitionHandler {
private BackAnimationController mBackAnimationController;
private WindowContainerToken mDepartingWindowContainerToken;
BackTransitionHandler(@NonNull BackAnimationController backAnimationController) {
mBackAnimationController = backAnimationController;
}
@Override
public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction startTransaction,
@NonNull SurfaceControl.Transaction finishTransaction,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
if (mDepartingWindowContainerToken != null) {
final TransitionInfo.Change change = info.getChange(mDepartingWindowContainerToken);
if (change == null) {
return false;
}
startTransaction.hide(change.getLeash());
startTransaction.apply();
mDepartingWindowContainerToken = null;
mBackAnimationController.finishTransition(finishCallback);
return true;
}
return false;
}
@Nullable
@Override
public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
@NonNull TransitionRequestInfo request) {
return null;
}
@Override
public void mergeAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
@NonNull SurfaceControl.Transaction t, @NonNull IBinder mergeTarget,
@NonNull Transitions.TransitionFinishCallback finishCallback) {
}
void setDepartingWindowContainerToken(
@Nullable WindowContainerToken departingWindowContainerToken) {
mDepartingWindowContainerToken = departingWindowContainerToken;
}
}

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

@@ -279,12 +279,13 @@ public abstract class WMShellBaseModule {
Context context,
ShellInit shellInit,
@ShellMainThread ShellExecutor shellExecutor,
@ShellBackgroundThread Handler backgroundHandler
@ShellBackgroundThread Handler backgroundHandler,
Transitions transitions
) {
if (BackAnimationController.IS_ENABLED) {
return Optional.of(
new BackAnimationController(shellInit, shellExecutor, backgroundHandler,
context));
context, transitions));
}
return Optional.empty();
}

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;
@@ -63,9 +61,9 @@ import com.android.internal.util.test.FakeSettingsProvider;
import com.android.wm.shell.ShellTestCase;
import com.android.wm.shell.TestShellExecutor;
import com.android.wm.shell.sysui.ShellInit;
import com.android.wm.shell.transition.Transitions;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -89,15 +87,21 @@ 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;
@Mock
private Transitions mTransitions;
private BackAnimationController mController;
private int mEventTime = 0;
@@ -115,27 +119,20 @@ 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);
mContentResolver, mTransitions);
mShellInit.init();
mEventTime = 0;
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 +140,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 +166,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 +185,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 +211,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);
mContentResolver, mTransitions);
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 +231,31 @@ 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();
// Pretend the transition handler called finishAnimation.
mController.finishBackNavigation();
// Verify that more events from a rejected swipe cannot start animation.
doMotionEvent(MotionEvent.ACTION_MOVE, 100);
@@ -294,39 +265,49 @@ 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();
// Pretend the transition handler called finishAnimation.
mController.finishBackNavigation();
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 +330,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;
@@ -459,7 +460,7 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub {
private final ClientLifecycleManager mLifecycleManager;
@Nullable
private final BackNavigationController mBackNavigationController;
final BackNavigationController mBackNavigationController;
private TaskChangeNotificationController mTaskChangeNotificationController;
/** The controller for all operations related to locktask. */
@@ -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,15 +37,20 @@ 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;
import android.window.WindowContainerToken;
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.
*/
@@ -50,7 +58,13 @@ class BackNavigationController {
private static final String TAG = "BackNavigationController";
private WindowManagerService mWindowManagerService;
private IWindowFocusObserver mFocusObserver;
private boolean mBackAnimationInProgress;
private boolean mShowWallpaper;
private Runnable mPendingAnimation;
// TODO (b/241808055) Find a appropriate time to remove during refactor
// Execute back animation with legacy transition system. Temporary flag for easier debugging.
static final boolean ENABLE_SHELL_TRANSITIONS = WindowManagerService.sEnableShellTransitions;
/**
* Returns true if the back predictability feature is enabled
*/
@@ -72,10 +86,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 +108,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,12 +209,13 @@ 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();
}
mBackAnimationInProgress = true;
// We don't have an application callback, let's find the destination of the back gesture
Task finalTask = currentTask;
prevActivity = currentTask.getActivity(
@@ -230,6 +236,7 @@ class BackNavigationController {
// Our Task should bring back to home
removedWindowContainer = currentTask;
backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
mShowWallpaper = true;
} else if (currentActivity.isRootOfTask()) {
// TODO(208789724): Create single source of truth for this, maybe in
// RootWindowContainer
@@ -242,12 +249,10 @@ class BackNavigationController {
} else {
backType = BackNavigationInfo.TYPE_CROSS_TASK;
}
mShowWallpaper = true;
}
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,167 +261,275 @@ 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()
|| removedWindowContainer.mTransitionController.inTransition())) {
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 */);
}
infoBuilder.setDepartingWCT(toWindowContainerToken(currentTask));
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 static WindowContainerToken toWindowContainerToken(WindowContainer<?> windowContainer) {
if (windowContainer == null || windowContainer.mRemoteToken == null) {
return null;
}
return windowContainer.mRemoteToken.toWindowContainerToken();
}
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) {
if (!ENABLE_SHELL_TRANSITIONS) {
// 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());
}
}
if (mShowWallpaper) {
currentTask.getDisplayContent().mWallpaperController.adjustWallpaperWindows();
// TODO(b/241808055): If the current animation need to show wallpaper and animate the
// wallpaper, start the wallpaper animation to collect wallpaper target and deliver it
// to the back animation controller.
}
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 (ENABLE_SHELL_TRANSITIONS) {
if (!triggerBack) {
if (!needsScreenshot(backType)) {
restoreLaunchBehind(finalPrevActivity);
}
}
} else {
if (triggerBack) {
final SurfaceControl surfaceControl =
removedWindowContainer.getSurfaceControl();
if (surfaceControl != null && surfaceControl.isValid()) {
// When going back to home, hide the task surface before it
// re-parented to avoid flicker.
finishedTransaction.hide(surfaceControl);
}
} else {
currentTask.mBackGestureStarted = false;
if (!needsScreenshot(backType)) {
restoreLaunchBehind(finalPrevActivity);
}
}
}
}
finishedTransaction.apply();
}
};
scheduleAnimationLocked(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 scheduleAnimationLocked(@BackNavigationInfo.BackTargetType int type,
RemoteAnimationTarget[] targets, BackAnimationAdapter backAnimationAdapter,
IBackAnimationFinishedCallback callback) {
mPendingAnimation = () -> {
try {
backAnimationAdapter.getRunner().onAnimationStart(type,
targets, null, null, callback);
} catch (RemoteException e) {
e.printStackTrace();
}
};
mWindowManagerService.mWindowPlacerLocked.requestTraversal();
}
void checkAnimationReady(WallpaperController wallpaperController) {
if (!mBackAnimationInProgress) {
return;
}
final boolean wallpaperReady = !mShowWallpaper
|| (wallpaperController.getWallpaperTarget() != null
&& wallpaperController.wallpaperTransitionReady());
if (wallpaperReady && mPendingAnimation != null) {
startAnimation();
}
}
void startAnimation() {
if (mPendingAnimation != null) {
mPendingAnimation.run();
mPendingAnimation = null;
}
}
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);
mFocusObserver = null;
}
mBackAnimationInProgress = false;
mShowWallpaper = false;
}
private HardwareBuffer getActivitySnapshot(@NonNull Task task,
@@ -450,20 +563,54 @@ 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);
}
boolean isWallpaperVisible(WindowState w) {
if (mBackAnimationInProgress && w.isFocused()) {
return mShowWallpaper;
}
return false;
}
}

View File

@@ -837,6 +837,11 @@ class RootWindowContainer extends WindowContainer<DisplayContent>
if (recentsAnimationController != null) {
recentsAnimationController.checkAnimationReady(defaultDisplay.mWallpaperController);
}
final BackNavigationController backNavigationController =
mWmService.mAtmService.mBackNavigationController;
if (backNavigationController != null) {
backNavigationController.checkAnimationReady(defaultDisplay.mWallpaperController);
}
for (int displayNdx = 0; displayNdx < mChildren.size(); ++displayNdx) {
final DisplayContent displayContent = mChildren.get(displayNdx);

View File

@@ -197,7 +197,7 @@ class WallpaperController {
&& animatingContainer.getAnimation() != null
&& animatingContainer.getAnimation().getShowWallpaper();
final boolean hasWallpaper = w.hasWallpaper() || animationWallpaper;
if (isRecentsTransitionTarget(w)) {
if (isRecentsTransitionTarget(w) || isBackNavigationTarget(w)) {
if (DEBUG_WALLPAPER) Slog.v(TAG, "Found recents animation wallpaper target: " + w);
mFindResults.setWallpaperTarget(w);
return true;
@@ -237,6 +237,12 @@ class WallpaperController {
return controller != null && controller.isWallpaperVisible(w);
}
private boolean isBackNavigationTarget(WindowState w) {
// The window is in animating by back navigation and set to show wallpaper.
final BackNavigationController controller = mService.mAtmService.mBackNavigationController;
return controller != null && controller.isWallpaperVisible(w);
}
/**
* @see #computeLastWallpaperZoomOut()
*/
@@ -822,6 +828,12 @@ class WallpaperController {
if (mService.getRecentsAnimationController() != null) {
mService.getRecentsAnimationController().startAnimation();
}
// If there was a pending back navigation animation that would show wallpaper, start
// the animation due to it was skipped in previous surface placement.
if (mService.mAtmService.mBackNavigationController != null) {
mService.mAtmService.mBackNavigationController.startAnimation();
}
return true;
}
return false;

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).scheduleAnimationLocked(
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;