diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java new file mode 100644 index 0000000000000..71fd917275eae --- /dev/null +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.wm.shell.transition; + +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.IBinder; +import android.os.RemoteException; +import android.util.Log; +import android.view.SurfaceControl; +import android.window.IRemoteTransition; +import android.window.IRemoteTransitionFinishedCallback; +import android.window.TransitionInfo; +import android.window.TransitionRequestInfo; +import android.window.WindowContainerTransaction; + +import com.android.internal.protolog.common.ProtoLog; +import com.android.wm.shell.common.ShellExecutor; +import com.android.wm.shell.protolog.ShellProtoLogGroup; + +/** + * Handler that forwards to a RemoteTransition. It is designed for one-shot use to attach a + * specific remote animation to a specific transition. + */ +public class OneShotRemoteHandler implements Transitions.TransitionHandler { + private final ShellExecutor mMainExecutor; + + /** The specific transition that this handler is associated with. Just for validation. */ + private IBinder mTransition = null; + + /** The remote to delegate animation to */ + private final IRemoteTransition mRemote; + + public OneShotRemoteHandler(@NonNull ShellExecutor mainExecutor, + @NonNull IRemoteTransition remote) { + mMainExecutor = mainExecutor; + mRemote = remote; + } + + public void setTransition(@NonNull IBinder transition) { + mTransition = transition; + } + + @Override + public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info, + @NonNull SurfaceControl.Transaction t, + @NonNull Transitions.TransitionFinishCallback finishCallback) { + if (mTransition != transition) return false; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "Using registered One-shot remote" + + " transition %s for %s.", mRemote, transition); + + final IBinder.DeathRecipient remoteDied = () -> { + Log.e(Transitions.TAG, "Remote transition died, finishing"); + mMainExecutor.execute( + () -> finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */)); + }; + IRemoteTransitionFinishedCallback cb = new IRemoteTransitionFinishedCallback.Stub() { + @Override + public void onTransitionFinished(WindowContainerTransaction wct) { + if (mRemote.asBinder() != null) { + mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); + } + mMainExecutor.execute( + () -> finishCallback.onTransitionFinished(wct, null /* wctCB */)); + } + }; + try { + if (mRemote.asBinder() != null) { + mRemote.asBinder().linkToDeath(remoteDied, 0 /* flags */); + } + mRemote.startAnimation(info, t, cb); + } catch (RemoteException e) { + if (mRemote.asBinder() != null) { + mRemote.asBinder().unlinkToDeath(remoteDied, 0 /* flags */); + } + Log.e(Transitions.TAG, "Error running remote transition.", e); + finishCallback.onTransitionFinished(null /* wct */, null /* wctCB */); + } + return true; + } + + @Override + @Nullable + public WindowContainerTransaction handleRequest(@NonNull IBinder transition, + @Nullable TransitionRequestInfo request) { + IRemoteTransition remote = request.getRemoteTransition(); + if (remote != mRemote) return null; + mTransition = transition; + ProtoLog.v(ShellProtoLogGroup.WM_SHELL_TRANSITIONS, "RemoteTransition directly requested" + + " for %s: %s", transition, remote); + return new WindowContainerTransaction(); + } +} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java index 926108c41e5e1..c1733de755351 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java @@ -22,6 +22,7 @@ import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN; import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW; import static android.view.WindowManager.TRANSIT_CHANGE; import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_FIRST_CUSTOM; import static android.view.WindowManager.TRANSIT_OPEN; import static android.view.WindowManager.TRANSIT_TO_BACK; import static android.view.WindowManager.TRANSIT_TO_FRONT; @@ -30,6 +31,8 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; @@ -295,6 +298,44 @@ public class ShellTransitionTests { verify(mOrganizer, times(1)).finishTransition(eq(transitToken), any(), any()); } + @Test + public void testOneShotRemoteHandler() { + Transitions transitions = new Transitions(mOrganizer, mTransactionPool, mContext, + mMainExecutor, mAnimExecutor); + transitions.replaceDefaultHandlerForTest(mDefaultHandler); + + final boolean[] remoteCalled = new boolean[]{false}; + final WindowContainerTransaction remoteFinishWCT = new WindowContainerTransaction(); + IRemoteTransition testRemote = new IRemoteTransition.Stub() { + @Override + public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishCallback) throws RemoteException { + remoteCalled[0] = true; + finishCallback.onTransitionFinished(remoteFinishWCT); + } + }; + + final int transitType = TRANSIT_FIRST_CUSTOM + 1; + + OneShotRemoteHandler oneShot = new OneShotRemoteHandler(mMainExecutor, testRemote); + // Verify that it responds to the remote but not other things. + IBinder transitToken = new Binder(); + assertNotNull(oneShot.handleRequest(transitToken, + new TransitionRequestInfo(transitType, null, testRemote))); + assertNull(oneShot.handleRequest(transitToken, + new TransitionRequestInfo(transitType, null, null))); + + Transitions.TransitionFinishCallback testFinish = + mock(Transitions.TransitionFinishCallback.class); + // Verify that it responds to animation properly + oneShot.setTransition(transitToken); + IBinder anotherToken = new Binder(); + assertFalse(oneShot.startAnimation(anotherToken, new TransitionInfo(transitType, 0), + mock(SurfaceControl.Transaction.class), testFinish)); + assertTrue(oneShot.startAnimation(transitToken, new TransitionInfo(transitType, 0), + mock(SurfaceControl.Transaction.class), testFinish)); + } + class TransitionInfoBuilder { final TransitionInfo mInfo; diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java index 0f386b192c9c3..4f3f86aeca97b 100644 --- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java @@ -50,6 +50,8 @@ import com.android.systemui.shared.recents.model.ThumbnailData; */ @DataClass public class RemoteTransitionCompat implements Parcelable { + private static final String TAG = "RemoteTransitionCompat"; + @NonNull final IRemoteTransition mTransition; @Nullable TransitionFilter mFilter = null; @@ -57,6 +59,23 @@ public class RemoteTransitionCompat implements Parcelable { mTransition = transition; } + public RemoteTransitionCompat(RemoteTransitionRunner runner) { + mTransition = new IRemoteTransition.Stub() { + @Override + public void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + IRemoteTransitionFinishedCallback finishedCallback) { + final Runnable finishAdapter = () -> { + try { + finishedCallback.onTransitionFinished(null /* wct */); + } catch (RemoteException e) { + Log.e(TAG, "Failed to call transition finished callback", e); + } + }; + runner.startAnimation(info, t, finishAdapter); + } + }; + } + /** Constructor specifically for recents animation */ public RemoteTransitionCompat(RecentsAnimationListener recents, RecentsAnimationControllerCompat controller) { diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java new file mode 100644 index 0000000000000..6002bca81c6c5 --- /dev/null +++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java @@ -0,0 +1,30 @@ +/* + * Copyright (C) 2021 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.shared.system; + +import android.view.SurfaceControl; +import android.window.TransitionInfo; + +/** Interface for something that runs a remote transition animation. */ +public interface RemoteTransitionRunner { + /** + * Starts a transition animation. Once complete, the implementation should call + * `finishCallback`. + */ + void startAnimation(TransitionInfo info, SurfaceControl.Transaction t, + Runnable finishCallback); +}