diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java index b77ac8a2b951e..e46ee28b3ddba 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SingleInstanceRemoteListener.java @@ -29,6 +29,9 @@ import java.util.function.Consumer; * Manages the lifecycle of a single instance of a remote listener, including the clean up if the * remote process dies. All calls on this class should happen on the main shell thread. * + * Any external interface using this listener should also unregister the listener when it is + * invalidated, otherwise it may leak binder death recipients. + * * @param The controller (must be RemoteCallable) * @param The remote listener interface type */ diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java index efe938f0a2746..01d81ff4e436d 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java @@ -207,7 +207,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb private Consumer mOnIsInPipStateChangedListener; - private interface PipAnimationListener { + @VisibleForTesting + interface PipAnimationListener { /** * Notifies the listener that the Pip animation is started. */ @@ -434,11 +435,7 @@ public class PipController implements PipTransitionController.PipTransitionCallb Optional oneHandedController, ShellExecutor mainExecutor ) { - // Ensure that we are the primary user's SystemUI. - final int processUser = UserManager.get(context).getProcessUserId(); - if (processUser != UserHandle.USER_SYSTEM) { - throw new IllegalStateException("Non-primary Pip component not currently supported."); - } + mContext = context; mShellCommandHandler = shellCommandHandler; @@ -872,11 +869,17 @@ public class PipController implements PipTransitionController.PipTransitionCallb animationType == PipAnimationController.ANIM_TYPE_BOUNDS); } - private void setPinnedStackAnimationListener(PipAnimationListener callback) { + @VisibleForTesting + void setPinnedStackAnimationListener(PipAnimationListener callback) { mPinnedStackAnimationRecentsCallback = callback; onPipResourceDimensionsChanged(); } + @VisibleForTesting + boolean hasPinnedStackAnimationListener() { + return mPinnedStackAnimationRecentsCallback != null; + } + private void onPipResourceDimensionsChanged() { if (mPinnedStackAnimationRecentsCallback != null) { mPinnedStackAnimationRecentsCallback.onPipResourceDimensionsChanged( @@ -1166,6 +1169,8 @@ public class PipController implements PipTransitionController.PipTransitionCallb @Override public void invalidate() { mController = null; + // Unregister the listener to ensure any registered binder death recipients are unlinked + mListener.unregister(); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java index db0f0bf6fda8f..8490f9f156c70 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java @@ -280,14 +280,21 @@ public class RecentTasksController implements TaskStackListenerCallback, } } - private void registerRecentTasksListener(IRecentTasksListener listener) { + @VisibleForTesting + void registerRecentTasksListener(IRecentTasksListener listener) { mListener = listener; } - private void unregisterRecentTasksListener() { + @VisibleForTesting + void unregisterRecentTasksListener() { mListener = null; } + @VisibleForTesting + boolean hasRecentTasksListener() { + return mListener != null; + } + @VisibleForTesting ArrayList getRecentTasks(int maxNum, int flags, int userId) { // Note: the returned task list is from the most-recent to least-recent order @@ -442,6 +449,8 @@ public class RecentTasksController implements TaskStackListenerCallback, @Override public void invalidate() { mController = null; + // Unregister the listener to ensure any registered binder death recipients are unlinked + mListener.unregister(); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java index ef70d9bd84eee..38099fc51d815 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java @@ -954,6 +954,8 @@ public class SplitScreenController implements DragAndDropPolicy.Starter, @Override public void invalidate() { mController = null; + // Unregister the listener to ensure any registered binder death recipients are unlinked + mListener.unregister(); } @Override diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java index 0c23f109feaf1..be2e79342d074 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java @@ -39,6 +39,7 @@ import android.window.TaskOrganizer; import android.window.TaskSnapshot; import androidx.annotation.BinderThread; +import androidx.annotation.VisibleForTesting; import com.android.internal.annotations.GuardedBy; import com.android.internal.util.function.TriConsumer; @@ -138,10 +139,16 @@ public class StartingWindowController implements RemoteCallable listener) { mTaskLaunchingCallback = listener; } + @VisibleForTesting + boolean hasStartingWindowListener() { + return mTaskLaunchingCallback != null; + } + /** * Called when a task need a starting window. */ @@ -281,6 +288,8 @@ public class StartingWindowController implements RemoteCallable mIntentCaptor; + private ShellController mShellController; private SplitScreenController mSplitScreenController; @Before public void setup() { assumeTrue(ActivityTaskManager.supportsSplitScreenMultiWindow(mContext)); MockitoAnnotations.initMocks(this); + mShellController = spy(new ShellController(mShellInit, mShellCommandHandler, + mMainExecutor)); mSplitScreenController = spy(new SplitScreenController(mContext, mShellInit, mShellCommandHandler, mShellController, mTaskOrganizer, mSyncQueue, mRootTDAOrganizer, mDisplayController, mDisplayImeController, @@ -118,7 +124,7 @@ public class SplitScreenControllerTests extends ShellTestCase { @Test public void instantiateController_addInitCallback() { - verify(mShellInit, times(1)).addInitCallback(any(), any()); + verify(mShellInit, times(1)).addInitCallback(any(), isA(SplitScreenController.class)); } @Test @@ -158,6 +164,19 @@ public class SplitScreenControllerTests extends ShellTestCase { eq(ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN), any(), any()); } + @Test + public void testInvalidateExternalInterface_unregistersListener() { + mSplitScreenController.onInit(); + mSplitScreenController.registerSplitScreenListener( + new SplitScreen.SplitScreenListener() {}); + verify(mStageCoordinator).registerSplitScreenListener(any()); + // Create initial interface + mShellController.createExternalInterfaces(new Bundle()); + // Recreate the interface to trigger invalidation of the previous instance + mShellController.createExternalInterfaces(new Bundle()); + verify(mStageCoordinator).unregisterSplitScreenListener(any()); + } + @Test public void testStartIntent_appendsNoUserActionFlag() { Intent startIntent = createStartIntent("startActivity"); diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java index 90165d1cd1b2b..10dec9ef12f9e 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java @@ -16,9 +16,12 @@ package com.android.wm.shell.startingsurface; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isA; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -27,16 +30,19 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.hardware.display.DisplayManager; +import android.os.Bundle; import android.view.Display; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.filters.SmallTest; +import com.android.internal.util.function.TriConsumer; import com.android.launcher3.icons.IconProvider; import com.android.wm.shell.ShellTaskOrganizer; import com.android.wm.shell.ShellTestCase; import com.android.wm.shell.common.ShellExecutor; import com.android.wm.shell.common.TransactionPool; +import com.android.wm.shell.sysui.ShellCommandHandler; import com.android.wm.shell.sysui.ShellController; import com.android.wm.shell.sysui.ShellInit; import com.android.wm.shell.sysui.ShellSharedConstants; @@ -59,7 +65,7 @@ public class StartingWindowControllerTests extends ShellTestCase { private @Mock Context mContext; private @Mock DisplayManager mDisplayManager; - private @Mock ShellController mShellController; + private @Mock ShellCommandHandler mShellCommandHandler; private @Mock ShellTaskOrganizer mTaskOrganizer; private @Mock ShellExecutor mMainExecutor; private @Mock StartingWindowTypeAlgorithm mTypeAlgorithm; @@ -67,6 +73,7 @@ public class StartingWindowControllerTests extends ShellTestCase { private @Mock TransactionPool mTransactionPool; private StartingWindowController mController; private ShellInit mShellInit; + private ShellController mShellController; @Before public void setUp() { @@ -74,6 +81,8 @@ public class StartingWindowControllerTests extends ShellTestCase { doReturn(mock(Display.class)).when(mDisplayManager).getDisplay(anyInt()); doReturn(mDisplayManager).when(mContext).getSystemService(eq(DisplayManager.class)); mShellInit = spy(new ShellInit(mMainExecutor)); + mShellController = spy(new ShellController(mShellInit, mShellCommandHandler, + mMainExecutor)); mController = new StartingWindowController(mContext, mShellInit, mShellController, mTaskOrganizer, mMainExecutor, mTypeAlgorithm, mIconProvider, mTransactionPool); mShellInit.init(); @@ -81,7 +90,7 @@ public class StartingWindowControllerTests extends ShellTestCase { @Test public void instantiateController_addInitCallback() { - verify(mShellInit, times(1)).addInitCallback(any(), any()); + verify(mShellInit, times(1)).addInitCallback(any(), isA(StartingWindowController.class)); } @Test @@ -89,4 +98,18 @@ public class StartingWindowControllerTests extends ShellTestCase { verify(mShellController, times(1)).addExternalInterface( eq(ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW), any(), any()); } + + @Test + public void testInvalidateExternalInterface_unregistersListener() { + mController.setStartingWindowListener(new TriConsumer() { + @Override + public void accept(Integer integer, Integer integer2, Integer integer3) {} + }); + assertTrue(mController.hasStartingWindowListener()); + // Create initial interface + mShellController.createExternalInterfaces(new Bundle()); + // Recreate the interface to trigger invalidation of the previous instance + mShellController.createExternalInterfaces(new Bundle()); + assertFalse(mController.hasStartingWindowListener()); + } }