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

Commit 6eae71bb authored by Bryce Lee's avatar Bryce Lee
Browse files

Enable DreamOverlay for Hub on Mobile.

This changelist enables and registers the DreamOverlayService for use
when the Hub on Mobile feature flag is enabled. Eventually, the
AndroidManifest entry for DreamOverlayService should be updated to
reflect the enabled value the supported devices.

Test: atest DreamOverlayRegistrantTest
Flag: com.android.systemui.communal_hub_on_mobile
Bug: 378111815
Change-Id: I07cc89fe70b41b93a4ab893fdb230df2ee4f2d98
parent 5df8c7e2
Loading
Loading
Loading
Loading
+0 −1
Original line number Diff line number Diff line
@@ -67,7 +67,6 @@ public class ComplicationCollectionLiveDataTest extends SysuiTestCase {
        mFeatureFlags.set(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS, true);
        mStateController = new DreamOverlayStateController(
                mExecutor,
                /* overlayEnabled= */ true,
                mFeatureFlags,
                FakeLogBuffer.Factory.Companion.create(),
                new FakeWeakReferenceFactory());
+165 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.systemui.dreams

import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.ServiceInfo
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.service.dreams.IDreamManager
import android.testing.TestableLooper
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.Flags
import com.android.systemui.SysuiTestCase
import com.android.systemui.log.core.FakeLogBuffer
import com.android.systemui.shared.condition.Monitor
import com.android.systemui.util.mockito.withArgCaptor
import kotlin.test.Test
import org.junit.Before
import org.junit.runner.RunWith
import org.mockito.Mockito
import org.mockito.kotlin.any
import org.mockito.kotlin.clearInvocations
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.verify
import org.mockito.kotlin.whenever

@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidJUnit4::class)
class DreamOverlayRegistrantTest : SysuiTestCase() {
    private val context = mock<Context>()

    private val packageManager = mock<PackageManager>()

    private val dreamManager = mock<IDreamManager>()

    private val componentName = mock<ComponentName>()

    private val serviceInfo = mock<ServiceInfo>()

    private val monitor = mock<Monitor>()

    private val logBuffer = FakeLogBuffer.Factory.Companion.create()

    private lateinit var underTest: DreamOverlayRegistrant

    @Before
    fun setup() {
        underTest =
            DreamOverlayRegistrant(
                context,
                componentName,
                monitor,
                packageManager,
                dreamManager,
                logBuffer,
            )

        whenever(packageManager.getComponentEnabledSetting(eq(componentName)))
            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
        whenever(
                packageManager.getServiceInfo(
                    eq(componentName),
                    eq(PackageManager.GET_META_DATA or PackageManager.MATCH_DISABLED_COMPONENTS),
                )
            )
            .thenReturn(serviceInfo)
        whenever(
                packageManager.setComponentEnabledSetting(
                    eq(componentName),
                    eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
                    eq(PackageManager.DONT_KILL_APP),
                )
            )
            .thenAnswer {
                setComponentEnabledState(PackageManager.COMPONENT_ENABLED_STATE_ENABLED, true)
            }

        serviceInfo.enabled = false
    }

    private fun start() {
        underTest.start()
        val subscription = withArgCaptor { verify(monitor).addSubscription(capture()) }
        subscription.callback.onConditionsChanged(true)
    }

    private fun setComponentEnabledState(enabledState: Int, triggerUpdate: Boolean) {
        whenever(packageManager.getComponentEnabledSetting(eq(componentName)))
            .thenReturn(enabledState)

        if (triggerUpdate) {
            withArgCaptor { verify(context).registerReceiver(capture(), any()) }
                .onReceive(context, Intent())
        }
    }

    /** Verify overlay registered when enabled in manifest. */
    @Test
    @DisableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
    fun testRegisteredWhenEnabledWithManifest() {
        serviceInfo.enabled = true
        start()

        verify(dreamManager).registerDreamOverlayService(componentName)
    }

    /** Verify overlay registered for mobile hub with flag. */
    @Test
    @EnableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
    fun testRegisteredForMobileHub() {
        start()

        verify(dreamManager).registerDreamOverlayService(componentName)
    }

    /**
     * Make sure dream overlay not registered when not in manifest and not hub mode on mobile is not
     * enabled.
     */
    @Test
    @DisableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
    fun testDisabledForMobileWithoutMobileHub() {
        start()

        verify(packageManager, never())
            .setComponentEnabledSetting(
                eq(componentName),
                eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
                eq(PackageManager.DONT_KILL_APP),
            )
        verify(dreamManager, never()).registerDreamOverlayService(componentName)
    }

    /** Ensure service unregistered when component is disabled at runtime. */
    @Test
    @EnableFlags(Flags.FLAG_COMMUNAL_HUB_ON_MOBILE)
    fun testUnregisteredWhenComponentDisabled() {
        start()
        verify(dreamManager).registerDreamOverlayService(componentName)
        clearInvocations(dreamManager)
        setComponentEnabledState(PackageManager.COMPONENT_ENABLED_STATE_DISABLED, true)
        verify(dreamManager).registerDreamOverlayService(Mockito.isNull())
    }
}
+17 −32
Original line number Diff line number Diff line
@@ -76,7 +76,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {

    @Test
    public void testStateChange_overlayActive() {
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();
        stateController.addCallback(mCallback);
        stateController.setOverlayActive(true);
        mExecutor.runAllReady();
@@ -97,7 +97,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {

    @Test
    public void testCallback() {
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();
        stateController.addCallback(mCallback);

        // Add complication and verify callback is notified.
@@ -122,7 +122,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {

    @Test
    public void testNotifyOnCallbackAdd() {
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();

        stateController.addComplication(mComplication);
        mExecutor.runAllReady();
@@ -133,23 +133,9 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
        verify(mCallback, times(1)).onComplicationsChanged();
    }

    @Test
    public void testNotifyOnCallbackAddOverlayDisabled() {
        final DreamOverlayStateController stateController = getDreamOverlayStateController(false);

        stateController.addComplication(mComplication);
        mExecutor.runAllReady();

        // Verify callback occurs on add when an overlay is already present.
        stateController.addCallback(mCallback);
        mExecutor.runAllReady();
        verify(mCallback, never()).onComplicationsChanged();
    }


    @Test
    public void testComplicationFilteringWhenShouldShowComplications() {
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();
        stateController.setShouldShowComplications(true);

        final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
@@ -188,7 +174,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {

    @Test
    public void testComplicationFilteringWhenShouldHideComplications() {
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();
        stateController.setShouldShowComplications(true);

        final Complication alwaysAvailableComplication = Mockito.mock(Complication.class);
@@ -234,7 +220,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
    @Test
    public void testComplicationWithNoTypeNotFiltered() {
        final Complication complication = Mockito.mock(Complication.class);
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();
        stateController.addComplication(complication);
        mExecutor.runAllReady();
        assertThat(stateController.getComplications(true).contains(complication))
@@ -244,7 +230,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
    @Test
    public void testComplicationsNotShownForHomeControlPanelDream() {
        final Complication complication = Mockito.mock(Complication.class);
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();

        // Add a complication and verify it's returned in getComplications.
        stateController.addComplication(complication);
@@ -261,7 +247,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
    @Test
    public void testComplicationsNotShownForLowLight() {
        final Complication complication = Mockito.mock(Complication.class);
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();

        // Add a complication and verify it's returned in getComplications.
        stateController.addComplication(complication);
@@ -277,7 +263,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {

    @Test
    public void testNotifyLowLightChanged() {
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();

        stateController.addCallback(mCallback);
        mExecutor.runAllReady();
@@ -292,7 +278,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {

    @Test
    public void testNotifyLowLightExit() {
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();

        stateController.addCallback(mCallback);
        mExecutor.runAllReady();
@@ -315,7 +301,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {

    @Test
    public void testNotifyEntryAnimationsFinishedChanged() {
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();

        stateController.addCallback(mCallback);
        mExecutor.runAllReady();
@@ -330,7 +316,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {

    @Test
    public void testNotifyDreamOverlayStatusBarVisibleChanged() {
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();

        stateController.addCallback(mCallback);
        mExecutor.runAllReady();
@@ -345,7 +331,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {

    @Test
    public void testNotifyHasAssistantAttentionChanged() {
        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();

        stateController.addCallback(mCallback);
        mExecutor.runAllReady();
@@ -362,7 +348,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
    public void testShouldShowComplicationsSetToFalse_stillShowsHomeControls_featureEnabled() {
        when(mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)).thenReturn(true);

        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();
        stateController.setShouldShowComplications(true);

        final Complication homeControlsComplication = Mockito.mock(Complication.class);
@@ -404,7 +390,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
    public void testHomeControlsDoNotShowIfNotAvailable_featureEnabled() {
        when(mFeatureFlags.isEnabled(Flags.ALWAYS_SHOW_HOME_CONTROLS_ON_DREAMS)).thenReturn(true);

        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();
        stateController.setShouldShowComplications(true);

        final Complication homeControlsComplication = Mockito.mock(Complication.class);
@@ -435,7 +421,7 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
        final DreamOverlayStateController.Callback callback2 = Mockito.mock(
                DreamOverlayStateController.Callback.class);

        final DreamOverlayStateController stateController = getDreamOverlayStateController(true);
        final DreamOverlayStateController stateController = getDreamOverlayStateController();
        stateController.addCallback(callback1);
        stateController.addCallback(callback2);
        mExecutor.runAllReady();
@@ -451,10 +437,9 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase {
        assertThat(stateController.isOverlayActive()).isTrue();
    }

    private DreamOverlayStateController getDreamOverlayStateController(boolean overlayEnabled) {
    private DreamOverlayStateController getDreamOverlayStateController() {
        return new DreamOverlayStateController(
                mExecutor,
                overlayEnabled,
                mFeatureFlags,
                mLogBuffer,
                mWeakReferenceFactory
+9 −0
Original line number Diff line number Diff line
@@ -29,6 +29,7 @@ import com.android.systemui.controls.dagger.StartControlsStartableModule
import com.android.systemui.dagger.qualifiers.PerUser
import com.android.systemui.dreams.AssistantAttentionMonitor
import com.android.systemui.dreams.DreamMonitor
import com.android.systemui.dreams.DreamOverlayRegistrant
import com.android.systemui.dreams.homecontrols.system.HomeControlsDreamStartable
import com.android.systemui.globalactions.GlobalActionsComponent
import com.android.systemui.haptics.msdl.MSDLCoreStartable
@@ -328,4 +329,12 @@ abstract class SystemUICoreStartableModule {
    @IntoMap
    @ClassKey(MSDLCoreStartable::class)
    abstract fun bindMSDLCoreStartable(impl: MSDLCoreStartable): CoreStartable

    /** Inject into DreamOverlay. */
    @Binds
    @IntoMap
    @ClassKey(DreamOverlayRegistrant::class)
    abstract fun bindDreamOverlayRegistrant(
        dreamOverlayRegistrant: DreamOverlayRegistrant
    ): CoreStartable
}
+90 −12
Original line number Diff line number Diff line
@@ -23,12 +23,13 @@ import android.content.IntentFilter
import android.content.pm.PackageManager
import android.os.PatternMatcher
import android.os.RemoteException
import android.os.ServiceManager
import android.service.dreams.DreamService
import android.service.dreams.IDreamManager
import android.util.Log
import com.android.systemui.Flags
import com.android.systemui.dagger.qualifiers.SystemUser
import com.android.systemui.dreams.dagger.DreamModule
import com.android.systemui.log.LogBuffer
import com.android.systemui.log.dagger.DreamLog
import com.android.systemui.shared.condition.Monitor
import com.android.systemui.util.condition.ConditionalCoreStartable
import javax.inject.Inject
@@ -45,10 +46,12 @@ constructor(
    @param:Named(DreamModule.DREAM_OVERLAY_SERVICE_COMPONENT)
    private val overlayServiceComponent: ComponentName,
    @SystemUser monitor: Monitor,
    private val packageManager: PackageManager,
    private val dreamManager: IDreamManager,
    @DreamLog private val logBuffer: LogBuffer,
) : ConditionalCoreStartable(monitor) {
    private val dreamManager: IDreamManager =
        IDreamManager.Stub.asInterface(ServiceManager.getService(DreamService.DREAM_SERVICE))
    private var currentRegisteredState = false
    private val logger: DreamLogger = DreamLogger(logBuffer, TAG)

    private val receiver: BroadcastReceiver =
        object : BroadcastReceiver() {
@@ -61,19 +64,91 @@ constructor(
            }
        }

    private fun registerOverlayService() {
        // Check to see if the service has been disabled by the user. In this case, we should not
        // proceed modifying the enabled setting.
        val packageManager = context.packageManager
    internal val enabledInManifest: Boolean
        get() {
            return packageManager
                .getServiceInfo(
                    overlayServiceComponent,
                    PackageManager.GET_META_DATA or PackageManager.MATCH_DISABLED_COMPONENTS,
                )
                .enabled
        }

    internal val enabled: Boolean
        get() {
            // Always disabled via setting
            if (
                packageManager.getComponentEnabledSetting(overlayServiceComponent) ==
                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED
            ) {
                return false
            }

            // If the overlay is available in the manifest, then it is already available
            if (enabledInManifest) {
                return true
            }

            if (
                Flags.communalHubOnMobile() &&
                    packageManager.getComponentEnabledSetting(overlayServiceComponent) ==
                        PackageManager.COMPONENT_ENABLED_STATE_ENABLED
            ) {
                return true
            }

            return false
        }

    /**
     * This method enables the dream overlay at runtime. This method allows expanding the eligible
     * device pool during development before enabling the component in said devices' manifest.
     */
    internal fun enableIfAvailable() {
        // If the overlay is available in the manifest, then it is already available
        if (enabledInManifest) {
            return
        }

        // Enable for hub on mobile
        if (Flags.communalHubOnMobile()) {
            // Not available on TV or auto
            if (
                packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) ||
                    packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK) ||
                    packageManager.hasSystemFeature(PackageManager.FEATURE_LEANBACK_ONLY)
            ) {
                if (DEBUG) {
                    Log.d(TAG, "unsupported platform")
                }
                return
            }

            // If the component is not in the default enabled state, then don't update
            if (
                packageManager.getComponentEnabledSetting(overlayServiceComponent) !=
                    PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
            ) {
                return
            }

            packageManager.setComponentEnabledSetting(
                overlayServiceComponent,
                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
                PackageManager.DONT_KILL_APP,
            )
        }
    }

    private fun registerOverlayService() {
        // The overlay service is only registered when its component setting is enabled.
        var register = false

        try {
            register =
                packageManager
                    .getServiceInfo(overlayServiceComponent, PackageManager.GET_META_DATA)
                    .enabled
            Log.d(TAG, "trying to find component:" + overlayServiceComponent)
            // Check to see if the service has been disabled by the user. In this case, we should
            // not proceed modifying the enabled setting.
            register = enabled
        } catch (e: PackageManager.NameNotFoundException) {
            Log.e(TAG, "could not find dream overlay service")
        }
@@ -97,6 +172,7 @@ constructor(
            dreamManager.registerDreamOverlayService(
                if (currentRegisteredState) overlayServiceComponent else null
            )
            logger.logDreamOverlayEnabled(currentRegisteredState)
        } catch (e: RemoteException) {
            Log.e(TAG, "could not register dream overlay service:$e")
        }
@@ -114,6 +190,8 @@ constructor(
        context.registerReceiver(receiver, filter)

        registerOverlayService()

        enableIfAvailable()
    }

    companion object {
Loading