From 51afda7a0e1b95dc24ce81c4c0066ba9b3d00dc2 Mon Sep 17 00:00:00 2001 From: Darrell Shi Date: Fri, 23 Sep 2022 20:52:39 +0000 Subject: [PATCH] Redraw dream overlay when a new dream is connected. Now that we allow two dreams to have a breif overlap to have a smooth transition, the overlay service can be bound to by two dreams at once. Extras put in the intent are now moved to startDream because onBind() is called only when the first dream is connected to it. Also fix a bug that under an edge case where the service is destroyed before dream is started, removing the view from window manager throws an illegal argument exception. Bug: 244315094 Bug: 249038780 Bug: 248496636 Fix: 244315094 Fix: 249038780 Test: atest DreamOverlayServiceTest Test: manually on device, see video in bug Change-Id: I8278e5b40a25539ce68a9c17c6cec9387d2da201 --- .../service/dreams/DreamOverlayService.java | 9 +- .../android/service/dreams/DreamService.java | 34 ++--- .../android/service/dreams/IDreamOverlay.aidl | 8 +- .../systemui/dreams/DreamOverlayService.java | 85 ++++++++---- .../dreams/DreamOverlayServiceTest.java | 129 ++++++++++++++---- 5 files changed, 180 insertions(+), 85 deletions(-) diff --git a/core/java/android/service/dreams/DreamOverlayService.java b/core/java/android/service/dreams/DreamOverlayService.java index 432444211bced..aa45c20a8e135 100644 --- a/core/java/android/service/dreams/DreamOverlayService.java +++ b/core/java/android/service/dreams/DreamOverlayService.java @@ -42,8 +42,11 @@ public abstract class DreamOverlayService extends Service { private IDreamOverlay mDreamOverlay = new IDreamOverlay.Stub() { @Override public void startDream(WindowManager.LayoutParams layoutParams, - IDreamOverlayCallback callback) { + IDreamOverlayCallback callback, String dreamComponent, + boolean shouldShowComplications) { mDreamOverlayCallback = callback; + mDreamComponent = ComponentName.unflattenFromString(dreamComponent); + mShowComplications = shouldShowComplications; onStartDream(layoutParams); } }; @@ -56,10 +59,6 @@ public abstract class DreamOverlayService extends Service { @Nullable @Override public final IBinder onBind(@NonNull Intent intent) { - mShowComplications = intent.getBooleanExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, - DreamService.DEFAULT_SHOW_COMPLICATIONS); - mDreamComponent = intent.getParcelableExtra(DreamService.EXTRA_DREAM_COMPONENT, - ComponentName.class); return mDreamOverlay.asBinder(); } diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java index 3b7698e3954bf..3c1fef02f9baa 100644 --- a/core/java/android/service/dreams/DreamService.java +++ b/core/java/android/service/dreams/DreamService.java @@ -213,19 +213,6 @@ public class DreamService extends Service implements Window.Callback { */ private static final String DREAM_META_DATA_ROOT_TAG = "dream"; - /** - * Extra containing a boolean for whether to show complications on the overlay. - * @hide - */ - public static final String EXTRA_SHOW_COMPLICATIONS = - "android.service.dreams.SHOW_COMPLICATIONS"; - - /** - * Extra containing the component name for the active dream. - * @hide - */ - public static final String EXTRA_DREAM_COMPONENT = "android.service.dreams.DREAM_COMPONENT"; - /** * The default value for whether to show complications on the overlay. * @@ -252,6 +239,9 @@ public class DreamService extends Service implements Window.Callback { private boolean mDebug = false; + private ComponentName mDreamComponent; + private boolean mShouldShowComplications; + private DreamServiceWrapper mDreamServiceWrapper; private Runnable mDispatchAfterOnAttachedToWindow; @@ -947,6 +937,11 @@ public class DreamService extends Service implements Window.Callback { @Override public void onCreate() { if (mDebug) Slog.v(mTag, "onCreate()"); + + mDreamComponent = new ComponentName(this, getClass()); + mShouldShowComplications = fetchShouldShowComplications(this /*context*/, + fetchServiceInfo(this /*context*/, mDreamComponent)); + super.onCreate(); } @@ -994,14 +989,7 @@ public class DreamService extends Service implements Window.Callback { // Connect to the overlay service if present. if (!mWindowless && overlayComponent != null) { final Resources resources = getResources(); - final ComponentName dreamService = new ComponentName(this, getClass()); - - final ServiceInfo serviceInfo = fetchServiceInfo(this, dreamService); - final Intent overlayIntent = new Intent() - .setComponent(overlayComponent) - .putExtra(EXTRA_SHOW_COMPLICATIONS, - fetchShouldShowComplications(this, serviceInfo)) - .putExtra(EXTRA_DREAM_COMPONENT, dreamService); + final Intent overlayIntent = new Intent().setComponent(overlayComponent); mOverlayConnection = new OverlayConnection( /* context= */ this, @@ -1364,7 +1352,9 @@ public class DreamService extends Service implements Window.Callback { // parameters once the window has been attached. mDreamStartOverlayConsumer = overlay -> { try { - overlay.startDream(mWindow.getAttributes(), mOverlayCallback); + overlay.startDream(mWindow.getAttributes(), mOverlayCallback, + mDreamComponent.flattenToString(), + mShouldShowComplications); } catch (RemoteException e) { Log.e(mTag, "could not send window attributes:" + e); } diff --git a/core/java/android/service/dreams/IDreamOverlay.aidl b/core/java/android/service/dreams/IDreamOverlay.aidl index 2b6633d93dc53..05ebbfe98c9fc 100644 --- a/core/java/android/service/dreams/IDreamOverlay.aidl +++ b/core/java/android/service/dreams/IDreamOverlay.aidl @@ -31,7 +31,11 @@ interface IDreamOverlay { * @param params The {@link LayoutParams} for the associated DreamWindow, including the window token of the Dream Activity. * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the - * dream. + * dream. + * @param dreamComponent The component name of the dream service requesting overlay. + * @param shouldShowComplications Whether the dream overlay should show complications, e.g. clock + * and weather. */ - void startDream(in LayoutParams params, in IDreamOverlayCallback callback); + void startDream(in LayoutParams params, in IDreamOverlayCallback callback, + in String dreamComponent, in boolean shouldShowComplications); } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java index 696fc7254308d..d1b73685a3f35 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java @@ -64,29 +64,26 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ private final Executor mExecutor; // A controller for the dream overlay container view (which contains both the status bar and the // content area). - private final DreamOverlayContainerViewController mDreamOverlayContainerViewController; + private DreamOverlayContainerViewController mDreamOverlayContainerViewController; private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; @Nullable private final ComponentName mLowLightDreamComponent; private final UiEventLogger mUiEventLogger; + private final WindowManager mWindowManager; // A reference to the {@link Window} used to hold the dream overlay. private Window mWindow; - // True if the service has been destroyed. - private boolean mDestroyed; + // True if a dream has bound to the service and dream overlay service has started. + private boolean mStarted = false; - private final Complication.Host mHost = new Complication.Host() { - @Override - public void requestExitDream() { - mExecutor.execute(DreamOverlayService.this::requestExit); - } - }; + // True if the service has been destroyed. + private boolean mDestroyed = false; + + private final DreamOverlayComponent mDreamOverlayComponent; private final LifecycleRegistry mLifecycleRegistry; - private ViewModelStore mViewModelStore = new ViewModelStore(); - private DreamOverlayTouchMonitor mDreamOverlayTouchMonitor; private final KeyguardUpdateMonitorCallback mKeyguardCallback = @@ -103,7 +100,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ } }; - private DreamOverlayStateController mStateController; + private final DreamOverlayStateController mStateController; @VisibleForTesting public enum DreamOverlayEvent implements UiEventLogger.UiEventEnum { @@ -128,6 +125,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ public DreamOverlayService( Context context, @Main Executor executor, + WindowManager windowManager, DreamOverlayComponent.Factory dreamOverlayComponentFactory, DreamOverlayStateController stateController, KeyguardUpdateMonitor keyguardUpdateMonitor, @@ -136,19 +134,19 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ ComponentName lowLightDreamComponent) { mContext = context; mExecutor = executor; + mWindowManager = windowManager; mKeyguardUpdateMonitor = keyguardUpdateMonitor; mLowLightDreamComponent = lowLightDreamComponent; mKeyguardUpdateMonitor.registerCallback(mKeyguardCallback); mStateController = stateController; mUiEventLogger = uiEventLogger; - final DreamOverlayComponent component = - dreamOverlayComponentFactory.create(mViewModelStore, mHost); - mDreamOverlayContainerViewController = component.getDreamOverlayContainerViewController(); + final ViewModelStore viewModelStore = new ViewModelStore(); + final Complication.Host host = + () -> mExecutor.execute(DreamOverlayService.this::requestExit); + mDreamOverlayComponent = dreamOverlayComponentFactory.create(viewModelStore, host); + mLifecycleRegistry = mDreamOverlayComponent.getLifecycleRegistry(); setCurrentState(Lifecycle.State.CREATED); - mLifecycleRegistry = component.getLifecycleRegistry(); - mDreamOverlayTouchMonitor = component.getDreamOverlayTouchMonitor(); - mDreamOverlayTouchMonitor.init(); } private void setCurrentState(Lifecycle.State state) { @@ -159,34 +157,48 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ public void onDestroy() { mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback); setCurrentState(Lifecycle.State.DESTROYED); - final WindowManager windowManager = mContext.getSystemService(WindowManager.class); - if (mWindow != null) { - windowManager.removeView(mWindow.getDecorView()); - } - mStateController.setOverlayActive(false); - mStateController.setLowLightActive(false); + + resetCurrentDreamOverlay(); + mDestroyed = true; super.onDestroy(); } @Override public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) { - mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START); setCurrentState(Lifecycle.State.STARTED); - final ComponentName dreamComponent = getDreamComponent(); - mStateController.setLowLightActive( - dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent)); + mExecutor.execute(() -> { + mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START); + if (mDestroyed) { // The task could still be executed after the service has been destroyed. Bail if // that is the case. return; } + + if (mStarted) { + // Reset the current dream overlay before starting a new one. This can happen + // when two dreams overlap (briefly, for a smoother dream transition) and both + // dreams are bound to the dream overlay service. + resetCurrentDreamOverlay(); + } + + mDreamOverlayContainerViewController = + mDreamOverlayComponent.getDreamOverlayContainerViewController(); + mDreamOverlayTouchMonitor = mDreamOverlayComponent.getDreamOverlayTouchMonitor(); + mDreamOverlayTouchMonitor.init(); + mStateController.setShouldShowComplications(shouldShowComplications()); addOverlayWindowLocked(layoutParams); setCurrentState(Lifecycle.State.RESUMED); mStateController.setOverlayActive(true); + final ComponentName dreamComponent = getDreamComponent(); + mStateController.setLowLightActive( + dreamComponent != null && dreamComponent.equals(mLowLightDreamComponent)); mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_COMPLETE_START); + + mStarted = true; }); } @@ -222,8 +234,7 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ removeContainerViewFromParent(); mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView()); - final WindowManager windowManager = mContext.getSystemService(WindowManager.class); - windowManager.addView(mWindow.getDecorView(), mWindow.getAttributes()); + mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes()); } private void removeContainerViewFromParent() { @@ -238,4 +249,18 @@ public class DreamOverlayService extends android.service.dreams.DreamOverlayServ Log.w(TAG, "Removing dream overlay container view parent!"); parentView.removeView(containerView); } + + private void resetCurrentDreamOverlay() { + if (mStarted && mWindow != null) { + mWindowManager.removeView(mWindow.getDecorView()); + } + + mStateController.setOverlayActive(false); + mStateController.setLowLightActive(false); + + mDreamOverlayContainerViewController = null; + mDreamOverlayTouchMonitor = null; + + mStarted = false; + } } 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 eec33ca2ff78d..f370be190761d 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java @@ -19,6 +19,7 @@ package com.android.systemui.dreams; 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.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -27,10 +28,10 @@ import android.content.ComponentName; import android.content.Intent; import android.os.IBinder; import android.os.RemoteException; -import android.service.dreams.DreamService; import android.service.dreams.IDreamOverlay; import android.service.dreams.IDreamOverlayCallback; import android.testing.AndroidTestingRunner; +import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowManagerImpl; @@ -53,6 +54,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; @@ -61,6 +64,7 @@ import org.mockito.MockitoAnnotations; public class DreamOverlayServiceTest extends SysuiTestCase { private static final ComponentName LOW_LIGHT_COMPONENT = new ComponentName("package", "lowlight"); + private static final String DREAM_COMPONENT = "package/dream"; private final FakeSystemClock mFakeSystemClock = new FakeSystemClock(); private final FakeExecutor mMainExecutor = new FakeExecutor(mFakeSystemClock); @@ -108,12 +112,14 @@ public class DreamOverlayServiceTest extends SysuiTestCase { @Mock UiEventLogger mUiEventLogger; + @Captor + ArgumentCaptor mViewCaptor; + DreamOverlayService mService; @Before public void setup() { MockitoAnnotations.initMocks(this); - mContext.addMockSystemService(WindowManager.class, mWindowManager); when(mDreamOverlayComponent.getDreamOverlayContainerViewController()) .thenReturn(mDreamOverlayContainerViewController); @@ -129,7 +135,7 @@ public class DreamOverlayServiceTest extends SysuiTestCase { when(mDreamOverlayContainerViewController.getContainerView()) .thenReturn(mDreamOverlayContainerView); - mService = new DreamOverlayService(mContext, mMainExecutor, + mService = new DreamOverlayService(mContext, mMainExecutor, mWindowManager, mDreamOverlayComponentFactory, mStateController, mKeyguardUpdateMonitor, @@ -143,7 +149,8 @@ public class DreamOverlayServiceTest extends SysuiTestCase { final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback); + overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + false /*shouldShowComplication*/); mMainExecutor.runAllReady(); verify(mUiEventLogger).log(DreamOverlayService.DreamOverlayEvent.DREAM_OVERLAY_ENTER_START); @@ -157,7 +164,8 @@ public class DreamOverlayServiceTest extends SysuiTestCase { final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback); + overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + false /*shouldShowComplication*/); mMainExecutor.runAllReady(); verify(mWindowManager).addView(any(), any()); @@ -169,7 +177,8 @@ public class DreamOverlayServiceTest extends SysuiTestCase { final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback); + overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + false /*shouldShowComplication*/); mMainExecutor.runAllReady(); verify(mDreamOverlayContainerViewController).init(); @@ -186,49 +195,76 @@ public class DreamOverlayServiceTest extends SysuiTestCase { final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback); + overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + false /*shouldShowComplication*/); mMainExecutor.runAllReady(); verify(mDreamOverlayContainerViewParent).removeView(mDreamOverlayContainerView); } @Test - public void testShouldShowComplicationsFalseByDefault() { - mService.onBind(new Intent()); + public void testShouldShowComplicationsSetByStartDream() throws RemoteException { + final IBinder proxy = mService.onBind(new Intent()); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); - assertThat(mService.shouldShowComplications()).isFalse(); - } - - @Test - public void testShouldShowComplicationsSetByIntentExtra() { - final Intent intent = new Intent(); - intent.putExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, true); - mService.onBind(intent); + // Inform the overlay service of dream starting. + overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + true /*shouldShowComplication*/); assertThat(mService.shouldShowComplications()).isTrue(); } @Test - public void testLowLightSetByIntentExtra() throws RemoteException { - final Intent intent = new Intent(); - intent.putExtra(DreamService.EXTRA_DREAM_COMPONENT, LOW_LIGHT_COMPONENT); - - final IBinder proxy = mService.onBind(intent); + public void testLowLightSetByStartDream() throws RemoteException { + final IBinder proxy = mService.onBind(new Intent()); final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); - assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback); + overlay.startDream(mWindowParams, mDreamOverlayCallback, + LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/); mMainExecutor.runAllReady(); + assertThat(mService.getDreamComponent()).isEqualTo(LOW_LIGHT_COMPONENT); verify(mStateController).setLowLightActive(true); } @Test - public void testDestroy() { + public void testDestroy() throws RemoteException { + final IBinder proxy = mService.onBind(new Intent()); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + + // Inform the overlay service of dream starting. + overlay.startDream(mWindowParams, mDreamOverlayCallback, + LOW_LIGHT_COMPONENT.flattenToString(), false /*shouldShowComplication*/); + mMainExecutor.runAllReady(); + + // Verify view added. + verify(mWindowManager).addView(mViewCaptor.capture(), any()); + + // Service destroyed. mService.onDestroy(); mMainExecutor.runAllReady(); + // Verify view removed. + verify(mWindowManager).removeView(mViewCaptor.getValue()); + + // Verify state correctly set. + verify(mKeyguardUpdateMonitor).removeCallback(any()); + verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED); + verify(mStateController).setOverlayActive(false); + verify(mStateController).setLowLightActive(false); + } + + @Test + public void testDoNotRemoveViewOnDestroyIfOverlayNotStarted() { + // Service destroyed without ever starting dream. + mService.onDestroy(); + mMainExecutor.runAllReady(); + + // Verify no view is removed. + verify(mWindowManager, never()).removeView(any()); + + // Verify state still correctly set. verify(mKeyguardUpdateMonitor).removeCallback(any()); verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED); verify(mStateController).setOverlayActive(false); @@ -245,7 +281,8 @@ public class DreamOverlayServiceTest extends SysuiTestCase { final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); // Inform the overlay service of dream starting. - overlay.startDream(mWindowParams, mDreamOverlayCallback); + overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + false /*shouldShowComplication*/); // Destroy the service. mService.onDestroy(); @@ -255,4 +292,44 @@ public class DreamOverlayServiceTest extends SysuiTestCase { verify(mWindowManager, never()).addView(any(), any()); } + + @Test + public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException { + final IBinder proxy = mService.onBind(new Intent()); + final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy); + + // Inform the overlay service of dream starting. Do not show dream complications. + overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + false /*shouldShowComplication*/); + mMainExecutor.runAllReady(); + + // Verify that a new window is added. + verify(mWindowManager).addView(mViewCaptor.capture(), any()); + final View windowDecorView = mViewCaptor.getValue(); + + // Assert that the overlay is not showing complications. + assertThat(mService.shouldShowComplications()).isFalse(); + + clearInvocations(mDreamOverlayComponent); + clearInvocations(mWindowManager); + + // New dream starting with dream complications showing. Note that when a new dream is + // binding to the dream overlay service, it receives the same instance of IBinder as the + // first one. + overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT, + true /*shouldShowComplication*/); + mMainExecutor.runAllReady(); + + // Assert that the overlay is showing complications. + assertThat(mService.shouldShowComplications()).isTrue(); + + // Verify that the old overlay window has been removed, and a new one created. + verify(mWindowManager).removeView(windowDecorView); + verify(mWindowManager).addView(any(), any()); + + // Verify that new instances of overlay container view controller and overlay touch monitor + // are created. + verify(mDreamOverlayComponent).getDreamOverlayContainerViewController(); + verify(mDreamOverlayComponent).getDreamOverlayTouchMonitor(); + } }