From 3f12009a16b160a5600efdca8cafb0e2abcea365 Mon Sep 17 00:00:00 2001 From: Nick Chameyev Date: Thu, 3 Mar 2022 18:21:32 +0000 Subject: [PATCH] Migrate unfold animation to Shell transitions [Part 1] Adds a trigger to DisplayContent that creates unfold transition before applying the display size and marks it as ready when the sizes are applied. Adds a placeholder animation for fullscreen tasks. Bug: 204925795 Test: enable shell transitions, open an app, unfold => check that app surface is animated Change-Id: Ia88178a7c7849a99c17b4d18117a05b2a0fb8b7f --- .../android/window/TransitionRequestInfo.java | 22 ++- .../com/android/wm/shell/ShellInitImpl.java | 5 + .../wm/shell/dagger/WMShellBaseModule.java | 18 +++ .../shell/unfold/UnfoldTransitionHandler.java | 130 ++++++++++++++++++ .../com/android/server/wm/DisplayContent.java | 11 ++ ...ysicalDisplaySwitchTransitionLauncher.java | 118 ++++++++++++++++ .../server/wm/SystemServicesTestRule.java | 5 + 7 files changed, 306 insertions(+), 3 deletions(-) create mode 100644 libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java create mode 100644 services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java index e0cdb133c4ce..48211a8234ee 100644 --- a/core/java/android/window/TransitionRequestInfo.java +++ b/core/java/android/window/TransitionRequestInfo.java @@ -67,6 +67,7 @@ public final class TransitionRequestInfo implements Parcelable { @Nullable private Rect mEndAbsBounds = null; private int mStartRotation = WindowConfiguration.ROTATION_UNDEFINED; private int mEndRotation = WindowConfiguration.ROTATION_UNDEFINED; + private boolean mPhysicalDisplayChanged = false; /** Create empty display-change. */ public DisplayChange(int displayId) { @@ -120,6 +121,11 @@ public final class TransitionRequestInfo implements Parcelable { return mEndRotation; } + @DataClass.Generated.Member + public boolean isPhysicalDisplayChanged() { + return mPhysicalDisplayChanged; + } + @DataClass.Generated.Member public @android.annotation.NonNull DisplayChange setStartAbsBounds(@android.annotation.NonNull Rect value) { mStartAbsBounds = value; @@ -144,6 +150,12 @@ public final class TransitionRequestInfo implements Parcelable { return this; } + @DataClass.Generated.Member + public @android.annotation.NonNull DisplayChange setPhysicalDisplayChanged( boolean value) { + mPhysicalDisplayChanged = value; + return this; + } + @Override @DataClass.Generated.Member public String toString() { @@ -155,7 +167,8 @@ public final class TransitionRequestInfo implements Parcelable { "startAbsBounds = " + mStartAbsBounds + ", " + "endAbsBounds = " + mEndAbsBounds + ", " + "startRotation = " + mStartRotation + ", " + - "endRotation = " + mEndRotation + + "endRotation = " + mEndRotation + ", " + + "physicalDisplayChanged = " + mPhysicalDisplayChanged + " }"; } @@ -166,6 +179,7 @@ public final class TransitionRequestInfo implements Parcelable { // void parcelFieldName(Parcel dest, int flags) { ... } byte flg = 0; + if (mPhysicalDisplayChanged) flg |= 0x20; if (mStartAbsBounds != null) flg |= 0x2; if (mEndAbsBounds != null) flg |= 0x4; dest.writeByte(flg); @@ -188,6 +202,7 @@ public final class TransitionRequestInfo implements Parcelable { // static FieldType unparcelFieldName(Parcel in) { ... } byte flg = in.readByte(); + boolean physicalDisplayChanged = (flg & 0x20) != 0; int displayId = in.readInt(); Rect startAbsBounds = (flg & 0x2) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR); Rect endAbsBounds = (flg & 0x4) == 0 ? null : (Rect) in.readTypedObject(Rect.CREATOR); @@ -199,6 +214,7 @@ public final class TransitionRequestInfo implements Parcelable { this.mEndAbsBounds = endAbsBounds; this.mStartRotation = startRotation; this.mEndRotation = endRotation; + this.mPhysicalDisplayChanged = physicalDisplayChanged; // onConstructed(); // You can define this method to get a callback } @@ -218,10 +234,10 @@ public final class TransitionRequestInfo implements Parcelable { }; @DataClass.Generated( - time = 1639445520915L, + time = 1648141181315L, codegenVersion = "1.0.23", sourceFile = "frameworks/base/core/java/android/window/TransitionRequestInfo.java", - inputSignatures = "private final int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate int mStartRotation\nprivate int mEndRotation\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)") + inputSignatures = "private final int mDisplayId\nprivate @android.annotation.Nullable android.graphics.Rect mStartAbsBounds\nprivate @android.annotation.Nullable android.graphics.Rect mEndAbsBounds\nprivate int mStartRotation\nprivate int mEndRotation\nprivate boolean mPhysicalDisplayChanged\nclass DisplayChange extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genSetters=true, genBuilder=false, genConstructor=false)") @Deprecated private void __metadata() {} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java index b729fe1e55dc..62fb840d29d1 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/ShellInitImpl.java @@ -35,6 +35,7 @@ import com.android.wm.shell.recents.RecentTasksController; import com.android.wm.shell.splitscreen.SplitScreenController; import com.android.wm.shell.startingsurface.StartingWindowController; import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.unfold.UnfoldTransitionHandler; import java.util.Optional; @@ -56,6 +57,7 @@ public class ShellInitImpl { private final Optional mPipTouchHandlerOptional; private final FullscreenTaskListener mFullscreenTaskListener; private final Optional mFullscreenUnfoldController; + private final Optional mUnfoldTransitionHandler; private final Optional mFreeformTaskListenerOptional; private final ShellExecutor mMainExecutor; private final Transitions mTransitions; @@ -77,6 +79,7 @@ public class ShellInitImpl { Optional pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Optional fullscreenUnfoldTransitionController, + Optional unfoldTransitionHandler, Optional freeformTaskListenerOptional, Optional recentTasks, Transitions transitions, @@ -94,6 +97,7 @@ public class ShellInitImpl { mFullscreenTaskListener = fullscreenTaskListener; mPipTouchHandlerOptional = pipTouchHandlerOptional; mFullscreenUnfoldController = fullscreenUnfoldTransitionController; + mUnfoldTransitionHandler = unfoldTransitionHandler; mFreeformTaskListenerOptional = freeformTaskListenerOptional; mRecentTasks = recentTasks; mTransitions = transitions; @@ -126,6 +130,7 @@ public class ShellInitImpl { if (Transitions.ENABLE_SHELL_TRANSITIONS) { mTransitions.register(mShellTaskOrganizer); + mUnfoldTransitionHandler.ifPresent(UnfoldTransitionHandler::init); } // TODO(b/181599115): This should really be the pip controller, but until we can provide the diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java index 026eeb09d741..4ad08688bd51 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java @@ -92,6 +92,7 @@ import com.android.wm.shell.tasksurfacehelper.TaskSurfaceHelperController; import com.android.wm.shell.transition.ShellTransitions; import com.android.wm.shell.transition.Transitions; import com.android.wm.shell.unfold.ShellUnfoldProgressProvider; +import com.android.wm.shell.unfold.UnfoldTransitionHandler; import java.util.Optional; @@ -327,6 +328,21 @@ public abstract class WMShellBaseModule { return Optional.empty(); } + @WMSingleton + @Provides + static Optional provideUnfoldTransitionHandler( + Optional progressProvider, + TransactionPool transactionPool, + Transitions transitions, + @ShellMainThread ShellExecutor executor) { + if (progressProvider.isPresent()) { + return Optional.of( + new UnfoldTransitionHandler(progressProvider.get(), transactionPool, executor, + transitions)); + } + return Optional.empty(); + } + // // Freeform (optional feature) // @@ -660,6 +676,7 @@ public abstract class WMShellBaseModule { Optional pipTouchHandlerOptional, FullscreenTaskListener fullscreenTaskListener, Optional appUnfoldTransitionController, + Optional unfoldTransitionHandler, Optional freeformTaskListener, Optional recentTasksOptional, Transitions transitions, @@ -677,6 +694,7 @@ public abstract class WMShellBaseModule { pipTouchHandlerOptional, fullscreenTaskListener, appUnfoldTransitionController, + unfoldTransitionHandler, freeformTaskListener, recentTasksOptional, transitions, diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java new file mode 100644 index 000000000000..639603941c18 --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java @@ -0,0 +1,130 @@ +/* + * 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.unfold; + +import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME; +import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; +import static android.view.WindowManager.TRANSIT_CHANGE; + +import android.os.IBinder; +import android.view.SurfaceControl; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.transition.Transitions; +import com.android.wm.shell.transition.Transitions.TransitionFinishCallback; +import com.android.wm.shell.transition.Transitions.TransitionHandler; +import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.Executor; + +public class UnfoldTransitionHandler implements TransitionHandler, UnfoldListener { + + private final ShellUnfoldProgressProvider mUnfoldProgressProvider; + private final Transitions mTransitions; + private final Executor mExecutor; + private final TransactionPool mTransactionPool; + + @Nullable + private TransitionFinishCallback mFinishCallback; + @Nullable + private IBinder mTransition; + + private final List mAnimatedFullscreenTasks = new ArrayList<>(); + + public UnfoldTransitionHandler(ShellUnfoldProgressProvider unfoldProgressProvider, + TransactionPool transactionPool, Executor executor, Transitions transitions) { + mUnfoldProgressProvider = unfoldProgressProvider; + mTransactionPool = transactionPool; + mExecutor = executor; + mTransitions = transitions; + } + + public void init() { + mTransitions.addHandler(this); + mUnfoldProgressProvider.addListener(mExecutor, this); + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction startTransaction, + @NonNull SurfaceControl.Transaction finishTransaction, + @NonNull TransitionFinishCallback finishCallback) { + + if (transition != mTransition) return false; + + startTransaction.apply(); + + mAnimatedFullscreenTasks.clear(); + info.getChanges().forEach(change -> { + final boolean allowedToAnimate = change.getTaskInfo() != null + && change.getTaskInfo().getWindowingMode() == WINDOWING_MODE_FULLSCREEN + && change.getTaskInfo().getActivityType() != ACTIVITY_TYPE_HOME + && change.getMode() == TRANSIT_CHANGE; + + if (allowedToAnimate) { + mAnimatedFullscreenTasks.add(change); + } + }); + + mFinishCallback = finishCallback; + mTransition = null; + return true; + } + + @Override + public void onStateChangeProgress(float progress) { + mAnimatedFullscreenTasks.forEach(change -> { + final SurfaceControl.Transaction transaction = mTransactionPool.acquire(); + + // TODO: this is a placeholder animation, replace with a spec version in the next CLs + final float testScale = 0.8f + 0.2f * progress; + transaction.setScale(change.getLeash(), testScale, testScale); + + transaction.apply(); + mTransactionPool.release(transaction); + }); + } + + @Override + public void onStateChangeFinished() { + if (mFinishCallback != null) { + mFinishCallback.onTransitionFinished(null, null); + mFinishCallback = null; + mAnimatedFullscreenTasks.clear(); + } + } + + @Nullable + @Override + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @NonNull TransitionRequestInfo request) { + if (request.getType() == TRANSIT_CHANGE && request.getDisplayChange() != null + && request.getDisplayChange().isPhysicalDisplayChanged()) { + mTransition = transition; + return new WindowContainerTransaction(); + } + return null; + } +} diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index a982078a25b6..66d02d67784b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -551,6 +551,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final FixedRotationTransitionListener mFixedRotationTransitionListener = new FixedRotationTransitionListener(); + private PhysicalDisplaySwitchTransitionLauncher mDisplaySwitchTransitionLauncher; + /** Windows added since {@link #mCurrentFocus} was set to null. Used for ANR blaming. */ final ArrayList mWinAddedSinceNullFocus = new ArrayList<>(); @@ -1047,6 +1049,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mAppTransitionController = new AppTransitionController(mWmService, this); mTransitionController.registerLegacyListener(mFixedRotationTransitionListener); mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this); + mDisplaySwitchTransitionLauncher = new PhysicalDisplaySwitchTransitionLauncher(this, + mTransitionController); final InputChannel inputChannel = mWmService.mInputManager.monitorInput( "PointerEventDispatcher" + mDisplayId, mDisplayId); @@ -2755,6 +2759,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // metrics are updated as rotation settings might depend on them mWmService.mDisplayWindowSettings.applySettingsToDisplayLocked(this, /* includeRotationSettings */ false); + mDisplaySwitchTransitionLauncher.requestDisplaySwitchTransitionIfNeeded(mDisplayId, + mInitialDisplayWidth, mInitialDisplayHeight, newWidth, newHeight); } // If there is an override set for base values - use it, otherwise use new values. @@ -2783,6 +2789,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mInitialRoundedCorners = newRoundedCorners; mCurrentUniqueDisplayId = newUniqueId; reconfigureDisplayLocked(); + + if (physicalDisplayChanged) { + mDisplaySwitchTransitionLauncher.onDisplayUpdated(); + } } } @@ -3109,6 +3119,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mTransitionController.unregisterLegacyListener(mFixedRotationTransitionListener); handleAnimatingStoppedAndTransition(); mWmService.stopFreezingDisplayLocked(); + mDisplaySwitchTransitionLauncher.destroy(); super.removeImmediately(); if (DEBUG_DISPLAY) Slog.v(TAG_WM, "Removing display=" + this); mPointerEventDispatcher.dispose(); diff --git a/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java new file mode 100644 index 000000000000..9c1d5601dad5 --- /dev/null +++ b/services/core/java/com/android/server/wm/PhysicalDisplaySwitchTransitionLauncher.java @@ -0,0 +1,118 @@ +/* + * 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.server.wm; + +import static android.view.WindowManager.TRANSIT_CHANGE; + +import static com.android.internal.R.bool.config_unfoldTransitionEnabled; +import static com.android.server.wm.ActivityTaskManagerService.POWER_MODE_REASON_CHANGE_DISPLAY; + +import android.animation.ValueAnimator; +import android.content.Context; +import android.graphics.Rect; +import android.hardware.devicestate.DeviceStateManager; +import android.os.HandlerExecutor; +import android.window.TransitionRequestInfo; + +public class PhysicalDisplaySwitchTransitionLauncher { + + private final DisplayContent mDisplayContent; + private final DeviceStateManager mDeviceStateManager; + private final Context mContext; + private final TransitionController mTransitionController; + + private DeviceStateListener mDeviceStateListener; + + /** + * If on a foldable device represents whether the device is folded or not + */ + private boolean mIsFolded; + private Transition mTransition; + + public PhysicalDisplaySwitchTransitionLauncher(DisplayContent displayContent, + TransitionController transitionController) { + mDisplayContent = displayContent; + mContext = mDisplayContent.mWmService.mContext; + mTransitionController = transitionController; + + mDeviceStateManager = mContext.getSystemService(DeviceStateManager.class); + + if (mDeviceStateManager != null) { + mDeviceStateListener = new DeviceStateListener(mContext); + mDeviceStateManager + .registerCallback(new HandlerExecutor(mDisplayContent.mWmService.mH), + mDeviceStateListener); + } + } + + public void destroy() { + if (mDeviceStateManager != null) { + mDeviceStateManager.unregisterCallback(mDeviceStateListener); + } + } + + /** + * Requests to start a transition for the physical display switch + */ + public void requestDisplaySwitchTransitionIfNeeded(int displayId, int oldDisplayWidth, + int oldDisplayHeight, int newDisplayWidth, int newDisplayHeight) { + if (!mTransitionController.isShellTransitionsEnabled()) return; + if (!mDisplayContent.getLastHasContent()) return; + + boolean shouldRequestUnfoldTransition = !mIsFolded + && mContext.getResources().getBoolean(config_unfoldTransitionEnabled) + && ValueAnimator.areAnimatorsEnabled(); + + if (!shouldRequestUnfoldTransition) { + return; + } + + final TransitionRequestInfo.DisplayChange displayChange = + new TransitionRequestInfo.DisplayChange(displayId); + + final Rect startAbsBounds = new Rect(0, 0, oldDisplayWidth, oldDisplayHeight); + displayChange.setStartAbsBounds(startAbsBounds); + final Rect endAbsBounds = new Rect(0, 0, newDisplayWidth, newDisplayHeight); + displayChange.setEndAbsBounds(endAbsBounds); + displayChange.setPhysicalDisplayChanged(true); + + final Transition t = mTransitionController.requestTransitionIfNeeded(TRANSIT_CHANGE, + 0 /* flags */, + mDisplayContent, mDisplayContent, null /* remoteTransition */, + displayChange); + + if (t != null) { + mDisplayContent.mAtmService.startLaunchPowerMode(POWER_MODE_REASON_CHANGE_DISPLAY); + mTransitionController.collectForDisplayChange(mDisplayContent, t); + mTransition = t; + } + } + + public void onDisplayUpdated() { + if (mTransition != null) { + mTransition.setAllReady(); + mTransition = null; + } + } + + class DeviceStateListener extends DeviceStateManager.FoldStateListener { + + DeviceStateListener(Context context) { + super(context, newIsFolded -> mIsFolded = newIsFolded); + } + } +} diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java index ce861595535c..dc9a62554fdb 100644 --- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java +++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java @@ -52,6 +52,7 @@ import android.content.IntentFilter; import android.content.pm.IPackageManager; import android.content.pm.PackageManagerInternal; import android.database.ContentObserver; +import android.hardware.devicestate.DeviceStateManager; import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerInternal; import android.net.Uri; @@ -229,6 +230,10 @@ public class SystemServicesTestRule implements TestRule { final AppOpsManager aom = mock(AppOpsManager.class); doReturn(aom).when(mContext).getSystemService(eq(Context.APP_OPS_SERVICE)); + // DeviceStateManager + final DeviceStateManager dsm = mock(DeviceStateManager.class); + doReturn(dsm).when(mContext).getSystemService(eq(Context.DEVICE_STATE_SERVICE)); + // Prevent "WakeLock finalized while still held: SCREEN_FROZEN". final PowerManager pm = mock(PowerManager.class); doReturn(pm).when(mContext).getSystemService(eq(Context.POWER_SERVICE));