diff options
6 files changed, 282 insertions, 63 deletions
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java index ed214749d6a7..3c90c9372c27 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/complication/ComplicationCollectionLiveDataTest.java @@ -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()); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt new file mode 100644 index 000000000000..790df03e6401 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayRegistrantTest.kt @@ -0,0 +1,165 @@ +/* + * 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()) + } +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java index b46f2aa61518..5a1d8bbb0bf1 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java @@ -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(); @@ -134,22 +134,8 @@ public class DreamOverlayStateControllerTest extends SysuiTestCase { } @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 diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 4cdf28670eab..7b91eaecd33a 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -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 } diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt index 4400ee600268..e76fd47c74de 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayRegistrant.kt @@ -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 { diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java index 7015cc992dad..bd1fda7ad03a 100644 --- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java +++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java @@ -16,8 +16,6 @@ package com.android.systemui.dreams; -import static com.android.systemui.dreams.dagger.DreamModule.DREAM_OVERLAY_ENABLED; - import android.service.dreams.DreamService; import androidx.annotation.NonNull; @@ -46,7 +44,6 @@ import java.util.function.Consumer; import java.util.stream.Collectors; import javax.inject.Inject; -import javax.inject.Named; /** * {@link DreamOverlayStateController} is the source of truth for Dream overlay configurations and @@ -103,7 +100,6 @@ public class DreamOverlayStateController implements } private final Executor mExecutor; - private final boolean mOverlayEnabled; private final ArrayList<WeakReference<Callback>> mCallbacks = new ArrayList<>(); @Complication.ComplicationType @@ -123,12 +119,10 @@ public class DreamOverlayStateController implements @VisibleForTesting @Inject public DreamOverlayStateController(@Main Executor executor, - @Named(DREAM_OVERLAY_ENABLED) boolean overlayEnabled, FeatureFlags featureFlags, @DreamLog LogBuffer logBuffer, WeakReferenceFactory weakReferenceFactory) { mExecutor = executor; - mOverlayEnabled = overlayEnabled; mLogger = new DreamLogger(logBuffer, TAG); mFeatureFlags = featureFlags; mWeakReferenceFactory = weakReferenceFactory; @@ -138,18 +132,12 @@ public class DreamOverlayStateController implements } else { mSupportedTypes = Complication.COMPLICATION_TYPE_NONE; } - mLogger.logDreamOverlayEnabled(mOverlayEnabled); } /** * Adds a complication to be included on the dream overlay. */ public void addComplication(Complication complication) { - if (!mOverlayEnabled) { - mLogger.logIgnoreAddComplication("overlay disabled", complication.toString()); - return; - } - mExecutor.execute(() -> { if (mComplications.add(complication)) { mLogger.logAddComplication(complication.toString()); @@ -162,11 +150,6 @@ public class DreamOverlayStateController implements * Removes a complication from inclusion on the dream overlay. */ public void removeComplication(Complication complication) { - if (!mOverlayEnabled) { - mLogger.logIgnoreRemoveComplication("overlay disabled", complication.toString()); - return; - } - mExecutor.execute(() -> { if (mComplications.remove(complication)) { mLogger.logRemoveComplication(complication.toString()); @@ -264,7 +247,7 @@ public class DreamOverlayStateController implements * @return {@code true} if overlay is active, {@code false} otherwise. */ public boolean isOverlayActive() { - return mOverlayEnabled && containsState(STATE_DREAM_OVERLAY_ACTIVE); + return containsState(STATE_DREAM_OVERLAY_ACTIVE); } /** |