Merge "Migrate unfold animation to Shell transitions [Part 1]" into tm-dev

This commit is contained in:
Nick Chameyev 2022-04-01 09:13:09 +00:00 committed by Android (Google) Code Review
commit a830367d97
7 changed files with 306 additions and 3 deletions

View File

@ -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() {}

View File

@ -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<PipTouchHandler> mPipTouchHandlerOptional;
private final FullscreenTaskListener mFullscreenTaskListener;
private final Optional<FullscreenUnfoldController> mFullscreenUnfoldController;
private final Optional<UnfoldTransitionHandler> mUnfoldTransitionHandler;
private final Optional<FreeformTaskListener> mFreeformTaskListenerOptional;
private final ShellExecutor mMainExecutor;
private final Transitions mTransitions;
@ -77,6 +79,7 @@ public class ShellInitImpl {
Optional<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Optional<FullscreenUnfoldController> fullscreenUnfoldTransitionController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformTaskListener> freeformTaskListenerOptional,
Optional<RecentTasksController> 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

View File

@ -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<UnfoldTransitionHandler> provideUnfoldTransitionHandler(
Optional<ShellUnfoldProgressProvider> 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<PipTouchHandler> pipTouchHandlerOptional,
FullscreenTaskListener fullscreenTaskListener,
Optional<FullscreenUnfoldController> appUnfoldTransitionController,
Optional<UnfoldTransitionHandler> unfoldTransitionHandler,
Optional<FreeformTaskListener> freeformTaskListener,
Optional<RecentTasksController> recentTasksOptional,
Transitions transitions,
@ -677,6 +694,7 @@ public abstract class WMShellBaseModule {
pipTouchHandlerOptional,
fullscreenTaskListener,
appUnfoldTransitionController,
unfoldTransitionHandler,
freeformTaskListener,
recentTasksOptional,
transitions,

View File

@ -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<TransitionInfo.Change> 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;
}
}

View File

@ -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<WindowState> mWinAddedSinceNullFocus = new ArrayList<>();
@ -1043,6 +1045,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);
@ -2751,6 +2755,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.
@ -2779,6 +2785,10 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
mInitialRoundedCorners = newRoundedCorners;
mCurrentUniqueDisplayId = newUniqueId;
reconfigureDisplayLocked();
if (physicalDisplayChanged) {
mDisplaySwitchTransitionLauncher.onDisplayUpdated();
}
}
}
@ -3105,6 +3115,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();

View File

@ -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);
}
}
}

View File

@ -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));