From b0514b19d841d64f22274cac7948f81c1bfa68fc Mon Sep 17 00:00:00 2001 From: Bryce Lee Date: Tue, 21 Feb 2023 17:23:52 -0800 Subject: [PATCH] Handle invalid dream window in DreamOverlayService. It is possible for a dream's window to become invalid shortly after starting. For example, another dream might begin almost immediately following. In these cases, it is possible that the DreamOverlayService might not process the start event until after the dream window becomes invalid. If not handled properly, an BadTokenException will cause SystemUI to crash. This changelist addresses the issue by catching the exception and tearing down the overlay. Test: atest DreamOverlayServiceTest Fixes: 268209056 Change-Id: I25ff6ff58e6a612ea609a4541219bea149eeace9 --- .../systemui/dreams/DreamOverlayService.java | 26 ++++++++++++++++--- .../dreams/DreamOverlayServiceTest.java | 26 +++++++++++++++++++ 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 5aebc3268b90b..9660aaeeceb28 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -195,7 +195,14 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ mDreamOverlayTouchMonitor.init(); mStateController.setShouldShowComplications(shouldShowComplications()); - addOverlayWindowLocked(layoutParams); + + // If we are not able to add the overlay window, reset the overlay. + if (!addOverlayWindowLocked(layoutParams)) { + resetCurrentDreamOverlayLocked(); + return; + } + + setCurrentStateLocked(Lifecycle.State.RESUMED); mStateController.setOverlayActive(true); final ComponentName dreamComponent = getDreamComponent(); @@ -241,7 +248,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ * @param layoutParams The {@link android.view.WindowManager.LayoutParams} which allow inserting * into the dream window. */ - private void addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) { + private boolean addOverlayWindowLocked(WindowManager.LayoutParams layoutParams) { mWindow = new PhoneWindow(mContext); // Default to SystemUI name for TalkBack. mWindow.setTitle(""); @@ -266,9 +273,22 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ // risk an IllegalStateException in some cases when setting the container view as the // window's content view and the container view hasn't been properly removed previously). removeContainerViewFromParentLocked(); + mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView()); - mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes()); + // It is possible that a dream's window (and the dream as a whole) is no longer valid by + // the time the overlay service processes the dream. This can happen for example if + // another dream is started immediately after the existing dream begins. In this case, the + // overlay service should identify the situation through the thrown exception and tear down + // the overlay. + try { + mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes()); + return true; + } catch (WindowManager.BadTokenException exception) { + Log.e(TAG, "Dream activity window invalid: " + layoutParams.packageName, + exception); + return false; + } } private void removeContainerViewFromParentLocked() { diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java index dfb4d5baeef5f..1820f6444fbde 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.clearInvocations; +import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -190,6 +191,31 @@ public class DreamOverlayServiceTest extends SysuiTestCase { verify(mWindowManager).addView(any(), any()); } + // Validates that {@link DreamOverlayService} properly handles the case where the dream's + // window is no longer valid by the time start is called. + @Test + public void testInvalidWindowAddStart() throws Exception { + final IDreamOverlayClient client = getClient(); + + doThrow(new WindowManager.BadTokenException()).when(mWindowManager).addView(any(), any()); + // Inform the overlay service of dream starting. + client.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + false /*shouldShowComplication*/); + mMainExecutor.runAllReady(); + + verify(mWindowManager).addView(any(), any()); + + verify(mStateController).setOverlayActive(false); + verify(mStateController).setLowLightActive(false); + verify(mStateController).setEntryAnimationsFinished(false); + + verify(mStateController, never()).setOverlayActive(true); + verify(mUiEventLogger, never()).log( + DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START); + + verify(mDreamOverlayCallbackController, never()).onStartDream(); + } + @Test public void testDreamOverlayContainerViewControllerInitialized() throws Exception { final IDreamOverlayClient client = getClient();