Loading services/core/java/com/android/server/wm/DisplayContent.java +1 −1 Original line number Diff line number Diff line Loading @@ -1162,7 +1162,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this); mDisplaySwitchTransitionLauncher = new PhysicalDisplaySwitchTransitionLauncher(this, mTransitionController); mRemoteDisplayChangeController = new RemoteDisplayChangeController(mWmService, mDisplayId); mRemoteDisplayChangeController = new RemoteDisplayChangeController(this); final InputChannel inputChannel = mWmService.mInputManager.monitorInput( "PointerEventDispatcher" + mDisplayId, mDisplayId); Loading services/core/java/com/android/server/wm/RemoteDisplayChangeController.java +27 −8 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.view.IDisplayChangeWindowCallback; import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import java.util.ArrayList; Loading @@ -44,16 +45,16 @@ public class RemoteDisplayChangeController { private static final int REMOTE_DISPLAY_CHANGE_TIMEOUT_MS = 800; private final WindowManagerService mService; private final int mDisplayId; private final DisplayContent mDisplayContent; private final Runnable mTimeoutRunnable = this::onContinueTimedOut; // all remote changes that haven't finished yet. private final List<ContinueRemoteDisplayChangeCallback> mCallbacks = new ArrayList<>(); public RemoteDisplayChangeController(WindowManagerService service, int displayId) { mService = service; mDisplayId = displayId; RemoteDisplayChangeController(@NonNull DisplayContent displayContent) { mService = displayContent.mWmService; mDisplayContent = displayContent; } /** Loading Loading @@ -99,8 +100,8 @@ public class RemoteDisplayChangeController { try { mService.mH.removeCallbacks(mTimeoutRunnable); mService.mH.postDelayed(mTimeoutRunnable, REMOTE_DISPLAY_CHANGE_TIMEOUT_MS); mService.mDisplayChangeController.onDisplayChange(mDisplayId, fromRotation, toRotation, newDisplayAreaInfo, remoteCallback); mService.mDisplayChangeController.onDisplayChange(mDisplayContent.mDisplayId, fromRotation, toRotation, newDisplayAreaInfo, remoteCallback); return true; } catch (RemoteException e) { Slog.e(TAG, "Exception while dispatching remote display-change", e); Loading @@ -117,10 +118,23 @@ public class RemoteDisplayChangeController { mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */); } mCallbacks.clear(); onCompleted(); } } private void continueDisplayChange(@NonNull ContinueRemoteDisplayChangeCallback callback, /** Called when all remote callbacks are done. */ private void onCompleted() { // Because DisplayContent#sendNewConfiguration() will be skipped if there are pending remote // changes, check again when all remote callbacks are done. E.g. callback X is done but // there is a pending callback Y so its invocation is skipped, and when the callback Y is // done, it doesn't call sendNewConfiguration(). if (mDisplayContent.mWaitingForConfig) { mDisplayContent.sendNewConfiguration(); } } @VisibleForTesting void continueDisplayChange(@NonNull ContinueRemoteDisplayChangeCallback callback, @Nullable WindowContainerTransaction transaction) { synchronized (mService.mGlobalLock) { int idx = mCallbacks.indexOf(callback); Loading @@ -133,11 +147,16 @@ public class RemoteDisplayChangeController { // ordering by continuing everything up until this one with empty transactions. mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */); } // The "toIndex" is exclusive, so it needs +1 to clear the current calling callback. mCallbacks.subList(0, idx + 1).clear(); if (mCallbacks.isEmpty()) { final boolean completed = mCallbacks.isEmpty(); if (completed) { mService.mH.removeCallbacks(mTimeoutRunnable); } callback.onContinueRemoteDisplayChange(transaction); if (completed) { onCompleted(); } } } Loading services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +48 −0 Original line number Diff line number Diff line Loading @@ -144,6 +144,7 @@ import android.window.DisplayAreaInfo; import android.window.IDisplayAreaOrganizer; import android.window.ScreenCapture; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; Loading Loading @@ -2012,6 +2013,53 @@ public class DisplayContentTests extends WindowTestsBase { assertFalse(mWm.mDisplayFrozen); } @Test public void testRemoteDisplayChange() { mWm.mDisplayChangeController = mock(IDisplayChangeWindowController.class); final Boolean[] isWaitingForRemote = new Boolean[2]; final var callbacks = new RemoteDisplayChangeController.ContinueRemoteDisplayChangeCallback[ isWaitingForRemote.length]; for (int i = 0; i < isWaitingForRemote.length; i++) { final int index = i; var callback = new RemoteDisplayChangeController.ContinueRemoteDisplayChangeCallback() { @Override public void onContinueRemoteDisplayChange(WindowContainerTransaction transaction) { isWaitingForRemote[index] = mDisplayContent.mRemoteDisplayChangeController .isWaitingForRemoteDisplayChange(); } }; mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange( ROTATION_0, ROTATION_0, null /* newDisplayAreaInfo */, callback); callbacks[i] = callback; } // The last callback is completed, all callbacks should be notified. mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[1], null /* transaction */); // When notifying 0, the callback 1 still exists. assertTrue(isWaitingForRemote[0]); assertFalse(isWaitingForRemote[1]); // The first callback is completed, other callbacks after it should remain. for (int i = 0; i < isWaitingForRemote.length; i++) { isWaitingForRemote[i] = null; mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange( ROTATION_0, ROTATION_0, null /* newDisplayAreaInfo */, callbacks[i]); } mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[0], null /* transaction */); assertTrue(isWaitingForRemote[0]); assertNull(isWaitingForRemote[1]); // Complete the last callback. It should be able to consume pending config change. mDisplayContent.mWaitingForConfig = true; mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[1], null /* transaction */); assertFalse(isWaitingForRemote[1]); assertFalse(mDisplayContent.mWaitingForConfig); } @Test public void testShellTransitRotation() { DisplayContent dc = createNewDisplay(); Loading Loading
services/core/java/com/android/server/wm/DisplayContent.java +1 −1 Original line number Diff line number Diff line Loading @@ -1162,7 +1162,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mUnknownAppVisibilityController = new UnknownAppVisibilityController(mWmService, this); mDisplaySwitchTransitionLauncher = new PhysicalDisplaySwitchTransitionLauncher(this, mTransitionController); mRemoteDisplayChangeController = new RemoteDisplayChangeController(mWmService, mDisplayId); mRemoteDisplayChangeController = new RemoteDisplayChangeController(this); final InputChannel inputChannel = mWmService.mInputManager.monitorInput( "PointerEventDispatcher" + mDisplayId, mDisplayId); Loading
services/core/java/com/android/server/wm/RemoteDisplayChangeController.java +27 −8 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.view.IDisplayChangeWindowCallback; import android.window.DisplayAreaInfo; import android.window.WindowContainerTransaction; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.protolog.common.ProtoLog; import java.util.ArrayList; Loading @@ -44,16 +45,16 @@ public class RemoteDisplayChangeController { private static final int REMOTE_DISPLAY_CHANGE_TIMEOUT_MS = 800; private final WindowManagerService mService; private final int mDisplayId; private final DisplayContent mDisplayContent; private final Runnable mTimeoutRunnable = this::onContinueTimedOut; // all remote changes that haven't finished yet. private final List<ContinueRemoteDisplayChangeCallback> mCallbacks = new ArrayList<>(); public RemoteDisplayChangeController(WindowManagerService service, int displayId) { mService = service; mDisplayId = displayId; RemoteDisplayChangeController(@NonNull DisplayContent displayContent) { mService = displayContent.mWmService; mDisplayContent = displayContent; } /** Loading Loading @@ -99,8 +100,8 @@ public class RemoteDisplayChangeController { try { mService.mH.removeCallbacks(mTimeoutRunnable); mService.mH.postDelayed(mTimeoutRunnable, REMOTE_DISPLAY_CHANGE_TIMEOUT_MS); mService.mDisplayChangeController.onDisplayChange(mDisplayId, fromRotation, toRotation, newDisplayAreaInfo, remoteCallback); mService.mDisplayChangeController.onDisplayChange(mDisplayContent.mDisplayId, fromRotation, toRotation, newDisplayAreaInfo, remoteCallback); return true; } catch (RemoteException e) { Slog.e(TAG, "Exception while dispatching remote display-change", e); Loading @@ -117,10 +118,23 @@ public class RemoteDisplayChangeController { mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */); } mCallbacks.clear(); onCompleted(); } } private void continueDisplayChange(@NonNull ContinueRemoteDisplayChangeCallback callback, /** Called when all remote callbacks are done. */ private void onCompleted() { // Because DisplayContent#sendNewConfiguration() will be skipped if there are pending remote // changes, check again when all remote callbacks are done. E.g. callback X is done but // there is a pending callback Y so its invocation is skipped, and when the callback Y is // done, it doesn't call sendNewConfiguration(). if (mDisplayContent.mWaitingForConfig) { mDisplayContent.sendNewConfiguration(); } } @VisibleForTesting void continueDisplayChange(@NonNull ContinueRemoteDisplayChangeCallback callback, @Nullable WindowContainerTransaction transaction) { synchronized (mService.mGlobalLock) { int idx = mCallbacks.indexOf(callback); Loading @@ -133,11 +147,16 @@ public class RemoteDisplayChangeController { // ordering by continuing everything up until this one with empty transactions. mCallbacks.get(i).onContinueRemoteDisplayChange(null /* transaction */); } // The "toIndex" is exclusive, so it needs +1 to clear the current calling callback. mCallbacks.subList(0, idx + 1).clear(); if (mCallbacks.isEmpty()) { final boolean completed = mCallbacks.isEmpty(); if (completed) { mService.mH.removeCallbacks(mTimeoutRunnable); } callback.onContinueRemoteDisplayChange(transaction); if (completed) { onCompleted(); } } } Loading
services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +48 −0 Original line number Diff line number Diff line Loading @@ -144,6 +144,7 @@ import android.window.DisplayAreaInfo; import android.window.IDisplayAreaOrganizer; import android.window.ScreenCapture; import android.window.WindowContainerToken; import android.window.WindowContainerTransaction; import androidx.test.filters.SmallTest; Loading Loading @@ -2012,6 +2013,53 @@ public class DisplayContentTests extends WindowTestsBase { assertFalse(mWm.mDisplayFrozen); } @Test public void testRemoteDisplayChange() { mWm.mDisplayChangeController = mock(IDisplayChangeWindowController.class); final Boolean[] isWaitingForRemote = new Boolean[2]; final var callbacks = new RemoteDisplayChangeController.ContinueRemoteDisplayChangeCallback[ isWaitingForRemote.length]; for (int i = 0; i < isWaitingForRemote.length; i++) { final int index = i; var callback = new RemoteDisplayChangeController.ContinueRemoteDisplayChangeCallback() { @Override public void onContinueRemoteDisplayChange(WindowContainerTransaction transaction) { isWaitingForRemote[index] = mDisplayContent.mRemoteDisplayChangeController .isWaitingForRemoteDisplayChange(); } }; mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange( ROTATION_0, ROTATION_0, null /* newDisplayAreaInfo */, callback); callbacks[i] = callback; } // The last callback is completed, all callbacks should be notified. mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[1], null /* transaction */); // When notifying 0, the callback 1 still exists. assertTrue(isWaitingForRemote[0]); assertFalse(isWaitingForRemote[1]); // The first callback is completed, other callbacks after it should remain. for (int i = 0; i < isWaitingForRemote.length; i++) { isWaitingForRemote[i] = null; mDisplayContent.mRemoteDisplayChangeController.performRemoteDisplayChange( ROTATION_0, ROTATION_0, null /* newDisplayAreaInfo */, callbacks[i]); } mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[0], null /* transaction */); assertTrue(isWaitingForRemote[0]); assertNull(isWaitingForRemote[1]); // Complete the last callback. It should be able to consume pending config change. mDisplayContent.mWaitingForConfig = true; mDisplayContent.mRemoteDisplayChangeController.continueDisplayChange(callbacks[1], null /* transaction */); assertFalse(isWaitingForRemote[1]); assertFalse(mDisplayContent.mWaitingForConfig); } @Test public void testShellTransitRotation() { DisplayContent dc = createNewDisplay(); Loading