Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 51afda7a authored by Darrell Shi's avatar Darrell Shi
Browse files

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
parent 562d88f2
Loading
Loading
Loading
Loading
+4 −5
Original line number Diff line number Diff line
@@ -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();
    }

+12 −22
Original line number Diff line number Diff line
@@ -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);
                                }
+6 −2
Original line number Diff line number Diff line
@@ -32,6 +32,10 @@ interface IDreamOverlay {
                    token of the Dream Activity.
    * @param callback The {@link IDreamOverlayCallback} for requesting actions such as exiting the
    *                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);
}
+54 −29
Original line number Diff line number Diff line
@@ -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 a dream has bound to the service and dream overlay service has started.
    private boolean mStarted = false;

    // True if the service has been destroyed.
    private boolean mDestroyed;
    private boolean mDestroyed = false;

    private final Complication.Host mHost = new Complication.Host() {
        @Override
        public void requestExitDream() {
            mExecutor.execute(DreamOverlayService.this::requestExit);
        }
    };
    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;
    }
}
+102 −25
Original line number Diff line number Diff line
@@ -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<View> 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();
        // Inform the overlay service of dream starting.
        overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                true /*shouldShowComplication*/);

        assertThat(mService.shouldShowComplications()).isTrue();
    }

    @Test
    public void testShouldShowComplicationsSetByIntentExtra() {
        final Intent intent = new Intent();
        intent.putExtra(DreamService.EXTRA_SHOW_COMPLICATIONS, true);
        mService.onBind(intent);
    public void testLowLightSetByStartDream() throws RemoteException {
        final IBinder proxy = mService.onBind(new Intent());
        final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);

        assertThat(mService.shouldShowComplications()).isTrue();
        // Inform the overlay service of dream starting.
        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 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 testDestroy() 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();

        verify(mStateController).setLowLightActive(true);
        // 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 testDestroy() {
    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();
    }
}