diff options
70 files changed, 1653 insertions, 887 deletions
diff --git a/core/java/android/hardware/display/DisplayManager.java b/core/java/android/hardware/display/DisplayManager.java index 0590a06f3f82..34e86a414533 100644 --- a/core/java/android/hardware/display/DisplayManager.java +++ b/core/java/android/hardware/display/DisplayManager.java @@ -623,6 +623,9 @@ public final class DisplayManager { * is triggered whenever the properties of a {@link android.view.Display}, such as size, * state, density are modified. * + * This event is not triggered for refresh rate changes as they can change very often. + * To monitor refresh rate changes, subscribe to {@link EVENT_TYPE_DISPLAY_REFRESH_RATE}. + * * @see #registerDisplayListener(DisplayListener, Handler, long) * */ @@ -801,6 +804,9 @@ public final class DisplayManager { * Registers a display listener to receive notifications about when * displays are added, removed or changed. * + * We encourage to use {@link #registerDisplayListener(Executor, long, DisplayListener)} + * instead to subscribe for explicit events of interest + * * @param listener The listener to register. * @param handler The handler on which the listener should be invoked, or null * if the listener should be invoked on the calling thread's looper. @@ -809,7 +815,9 @@ public final class DisplayManager { */ public void registerDisplayListener(DisplayListener listener, Handler handler) { registerDisplayListener(listener, handler, EVENT_TYPE_DISPLAY_ADDED - | EVENT_TYPE_DISPLAY_CHANGED | EVENT_TYPE_DISPLAY_REMOVED); + | EVENT_TYPE_DISPLAY_CHANGED + | EVENT_TYPE_DISPLAY_REFRESH_RATE + | EVENT_TYPE_DISPLAY_REMOVED); } /** diff --git a/core/java/android/hardware/display/DisplayManagerGlobal.java b/core/java/android/hardware/display/DisplayManagerGlobal.java index b5715ed25bd9..339dbf2c2029 100644 --- a/core/java/android/hardware/display/DisplayManagerGlobal.java +++ b/core/java/android/hardware/display/DisplayManagerGlobal.java @@ -1766,29 +1766,23 @@ public final class DisplayManagerGlobal { } if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_CHANGED) != 0) { - // For backward compatibility, a client subscribing to - // DisplayManager.EVENT_FLAG_DISPLAY_CHANGED will be enrolled to both Basic and - // RR changes - baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED - | INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE; + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED; } - if ((eventFlags - & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) { + if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REMOVED) != 0) { baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REMOVED; } - if (Flags.displayListenerPerformanceImprovements()) { - if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) { - baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE; - } + if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_REFRESH_RATE) != 0) { + baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_REFRESH_RATE; + } + if (Flags.displayListenerPerformanceImprovements()) { if ((eventFlags & DisplayManager.EVENT_TYPE_DISPLAY_STATE) != 0) { baseEventMask |= INTERNAL_EVENT_FLAG_DISPLAY_STATE; } } - return baseEventMask; } } diff --git a/core/java/android/os/PowerManager.java b/core/java/android/os/PowerManager.java index 2a5666cbe83c..e769abec7dd9 100644 --- a/core/java/android/os/PowerManager.java +++ b/core/java/android/os/PowerManager.java @@ -624,6 +624,7 @@ public final class PowerManager { WAKE_REASON_TAP, WAKE_REASON_LIFT, WAKE_REASON_BIOMETRIC, + WAKE_REASON_DOCK, }) @Retention(RetentionPolicy.SOURCE) public @interface WakeReason{} @@ -765,6 +766,12 @@ public final class PowerManager { public static final int WAKE_REASON_BIOMETRIC = 17; /** + * Wake up reason code: Waking up due to a user docking the device. + * @hide + */ + public static final int WAKE_REASON_DOCK = 18; + + /** * Convert the wake reason to a string for debugging purposes. * @hide */ @@ -788,6 +795,7 @@ public final class PowerManager { case WAKE_REASON_TAP: return "WAKE_REASON_TAP"; case WAKE_REASON_LIFT: return "WAKE_REASON_LIFT"; case WAKE_REASON_BIOMETRIC: return "WAKE_REASON_BIOMETRIC"; + case WAKE_REASON_DOCK: return "WAKE_REASON_DOCK"; default: return Integer.toString(wakeReason); } } diff --git a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java index 8fa510381060..dc2f0a69375d 100644 --- a/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java +++ b/core/tests/coretests/src/android/hardware/display/DisplayManagerGlobalTest.java @@ -307,8 +307,10 @@ public class DisplayManagerGlobalTest { assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_ADDED, mDisplayManagerGlobal .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_ADDED, 0)); - assertEquals(DISPLAY_CHANGE_EVENTS, mDisplayManagerGlobal - .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED, 0)); + assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_BASIC_CHANGED, + mDisplayManagerGlobal + .mapFiltersToInternalEventFlag(DisplayManager.EVENT_TYPE_DISPLAY_CHANGED, + 0)); assertEquals(DisplayManagerGlobal.INTERNAL_EVENT_FLAG_DISPLAY_REMOVED, mDisplayManagerGlobal.mapFiltersToInternalEventFlag( DisplayManager.EVENT_TYPE_DISPLAY_REMOVED, 0)); diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS index b6096a1acd85..a600017119e2 100644 --- a/media/java/android/media/OWNERS +++ b/media/java/android/media/OWNERS @@ -1,6 +1,7 @@ # Bug component: 1344 -fgoldfain@google.com +pshehane@google.com elaurent@google.com +etalvala@google.com lajos@google.com jmtrivi@google.com diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index dace50fafbf1..b421e69f5171 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -196,6 +196,18 @@ flag { } flag { + name: "notification_undo_guts_on_config_changed" + namespace: "systemui" + description: "Fixes a bug where a theme or font change while notification guts were open" + " (e.g. the snooze options or notification info) would show an empty notification by" + " closing the guts and undoing changes." + bug: "379267630" + metadata { + purpose: PURPOSE_BUGFIX + } +} + +flag { name: "pss_app_selector_recents_split_screen" namespace: "systemui" description: "Allows recent apps selected for partial screenshare to be launched in split screen mode" @@ -575,20 +587,6 @@ flag { } flag { - name: "clock_reactive_variants" - namespace: "systemui" - description: "Add reactive variant fonts to some clocks" - bug: "343495953" -} - -flag { - name: "lockscreen_custom_clocks" - namespace: "systemui" - description: "Enable lockscreen custom clocks" - bug: "378486437" -} - -flag { name: "faster_unlock_transition" namespace: "systemui" description: "Faster wallpaper unlock transition" diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt index 809099e0d464..eb1f1d9c52f4 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/data/repository/CommunalSettingsRepositoryImplTest.kt @@ -38,19 +38,19 @@ import com.android.systemui.communal.data.model.DisabledReason import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryImpl.Companion.GLANCEABLE_HUB_BACKGROUND_SETTING import com.android.systemui.communal.domain.interactor.setCommunalV2Enabled import com.android.systemui.communal.shared.model.CommunalBackgroundType -import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.communal.shared.model.WhenToDream import com.android.systemui.flags.Flags.COMMUNAL_SERVICE_ENABLED import com.android.systemui.flags.fakeFeatureFlagsClassic +import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.collectLastValue import com.android.systemui.kosmos.runTest -import com.android.systemui.kosmos.testScope +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.testKosmos import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertWithMessage -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith @@ -62,9 +62,11 @@ import platform.test.runner.parameterized.Parameters @RunWith(ParameterizedAndroidJunit4::class) class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiTestCase() { private val kosmos = - testKosmos().apply { mainResources = mContext.orCreateTestableResources.resources } - private val testScope = kosmos.testScope - private lateinit var underTest: CommunalSettingsRepository + testKosmos() + .apply { mainResources = mContext.orCreateTestableResources.resources } + .useUnconfinedTestDispatcher() + + private val Kosmos.underTest by Kosmos.Fixture { communalSettingsRepository } init { mSetFlagsRule.setFlagsParameterization(flags!!) @@ -76,98 +78,105 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_FEATURES_NONE) setKeyguardFeaturesDisabled(SECONDARY_USER, KEYGUARD_DISABLE_FEATURES_NONE) setKeyguardFeaturesDisabled(WORK_PROFILE, KEYGUARD_DISABLE_FEATURES_NONE) - underTest = kosmos.communalSettingsRepository } @EnableFlags(FLAG_COMMUNAL_HUB) @DisableFlags(FLAG_GLANCEABLE_HUB_V2) @Test - fun getFlagEnabled_bothEnabled() { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + fun getFlagEnabled_bothEnabled() = + kosmos.runTest { + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) - assertThat(underTest.getFlagEnabled()).isTrue() - } + assertThat(underTest.getFlagEnabled()).isTrue() + } @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) @Test - fun getFlagEnabled_bothDisabled() { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + fun getFlagEnabled_bothDisabled() = + kosmos.runTest { + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) - assertThat(underTest.getFlagEnabled()).isFalse() - } + assertThat(underTest.getFlagEnabled()).isFalse() + } @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) @Test - fun getFlagEnabled_onlyClassicFlagEnabled() { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) + fun getFlagEnabled_onlyClassicFlagEnabled() = + kosmos.runTest { + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, true) - assertThat(underTest.getFlagEnabled()).isFalse() - } + assertThat(underTest.getFlagEnabled()).isFalse() + } @EnableFlags(FLAG_COMMUNAL_HUB) @DisableFlags(FLAG_GLANCEABLE_HUB_V2) @Test - fun getFlagEnabled_onlyTrunkFlagEnabled() { - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + fun getFlagEnabled_onlyTrunkFlagEnabled() = + kosmos.runTest { + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) - assertThat(underTest.getFlagEnabled()).isFalse() - } + assertThat(underTest.getFlagEnabled()).isFalse() + } @EnableFlags(FLAG_GLANCEABLE_HUB_V2) @DisableFlags(FLAG_COMMUNAL_HUB) @Test - fun getFlagEnabled_mobileConfigEnabled() { - mContext.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_glanceableHubEnabled, - true, - ) + fun getFlagEnabled_mobileConfigEnabled() = + kosmos.runTest { + mContext.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_glanceableHubEnabled, + true, + ) - assertThat(underTest.getFlagEnabled()).isTrue() - } + assertThat(underTest.getFlagEnabled()).isTrue() + } @DisableFlags(FLAG_GLANCEABLE_HUB_V2, FLAG_COMMUNAL_HUB) @Test - fun getFlagEnabled_onlyMobileConfigEnabled() { - mContext.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_glanceableHubEnabled, - true, - ) + fun getFlagEnabled_onlyMobileConfigEnabled() = + kosmos.runTest { + mContext.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_glanceableHubEnabled, + true, + ) - assertThat(underTest.getFlagEnabled()).isFalse() - } + assertThat(underTest.getFlagEnabled()).isFalse() + } @EnableFlags(FLAG_GLANCEABLE_HUB_V2) @DisableFlags(FLAG_COMMUNAL_HUB) @Test - fun getFlagEnabled_onlyMobileFlagEnabled() { - mContext.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_glanceableHubEnabled, - false, - ) + fun getFlagEnabled_onlyMobileFlagEnabled() = + kosmos.runTest { + mContext.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_glanceableHubEnabled, + false, + ) - assertThat(underTest.getFlagEnabled()).isFalse() - } + assertThat(underTest.getFlagEnabled()).isFalse() + } @EnableFlags(FLAG_GLANCEABLE_HUB_V2) @DisableFlags(FLAG_COMMUNAL_HUB) @Test - fun getFlagEnabled_oldFlagIgnored() { - // New config flag enabled. - mContext.orCreateTestableResources.addOverride( - com.android.internal.R.bool.config_glanceableHubEnabled, - true, - ) + fun getFlagEnabled_oldFlagIgnored() = + kosmos.runTest { + // New config flag enabled. + mContext.orCreateTestableResources.addOverride( + com.android.internal.R.bool.config_glanceableHubEnabled, + true, + ) - // Old config flag disabled. - kosmos.fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) + // Old config flag disabled. + fakeFeatureFlagsClassic.set(COMMUNAL_SERVICE_ENABLED, false) - assertThat(underTest.getFlagEnabled()).isTrue() - } + assertThat(underTest.getFlagEnabled()).isTrue() + } @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun secondaryUserIsInvalid() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getEnabledState(SECONDARY_USER)) assertThat(enabledState?.enabled).isFalse() @@ -187,7 +196,7 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @DisableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) @Test fun communalHubFlagIsDisabled() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledState?.enabled).isFalse() assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_FLAG) @@ -196,35 +205,23 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun hubIsDisabledByUser() = - testScope.runTest { - kosmos.fakeSettings.putIntForUser( - Settings.Secure.GLANCEABLE_HUB_ENABLED, - 0, - PRIMARY_USER.id, - ) + kosmos.runTest { + fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id) val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledState?.enabled).isFalse() assertThat(enabledState).containsExactly(DisabledReason.DISABLED_REASON_USER_SETTING) - kosmos.fakeSettings.putIntForUser( - Settings.Secure.GLANCEABLE_HUB_ENABLED, - 1, - SECONDARY_USER.id, - ) + fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, SECONDARY_USER.id) assertThat(enabledState?.enabled).isFalse() - kosmos.fakeSettings.putIntForUser( - Settings.Secure.GLANCEABLE_HUB_ENABLED, - 1, - PRIMARY_USER.id, - ) + fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 1, PRIMARY_USER.id) assertThat(enabledState?.enabled).isTrue() } @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun hubIsDisabledByDevicePolicy() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledState?.enabled).isTrue() @@ -236,7 +233,7 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun widgetsAllowedForWorkProfile_isFalse_whenDisallowedByDevicePolicy() = - testScope.runTest { + kosmos.runTest { val widgetsAllowedForWorkProfile by collectLastValue(underTest.getAllowedByDevicePolicy(WORK_PROFILE)) assertThat(widgetsAllowedForWorkProfile).isTrue() @@ -248,7 +245,7 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun hubIsEnabled_whenDisallowedByDevicePolicyForWorkProfile() = - testScope.runTest { + kosmos.runTest { val enabledStateForPrimaryUser by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledStateForPrimaryUser?.enabled).isTrue() @@ -260,15 +257,11 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @EnableFlags(FLAG_COMMUNAL_HUB) @Test fun hubIsDisabledByUserAndDevicePolicy() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getEnabledState(PRIMARY_USER)) assertThat(enabledState?.enabled).isTrue() - kosmos.fakeSettings.putIntForUser( - Settings.Secure.GLANCEABLE_HUB_ENABLED, - 0, - PRIMARY_USER.id, - ) + fakeSettings.putIntForUser(Settings.Secure.GLANCEABLE_HUB_ENABLED, 0, PRIMARY_USER.id) setKeyguardFeaturesDisabled(PRIMARY_USER, KEYGUARD_DISABLE_WIDGETS_ALL) assertThat(enabledState?.enabled).isFalse() @@ -282,17 +275,17 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @Test @DisableFlags(FLAG_GLANCEABLE_HUB_BLURRED_BACKGROUND) fun backgroundType_defaultValue() = - testScope.runTest { + kosmos.runTest { val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER)) assertThat(backgroundType).isEqualTo(CommunalBackgroundType.ANIMATED) } @Test fun backgroundType_verifyAllValues() = - testScope.runTest { + kosmos.runTest { val backgroundType by collectLastValue(underTest.getBackground(PRIMARY_USER)) for (type in CommunalBackgroundType.entries) { - kosmos.fakeSettings.putIntForUser( + fakeSettings.putIntForUser( GLANCEABLE_HUB_BACKGROUND_SETTING, type.value, PRIMARY_USER.id, @@ -308,30 +301,71 @@ class CommunalSettingsRepositoryImplTest(flags: FlagsParameterization?) : SysuiT @Test fun screensaverDisabledByUser() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) - kosmos.fakeSettings.putIntForUser( - Settings.Secure.SCREENSAVER_ENABLED, - 0, - PRIMARY_USER.id, - ) + fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 0, PRIMARY_USER.id) assertThat(enabledState).isFalse() } @Test fun screensaverEnabledByUser() = - testScope.runTest { + kosmos.runTest { val enabledState by collectLastValue(underTest.getScreensaverEnabledState(PRIMARY_USER)) - kosmos.fakeSettings.putIntForUser( - Settings.Secure.SCREENSAVER_ENABLED, + fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ENABLED, 1, PRIMARY_USER.id) + + assertThat(enabledState).isTrue() + } + + @Test + fun whenToDream_charging() = + kosmos.runTest { + val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER)) + + fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 1, PRIMARY_USER.id, ) - assertThat(enabledState).isTrue() + assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_CHARGING) + } + + @Test + fun whenToDream_docked() = + kosmos.runTest { + val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER)) + + fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, + 1, + PRIMARY_USER.id, + ) + + assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_DOCKED) + } + + @Test + fun whenToDream_postured() = + kosmos.runTest { + val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER)) + + fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + 1, + PRIMARY_USER.id, + ) + + assertThat(whenToDreamState).isEqualTo(WhenToDream.WHILE_POSTURED) + } + + @Test + fun whenToDream_default() = + kosmos.runTest { + val whenToDreamState by collectLastValue(underTest.getWhenToDreamState(PRIMARY_USER)) + assertThat(whenToDreamState).isEqualTo(WhenToDream.NEVER) } private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt index 7ae0577bd289..c9e7a5d7df05 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt @@ -38,13 +38,9 @@ import com.android.systemui.Flags.FLAG_COMMUNAL_WIDGET_RESIZING import com.android.systemui.Flags.FLAG_GLANCEABLE_HUB_V2 import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.common.data.repository.batteryRepository +import com.android.systemui.common.data.repository.fake import com.android.systemui.communal.data.model.CommunalSmartspaceTimer -import com.android.systemui.communal.data.repository.FakeCommunalMediaRepository -import com.android.systemui.communal.data.repository.FakeCommunalPrefsRepository -import com.android.systemui.communal.data.repository.FakeCommunalSceneRepository -import com.android.systemui.communal.data.repository.FakeCommunalSmartspaceRepository -import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository -import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository import com.android.systemui.communal.data.repository.fakeCommunalMediaRepository import com.android.systemui.communal.data.repository.fakeCommunalPrefsRepository import com.android.systemui.communal.data.repository.fakeCommunalSceneRepository @@ -53,52 +49,49 @@ import com.android.systemui.communal.data.repository.fakeCommunalTutorialReposit import com.android.systemui.communal.data.repository.fakeCommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel +import com.android.systemui.communal.posturing.data.repository.fake +import com.android.systemui.communal.posturing.data.repository.posturingRepository +import com.android.systemui.communal.posturing.shared.model.PosturedState import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.EditModeState -import com.android.systemui.communal.widgets.EditWidgetsActivityStarter -import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.dock.DockManager +import com.android.systemui.dock.fakeDockManager import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic -import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope -import com.android.systemui.plugins.ActivityStarter +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.plugins.activityStarter -import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Scenes -import com.android.systemui.settings.FakeUserTracker import com.android.systemui.settings.fakeUserTracker import com.android.systemui.statusbar.phone.fakeManagedProfileController import com.android.systemui.testKosmos -import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.argumentCaptor import com.android.systemui.util.mockito.capture import com.android.systemui.util.mockito.nullable import com.android.systemui.util.mockito.whenever -import com.android.systemui.utils.leaks.FakeManagedProfileController +import com.android.systemui.util.settings.fakeSettings import com.google.common.truth.Truth.assertThat -import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.test.advanceTimeBy -import kotlinx.coroutines.test.runCurrent -import kotlinx.coroutines.test.runTest import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.anyInt import org.mockito.ArgumentMatchers.eq -import org.mockito.Mock import org.mockito.Mockito.mock import org.mockito.Mockito.verify -import org.mockito.MockitoAnnotations import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @@ -107,32 +100,15 @@ import platform.test.runner.parameterized.Parameters * [CommunalInteractorCommunalDisabledTest]. */ @SmallTest -@OptIn(ExperimentalCoroutinesApi::class) @RunWith(ParameterizedAndroidJunit4::class) class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { - @Mock private lateinit var mainUser: UserInfo - @Mock private lateinit var secondaryUser: UserInfo - - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - - private lateinit var tutorialRepository: FakeCommunalTutorialRepository - private lateinit var communalRepository: FakeCommunalSceneRepository - private lateinit var mediaRepository: FakeCommunalMediaRepository - private lateinit var widgetRepository: FakeCommunalWidgetRepository - private lateinit var smartspaceRepository: FakeCommunalSmartspaceRepository - private lateinit var userRepository: FakeUserRepository - private lateinit var keyguardRepository: FakeKeyguardRepository - private lateinit var communalPrefsRepository: FakeCommunalPrefsRepository - private lateinit var editWidgetsActivityStarter: EditWidgetsActivityStarter - private lateinit var sceneInteractor: SceneInteractor - private lateinit var communalSceneInteractor: CommunalSceneInteractor - private lateinit var userTracker: FakeUserTracker - private lateinit var activityStarter: ActivityStarter - private lateinit var userManager: UserManager - private lateinit var managedProfileController: FakeManagedProfileController - - private lateinit var underTest: CommunalInteractor + private val mainUser = + UserInfo(/* id= */ 0, /* name= */ "primary user", /* flags= */ UserInfo.FLAG_MAIN) + private val secondaryUser = UserInfo(/* id= */ 1, /* name= */ "secondary user", /* flags= */ 0) + + private val kosmos = testKosmos().useUnconfinedTestDispatcher() + + private val Kosmos.underTest by Kosmos.Fixture { communalInteractor } init { mSetFlagsRule.setFlagsParameterization(flags) @@ -140,128 +116,104 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Before fun setUp() { - MockitoAnnotations.initMocks(this) - - tutorialRepository = kosmos.fakeCommunalTutorialRepository - communalRepository = kosmos.fakeCommunalSceneRepository - mediaRepository = kosmos.fakeCommunalMediaRepository - widgetRepository = kosmos.fakeCommunalWidgetRepository - smartspaceRepository = kosmos.fakeCommunalSmartspaceRepository - userRepository = kosmos.fakeUserRepository - keyguardRepository = kosmos.fakeKeyguardRepository - editWidgetsActivityStarter = kosmos.editWidgetsActivityStarter - communalPrefsRepository = kosmos.fakeCommunalPrefsRepository - sceneInteractor = kosmos.sceneInteractor - communalSceneInteractor = kosmos.communalSceneInteractor - userTracker = kosmos.fakeUserTracker - activityStarter = kosmos.activityStarter - userManager = kosmos.userManager - managedProfileController = kosmos.fakeManagedProfileController - - whenever(mainUser.isMain).thenReturn(true) - whenever(secondaryUser.isMain).thenReturn(false) - whenever(userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false) - whenever(userManager.isManagedProfile(anyInt())).thenReturn(false) - userRepository.setUserInfos(listOf(mainUser, secondaryUser)) + whenever(kosmos.userManager.isQuietModeEnabled(any<UserHandle>())).thenReturn(false) + whenever(kosmos.userManager.isManagedProfile(anyInt())).thenReturn(false) + kosmos.fakeUserRepository.setUserInfos(listOf(mainUser, secondaryUser)) kosmos.fakeFeatureFlagsClassic.set(Flags.COMMUNAL_SERVICE_ENABLED, true) mSetFlagsRule.enableFlags(FLAG_COMMUNAL_HUB) - - underTest = kosmos.communalInteractor } @Test fun communalEnabled_true() = - testScope.runTest { - userRepository.setSelectedUserInfo(mainUser) - runCurrent() + kosmos.runTest { + fakeUserRepository.setSelectedUserInfo(mainUser) assertThat(underTest.isCommunalEnabled.value).isTrue() } @Test fun isCommunalAvailable_storageUnlockedAndMainUser_true() = - testScope.runTest { + kosmos.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - keyguardRepository.setIsEncryptedOrLockdown(false) - userRepository.setSelectedUserInfo(mainUser) - keyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isTrue() } @Test fun isCommunalAvailable_storageLockedAndMainUser_false() = - testScope.runTest { + kosmos.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - keyguardRepository.setIsEncryptedOrLockdown(true) - userRepository.setSelectedUserInfo(mainUser) - keyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setIsEncryptedOrLockdown(true) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isFalse() } @Test fun isCommunalAvailable_storageUnlockedAndSecondaryUser_false() = - testScope.runTest { + kosmos.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - keyguardRepository.setIsEncryptedOrLockdown(false) - userRepository.setSelectedUserInfo(secondaryUser) - keyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(secondaryUser) + fakeKeyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isFalse() } @Test fun isCommunalAvailable_whenKeyguardShowing_true() = - testScope.runTest { + kosmos.runTest { val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - keyguardRepository.setIsEncryptedOrLockdown(false) - userRepository.setSelectedUserInfo(mainUser) - keyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isTrue() } @Test fun isCommunalAvailable_communalDisabled_false() = - testScope.runTest { + kosmos.runTest { mSetFlagsRule.disableFlags(FLAG_COMMUNAL_HUB, FLAG_GLANCEABLE_HUB_V2) val isAvailable by collectLastValue(underTest.isCommunalAvailable) assertThat(isAvailable).isFalse() - keyguardRepository.setIsEncryptedOrLockdown(false) - userRepository.setSelectedUserInfo(mainUser) - keyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) assertThat(isAvailable).isFalse() } @Test fun widget_tutorialCompletedAndWidgetsAvailable_showWidgetContent() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // Widgets available. - widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) - widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) - widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) val widgetContent by collectLastValue(underTest.widgetContent) @@ -356,18 +308,18 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { totalTargets: Int, expectedSizes: List<CommunalContentSize>, ) = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) val targets = mutableListOf<CommunalSmartspaceTimer>() for (index in 0 until totalTargets) { targets.add(smartspaceTimer(index.toString())) } - smartspaceRepository.setTimers(targets) + fakeCommunalSmartspaceRepository.setTimers(targets) val smartspaceContent by collectLastValue(underTest.ongoingContent(false)) assertThat(smartspaceContent?.size).isEqualTo(totalTargets) @@ -378,12 +330,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun umo_mediaPlaying_showsUmo() = - testScope.runTest { + kosmos.runTest { // Tutorial completed. - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) // Media is playing. - mediaRepository.mediaActive() + fakeCommunalMediaRepository.mediaActive() val umoContent by collectLastValue(underTest.ongoingContent(true)) @@ -394,12 +346,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun umo_mediaPlaying_mediaHostNotVisible_hidesUmo() = - testScope.runTest { + kosmos.runTest { // Tutorial completed. - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) // Media is playing. - mediaRepository.mediaActive() + fakeCommunalMediaRepository.mediaActive() val umoContent by collectLastValue(underTest.ongoingContent(false)) assertThat(umoContent?.size).isEqualTo(0) @@ -409,26 +361,26 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun ongoing_shouldOrderAndSizeByTimestamp() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) // Timer1 started val timer1 = smartspaceTimer("timer1", timestamp = 1L) - smartspaceRepository.setTimers(listOf(timer1)) + fakeCommunalSmartspaceRepository.setTimers(listOf(timer1)) // Umo started - mediaRepository.mediaActive(timestamp = 2L) + fakeCommunalMediaRepository.mediaActive(timestamp = 2L) // Timer2 started val timer2 = smartspaceTimer("timer2", timestamp = 3L) - smartspaceRepository.setTimers(listOf(timer1, timer2)) + fakeCommunalSmartspaceRepository.setTimers(listOf(timer1, timer2)) // Timer3 started val timer3 = smartspaceTimer("timer3", timestamp = 4L) - smartspaceRepository.setTimers(listOf(timer1, timer2, timer3)) + fakeCommunalSmartspaceRepository.setTimers(listOf(timer1, timer2, timer3)) val ongoingContent by collectLastValue(underTest.ongoingContent(true)) assertThat(ongoingContent?.size).isEqualTo(4) @@ -447,8 +399,8 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun ctaTile_showsByDefault() = - testScope.runTest { - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + kosmos.runTest { + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) val ctaTileContent by collectLastValue(underTest.ctaTileContent) @@ -461,14 +413,13 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun ctaTile_afterDismiss_doesNotShow() = - testScope.runTest { + kosmos.runTest { // Set to main user, so we can dismiss the tile for the main user. - val user = userRepository.asMainUser() - userTracker.set(userInfos = listOf(user), selectedUserIndex = 0) - runCurrent() + val user = fakeUserRepository.asMainUser() + fakeUserTracker.set(userInfos = listOf(user), selectedUserIndex = 0) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) - communalPrefsRepository.setCtaDismissed(user) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeCommunalPrefsRepository.setCtaDismissed(user) val ctaTileContent by collectLastValue(underTest.ctaTileContent) @@ -477,36 +428,30 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun listensToSceneChange() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) - runCurrent() - var desiredScene = collectLastValue(underTest.desiredScene) - runCurrent() - assertThat(desiredScene()).isEqualTo(CommunalScenes.Blank) + val desiredScene by collectLastValue(underTest.desiredScene) + assertThat(desiredScene).isEqualTo(CommunalScenes.Blank) val targetScene = CommunalScenes.Communal - communalRepository.changeScene(targetScene) - desiredScene = collectLastValue(underTest.desiredScene) - runCurrent() - assertThat(desiredScene()).isEqualTo(targetScene) + fakeCommunalSceneRepository.changeScene(targetScene) + assertThat(desiredScene).isEqualTo(targetScene) } @Test fun updatesScene() = - testScope.runTest { + kosmos.runTest { val targetScene = CommunalScenes.Communal - underTest.changeScene(targetScene, "test") - val desiredScene = collectLastValue(communalRepository.currentScene) - runCurrent() - assertThat(desiredScene()).isEqualTo(targetScene) + val desiredScene by collectLastValue(fakeCommunalSceneRepository.currentScene) + assertThat(desiredScene).isEqualTo(targetScene) } @Test fun transitionProgress_onTargetScene_fullProgress() = - testScope.runTest { + kosmos.runTest { val targetScene = CommunalScenes.Blank val transitionProgressFlow = underTest.transitionProgressToScene(targetScene) val transitionProgress by collectLastValue(transitionProgressFlow) @@ -524,7 +469,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun transitionProgress_notOnTargetScene_noProgress() = - testScope.runTest { + kosmos.runTest { val targetScene = CommunalScenes.Blank val currentScene = CommunalScenes.Communal val transitionProgressFlow = underTest.transitionProgressToScene(targetScene) @@ -543,7 +488,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun transitionProgress_transitioningToTrackedScene() = - testScope.runTest { + kosmos.runTest { val currentScene = CommunalScenes.Communal val targetScene = CommunalScenes.Blank val transitionProgressFlow = underTest.transitionProgressToScene(targetScene) @@ -591,7 +536,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun transitionProgress_transitioningAwayFromTrackedScene() = - testScope.runTest { + kosmos.runTest { val currentScene = CommunalScenes.Blank val targetScene = CommunalScenes.Communal val transitionProgressFlow = underTest.transitionProgressToScene(currentScene) @@ -642,52 +587,42 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun isCommunalShowing() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) - runCurrent() - var isCommunalShowing = collectLastValue(underTest.isCommunalShowing) - runCurrent() - assertThat(isCommunalShowing()).isEqualTo(false) + val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) + assertThat(isCommunalShowing).isEqualTo(false) underTest.changeScene(CommunalScenes.Communal, "test") - - isCommunalShowing = collectLastValue(underTest.isCommunalShowing) - runCurrent() - assertThat(isCommunalShowing()).isEqualTo(true) + assertThat(isCommunalShowing).isEqualTo(true) } @Test fun isCommunalShowing_whenSceneContainerDisabled() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) - runCurrent() // Verify default is false val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) - runCurrent() assertThat(isCommunalShowing).isFalse() // Verify scene changes with the flag doesn't have any impact sceneInteractor.changeScene(Scenes.Communal, loggingReason = "") - runCurrent() assertThat(isCommunalShowing).isFalse() // Verify scene changes (without the flag) to communal sets the value to true underTest.changeScene(CommunalScenes.Communal, "test") - runCurrent() assertThat(isCommunalShowing).isTrue() // Verify scene changes (without the flag) to blank sets the value back to false underTest.changeScene(CommunalScenes.Blank, "test") - runCurrent() assertThat(isCommunalShowing).isFalse() } @Test @EnableSceneContainer fun isCommunalShowing_whenSceneContainerEnabled() = - testScope.runTest { + kosmos.runTest { // Verify default is false val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) assertThat(isCommunalShowing).isFalse() @@ -704,7 +639,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test @EnableSceneContainer fun isCommunalShowing_whenSceneContainerEnabledAndChangeToLegacyScene() = - testScope.runTest { + kosmos.runTest { // Verify default is false val isCommunalShowing by collectLastValue(underTest.isCommunalShowing) assertThat(isCommunalShowing).isFalse() @@ -720,21 +655,19 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun isIdleOnCommunal() = - testScope.runTest { + kosmos.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(CommunalScenes.Blank) ) - communalRepository.setTransitionState(transitionState) + fakeCommunalSceneRepository.setTransitionState(transitionState) // isIdleOnCommunal is false when not on communal. val isIdleOnCommunal by collectLastValue(underTest.isIdleOnCommunal) - runCurrent() assertThat(isIdleOnCommunal).isEqualTo(false) // Transition to communal. transitionState.value = ObservableTransitionState.Idle(CommunalScenes.Communal) - runCurrent() // isIdleOnCommunal is now true since we're on communal. assertThat(isIdleOnCommunal).isEqualTo(true) @@ -749,7 +682,6 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { isInitiatedByUserInput = false, isUserInputOngoing = flowOf(false), ) - runCurrent() // isIdleOnCommunal turns false as soon as transition away starts. assertThat(isIdleOnCommunal).isEqualTo(false) @@ -757,12 +689,12 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun isCommunalVisible() = - testScope.runTest { + kosmos.runTest { val transitionState = MutableStateFlow<ObservableTransitionState>( ObservableTransitionState.Idle(CommunalScenes.Blank) ) - communalRepository.setTransitionState(transitionState) + fakeCommunalSceneRepository.setTransitionState(transitionState) // isCommunalVisible is false when not on communal. val isCommunalVisible by collectLastValue(underTest.isCommunalVisible) @@ -805,7 +737,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun testShowWidgetEditorStartsActivity() = - testScope.runTest { + kosmos.runTest { val editModeState by collectLastValue(communalSceneInteractor.editModeState) underTest.showWidgetEditor() @@ -816,14 +748,14 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showWidgetEditor_openWidgetPickerOnStart_startsActivity() = - testScope.runTest { + kosmos.runTest { underTest.showWidgetEditor(shouldOpenWidgetPickerOnStart = true) verify(editWidgetsActivityStarter).startActivity(shouldOpenWidgetPickerOnStart = true) } @Test fun navigateToCommunalWidgetSettings_startsActivity() = - testScope.runTest { + kosmos.runTest { underTest.navigateToCommunalWidgetSettings() val intentCaptor = argumentCaptor<Intent>() verify(activityStarter) @@ -833,23 +765,22 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun filterWidgets_whenUserProfileRemoved() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) // Only main user exists. val userInfos = listOf(MAIN_USER_INFO) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) val widgetContent by collectLastValue(underTest.widgetContent) // Given three widgets, and one of them is associated with pre-existing work profile. - widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) - widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) - widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) // One widget is filtered out and the remaining two link to main user id. assertThat(checkNotNull(widgetContent).size).isEqualTo(2) @@ -867,17 +798,16 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun widgetContent_inQuietMode() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) // Work profile is set up. val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // When work profile is paused. whenever(userManager.isQuietModeEnabled(eq(UserHandle.of(USER_INFO_WORK.id)))) @@ -885,9 +815,9 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { whenever(userManager.isManagedProfile(eq(USER_INFO_WORK.id))).thenReturn(true) val widgetContent by collectLastValue(underTest.widgetContent) - widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) - widgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) - widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 2, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) // The work profile widget is in quiet mode, while other widgets are not. assertThat(widgetContent).hasSize(3) @@ -911,23 +841,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun filterWidgets_whenDisallowedByDevicePolicyForWorkProfile() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - userRepository.setSelectedUserInfo(MAIN_USER_INFO) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) + fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO) val widgetContent by collectLastValue(underTest.widgetContent) // One available work widget, one pending work widget, and one regular available widget. - widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) - widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id) - widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) + fakeCommunalWidgetRepository.addPendingWidget( + appWidgetId = 2, + userId = USER_INFO_WORK.id, + ) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) setKeyguardFeaturesDisabled( USER_INFO_WORK, @@ -941,23 +873,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun filterWidgets_whenAllowedByDevicePolicyForWorkProfile() = - testScope.runTest { + kosmos.runTest { // Keyguard showing, and tutorial completed. - keyguardRepository.setKeyguardShowing(true) - keyguardRepository.setKeyguardOccluded(false) - tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeKeyguardRepository.setKeyguardOccluded(false) + fakeCommunalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED) val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - userRepository.setSelectedUserInfo(MAIN_USER_INFO) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) + fakeUserRepository.setSelectedUserInfo(MAIN_USER_INFO) val widgetContent by collectLastValue(underTest.widgetContent) // Given three widgets, and one of them is associated with work profile. - widgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) - widgetRepository.addPendingWidget(appWidgetId = 2, userId = USER_INFO_WORK.id) - widgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 1, userId = USER_INFO_WORK.id) + fakeCommunalWidgetRepository.addPendingWidget( + appWidgetId = 2, + userId = USER_INFO_WORK.id, + ) + fakeCommunalWidgetRepository.addWidget(appWidgetId = 3, userId = MAIN_USER_INFO.id) setKeyguardFeaturesDisabled( USER_INFO_WORK, @@ -973,7 +907,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalFromOccluded_enteredOccludedFromHub() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) assertThat(showCommunalFromOccluded).isFalse() @@ -989,7 +923,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalFromOccluded_enteredOccludedFromLockscreen() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) assertThat(showCommunalFromOccluded).isFalse() @@ -1005,7 +939,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalFromOccluded_communalBecomesUnavailableWhileOccluded() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) assertThat(showCommunalFromOccluded).isFalse() @@ -1015,7 +949,6 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { to = KeyguardState.OCCLUDED, testScope, ) - runCurrent() kosmos.setCommunalAvailable(false) assertThat(showCommunalFromOccluded).isFalse() @@ -1023,7 +956,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalFromOccluded_showBouncerWhileOccluded() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) assertThat(showCommunalFromOccluded).isFalse() @@ -1033,7 +966,6 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { to = KeyguardState.OCCLUDED, testScope, ) - runCurrent() kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps( from = KeyguardState.OCCLUDED, to = KeyguardState.PRIMARY_BOUNCER, @@ -1045,7 +977,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun showCommunalFromOccluded_enteredOccludedFromDreaming() = - testScope.runTest { + kosmos.runTest { kosmos.setCommunalAvailable(true) val showCommunalFromOccluded by collectLastValue(underTest.showCommunalFromOccluded) assertThat(showCommunalFromOccluded).isFalse() @@ -1069,7 +1001,7 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun dismissDisclaimerSetsDismissedFlag() = - testScope.runTest { + kosmos.runTest { val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed) assertThat(disclaimerDismissed).isFalse() underTest.setDisclaimerDismissed() @@ -1078,17 +1010,17 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun dismissDisclaimerTimeoutResetsDismissedFlag() = - testScope.runTest { + kosmos.runTest { val disclaimerDismissed by collectLastValue(underTest.isDisclaimerDismissed) underTest.setDisclaimerDismissed() assertThat(disclaimerDismissed).isTrue() - advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS) + testScope.advanceTimeBy(CommunalInteractor.DISCLAIMER_RESET_MILLIS) assertThat(disclaimerDismissed).isFalse() } @Test fun settingSelectedKey_flowUpdated() { - testScope.runTest { + kosmos.runTest { val key = "test" val selectedKey by collectLastValue(underTest.selectedKey) underTest.setSelectedKey(key) @@ -1098,36 +1030,35 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test fun unpauseWorkProfileEnablesWorkMode() = - testScope.runTest { + kosmos.runTest { underTest.unpauseWorkProfile() - assertThat(managedProfileController.isWorkModeEnabled()).isTrue() + assertThat(fakeManagedProfileController.isWorkModeEnabled()).isTrue() } @Test @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING) @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun resizeWidget_withoutUpdatingOrder() = - testScope.runTest { + kosmos.runTest { val userInfos = listOf(MAIN_USER_INFO) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // Widgets available. - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 1, userId = MAIN_USER_INFO.id, rank = 0, spanY = CommunalContentSize.FixedSize.HALF.span, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 2, userId = MAIN_USER_INFO.id, rank = 1, spanY = CommunalContentSize.FixedSize.HALF.span, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 3, userId = MAIN_USER_INFO.id, rank = 2, @@ -1159,26 +1090,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID) fun resizeWidget_withoutUpdatingOrder_responsive() = - testScope.runTest { + kosmos.runTest { val userInfos = listOf(MAIN_USER_INFO) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // Widgets available. - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 1, userId = MAIN_USER_INFO.id, rank = 0, spanY = 1, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 2, userId = MAIN_USER_INFO.id, rank = 1, spanY = 1, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 3, userId = MAIN_USER_INFO.id, rank = 2, @@ -1211,26 +1141,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING) @DisableFlags(FLAG_COMMUNAL_RESPONSIVE_GRID) fun resizeWidget_andUpdateOrder() = - testScope.runTest { + kosmos.runTest { val userInfos = listOf(MAIN_USER_INFO) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // Widgets available. - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 1, userId = MAIN_USER_INFO.id, rank = 0, spanY = CommunalContentSize.FixedSize.HALF.span, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 2, userId = MAIN_USER_INFO.id, rank = 1, spanY = CommunalContentSize.FixedSize.HALF.span, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 3, userId = MAIN_USER_INFO.id, rank = 2, @@ -1266,26 +1195,25 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { @Test @EnableFlags(FLAG_COMMUNAL_WIDGET_RESIZING, FLAG_COMMUNAL_RESPONSIVE_GRID) fun resizeWidget_andUpdateOrder_responsive() = - testScope.runTest { + kosmos.runTest { val userInfos = listOf(MAIN_USER_INFO) - userRepository.setUserInfos(userInfos) - userTracker.set(userInfos = userInfos, selectedUserIndex = 0) - runCurrent() + fakeUserRepository.setUserInfos(userInfos) + fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) // Widgets available. - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 1, userId = MAIN_USER_INFO.id, rank = 0, spanY = 1, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 2, userId = MAIN_USER_INFO.id, rank = 1, spanY = 1, ) - widgetRepository.addWidget( + fakeCommunalWidgetRepository.addWidget( appWidgetId = 3, userId = MAIN_USER_INFO.id, rank = 2, @@ -1318,6 +1246,66 @@ class CommunalInteractorTest(flags: FlagsParameterization) : SysuiTestCase() { .inOrder() } + @Test + fun showCommunalWhileCharging() = + kosmos.runTest { + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, + 1, + mainUser.id, + ) + + val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal) + batteryRepository.fake.setDevicePluggedIn(false) + assertThat(shouldShowCommunal).isFalse() + + batteryRepository.fake.setDevicePluggedIn(true) + assertThat(shouldShowCommunal).isTrue() + } + + @Test + fun showCommunalWhilePosturedAndCharging() = + kosmos.runTest { + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeSettings.putIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + 1, + mainUser.id, + ) + + val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal) + batteryRepository.fake.setDevicePluggedIn(true) + posturingRepository.fake.setPosturedState(PosturedState.NotPostured) + assertThat(shouldShowCommunal).isFalse() + + posturingRepository.fake.setPosturedState(PosturedState.Postured(1f)) + assertThat(shouldShowCommunal).isTrue() + } + + @Test + fun showCommunalWhileDocked() = + kosmos.runTest { + fakeKeyguardRepository.setIsEncryptedOrLockdown(false) + fakeUserRepository.setSelectedUserInfo(mainUser) + fakeKeyguardRepository.setKeyguardShowing(true) + fakeSettings.putIntForUser(Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, 1, mainUser.id) + + batteryRepository.fake.setDevicePluggedIn(true) + fakeDockManager.setIsDocked(false) + + val shouldShowCommunal by collectLastValue(underTest.shouldShowCommunal) + assertThat(shouldShowCommunal).isFalse() + + fakeDockManager.setIsDocked(true) + fakeDockManager.setDockEvent(DockManager.STATE_DOCKED) + assertThat(shouldShowCommunal).isTrue() + } + private fun setKeyguardFeaturesDisabled(user: UserInfo, disabledFlags: Int) { whenever(kosmos.devicePolicyManager.getKeyguardDisabledFeatures(nullable(), eq(user.id))) .thenReturn(disabledFlags) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt index e4916b1a7e46..310bf6486413 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractorTest.kt @@ -21,19 +21,17 @@ import android.app.admin.devicePolicyManager import android.content.Intent import android.content.pm.UserInfo import android.os.UserManager -import android.os.userManager import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.broadcast.broadcastDispatcher -import com.android.systemui.coroutines.collectLastValue -import com.android.systemui.kosmos.testScope -import com.android.systemui.settings.FakeUserTracker +import com.android.systemui.kosmos.Kosmos +import com.android.systemui.kosmos.collectLastValue +import com.android.systemui.kosmos.runTest +import com.android.systemui.kosmos.useUnconfinedTestDispatcher import com.android.systemui.settings.fakeUserTracker import com.android.systemui.testKosmos -import com.android.systemui.user.data.repository.FakeUserRepository import com.android.systemui.user.data.repository.fakeUserRepository -import kotlinx.coroutines.test.runTest import org.junit.Assert.assertEquals import org.junit.Assert.assertNotNull import org.junit.Assert.assertNull @@ -48,34 +46,20 @@ import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) class CommunalSettingsInteractorTest : SysuiTestCase() { - private lateinit var userManager: UserManager - private lateinit var userRepository: FakeUserRepository - private lateinit var userTracker: FakeUserTracker + private val kosmos = testKosmos().useUnconfinedTestDispatcher() - private val kosmos = testKosmos() - private val testScope = kosmos.testScope - - private lateinit var underTest: CommunalSettingsInteractor + private val Kosmos.underTest by Kosmos.Fixture { communalSettingsInteractor } @Before fun setUp() { - userManager = kosmos.userManager - userRepository = kosmos.fakeUserRepository - userTracker = kosmos.fakeUserTracker - val userInfos = listOf(MAIN_USER_INFO, USER_INFO_WORK) - userRepository.setUserInfos(userInfos) - userTracker.set( - userInfos = userInfos, - selectedUserIndex = 0, - ) - - underTest = kosmos.communalSettingsInteractor + kosmos.fakeUserRepository.setUserInfos(userInfos) + kosmos.fakeUserTracker.set(userInfos = userInfos, selectedUserIndex = 0) } @Test fun filterUsers_dontFilteredUsersWhenAllAreAllowed() = - testScope.runTest { + kosmos.runTest { // If no users have any keyguard features disabled... val disallowedUser by collectLastValue(underTest.workProfileUserDisallowedByDevicePolicy) @@ -85,11 +69,11 @@ class CommunalSettingsInteractorTest : SysuiTestCase() { @Test fun filterUsers_filterWorkProfileUserWhenDisallowed() = - testScope.runTest { + kosmos.runTest { // If the work profile user has keyguard widgets disabled... setKeyguardFeaturesDisabled( USER_INFO_WORK, - DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL + DevicePolicyManager.KEYGUARD_DISABLE_WIDGETS_ALL, ) // ...then the disallowed user match the work profile val disallowedUser by @@ -102,7 +86,7 @@ class CommunalSettingsInteractorTest : SysuiTestCase() { whenever( kosmos.devicePolicyManager.getKeyguardDisabledFeatures( anyOrNull(), - ArgumentMatchers.eq(user.id) + ArgumentMatchers.eq(user.id), ) ) .thenReturn(disabledFlags) diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt index 6c955bf1818d..5fd480f90ac9 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractorTest.kt @@ -176,14 +176,14 @@ class DeviceEntryHapticsInteractorTest : SysuiTestCase() { } @Test - fun nonPowerButtonFPS_coExFaceFailure_doNotVibrateError() = + fun nonPowerButtonFPS_coExFaceFailure_vibrateError() = testScope.runTest { val playErrorHaptic by collectLastValue(underTest.playErrorHaptic) enrollFingerprint(FingerprintSensorType.UDFPS_ULTRASONIC) enrollFace() runCurrent() faceFailure() - assertThat(playErrorHaptic).isNull() + assertThat(playErrorHaptic).isNotNull() } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt index ef70305a3f47..af30e435da73 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/domain/startable/SceneContainerStartableTest.kt @@ -1149,7 +1149,7 @@ class SceneContainerStartableTest : SysuiTestCase() { @Test @DisableFlags(Flags.FLAG_MSDL_FEEDBACK) - fun skipsFaceErrorHaptics_nonSfps_coEx() = + fun playsFaceErrorHaptics_nonSfps_coEx() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) @@ -1161,14 +1161,15 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() updateFaceAuthStatus(isSuccess = false) - assertThat(playErrorHaptic).isNull() - verify(vibratorHelper, never()).vibrateAuthError(anyString()) + assertThat(playErrorHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + verify(vibratorHelper).vibrateAuthError(anyString()) verify(vibratorHelper, never()).vibrateAuthSuccess(anyString()) } @Test @EnableFlags(Flags.FLAG_MSDL_FEEDBACK) - fun skipsMSDLFaceErrorHaptics_nonSfps_coEx() = + fun playsMSDLFaceErrorHaptics_nonSfps_coEx() = testScope.runTest { val currentSceneKey by collectLastValue(sceneInteractor.currentScene) val playErrorHaptic by collectLastValue(deviceEntryHapticsInteractor.playErrorHaptic) @@ -1180,9 +1181,10 @@ class SceneContainerStartableTest : SysuiTestCase() { underTest.start() updateFaceAuthStatus(isSuccess = false) - assertThat(playErrorHaptic).isNull() - assertThat(msdlPlayer.latestTokenPlayed).isNull() - assertThat(msdlPlayer.latestPropertiesPlayed).isNull() + assertThat(playErrorHaptic).isNotNull() + assertThat(currentSceneKey).isEqualTo(Scenes.Lockscreen) + assertThat(msdlPlayer.latestTokenPlayed).isEqualTo(MSDLToken.FAILURE) + assertThat(msdlPlayer.latestPropertiesPlayed).isEqualTo(authInteractionProperties) } @Test diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt index 39c42f183481..28b2ee8dde06 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationGutsManagerTest.kt @@ -269,6 +269,36 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( } @Test + fun testOpenAndCloseGutsWithoutSave() { + val guts = spy(NotificationGuts(mContext)) + whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock -> + handler.post(((invocation.arguments[0] as Runnable))) + null + } + + // Test doesn't support animation since the guts view is not attached. + doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any()) + + val realRow = createTestNotificationRow() + val menuItem = createTestMenuItem(realRow) + + val row = spy(realRow) + whenever(row.windowToken).thenReturn(Binder()) + whenever(row.guts).thenReturn(guts) + + assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) + executor.runAllReady() + verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>()) + + gutsManager.closeAndUndoGuts() + + verify(guts).closeControls(anyInt(), anyInt(), eq(false), eq(false)) + verify(row, times(1)).setGutsView(any<MenuItem>()) + executor.runAllReady() + verify(headsUpManager).setGutsShown(realRow.entry, false) + } + + @Test fun testLockscreenShadeVisible_visible_gutsNotClosed() = testScope.runTest { // First, start out lockscreen or shade as not visible @@ -377,52 +407,6 @@ class NotificationGutsManagerTest(flags: FlagsParameterization) : SysuiTestCase( } @Test - fun testChangeDensityOrFontScale() { - val guts = spy(NotificationGuts(mContext)) - whenever(guts.post(any())).thenAnswer { invocation: InvocationOnMock -> - handler.post(((invocation.arguments[0] as Runnable))) - null - } - - // Test doesn't support animation since the guts view is not attached. - doNothing().whenever(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>()) - - val realRow = createTestNotificationRow() - val menuItem = createTestMenuItem(realRow) - - val row = spy(realRow) - - whenever(row.windowToken).thenReturn(Binder()) - whenever(row.guts).thenReturn(guts) - doNothing().whenever(row).ensureGutsInflated() - - val realEntry = realRow.entry - val entry = spy(realEntry) - - whenever(entry.row).thenReturn(row) - whenever(entry.guts).thenReturn(guts) - - assertTrue(gutsManager.openGutsInternal(row, 0, 0, menuItem)) - executor.runAllReady() - verify(guts).openControls(anyInt(), anyInt(), anyBoolean(), any<Runnable>()) - - // called once by mGutsManager.bindGuts() in mGutsManager.openGuts() - verify(row).setGutsView(any<MenuItem>()) - - row.onDensityOrFontScaleChanged() - gutsManager.onDensityOrFontScaleChanged(entry) - - executor.runAllReady() - - gutsManager.closeAndSaveGuts(false, false, false, 0, 0, false) - - verify(guts).closeControls(anyBoolean(), anyBoolean(), anyInt(), anyInt(), anyBoolean()) - - // called again by mGutsManager.bindGuts(), in mGutsManager.onDensityOrFontScaleChanged() - verify(row, times(2)).setGutsView(any<MenuItem>()) - } - - @Test fun testAppOpsSettingsIntent_camera() { val row = createTestNotificationRow() val ops = ArraySet<Int>() diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java index 6435e8203d3d..af67a04d2f2a 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/notification/row/NotificationSnoozeTest.java @@ -16,29 +16,44 @@ package com.android.systemui.statusbar.notification.row; +import static com.google.common.truth.Truth.assertThat; + import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertNotNull; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import android.platform.test.annotations.EnableFlags; import android.provider.Settings; import android.testing.TestableResources; -import android.util.KeyValueListParser; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ImageView; +import android.widget.TextView; import androidx.test.annotation.UiThreadTest; 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.animation.AnimatorTestRule; +import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; import com.android.systemui.res.R; +import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import java.util.ArrayList; +import java.util.List; @SmallTest @RunWith(AndroidJUnit4.class) @@ -46,8 +61,12 @@ import java.util.ArrayList; public class NotificationSnoozeTest extends SysuiTestCase { private static final int RES_DEFAULT = 2; private static final int[] RES_OPTIONS = {1, 2, 3}; - private NotificationSnooze mNotificationSnooze; - private KeyValueListParser mMockParser; + private final NotificationSwipeActionHelper mSnoozeListener = mock( + NotificationSwipeActionHelper.class); + private NotificationSnooze mUnderTest; + + @Rule + public final AnimatorTestRule mAnimatorTestRule = new AnimatorTestRule(this); @Before public void setUp() throws Exception { @@ -56,62 +75,117 @@ public class NotificationSnoozeTest extends SysuiTestCase { TestableResources resources = mContext.getOrCreateTestableResources(); resources.addOverride(R.integer.config_notification_snooze_time_default, RES_DEFAULT); resources.addOverride(R.array.config_notification_snooze_times, RES_OPTIONS); - mNotificationSnooze = new NotificationSnooze(mContext, null); - mMockParser = mock(KeyValueListParser.class); + + mUnderTest = new NotificationSnooze(mContext, null); + mUnderTest.setSnoozeListener(mSnoozeListener); + mUnderTest.mExpandButton = mock(ImageView.class); + mUnderTest.mSnoozeView = mock(View.class); + mUnderTest.mSelectedOptionText = mock(TextView.class); + mUnderTest.mDivider = mock(View.class); + mUnderTest.mSnoozeOptionContainer = mock(ViewGroup.class); + mUnderTest.mSnoozeOptions = mock(List.class); + } + + @After + public void tearDown() { + // Make sure all animations are finished + mAnimatorTestRule.advanceTimeBy(1000L); + } + + @Test + @EnableFlags(Flags.FLAG_NOTIFICATION_UNDO_GUTS_ON_CONFIG_CHANGED) + public void closeControls_withoutSave_performsUndo() { + ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions(); + mUnderTest.mSelectedOption = options.getFirst(); + mUnderTest.showSnoozeOptions(true); + + assertThat( + mUnderTest.handleCloseControls(/* save = */ false, /* force = */ false)).isFalse(); + + assertThat(mUnderTest.mSelectedOption).isNull(); + assertThat(mUnderTest.isExpanded()).isFalse(); + verify(mSnoozeListener, times(0)).snooze(any(), any()); + } + + @Test + public void closeControls_whenExpanded_collapsesOptions() { + ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions(); + mUnderTest.mSelectedOption = options.getFirst(); + mUnderTest.showSnoozeOptions(true); + + assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ false)).isTrue(); + + assertThat(mUnderTest.mSelectedOption).isNotNull(); + assertThat(mUnderTest.isExpanded()).isFalse(); + } + + @Test + public void closeControls_whenCollapsed_commitsChanges() { + ArrayList<SnoozeOption> options = mUnderTest.getDefaultSnoozeOptions(); + mUnderTest.mSelectedOption = options.getFirst(); + + assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ false)).isTrue(); + + verify(mSnoozeListener).snooze(any(), any()); + } + + @Test + public void closeControls_withForce_returnsFalse() { + assertThat(mUnderTest.handleCloseControls(/* save = */ true, /* force = */ true)).isFalse(); } @Test - public void testGetOptionsWithNoConfig() throws Exception { - ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + public void testGetOptionsWithNoConfig() { + ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions(); assertEquals(3, result.size()); assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order assertEquals(2, result.get(1).getMinutesToSnoozeFor()); assertEquals(3, result.get(2).getMinutesToSnoozeFor()); - assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor()); + assertEquals(2, mUnderTest.getDefaultOption().getMinutesToSnoozeFor()); } @Test - public void testGetOptionsWithInvalidConfig() throws Exception { + public void testGetOptionsWithInvalidConfig() { Settings.Global.putString(mContext.getContentResolver(), Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, "this is garbage"); - ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions(); assertEquals(3, result.size()); assertEquals(1, result.get(0).getMinutesToSnoozeFor()); // respect order assertEquals(2, result.get(1).getMinutesToSnoozeFor()); assertEquals(3, result.get(2).getMinutesToSnoozeFor()); - assertEquals(2, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor()); + assertEquals(2, mUnderTest.getDefaultOption().getMinutesToSnoozeFor()); } @Test - public void testGetOptionsWithValidDefault() throws Exception { + public void testGetOptionsWithValidDefault() { Settings.Global.putString(mContext.getContentResolver(), Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, "default=10,options_array=4:5:6:7"); - ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); - assertNotNull(mNotificationSnooze.getDefaultOption()); // pick one + ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions(); + assertNotNull(mUnderTest.getDefaultOption()); // pick one } @Test - public void testGetOptionsWithValidConfig() throws Exception { + public void testGetOptionsWithValidConfig() { Settings.Global.putString(mContext.getContentResolver(), Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, "default=6,options_array=4:5:6:7"); - ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions(); assertEquals(4, result.size()); assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order assertEquals(5, result.get(1).getMinutesToSnoozeFor()); assertEquals(6, result.get(2).getMinutesToSnoozeFor()); assertEquals(7, result.get(3).getMinutesToSnoozeFor()); - assertEquals(6, mNotificationSnooze.getDefaultOption().getMinutesToSnoozeFor()); + assertEquals(6, mUnderTest.getDefaultOption().getMinutesToSnoozeFor()); } @Test - public void testGetOptionsWithLongConfig() throws Exception { + public void testGetOptionsWithLongConfig() { Settings.Global.putString(mContext.getContentResolver(), Settings.Global.NOTIFICATION_SNOOZE_OPTIONS, "default=6,options_array=4:5:6:7:8:9:10:11:12:13:14:15:16:17"); - ArrayList<SnoozeOption> result = mNotificationSnooze.getDefaultSnoozeOptions(); + ArrayList<SnoozeOption> result = mUnderTest.getDefaultSnoozeOptions(); assertTrue(result.size() > 3); assertEquals(4, result.get(0).getMinutesToSnoozeFor()); // respect order assertEquals(5, result.get(1).getMinutesToSnoozeFor()); diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt index 09be93de9f3e..ea91b7a9d6e2 100644 --- a/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt @@ -19,6 +19,7 @@ package com.android.systemui.unfold import android.content.Context import android.content.res.Resources import android.hardware.devicestate.DeviceStateManager +import android.os.PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.internal.R @@ -27,16 +28,20 @@ import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImp import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractorImpl import com.android.systemui.defaultDeviceState import com.android.systemui.deviceStateManager -import com.android.systemui.display.data.repository.DeviceStateRepository import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState +import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.FOLDED +import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.HALF_FOLDED +import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState.UNFOLDED +import com.android.systemui.display.data.repository.fakeDeviceStateRepository import com.android.systemui.foldedDeviceStateList import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.kosmos.Kosmos -import com.android.systemui.power.domain.interactor.PowerInteractor -import com.android.systemui.power.shared.model.ScreenPowerState -import com.android.systemui.power.shared.model.WakeSleepReason -import com.android.systemui.power.shared.model.WakefulnessModel -import com.android.systemui.power.shared.model.WakefulnessState +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAsleepForTest +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setAwakeForTest +import com.android.systemui.power.domain.interactor.PowerInteractor.Companion.setScreenPowerState +import com.android.systemui.power.domain.interactor.PowerInteractorFactory +import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_OFF +import com.android.systemui.power.shared.model.ScreenPowerState.SCREEN_ON import com.android.systemui.shared.system.SysUiStatsLog import com.android.systemui.statusbar.policy.FakeConfigurationController import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_CLOSED @@ -45,7 +50,7 @@ import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLate import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor import com.android.systemui.unfoldedDeviceState -import com.android.systemui.util.animation.data.repository.AnimationStatusRepository +import com.android.systemui.util.animation.data.repository.fakeAnimationStatusRepository import com.android.systemui.util.mockito.any import com.android.systemui.util.mockito.capture import com.android.systemui.util.time.FakeSystemClock @@ -77,14 +82,15 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { private lateinit var displaySwitchLatencyTracker: DisplaySwitchLatencyTracker @Captor private lateinit var loggerArgumentCaptor: ArgumentCaptor<DisplaySwitchLatencyEvent> + private val kosmos = Kosmos() private val mockContext = mock<Context>() private val resources = mock<Resources>() - private val foldStateRepository = mock<DeviceStateRepository>() - private val powerInteractor = mock<PowerInteractor>() - private val animationStatusRepository = mock<AnimationStatusRepository>() + private val foldStateRepository = kosmos.fakeDeviceStateRepository + private val powerInteractor = PowerInteractorFactory.create().powerInteractor + private val animationStatusRepository = kosmos.fakeAnimationStatusRepository private val keyguardInteractor = mock<KeyguardInteractor>() private val displaySwitchLatencyLogger = mock<DisplaySwitchLatencyLogger>() - private val kosmos = Kosmos() + private val deviceStateManager = kosmos.deviceStateManager private val closedDeviceState = kosmos.foldedDeviceStateList.first() private val openDeviceState = kosmos.unfoldedDeviceState @@ -94,12 +100,7 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { private val testDispatcher: TestDispatcher = StandardTestDispatcher() private val testScope: TestScope = TestScope(testDispatcher) - private val isAsleep = MutableStateFlow(false) private val isAodAvailable = MutableStateFlow(false) - private val deviceState = MutableStateFlow(DeviceState.UNFOLDED) - private val screenPowerState = MutableStateFlow(ScreenPowerState.SCREEN_ON) - private val areAnimationEnabled = MutableStateFlow(true) - private val lastWakefulnessEvent = MutableStateFlow(WakefulnessModel()) private val systemClock = FakeSystemClock() private val configurationController = FakeConfigurationController() private val configurationRepository = @@ -126,13 +127,10 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { .thenReturn(listOf(closedDeviceState, openDeviceState)) whenever(resources.getIntArray(R.array.config_foldedDeviceStates)) .thenReturn(nonEmptyClosedDeviceStatesArray) - whenever(foldStateRepository.state).thenReturn(deviceState) - whenever(powerInteractor.isAsleep).thenReturn(isAsleep) - whenever(animationStatusRepository.areAnimationsEnabled()).thenReturn(areAnimationEnabled) - whenever(powerInteractor.screenPowerState).thenReturn(screenPowerState) whenever(keyguardInteractor.isAodAvailable).thenReturn(isAodAvailable) - whenever(powerInteractor.detailedWakefulness).thenReturn(lastWakefulnessEvent) - + animationStatusRepository.onAnimationStatusChanged(true) + powerInteractor.setAwakeForTest() + powerInteractor.setScreenPowerState(SCREEN_ON) displaySwitchLatencyTracker = DisplaySwitchLatencyTracker( mockContext, @@ -152,21 +150,19 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun unfold_logsLatencyTillTransitionStarted() { testScope.runTest { - areAnimationEnabled.emit(true) - displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.FOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setScreenPowerState(SCREEN_OFF) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) runCurrent() systemClock.advanceTime(50) - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) systemClock.advanceTime(200) unfoldTransitionProgressProvider.onTransitionStarted() runCurrent() - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) val loggedEvent = loggerArgumentCaptor.value @@ -202,23 +198,22 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { systemClock, deviceStateManager, ) - areAnimationEnabled.emit(true) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.FOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setScreenPowerState(SCREEN_OFF) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) systemClock.advanceTime(50) runCurrent() systemClock.advanceTime(200) unfoldTransitionProgressProvider.onTransitionStarted() runCurrent() - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) val loggedEvent = loggerArgumentCaptor.value @@ -235,23 +230,23 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun unfold_animationDisabled_logsLatencyTillScreenTurnedOn() { testScope.runTest { - areAnimationEnabled.emit(false) + animationStatusRepository.onAnimationStatusChanged(false) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.FOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setScreenPowerState(SCREEN_OFF) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) systemClock.advanceTime(50) runCurrent() unfoldTransitionProgressProvider.onTransitionStarted() systemClock.advanceTime(200) runCurrent() - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) val loggedEvent = loggerArgumentCaptor.value @@ -268,19 +263,18 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun foldWhileStayingAwake_logsLatency() { testScope.runTest { - areAnimationEnabled.emit(true) - deviceState.emit(DeviceState.UNFOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + setDeviceState(UNFOLDED) + powerInteractor.setScreenPowerState(SCREEN_ON) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.FOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setScreenPowerState(SCREEN_OFF) runCurrent() systemClock.advanceTime(200) - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) runCurrent() verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) @@ -298,25 +292,19 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun foldToAod_capturesToStateAsAod() { testScope.runTest { - areAnimationEnabled.emit(true) - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) isAodAvailable.emit(true) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.FOLDED) - lastWakefulnessEvent.emit( - WakefulnessModel( - internalWakefulnessState = WakefulnessState.ASLEEP, - lastSleepReason = WakeSleepReason.FOLD, - ) - ) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setAsleepForTest(sleepReason = GO_TO_SLEEP_REASON_DEVICE_FOLD) + powerInteractor.setScreenPowerState(SCREEN_OFF) runCurrent() systemClock.advanceTime(200) - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) runCurrent() verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) @@ -335,22 +323,21 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun fold_notAFoldable_shouldNotLogLatency() { testScope.runTest { - areAnimationEnabled.emit(true) - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) whenever(resources.getIntArray(R.array.config_foldedDeviceStates)) .thenReturn(IntArray(0)) whenever(deviceStateManager.supportedDeviceStates) .thenReturn(listOf(defaultDeviceState)) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.FOLDED) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setScreenPowerState(SCREEN_OFF) runCurrent() systemClock.advanceTime(200) - screenPowerState.emit(ScreenPowerState.SCREEN_ON) + powerInteractor.setScreenPowerState(SCREEN_ON) runCurrent() verify(displaySwitchLatencyLogger, never()).log(any()) @@ -360,22 +347,16 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { @Test fun foldToScreenOff_capturesToStateAsScreenOff() { testScope.runTest { - areAnimationEnabled.emit(true) - deviceState.emit(DeviceState.UNFOLDED) + setDeviceState(UNFOLDED) isAodAvailable.emit(false) displaySwitchLatencyTracker.start() - deviceState.emit(DeviceState.HALF_FOLDED) + setDeviceState(HALF_FOLDED) systemClock.advanceTime(50) runCurrent() - deviceState.emit(DeviceState.FOLDED) - lastWakefulnessEvent.emit( - WakefulnessModel( - internalWakefulnessState = WakefulnessState.ASLEEP, - lastSleepReason = WakeSleepReason.FOLD, - ) - ) - screenPowerState.emit(ScreenPowerState.SCREEN_OFF) + setDeviceState(FOLDED) + powerInteractor.setAsleepForTest(sleepReason = GO_TO_SLEEP_REASON_DEVICE_FOLD) + powerInteractor.setScreenPowerState(SCREEN_OFF) runCurrent() verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor)) @@ -390,4 +371,8 @@ class DisplaySwitchLatencyTrackerTest : SysuiTestCase() { assertThat(loggedEvent).isEqualTo(expectedLoggedEvent) } } + + private suspend fun setDeviceState(state: DeviceState) { + foldStateRepository.emit(state) + } } diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java index e76f38c8c75c..9507b0483a06 100644 --- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java +++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java @@ -62,15 +62,14 @@ public abstract class ClockRegistryModule { scope, mainDispatcher, bgDispatcher, - com.android.systemui.Flags.lockscreenCustomClocks() + com.android.systemui.shared.Flags.lockscreenCustomClocks() || featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS), /* handleAllUsers= */ true, new DefaultClockProvider( context, layoutInflater, resources, - - com.android.systemui.Flags.clockReactiveVariants() + com.android.systemui.shared.Flags.clockReactiveVariants() ), context.getString(R.string.lockscreen_clock_id_fallback), clockBuffers, diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt index 1c994731c393..126471234fa1 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt @@ -32,6 +32,9 @@ import com.android.systemui.authentication.shared.model.AuthenticationWipeModel. import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.time.SystemClock import javax.inject.Inject @@ -66,6 +69,7 @@ constructor( @Background private val backgroundDispatcher: CoroutineDispatcher, private val repository: AuthenticationRepository, private val selectedUserInteractor: SelectedUserInteractor, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) { /** * The currently-configured authentication method. This determines how the authentication @@ -85,7 +89,11 @@ constructor( * `true` even when the lockscreen is showing and still needs to be dismissed by the user to * proceed. */ - val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod + val authenticationMethod: Flow<AuthenticationMethodModel> = + repository.authenticationMethod.logDiffsForTable( + tableLogBuffer = tableLogBuffer, + initialValue = AuthenticationMethodModel.None, + ) /** * Whether the auto confirm feature is enabled for the currently-selected user. diff --git a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt index 4e45fcc25fb8..744fd7e94ab4 100644 --- a/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt +++ b/packages/SystemUI/src/com/android/systemui/authentication/shared/model/AuthenticationMethodModel.kt @@ -16,6 +16,9 @@ package com.android.systemui.authentication.shared.model +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger + /** Enumerates all known authentication methods. */ sealed class AuthenticationMethodModel( /** @@ -24,8 +27,8 @@ sealed class AuthenticationMethodModel( * "Secure" authentication methods require authentication to unlock the device. Non-secure auth * methods simply require user dismissal. */ - open val isSecure: Boolean, -) { + open val isSecure: Boolean +) : Diffable<AuthenticationMethodModel> { /** * Device doesn't use a secure authentication method. Either there is no lockscreen or the lock * screen can be swiped away when displayed. @@ -39,4 +42,8 @@ sealed class AuthenticationMethodModel( data object Pattern : AuthenticationMethodModel(isSecure = true) data object Sim : AuthenticationMethodModel(isSecure = true) + + override fun logDiffs(prevVal: AuthenticationMethodModel, row: TableRowLogger) { + row.logChange(columnName = "authenticationMethod", value = toString()) + } } diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt index 19238804fb12..79e66a89cd6b 100644 --- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt +++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt @@ -16,6 +16,8 @@ package com.android.systemui.common.data +import com.android.systemui.common.data.repository.BatteryRepository +import com.android.systemui.common.data.repository.BatteryRepositoryImpl import com.android.systemui.common.data.repository.PackageChangeRepository import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl import dagger.Binds @@ -27,4 +29,6 @@ abstract class CommonDataLayerModule { abstract fun bindPackageChangeRepository( impl: PackageChangeRepositoryImpl ): PackageChangeRepository + + @Binds abstract fun bindBatteryRepository(impl: BatteryRepositoryImpl): BatteryRepository } diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt new file mode 100644 index 000000000000..63b051339d4b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/BatteryRepository.kt @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2025 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.common.data.repository + +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.statusbar.policy.BatteryController +import com.android.systemui.util.kotlin.isDevicePluggedIn +import javax.inject.Inject +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.stateIn + +interface BatteryRepository { + val isDevicePluggedIn: Flow<Boolean> +} + +@SysUISingleton +class BatteryRepositoryImpl +@Inject +constructor(@Background bgScope: CoroutineScope, batteryController: BatteryController) : + BatteryRepository { + + /** Returns {@code true} if the device is currently plugged in or wireless charging. */ + override val isDevicePluggedIn: Flow<Boolean> = + batteryController + .isDevicePluggedIn() + .stateIn(bgScope, SharingStarted.WhileSubscribed(), batteryController.isPluggedIn) +} diff --git a/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt new file mode 100644 index 000000000000..987776d14b2b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/common/domain/interactor/BatteryInteractor.kt @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2025 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.common.domain.interactor + +import com.android.systemui.common.data.repository.BatteryRepository +import com.android.systemui.dagger.SysUISingleton +import javax.inject.Inject + +@SysUISingleton +class BatteryInteractor @Inject constructor(batteryRepository: BatteryRepository) { + val isDevicePluggedIn = batteryRepository.isDevicePluggedIn +} diff --git a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt index 53122c56ed2c..abd101693b43 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/data/repository/CommunalSettingsRepository.kt @@ -34,6 +34,7 @@ import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_I import com.android.systemui.communal.data.model.DisabledReason.DISABLED_REASON_USER_SETTING import com.android.systemui.communal.data.repository.CommunalSettingsRepositoryModule.Companion.DEFAULT_BACKGROUND_TYPE import com.android.systemui.communal.shared.model.CommunalBackgroundType +import com.android.systemui.communal.shared.model.WhenToDream import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -60,6 +61,12 @@ interface CommunalSettingsRepository { fun getScreensaverEnabledState(user: UserInfo): Flow<Boolean> /** + * Returns a [WhenToDream] for the specified user, indicating what state the device should be in + * to trigger dreams. + */ + fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream> + + /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * * This should be used for preventing basic glanceable hub functionality from running on devices @@ -157,6 +164,49 @@ constructor( } .flowOn(bgDispatcher) + override fun getWhenToDreamState(user: UserInfo): Flow<WhenToDream> = + secureSettings + .observerFlow( + userId = user.id, + names = + arrayOf( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, + Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + ), + ) + .emitOnStart() + .map { + if ( + secureSettings.getIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, + 0, + user.id, + ) == 1 + ) { + WhenToDream.WHILE_CHARGING + } else if ( + secureSettings.getIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK, + 0, + user.id, + ) == 1 + ) { + WhenToDream.WHILE_DOCKED + } else if ( + secureSettings.getIntForUser( + Settings.Secure.SCREENSAVER_ACTIVATE_ON_POSTURED, + 0, + user.id, + ) == 1 + ) { + WhenToDream.WHILE_POSTURED + } else { + WhenToDream.NEVER + } + } + .flowOn(bgDispatcher) + override fun getAllowedByDevicePolicy(user: UserInfo): Flow<Boolean> = broadcastDispatcher .broadcastFlow( diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt index 465a8e3eccca..b4e6e9348b3d 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt @@ -30,11 +30,13 @@ import com.android.compose.animation.scene.TransitionKey import com.android.systemui.Flags.communalResponsiveGrid import com.android.systemui.Flags.glanceableHubBlurredBackground import com.android.systemui.broadcast.BroadcastDispatcher +import com.android.systemui.common.domain.interactor.BatteryInteractor import com.android.systemui.communal.data.repository.CommunalMediaRepository import com.android.systemui.communal.data.repository.CommunalSmartspaceRepository import com.android.systemui.communal.data.repository.CommunalWidgetRepository import com.android.systemui.communal.domain.model.CommunalContentModel import com.android.systemui.communal.domain.model.CommunalContentModel.WidgetContent +import com.android.systemui.communal.posturing.domain.interactor.PosturingInteractor import com.android.systemui.communal.shared.model.CommunalBackgroundType import com.android.systemui.communal.shared.model.CommunalContentSize import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize.FULL @@ -43,11 +45,14 @@ import com.android.systemui.communal.shared.model.CommunalContentSize.FixedSize. import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.communal.shared.model.CommunalWidgetContentModel import com.android.systemui.communal.shared.model.EditModeState +import com.android.systemui.communal.shared.model.WhenToDream import com.android.systemui.communal.widgets.EditWidgetsActivityStarter import com.android.systemui.communal.widgets.WidgetConfigurator import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.dagger.qualifiers.Background +import com.android.systemui.dock.DockManager +import com.android.systemui.dock.retrieveIsDocked import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor import com.android.systemui.keyguard.shared.model.Edge @@ -67,6 +72,7 @@ import com.android.systemui.statusbar.phone.ManagedProfileController import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf import com.android.systemui.util.kotlin.BooleanFlowOperators.not import com.android.systemui.util.kotlin.emitOnStart +import com.android.systemui.util.kotlin.isDevicePluggedIn import javax.inject.Inject import kotlin.time.Duration.Companion.minutes import kotlinx.coroutines.CoroutineDispatcher @@ -86,6 +92,7 @@ import kotlinx.coroutines.flow.emptyFlow import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -117,6 +124,9 @@ constructor( @CommunalLog logBuffer: LogBuffer, @CommunalTableLog tableLogBuffer: TableLogBuffer, private val managedProfileController: ManagedProfileController, + private val batteryInteractor: BatteryInteractor, + private val dockManager: DockManager, + private val posturingInteractor: PosturingInteractor, ) { private val logger = Logger(logBuffer, "CommunalInteractor") @@ -172,6 +182,33 @@ constructor( replay = 1, ) + /** + * Whether communal hub should be shown automatically, depending on the user's [WhenToDream] + * state. + */ + val shouldShowCommunal: Flow<Boolean> = + allOf( + isCommunalAvailable, + communalSettingsInteractor.whenToDream + .flatMapLatest { whenToDream -> + when (whenToDream) { + WhenToDream.NEVER -> flowOf(false) + + WhenToDream.WHILE_CHARGING -> batteryInteractor.isDevicePluggedIn + + WhenToDream.WHILE_DOCKED -> + allOf( + batteryInteractor.isDevicePluggedIn, + dockManager.retrieveIsDocked(), + ) + + WhenToDream.WHILE_POSTURED -> + allOf(batteryInteractor.isDevicePluggedIn, posturingInteractor.postured) + } + } + .flowOn(bgDispatcher), + ) + private val _isDisclaimerDismissed = MutableStateFlow(false) val isDisclaimerDismissed: Flow<Boolean> = _isDisclaimerDismissed.asStateFlow() diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt index 1738f37b7f0c..a0b1261df346 100644 --- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSettingsInteractor.kt @@ -21,6 +21,7 @@ import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCall import com.android.systemui.communal.data.model.CommunalEnabledState import com.android.systemui.communal.data.repository.CommunalSettingsRepository import com.android.systemui.communal.shared.model.CommunalBackgroundType +import com.android.systemui.communal.shared.model.WhenToDream import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.log.dagger.CommunalTableLog @@ -73,6 +74,12 @@ constructor( repository.getScreensaverEnabledState(user) } + /** When to dream for the currently selected user. */ + val whenToDream: Flow<WhenToDream> = + userInteractor.selectedUserInfo.flatMapLatest { user -> + repository.getWhenToDreamState(user) + } + /** * Returns true if any glanceable hub functionality should be enabled via configs and flags. * diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt new file mode 100644 index 000000000000..0d4eb60c5240 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/WhenToDream.kt @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2025 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.communal.shared.model + +enum class WhenToDream { + NEVER, + WHILE_CHARGING, + WHILE_DOCKED, + WHILE_POSTURED, +} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt index 1e7bec257432..69da67e055fe 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryBiometricAuthInteractor.kt @@ -68,4 +68,10 @@ constructor( emptyFlow() } } + + /** Triggered if a face failure occurs regardless of the mode. */ + val faceFailure: Flow<FailedFaceAuthenticationStatus> = + deviceEntryFaceAuthInteractor.authenticationStatus.filterIsInstance< + FailedFaceAuthenticationStatus + >() } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt index cdd2b054711e..079d624e6fe0 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFaceAuthInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.CoreStartable import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus +import com.android.systemui.log.table.TableLogBuffer import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow @@ -81,6 +82,8 @@ interface DeviceEntryFaceAuthInteractor : CoreStartable { /** Whether face auth is considered class 3 */ fun isFaceAuthStrong(): Boolean + + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) } /** @@ -93,17 +96,17 @@ interface DeviceEntryFaceAuthInteractor : CoreStartable { */ interface FaceAuthenticationListener { /** Receive face isAuthenticated updates */ - fun onAuthenticatedChanged(isAuthenticated: Boolean) + fun onAuthenticatedChanged(isAuthenticated: Boolean) = Unit /** Receive face authentication status updates */ - fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus) + fun onAuthenticationStatusChanged(status: FaceAuthenticationStatus) = Unit /** Receive status updates whenever face detection runs */ - fun onDetectionStatusChanged(status: FaceDetectionStatus) + fun onDetectionStatusChanged(status: FaceDetectionStatus) = Unit - fun onLockoutStateChanged(isLockedOut: Boolean) + fun onLockoutStateChanged(isLockedOut: Boolean) = Unit - fun onRunningStateChanged(isRunning: Boolean) + fun onRunningStateChanged(isRunning: Boolean) = Unit - fun onAuthEnrollmentStateChanged(enrolled: Boolean) + fun onAuthEnrollmentStateChanged(enrolled: Boolean) = Unit } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt index cd456a618c48..38e0503440f9 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt @@ -123,7 +123,7 @@ constructor( private val playErrorHapticForBiometricFailure: Flow<Unit> = merge( deviceEntryFingerprintAuthInteractor.fingerprintFailure, - deviceEntryBiometricAuthInteractor.faceOnlyFaceFailure, + deviceEntryBiometricAuthInteractor.faceFailure, ) // map to Unit .map {} diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt index 4ddc98cd434f..5b6859761705 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt @@ -25,7 +25,10 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.data.repository.DeviceEntryRepository import com.android.systemui.keyguard.DismissCallbackRegistry +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.scene.data.model.asIterable +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.scene.domain.interactor.SceneBackInteractor import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.model.Scenes @@ -33,9 +36,11 @@ import com.android.systemui.util.kotlin.pairwise import com.android.systemui.utils.coroutines.flow.mapLatestConflated import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter @@ -43,6 +48,7 @@ import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch /** * Hosts application business logic related to device entry. @@ -62,6 +68,7 @@ constructor( private val alternateBouncerInteractor: AlternateBouncerInteractor, private val dismissCallbackRegistry: DismissCallbackRegistry, sceneBackInteractor: SceneBackInteractor, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) { /** * Whether the device is unlocked. @@ -147,6 +154,11 @@ constructor( ) { enteredDirectly, enteredOnBackStack -> enteredOnBackStack || enteredDirectly } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isDeviceEntered", + initialValue = false, + ) .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -184,6 +196,11 @@ constructor( deviceUnlockStatus.deviceUnlockSource?.dismissesLockscreen == false)) && !isDeviceEntered } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "canSwipeToEnter", + initialValue = false, + ) .stateIn( scope = applicationScope, started = SharingStarted.Eagerly, @@ -271,4 +288,29 @@ constructor( fun lockNow() { deviceUnlockedInteractor.lockNow() } + + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + coroutineScope { + launch { + isDeviceEntered + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isDeviceEntered", + initialValue = isDeviceEntered.value, + ) + .collect() + } + + launch { + canSwipeToEnter + .map { it?.toString() ?: "" } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "canSwipeToEnter", + initialValue = canSwipeToEnter.value?.toString() ?: "", + ) + .collect() + } + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt index 68aef521be7b..b1be9a209a0a 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractor.kt @@ -33,8 +33,11 @@ import com.android.systemui.keyguard.KeyguardViewMediator import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.TrustInteractor import com.android.systemui.lifecycle.ExclusiveActivatable +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.WakeSleepReason +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.util.settings.repository.UserAwareSecureSettingsRepository import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated @@ -48,6 +51,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.distinctUntilChanged @@ -74,6 +78,7 @@ constructor( private val systemPropertiesHelper: SystemPropertiesHelper, private val userAwareSecureSettingsRepository: UserAwareSecureSettingsRepository, private val keyguardInteractor: KeyguardInteractor, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) : ExclusiveActivatable() { private val deviceUnlockSource = @@ -179,17 +184,33 @@ constructor( private val lockNowRequests = Channel<Unit>() override suspend fun onActivated(): Nothing { - authenticationInteractor.authenticationMethod.collectLatest { authMethod -> - if (!authMethod.isSecure) { - // Device remains unlocked as long as the authentication method is not secure. - Log.d(TAG, "remaining unlocked because auth method not secure") - repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null) - } else if (authMethod == AuthenticationMethodModel.Sim) { - // Device remains locked while SIM is locked. - Log.d(TAG, "remaining locked because SIM locked") - repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null) - } else { - handleLockAndUnlockEvents() + coroutineScope { + launch { + authenticationInteractor.authenticationMethod.collectLatest { authMethod -> + if (!authMethod.isSecure) { + // Device remains unlocked as long as the authentication method is not + // secure. + Log.d(TAG, "remaining unlocked because auth method not secure") + repository.deviceUnlockStatus.value = DeviceUnlockStatus(true, null) + } else if (authMethod == AuthenticationMethodModel.Sim) { + // Device remains locked while SIM is locked. + Log.d(TAG, "remaining locked because SIM locked") + repository.deviceUnlockStatus.value = DeviceUnlockStatus(false, null) + } else { + handleLockAndUnlockEvents() + } + } + } + + launch { + deviceUnlockStatus + .map { it.isUnlocked } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isUnlocked", + initialValue = deviceUnlockStatus.value.isUnlocked, + ) + .collect() } } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt index 9b8c2b1acc33..ecc4dbc2326a 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/NoopDeviceEntryFaceAuthInteractor.kt @@ -19,6 +19,7 @@ package com.android.systemui.deviceentry.domain.interactor import com.android.systemui.dagger.SysUISingleton import com.android.systemui.deviceentry.shared.model.FaceAuthenticationStatus import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus +import com.android.systemui.log.table.TableLogBuffer import javax.inject.Inject import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow @@ -73,4 +74,6 @@ class NoopDeviceEntryFaceAuthInteractor @Inject constructor() : DeviceEntryFaceA override fun onWalletLaunched() = Unit override fun onDeviceUnfolded() {} + + override suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) {} } diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt index b19b2d9ece02..4b90e1d52ea0 100644 --- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/SystemUIDeviceEntryFaceAuthInteractor.kt @@ -44,6 +44,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OFF import com.android.systemui.keyguard.shared.model.TransitionState import com.android.systemui.log.FaceAuthenticationLogger +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor @@ -53,13 +55,16 @@ import com.android.systemui.user.data.model.SelectionStatus import com.android.systemui.user.data.repository.UserRepository import com.android.systemui.util.kotlin.pairwise import com.android.systemui.util.kotlin.sample +import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull @@ -379,6 +384,27 @@ constructor( .launchIn(applicationScope) } + override suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + conflatedCallbackFlow { + val listener = + object : FaceAuthenticationListener { + override fun onAuthEnrollmentStateChanged(enrolled: Boolean) { + trySend(isFaceAuthEnabledAndEnrolled()) + } + } + + registerListener(listener) + + awaitClose { unregisterListener(listener) } + } + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isFaceAuthEnabledAndEnrolled", + initialValue = isFaceAuthEnabledAndEnrolled(), + ) + .collect() + } + companion object { const val TAG = "DeviceEntryFaceAuthInteractor" } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt index 8c6037107c5a..cf712f111034 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractor.kt @@ -19,8 +19,12 @@ package com.android.systemui.keyguard.domain.interactor import android.animation.ValueAnimator import android.util.MathUtils import com.android.app.animation.Interpolators +import com.android.app.tracing.coroutines.launchTraced as launch import com.android.systemui.Flags.communalSceneKtfRefactor +import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor import com.android.systemui.communal.domain.interactor.CommunalSettingsInteractor +import com.android.systemui.communal.shared.model.CommunalScenes import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.dagger.qualifiers.Main @@ -39,6 +43,10 @@ import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.scene.shared.model.Scenes import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.util.kotlin.sample +import java.util.UUID +import javax.inject.Inject +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow @@ -47,11 +55,6 @@ import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onStart -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds -import java.util.UUID -import javax.inject.Inject -import com.android.app.tracing.coroutines.launchTraced as launch @SysUISingleton class FromLockscreenTransitionInteractor @@ -68,6 +71,8 @@ constructor( powerInteractor: PowerInteractor, private val glanceableHubTransitions: GlanceableHubTransitions, private val communalSettingsInteractor: CommunalSettingsInteractor, + private val communalInteractor: CommunalInteractor, + private val communalSceneInteractor: CommunalSceneInteractor, private val swipeToDismissInteractor: SwipeToDismissInteractor, keyguardOcclusionInteractor: KeyguardOcclusionInteractor, ) : @@ -94,6 +99,9 @@ constructor( if (!communalSceneKtfRefactor()) { listenForLockscreenToGlanceableHub() } + if (communalSettingsInteractor.isV2FlagEnabled()) { + listenForLockscreenToGlanceableHubV2() + } } /** @@ -268,9 +276,7 @@ constructor( it.transitionState == TransitionState.CANCELED && it.to == KeyguardState.PRIMARY_BOUNCER } - .collect { - transitionId = null - } + .collect { transitionId = null } } } @@ -370,6 +376,19 @@ constructor( } } + private fun listenForLockscreenToGlanceableHubV2() { + scope.launch { + communalInteractor.shouldShowCommunal + .filterRelevantKeyguardStateAnd { shouldShow -> shouldShow } + .collect { + communalSceneInteractor.changeScene( + newScene = CommunalScenes.Communal, + loggingReason = "lockscreen to communal", + ) + } + } + } + override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator { return ValueAnimator().apply { interpolator = Interpolators.LINEAR diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt index 42cbd7d39248..a1f288edcdd3 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardEnabledInteractor.kt @@ -24,6 +24,8 @@ import com.android.systemui.dagger.qualifiers.Background import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository import com.android.systemui.keyguard.data.repository.KeyguardRepository import com.android.systemui.keyguard.shared.model.KeyguardState +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.scene.shared.flag.SceneContainerFlag import com.android.systemui.user.domain.interactor.SelectedUserInteractor import com.android.systemui.util.kotlin.sample @@ -32,6 +34,7 @@ import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach @@ -166,4 +169,14 @@ constructor( isKeyguardEnabled.value && lockPatternUtils.isLockScreenDisabled(userId) } } + + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + isKeyguardEnabled + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isKeyguardEnabled", + initialValue = isKeyguardEnabled.value, + ) + .collect() + } } diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt index 75178f0ffef0..3739d17da6c4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt @@ -42,6 +42,8 @@ import com.android.systemui.keyguard.shared.model.KeyguardState.GONE import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN import com.android.systemui.keyguard.shared.model.KeyguardState.OCCLUDED import com.android.systemui.keyguard.shared.model.StatusBarState +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.SceneInteractor import com.android.systemui.scene.shared.flag.SceneContainerFlag @@ -60,6 +62,7 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combineTransform import kotlinx.coroutines.flow.debounce @@ -533,6 +536,16 @@ constructor( repository.setNotificationStackAbsoluteBottom(bottom) } + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + isDozing + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + columnName = "isDozing", + initialValue = isDozing.value, + ) + .collect() + } + companion object { private const val TAG = "KeyguardInteractor" /** diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt index de5088c3521c..898b68d0f4b4 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt @@ -475,7 +475,7 @@ constructor( KeyguardPickerFlag( name = Contract.FlagsTable.FLAG_NAME_CUSTOM_CLOCKS_ENABLED, value = - com.android.systemui.Flags.lockscreenCustomClocks() || + com.android.systemui.shared.Flags.lockscreenCustomClocks() || featureFlags.isEnabled(Flags.LOCKSCREEN_CUSTOM_CLOCKS), ), KeyguardPickerFlag( diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt index 454ba9af5745..d2808627163e 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/InWindowLauncherUnlockAnimationManager.kt @@ -19,6 +19,7 @@ package com.android.systemui.keyguard.ui.view import android.graphics.Rect +import android.os.DeadObjectException import android.util.Log import android.view.View import com.android.systemui.dagger.SysUISingleton @@ -192,7 +193,12 @@ constructor( launcherAnimationController?.let { manualUnlockAmount = amount - it.setUnlockAmount(amount, forceIfAnimating) + + try { + it.setUnlockAmount(amount, forceIfAnimating) + } catch (e: DeadObjectException) { + Log.e(TAG, "DeadObjectException in setUnlockAmount($amount, $forceIfAnimating)", e) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt index f15a7b30dce7..f8d442de0f55 100644 --- a/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/power/domain/interactor/PowerInteractor.kt @@ -22,6 +22,8 @@ import com.android.systemui.camera.CameraGestureHelper import com.android.systemui.classifier.FalsingCollector import com.android.systemui.classifier.FalsingCollectorActual import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable import com.android.systemui.plugins.statusbar.StatusBarStateController import com.android.systemui.power.data.repository.PowerRepository import com.android.systemui.power.shared.model.DozeScreenStateModel @@ -35,6 +37,7 @@ import javax.inject.Provider import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.map @@ -228,6 +231,15 @@ constructor( repository.updateWakefulness(powerButtonLaunchGestureTriggered = true) } + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + detailedWakefulness + .logDiffsForTable( + tableLogBuffer = tableLogBuffer, + initialValue = detailedWakefulness.value, + ) + .collect() + } + companion object { private const val FSI_WAKE_WHY = "full_screen_intent" diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt index 0f49c94c3195..297c6af5a4a7 100644 --- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt +++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakefulnessModel.kt @@ -1,6 +1,8 @@ package com.android.systemui.power.shared.model import com.android.systemui.keyguard.KeyguardService +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger /** * Models whether the device is awake or asleep, along with information about why we're in that @@ -35,7 +37,7 @@ data class WakefulnessModel( * to a subsequent power gesture. */ val powerButtonLaunchGestureTriggered: Boolean = false, -) { +) : Diffable<WakefulnessModel> { fun isAwake() = internalWakefulnessState == WakefulnessState.AWAKE || internalWakefulnessState == WakefulnessState.STARTING_TO_WAKE @@ -58,4 +60,8 @@ data class WakefulnessModel( return isAwake() && (lastWakeReason == WakeSleepReason.TAP || lastWakeReason == WakeSleepReason.GESTURE) } + + override fun logDiffs(prevVal: WakefulnessModel, row: TableRowLogger) { + row.logChange(columnName = "wakefulness", value = toString()) + } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt index 07de4662e82f..a3893bc3a54a 100644 --- a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt @@ -32,11 +32,7 @@ import androidx.activity.OnBackPressedDispatcher import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner import androidx.annotation.VisibleForTesting -import androidx.compose.animation.AnimatedContent import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import androidx.compose.animation.togetherWith import androidx.compose.foundation.ScrollState import androidx.compose.foundation.layout.Arrangement.spacedBy import androidx.compose.foundation.layout.Box @@ -120,6 +116,7 @@ import com.android.systemui.qs.composefragment.ui.GridAnchor import com.android.systemui.qs.composefragment.ui.NotificationScrimClipParams import com.android.systemui.qs.composefragment.ui.notificationScrimClip import com.android.systemui.qs.composefragment.ui.quickQuickSettingsToQuickSettings +import com.android.systemui.qs.composefragment.ui.toEditMode import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel import com.android.systemui.qs.flags.QSComposeFragment import com.android.systemui.qs.footer.ui.compose.FooterActions @@ -144,6 +141,7 @@ import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -273,36 +271,7 @@ constructor( // by the composables. .gesturesDisabled(viewModel.showingMirror) ) { - val isEditing by - viewModel.containerViewModel.editModeViewModel.isEditing - .collectAsStateWithLifecycle() - val animationSpecEditMode = tween<Float>(EDIT_MODE_TIME_MILLIS) - AnimatedContent( - targetState = isEditing, - transitionSpec = { - fadeIn(animationSpecEditMode) togetherWith - fadeOut(animationSpecEditMode) - }, - label = "EditModeAnimatedContent", - ) { editing -> - if (editing) { - val qqsPadding = viewModel.qqsHeaderHeight - EditMode( - viewModel = viewModel.containerViewModel.editModeViewModel, - modifier = - Modifier.fillMaxWidth() - .padding(top = { qqsPadding }) - .padding( - horizontal = { - QuickSettingsShade.Dimensions.Padding - .roundToPx() - } - ), - ) - } else { - CollapsableQuickSettingsSTL() - } - } + CollapsableQuickSettingsSTL() } } } @@ -324,12 +293,17 @@ constructor( from(QuickQuickSettings, QuickSettings) { quickQuickSettingsToQuickSettings(viewModel::animateTilesExpansion::get) } + to(SceneKeys.EditMode) { + spec = tween(durationMillis = EDIT_MODE_TIME_MILLIS) + toEditMode() + } }, ) LaunchedEffect(Unit) { synchronizeQsState( sceneState, + viewModel.containerViewModel.editModeViewModel.isEditing, snapshotFlow { viewModel.expansionState }.map { it.progress }, ) } @@ -342,7 +316,15 @@ constructor( scene(QuickQuickSettings) { LaunchedEffect(Unit) { viewModel.onQQSOpen() } - QuickQuickSettingsElement() + // Cannot pass the element modifier in because the top element has a `testTag` + // and this would overwrite it. + Box(Modifier.element(QuickQuickSettings.rootElementKey)) { + QuickQuickSettingsElement() + } + } + + scene(SceneKeys.EditMode) { + EditModeElement(Modifier.element(SceneKeys.EditMode.rootElementKey)) } } } @@ -582,7 +564,7 @@ constructor( } @Composable - private fun ContentScope.QuickQuickSettingsElement() { + private fun ContentScope.QuickQuickSettingsElement(modifier: Modifier = Modifier) { val qqsPadding = viewModel.qqsHeaderHeight val bottomPadding = viewModel.qqsBottomPadding DisposableEffect(Unit) { @@ -595,7 +577,7 @@ constructor( .squishiness .collectAsStateWithLifecycle() - Column(modifier = Modifier.sysuiResTag(ResIdTags.quickQsPanel)) { + Column(modifier = modifier.sysuiResTag(ResIdTags.quickQsPanel)) { Box( modifier = Modifier.fillMaxWidth() @@ -666,12 +648,12 @@ constructor( } @Composable - private fun ContentScope.QuickSettingsElement() { + private fun ContentScope.QuickSettingsElement(modifier: Modifier = Modifier) { val qqsPadding = viewModel.qqsHeaderHeight val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top) Column( modifier = - Modifier.collapseExpandSemanticAction( + modifier.collapseExpandSemanticAction( stringResource(id = R.string.accessibility_quick_settings_collapse) ) ) { @@ -776,6 +758,18 @@ constructor( } } + @Composable + private fun EditModeElement(modifier: Modifier = Modifier) { + // No need for top padding, the Scaffold inside takes care of the correct insets + EditMode( + viewModel = viewModel.containerViewModel.editModeViewModel, + modifier = + modifier + .fillMaxWidth() + .padding(horizontal = { QuickSettingsShade.Dimensions.Padding.roundToPx() }), + ) + } + private fun Modifier.collapseExpandSemanticAction(label: String): Modifier { return viewModel.collapseExpandAccessibilityAction?.let { semantics { @@ -863,6 +857,7 @@ private val instanceProvider = object SceneKeys { val QuickQuickSettings = SceneKey("QuickQuickSettingsScene") val QuickSettings = SceneKey("QuickSettingsScene") + val EditMode = SceneKey("EditModeScene") fun QSFragmentComposeViewModel.QSExpansionState.toIdleSceneKey(): SceneKey { return when { @@ -880,7 +875,11 @@ object SceneKeys { } } -suspend fun synchronizeQsState(state: MutableSceneTransitionLayoutState, expansion: Flow<Float>) { +private suspend fun synchronizeQsState( + state: MutableSceneTransitionLayoutState, + editMode: Flow<Boolean>, + expansion: Flow<Float>, +) { coroutineScope { val animationScope = this @@ -891,23 +890,30 @@ suspend fun synchronizeQsState(state: MutableSceneTransitionLayoutState, expansi currentTransition = null } - expansion.collectLatest { progress -> - when (progress) { - 0f -> snapTo(QuickQuickSettings) - 1f -> snapTo(QuickSettings) - else -> { - val transition = currentTransition - if (transition != null) { - transition.progress = progress - return@collectLatest - } + editMode.combine(expansion, ::Pair).collectLatest { (editMode, progress) -> + if (editMode && state.currentScene != SceneKeys.EditMode) { + state.setTargetScene(SceneKeys.EditMode, animationScope)?.second?.join() + } else if (!editMode && state.currentScene == SceneKeys.EditMode) { + state.setTargetScene(SceneKeys.QuickSettings, animationScope)?.second?.join() + } + if (!editMode) { + when (progress) { + 0f -> snapTo(QuickQuickSettings) + 1f -> snapTo(QuickSettings) + else -> { + val transition = currentTransition + if (transition != null) { + transition.progress = progress + return@collectLatest + } - val newTransition = - ExpansionTransition(progress).also { currentTransition = it } - state.startTransitionImmediately( - animationScope = animationScope, - transition = newTransition, - ) + val newTransition = + ExpansionTransition(progress).also { currentTransition = it } + state.startTransitionImmediately( + animationScope = animationScope, + transition = newTransition, + ) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/ToEditMode.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/ToEditMode.kt new file mode 100644 index 000000000000..0c6f3ee88312 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/ui/ToEditMode.kt @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2025 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.qs.composefragment.ui + +import com.android.compose.animation.scene.TransitionBuilder +import com.android.systemui.qs.composefragment.SceneKeys + +fun TransitionBuilder.toEditMode() { + fractionRange(start = 0.5f) { fade(SceneKeys.EditMode.rootElementKey) } + fractionRange(end = 0.5f) { + fade(SceneKeys.QuickQuickSettings.rootElementKey) + fade(SceneKeys.QuickSettings.rootElementKey) + } +} diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt index be792df340c9..f2f237ac987e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/SceneDomainModule.kt @@ -16,13 +16,27 @@ package com.android.systemui.scene.domain +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableLogBufferFactory import com.android.systemui.scene.domain.resolver.SceneResolverModule import dagger.Module +import dagger.Provides +import javax.inject.Qualifier -@Module( - includes = - [ - SceneResolverModule::class, - ] -) -object SceneDomainModule +@Module(includes = [SceneResolverModule::class]) +object SceneDomainModule { + + @JvmStatic + @Provides + @SysUISingleton + @SceneFrameworkTableLog + fun provideSceneFrameworkTableLogBuffer(factory: TableLogBufferFactory): TableLogBuffer { + return factory.create("SceneFrameworkTableLog", 100) + } +} + +@Qualifier +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +annotation class SceneFrameworkTableLog diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt index bebd398ac972..c9d8e0244d20 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneBackInteractor.kt @@ -18,11 +18,16 @@ package com.android.systemui.scene.domain.interactor import com.android.compose.animation.scene.SceneKey import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableRowLogger import com.android.systemui.scene.data.model.SceneStack +import com.android.systemui.scene.data.model.asIterable import com.android.systemui.scene.data.model.peek import com.android.systemui.scene.data.model.pop import com.android.systemui.scene.data.model.push import com.android.systemui.scene.data.model.sceneStackOf +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.SceneContainerConfig import javax.inject.Inject @@ -39,6 +44,7 @@ class SceneBackInteractor constructor( private val logger: SceneLogger, private val sceneContainerConfig: SceneContainerConfig, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) { private val _backStack = MutableStateFlow(sceneStackOf()) val backStack: StateFlow<SceneStack> = _backStack.asStateFlow() @@ -58,6 +64,7 @@ constructor( fun onSceneChange(from: SceneKey, to: SceneKey) { check(from != to) { "from == to, from=${from.debugName}, to=${to.debugName}" } + val prevVal = backStack.value _backStack.update { stack -> when (stackOperation(from, to, stack)) { null -> stack @@ -68,12 +75,21 @@ constructor( } } logger.logSceneBackStack(backStack.value) + tableLogBuffer.logDiffs( + prevVal = DiffableSceneStack(prevVal), + newVal = DiffableSceneStack(backStack.value), + ) } /** Applies the given [transform] to the back stack. */ fun updateBackStack(transform: (SceneStack) -> SceneStack) { + val prevVal = backStack.value _backStack.update { stack -> transform(stack) } logger.logSceneBackStack(backStack.value) + tableLogBuffer.logDiffs( + prevVal = DiffableSceneStack(prevVal), + newVal = DiffableSceneStack(backStack.value), + ) } private fun stackOperation(from: SceneKey, to: SceneKey, stack: SceneStack): StackOperation? { @@ -106,4 +122,15 @@ constructor( private data object Push : StackOperation private data object Pop : StackOperation + + private class DiffableSceneStack(private val sceneStack: SceneStack) : + Diffable<DiffableSceneStack> { + + override fun logDiffs(prevVal: DiffableSceneStack, row: TableRowLogger) { + row.logChange( + columnName = "backStack", + value = sceneStack.asIterable().joinToString { it.debugName }, + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt index 8bc9d96c064a..9c04323f2a0e 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt @@ -27,14 +27,19 @@ import com.android.systemui.dagger.SysUISingleton import com.android.systemui.dagger.qualifiers.Application import com.android.systemui.deviceentry.domain.interactor.DeviceUnlockedInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.TableRowLogger import com.android.systemui.scene.data.repository.SceneContainerRepository import com.android.systemui.scene.domain.resolver.SceneResolver import com.android.systemui.scene.shared.logger.SceneLogger import com.android.systemui.scene.shared.model.SceneFamilies import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.util.kotlin.pairwise import dagger.Lazy import javax.inject.Inject import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -47,6 +52,7 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch /** * Generic business logic and app state accessors for the scene framework. @@ -562,6 +568,28 @@ constructor( decrementActiveTransitionAnimationCount() } + suspend fun hydrateTableLogBuffer(tableLogBuffer: TableLogBuffer) { + coroutineScope { + launch { + currentScene + .map { sceneKey -> DiffableSceneKey(key = sceneKey) } + .pairwise() + .collect { (prev, current) -> + tableLogBuffer.logDiffs(prevVal = prev, newVal = current) + } + } + + launch { + currentOverlays + .map { overlayKeys -> DiffableOverlayKeys(keys = overlayKeys) } + .pairwise() + .collect { (prev, current) -> + tableLogBuffer.logDiffs(prevVal = prev, newVal = current) + } + } + } + } + private fun decrementActiveTransitionAnimationCount() { repository.activeTransitionAnimationCount.update { current -> (current - 1).also { @@ -573,4 +601,20 @@ constructor( } } } + + private class DiffableSceneKey(private val key: SceneKey) : Diffable<DiffableSceneKey> { + override fun logDiffs(prevVal: DiffableSceneKey, row: TableRowLogger) { + row.logChange(columnName = "currentScene", value = key.debugName) + } + } + + private class DiffableOverlayKeys(private val keys: Set<OverlayKey>) : + Diffable<DiffableOverlayKeys> { + override fun logDiffs(prevVal: DiffableOverlayKeys, row: TableRowLogger) { + row.logChange( + columnName = "currentOverlays", + value = keys.joinToString { key -> key.debugName }, + ) + } + } } diff --git a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt index 8602884ec4ee..2fd584176220 100644 --- a/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt +++ b/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt @@ -45,6 +45,7 @@ import com.android.systemui.keyguard.DismissCallbackRegistry import com.android.systemui.keyguard.domain.interactor.KeyguardEnabledInteractor import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor.Companion.keyguardScenes +import com.android.systemui.log.table.TableLogBuffer import com.android.systemui.model.SceneContainerPlugin import com.android.systemui.model.SysUiState import com.android.systemui.model.updateFlags @@ -54,6 +55,7 @@ import com.android.systemui.power.domain.interactor.PowerInteractor import com.android.systemui.power.shared.model.WakeSleepReason import com.android.systemui.scene.data.model.asIterable import com.android.systemui.scene.data.model.sceneStackOf +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.scene.domain.interactor.DisabledContentInteractor import com.android.systemui.scene.domain.interactor.SceneBackInteractor import com.android.systemui.scene.domain.interactor.SceneContainerOcclusionInteractor @@ -145,6 +147,7 @@ constructor( private val disabledContentInteractor: DisabledContentInteractor, private val activityTransitionAnimator: ActivityTransitionAnimator, private val shadeModeInteractor: ShadeModeInteractor, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) : CoreStartable { private val centralSurfaces: CentralSurfaces? get() = centralSurfacesOptLazy.get().getOrNull() @@ -154,6 +157,7 @@ constructor( override fun start() { if (SceneContainerFlag.isEnabled) { sceneLogger.logFrameworkEnabled(isEnabled = true) + applicationScope.launch { hydrateTableLogBuffer() } hydrateVisibility() automaticallySwitchScenes() hydrateSystemUiState() @@ -224,6 +228,16 @@ constructor( } } + private suspend fun hydrateTableLogBuffer() { + coroutineScope { + launch { sceneInteractor.hydrateTableLogBuffer(tableLogBuffer) } + launch { keyguardEnabledInteractor.hydrateTableLogBuffer(tableLogBuffer) } + launch { faceUnlockInteractor.hydrateTableLogBuffer(tableLogBuffer) } + launch { powerInteractor.hydrateTableLogBuffer(tableLogBuffer) } + launch { keyguardInteractor.hydrateTableLogBuffer(tableLogBuffer) } + } + } + private fun resetShadeSessions() { applicationScope.launch { combine( diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt index 59d812403777..01451502b859 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractor.kt @@ -19,6 +19,9 @@ package com.android.systemui.shade.domain.interactor import android.provider.Settings import androidx.annotation.FloatRange import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.log.table.TableLogBuffer +import com.android.systemui.log.table.logDiffsForTable +import com.android.systemui.scene.domain.SceneFrameworkTableLog import com.android.systemui.shade.data.repository.ShadeRepository import com.android.systemui.shade.shared.flag.DualShade import com.android.systemui.shade.shared.model.ShadeMode @@ -81,8 +84,9 @@ class ShadeModeInteractorImpl @Inject constructor( @Application applicationScope: CoroutineScope, - repository: ShadeRepository, + private val repository: ShadeRepository, secureSettingsRepository: SecureSettingsRepository, + @SceneFrameworkTableLog private val tableLogBuffer: TableLogBuffer, ) : ShadeModeInteractor { private val isDualShadeEnabled: Flow<Boolean> = @@ -93,17 +97,17 @@ constructor( override val isShadeLayoutWide: StateFlow<Boolean> = repository.isShadeLayoutWide + private val shadeModeInitialValue: ShadeMode + get() = + determineShadeMode( + isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT, + isShadeLayoutWide = repository.isShadeLayoutWide.value, + ) + override val shadeMode: StateFlow<ShadeMode> = combine(isDualShadeEnabled, repository.isShadeLayoutWide, ::determineShadeMode) - .stateIn( - applicationScope, - SharingStarted.Eagerly, - initialValue = - determineShadeMode( - isDualShadeEnabled = DUAL_SHADE_ENABLED_DEFAULT, - isShadeLayoutWide = repository.isShadeLayoutWide.value, - ), - ) + .logDiffsForTable(tableLogBuffer = tableLogBuffer, initialValue = shadeModeInitialValue) + .stateIn(applicationScope, SharingStarted.Eagerly, initialValue = shadeModeInitialValue) @FloatRange(from = 0.0, to = 1.0) override fun getTopEdgeSplitFraction(): Float = 0.5f diff --git a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt index a8199a402ef1..8b3ce0f69742 100644 --- a/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt +++ b/packages/SystemUI/src/com/android/systemui/shade/shared/model/ShadeMode.kt @@ -16,15 +16,18 @@ package com.android.systemui.shade.shared.model +import com.android.systemui.log.table.Diffable +import com.android.systemui.log.table.TableRowLogger + /** Enumerates all known modes of operation of the shade. */ -sealed interface ShadeMode { +sealed class ShadeMode : Diffable<ShadeMode> { /** * The single or "accordion" shade where the QS and notification parts are in two vertically * stacked panels and the user can swipe up and down to expand or collapse between the two * parts. */ - data object Single : ShadeMode + data object Single : ShadeMode() /** * The split shade where, on large screens and unfolded foldables, the QS and notification parts @@ -32,14 +35,18 @@ sealed interface ShadeMode { * * Note: This isn't the only mode where the shade is wide. */ - data object Split : ShadeMode + data object Split : ShadeMode() /** * The dual shade where the QS and notification parts each have their own independently * expandable/collapsible panel on either side of the large screen / unfolded device or sharing * a space on a small screen or folded device. */ - data object Dual : ShadeMode + data object Dual : ShadeMode() + + override fun logDiffs(prevVal: ShadeMode, row: TableRowLogger) { + row.logChange("shadeMode", toString()) + } companion object { @JvmStatic fun dual(): Dual = Dual diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt index 2d1eccdf1abd..a0a86710b4ba 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/ViewConfigCoordinator.kt @@ -22,6 +22,7 @@ import com.android.internal.widget.MessagingGroup import com.android.internal.widget.MessagingMessage import com.android.keyguard.KeyguardUpdateMonitor import com.android.keyguard.KeyguardUpdateMonitorCallback +import com.android.systemui.Flags import com.android.systemui.shade.ShadeDisplayAware import com.android.systemui.statusbar.NotificationLockscreenUserManager import com.android.systemui.statusbar.NotificationLockscreenUserManager.UserChangedListener @@ -144,7 +145,12 @@ internal constructor( ) log { "ViewConfigCoordinator.updateNotificationsOnUiModeChanged()" } traceSection("updateNotifOnUiModeChanged") { - mPipeline?.allNotifs?.forEach { entry -> entry.row?.onUiModeChanged() } + mPipeline?.allNotifs?.forEach { entry -> + entry.row?.onUiModeChanged() + if (Flags.notificationUndoGutsOnConfigChanged()) { + mGutsManager.closeAndUndoGuts() + } + } } } @@ -152,9 +158,15 @@ internal constructor( colorUpdateLogger.logEvent("VCC.updateNotificationsOnDensityOrFontScaleChanged()") mPipeline?.allNotifs?.forEach { entry -> entry.onDensityOrFontScaleChanged() - val exposedGuts = entry.areGutsExposed() - if (exposedGuts) { - mGutsManager.onDensityOrFontScaleChanged(entry) + if (Flags.notificationUndoGutsOnConfigChanged()) { + mGutsManager.closeAndUndoGuts() + } else { + // This property actually gets reset when the guts are re-inflated, so we're never + // actually calling onDensityOrFontScaleChanged below. + val exposedGuts = entry.areGutsExposed() + if (exposedGuts) { + mGutsManager.onDensityOrFontScaleChanged(entry) + } } } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java index b86d1d934269..75d1c7c3d51e 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGuts.java @@ -287,7 +287,7 @@ public class NotificationGuts extends FrameLayout { * @param save whether the state should be saved * @param force whether the guts should be force-closed regardless of state. */ - private void closeControls(int x, int y, boolean save, boolean force) { + public void closeControls(int x, int y, boolean save, boolean force) { // First try to dismiss any blocking helper. if (getWindowToken() == null) { if (mClosedListener != null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java index b1e5b22f9b1a..445cd010cd86 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationGutsManager.java @@ -48,6 +48,7 @@ import com.android.internal.logging.nano.MetricsProto; import com.android.internal.statusbar.IStatusBarService; import com.android.settingslib.notification.ConversationIconFactory; import com.android.systemui.CoreStartable; +import com.android.systemui.Flags; import com.android.systemui.dagger.SysUISingleton; import com.android.systemui.dagger.qualifiers.Background; import com.android.systemui.dagger.qualifiers.Main; @@ -223,6 +224,10 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta } public void onDensityOrFontScaleChanged(NotificationEntry entry) { + if (!Flags.notificationUndoGutsOnConfigChanged()) { + Log.wtf(TAG, "onDensityOrFontScaleChanged should not be called if" + + " notificationUndoGutsOnConfigChanged is off"); + } setExposedGuts(entry.getGuts()); bindGuts(entry.getRow()); } @@ -590,7 +595,8 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta } /** - * Closes guts or notification menus that might be visible and saves any changes. + * Closes guts or notification menus that might be visible and saves any changes if applicable + * (see {@link NotificationGuts.GutsContent#shouldBeSavedOnClose}). * * @param removeLeavebehinds true if leavebehinds (e.g. snooze) should be closed. * @param force true if guts should be closed regardless of state (used for snooze only). @@ -611,6 +617,20 @@ public class NotificationGutsManager implements NotifGutsViewManager, CoreStarta } /** + * Closes all guts that might be visible without saving changes. + */ + public void closeAndUndoGuts() { + if (mNotificationGutsExposed != null) { + mNotificationGutsExposed.removeCallbacks(mOpenRunnable); + mNotificationGutsExposed.closeControls( + /* x = */ -1, + /* y = */ -1, + /* save = */ false, + /* force = */ false); + } + } + + /** * Returns the exposed NotificationGuts or null if none are exposed. */ public NotificationGuts getExposedGuts() { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java index 99a6f6a59bd0..83897f5bc3a7 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotificationSnooze.java @@ -51,6 +51,7 @@ import com.android.app.animation.Interpolators; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.systemui.Flags; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper; import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption; import com.android.systemui.res.R; @@ -86,18 +87,26 @@ public class NotificationSnooze extends LinearLayout private NotificationSwipeActionHelper mSnoozeListener; private StatusBarNotification mSbn; - private View mSnoozeView; - private TextView mSelectedOptionText; + @VisibleForTesting + public View mSnoozeView; + @VisibleForTesting + public TextView mSelectedOptionText; private TextView mUndoButton; - private ImageView mExpandButton; - private View mDivider; - private ViewGroup mSnoozeOptionContainer; - private List<SnoozeOption> mSnoozeOptions; + @VisibleForTesting + public ImageView mExpandButton; + @VisibleForTesting + public View mDivider; + @VisibleForTesting + public ViewGroup mSnoozeOptionContainer; + @VisibleForTesting + public List<SnoozeOption> mSnoozeOptions; private int mCollapsedHeight; private SnoozeOption mDefaultOption; - private SnoozeOption mSelectedOption; + @VisibleForTesting + public SnoozeOption mSelectedOption; private boolean mSnoozing; - private boolean mExpanded; + @VisibleForTesting + public boolean mExpanded; private AnimatorSet mExpandAnimation; private KeyValueListParser mParser; @@ -334,7 +343,8 @@ public class NotificationSnooze extends LinearLayout } } - private void showSnoozeOptions(boolean show) { + @VisibleForTesting + public void showSnoozeOptions(boolean show) { int drawableId = show ? com.android.internal.R.drawable.ic_collapse_notification : com.android.internal.R.drawable.ic_expand_notification; mExpandButton.setImageResource(drawableId); @@ -381,7 +391,8 @@ public class NotificationSnooze extends LinearLayout mExpandAnimation.start(); } - private void setSelected(SnoozeOption option, boolean userAction) { + @VisibleForTesting + public void setSelected(SnoozeOption option, boolean userAction) { if (option != mSelectedOption) { mSelectedOption = option; mSelectedOptionText.setText(option.getConfirmation()); @@ -466,7 +477,12 @@ public class NotificationSnooze extends LinearLayout @Override public boolean handleCloseControls(boolean save, boolean force) { - if (mExpanded && !force) { + if (Flags.notificationUndoGutsOnConfigChanged() && !save) { + // Undo changes and let the guts handle closing the view + mSelectedOption = null; + showSnoozeOptions(false); + return false; + } else if (mExpanded && !force) { // Collapse expanded state on outside touch showSnoozeOptions(false); return true; diff --git a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt index 9795cda97f37..eecea9228ea3 100644 --- a/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt +++ b/packages/SystemUI/src/com/android/systemui/touchpad/tutorial/ui/composable/TutorialSelectionScreen.kt @@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons @@ -82,18 +83,29 @@ fun TutorialSelectionScreen( } ), ) { - val padding = if (hasCompactWindowSize()) 24.dp else 60.dp + val isCompactWindow = hasCompactWindowSize() + val padding = if (isCompactWindow) 24.dp else 60.dp val configuration = LocalConfiguration.current when (configuration.orientation) { Configuration.ORIENTATION_LANDSCAPE -> { - HorizontalSelectionButtons( - onBackTutorialClicked = onBackTutorialClicked, - onHomeTutorialClicked = onHomeTutorialClicked, - onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, - onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).padding(padding), - lastSelectedScreen, - ) + if (isCompactWindow) + HorizontalCompactSelectionButtons( + onBackTutorialClicked = onBackTutorialClicked, + onHomeTutorialClicked = onHomeTutorialClicked, + onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, + lastSelectedScreen, + modifier = Modifier.weight(1f).padding(padding), + ) + else + HorizontalSelectionButtons( + onBackTutorialClicked = onBackTutorialClicked, + onHomeTutorialClicked = onHomeTutorialClicked, + onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, + lastSelectedScreen, + modifier = Modifier.weight(1f).padding(padding), + ) } else -> { VerticalSelectionButtons( @@ -101,8 +113,8 @@ fun TutorialSelectionScreen( onHomeTutorialClicked = onHomeTutorialClicked, onRecentAppsTutorialClicked = onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked = onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).padding(padding), lastSelectedScreen, + modifier = Modifier.weight(1f).padding(padding), ) } } @@ -120,11 +132,99 @@ private fun HorizontalSelectionButtons( onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, onSwitchAppsTutorialClicked: () -> Unit, + lastSelectedScreen: Screen, modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + TwoByTwoTutorialButtons( + onBackTutorialClicked, + onHomeTutorialClicked, + onRecentAppsTutorialClicked, + onSwitchAppsTutorialClicked, + lastSelectedScreen, + modifier = Modifier.weight(1f).fillMaxSize(), + ) + } +} + +@Composable +private fun TwoByTwoTutorialButtons( + onBackTutorialClicked: () -> Unit, + onHomeTutorialClicked: () -> Unit, + onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, lastSelectedScreen: Screen, + modifier: Modifier = Modifier, +) { + val homeFocusRequester = remember { FocusRequester() } + val backFocusRequester = remember { FocusRequester() } + val recentAppsFocusRequester = remember { FocusRequester() } + val switchAppsFocusRequester = remember { FocusRequester() } + LaunchedEffect(Unit) { + when (lastSelectedScreen) { + Screen.HOME_GESTURE -> homeFocusRequester.requestFocus() + Screen.BACK_GESTURE -> backFocusRequester.requestFocus() + Screen.RECENT_APPS_GESTURE -> recentAppsFocusRequester.requestFocus() + Screen.SWITCH_APPS_GESTURE -> switchAppsFocusRequester.requestFocus() + else -> {} // No-Op. + } + } + Column { + Row(Modifier.weight(1f)) { + TutorialButton( + text = stringResource(R.string.touchpad_tutorial_home_gesture_button), + icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_home_icon), + iconColor = MaterialTheme.colorScheme.onPrimary, + onClick = onHomeTutorialClicked, + backgroundColor = MaterialTheme.colorScheme.primary, + modifier = modifier.focusRequester(homeFocusRequester).focusable().fillMaxSize(), + ) + Spacer(modifier = Modifier.size(16.dp)) + TutorialButton( + text = stringResource(R.string.touchpad_tutorial_back_gesture_button), + icon = Icons.AutoMirrored.Outlined.ArrowBack, + iconColor = MaterialTheme.colorScheme.onTertiary, + onClick = onBackTutorialClicked, + backgroundColor = MaterialTheme.colorScheme.tertiary, + modifier = modifier.focusRequester(backFocusRequester).focusable().fillMaxSize(), + ) + } + Spacer(modifier = Modifier.size(16.dp)) + Row(Modifier.weight(1f)) { + TutorialButton( + text = stringResource(R.string.touchpad_tutorial_recent_apps_gesture_button), + icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_recents_icon), + iconColor = MaterialTheme.colorScheme.onSecondary, + onClick = onRecentAppsTutorialClicked, + backgroundColor = MaterialTheme.colorScheme.secondary, + modifier = + modifier.focusRequester(recentAppsFocusRequester).focusable().fillMaxSize(), + ) + Spacer(modifier = Modifier.size(16.dp)) + TutorialButton( + text = stringResource(R.string.touchpad_tutorial_switch_apps_gesture_button), + icon = ImageVector.vectorResource(id = R.drawable.touchpad_tutorial_apps_icon), + iconColor = MaterialTheme.colorScheme.primary, + onClick = onSwitchAppsTutorialClicked, + backgroundColor = MaterialTheme.colorScheme.onPrimary, + modifier = + modifier.focusRequester(switchAppsFocusRequester).focusable().fillMaxSize(), + ) + } + } +} + +@Composable +private fun HorizontalCompactSelectionButtons( + onBackTutorialClicked: () -> Unit, + onHomeTutorialClicked: () -> Unit, + onRecentAppsTutorialClicked: () -> Unit, + onSwitchAppsTutorialClicked: () -> Unit, + lastSelectedScreen: Screen, + modifier: Modifier = Modifier, ) { Row( - horizontalArrangement = Arrangement.spacedBy(20.dp), + horizontalArrangement = Arrangement.spacedBy(16.dp), verticalAlignment = Alignment.CenterVertically, modifier = modifier, ) { @@ -133,8 +233,8 @@ private fun HorizontalSelectionButtons( onHomeTutorialClicked, onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).fillMaxSize(), lastSelectedScreen, + modifier = Modifier.weight(1f).fillMaxSize(), ) } } @@ -145,8 +245,8 @@ private fun VerticalSelectionButtons( onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, onSwitchAppsTutorialClicked: () -> Unit, - modifier: Modifier = Modifier, lastSelectedScreen: Screen, + modifier: Modifier = Modifier, ) { Column( verticalArrangement = Arrangement.spacedBy(16.dp), @@ -158,8 +258,8 @@ private fun VerticalSelectionButtons( onHomeTutorialClicked, onRecentAppsTutorialClicked, onSwitchAppsTutorialClicked, - modifier = Modifier.weight(1f).fillMaxSize(), lastSelectedScreen, + modifier = Modifier.weight(1f).fillMaxSize(), ) } } @@ -170,8 +270,8 @@ private fun FourTutorialButtons( onHomeTutorialClicked: () -> Unit, onRecentAppsTutorialClicked: () -> Unit, onSwitchAppsTutorialClicked: () -> Unit, - modifier: Modifier = Modifier, lastSelectedScreen: Screen, + modifier: Modifier = Modifier, ) { val homeFocusRequester = remember { FocusRequester() } val backFocusRequester = remember { FocusRequester() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt index 2bd104dd375d..48b801cb06be 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt @@ -20,6 +20,7 @@ import com.android.systemui.authentication.data.repository.authenticationReposit import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope import com.android.systemui.kosmos.testDispatcher +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.user.domain.interactor.selectedUserInteractor val Kosmos.authenticationInteractor by @@ -29,5 +30,6 @@ val Kosmos.authenticationInteractor by backgroundDispatcher = testDispatcher, repository = authenticationRepository, selectedUserInteractor = selectedUserInteractor, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt new file mode 100644 index 000000000000..edfe8ecd0775 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/BatteryRepositoryKosmos.kt @@ -0,0 +1,21 @@ +/* + * Copyright (C) 2025 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.common.data.repository + +import com.android.systemui.kosmos.Kosmos + +val Kosmos.batteryRepository: BatteryRepository by Kosmos.Fixture { FakeBatteryRepository() } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt new file mode 100644 index 000000000000..ac94335b42c3 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakeBatteryRepository.kt @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2025 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.common.data.repository + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +class FakeBatteryRepository : BatteryRepository { + private val _isDevicePluggedIn = MutableStateFlow(false) + + override val isDevicePluggedIn: Flow<Boolean> = _isDevicePluggedIn.asStateFlow() + + fun setDevicePluggedIn(isPluggedIn: Boolean) { + _isDevicePluggedIn.value = isPluggedIn + } +} + +val BatteryRepository.fake + get() = this as FakeBatteryRepository diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt new file mode 100644 index 000000000000..2153955f3cc1 --- /dev/null +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/domain/interactor/BatteryInteractorKosmos.kt @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2025 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.common.domain.interactor + +import com.android.systemui.common.data.repository.batteryRepository +import com.android.systemui.kosmos.Kosmos + +var Kosmos.batteryInteractor by Kosmos.Fixture { BatteryInteractor(batteryRepository) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt index 89aad4be7cc0..b0a6de1f931a 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalInteractorKosmos.kt @@ -19,10 +19,13 @@ package com.android.systemui.communal.domain.interactor import android.content.testableContext import android.os.userManager import com.android.systemui.broadcast.broadcastDispatcher +import com.android.systemui.common.domain.interactor.batteryInteractor import com.android.systemui.communal.data.repository.communalMediaRepository import com.android.systemui.communal.data.repository.communalSmartspaceRepository import com.android.systemui.communal.data.repository.communalWidgetRepository +import com.android.systemui.communal.posturing.domain.interactor.posturingInteractor import com.android.systemui.communal.widgets.EditWidgetsActivityStarter +import com.android.systemui.dock.dockManager import com.android.systemui.flags.Flags import com.android.systemui.flags.fakeFeatureFlagsClassic import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository @@ -64,6 +67,9 @@ val Kosmos.communalInteractor by Fixture { logBuffer = logcatLogBuffer("CommunalInteractor"), tableLogBuffer = mock(), managedProfileController = fakeManagedProfileController, + batteryInteractor = batteryInteractor, + dockManager = dockManager, + posturingInteractor = posturingInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt index 1d3fd300da06..c927b5563bba 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorKosmos.kt @@ -22,6 +22,7 @@ import com.android.systemui.deviceentry.data.repository.deviceEntryRepository import com.android.systemui.keyguard.dismissCallbackRegistry import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.scene.domain.interactor.sceneBackInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor @@ -36,5 +37,6 @@ val Kosmos.deviceEntryInteractor by alternateBouncerInteractor = alternateBouncerInteractor, dismissCallbackRegistry = dismissCallbackRegistry, sceneBackInteractor = sceneBackInteractor, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt index e4c7df64fdc6..9e36428d119d 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/deviceentry/domain/interactor/DeviceUnlockedInteractorKosmos.kt @@ -25,6 +25,7 @@ import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.util.settings.data.repository.userAwareSecureSettingsRepository @@ -40,6 +41,7 @@ val Kosmos.deviceUnlockedInteractor by Fixture { systemPropertiesHelper = fakeSystemPropertiesHelper, userAwareSecureSettingsRepository = userAwareSecureSettingsRepository, keyguardInteractor = keyguardInteractor, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) .apply { activateIn(testScope) } } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt index b07de16be567..ff7a06c5087e 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromLockscreenTransitionInteractorKosmos.kt @@ -16,6 +16,8 @@ package com.android.systemui.keyguard.domain.interactor +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.communalSceneInteractor import com.android.systemui.communal.domain.interactor.communalSettingsInteractor import com.android.systemui.keyguard.data.repository.keyguardTransitionRepository import com.android.systemui.kosmos.Kosmos @@ -41,5 +43,7 @@ var Kosmos.fromLockscreenTransitionInteractor by communalSettingsInteractor = communalSettingsInteractor, swipeToDismissInteractor = swipeToDismissInteractor, keyguardOcclusionInteractor = keyguardOcclusionInteractor, + communalInteractor = communalInteractor, + communalSceneInteractor = communalSceneInteractor, ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt index e46ede65bfb6..e9ba42547883 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/interactor/SceneBackInteractorKosmos.kt @@ -18,6 +18,7 @@ package com.android.systemui.scene.domain.interactor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.scene.sceneContainerConfig import com.android.systemui.scene.shared.logger.sceneLogger @@ -25,5 +26,6 @@ val Kosmos.sceneBackInteractor by Fixture { SceneBackInteractor( logger = sceneLogger, sceneContainerConfig = sceneContainerConfig, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt index d105326ec3d0..65bfafbfa9b0 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/domain/startable/SceneContainerStartableKosmos.kt @@ -36,6 +36,7 @@ import com.android.systemui.keyguard.domain.interactor.keyguardInteractor import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.testScope +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.model.sysUiState import com.android.systemui.power.domain.interactor.powerInteractor import com.android.systemui.scene.domain.interactor.disabledContentInteractor @@ -89,5 +90,6 @@ val Kosmos.sceneContainerStartable by Fixture { disabledContentInteractor = disabledContentInteractor, activityTransitionAnimator = activityTransitionAnimator, shadeModeInteractor = shadeModeInteractor, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) } diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt index a4631f17cb37..2ba9c8094aac 100644 --- a/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt +++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shade/domain/interactor/ShadeModeInteractorKosmos.kt @@ -21,6 +21,7 @@ import android.provider.Settings import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.Kosmos.Fixture import com.android.systemui.kosmos.applicationCoroutineScope +import com.android.systemui.log.table.logcatTableLogBuffer import com.android.systemui.res.R import com.android.systemui.shade.data.repository.fakeShadeRepository import com.android.systemui.shade.data.repository.shadeRepository @@ -31,6 +32,7 @@ val Kosmos.shadeModeInteractor by Fixture { applicationScope = applicationCoroutineScope, repository = shadeRepository, secureSettingsRepository = fakeSecureSettingsRepository, + tableLogBuffer = logcatTableLogBuffer(this, "sceneFrameworkTableLogBuffer"), ) } diff --git a/services/core/java/com/android/server/DockObserver.java b/services/core/java/com/android/server/DockObserver.java index 3de84f17b583..d2db8f74cd05 100644 --- a/services/core/java/com/android/server/DockObserver.java +++ b/services/core/java/com/android/server/DockObserver.java @@ -27,7 +27,6 @@ import android.media.RingtoneManager; import android.net.Uri; import android.os.Binder; import android.os.Handler; -import android.os.Message; import android.os.PowerManager; import android.os.SystemClock; import android.os.UEventObserver; @@ -37,6 +36,7 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.DumpUtils; import com.android.internal.util.FrameworkStatsLog; @@ -57,8 +57,6 @@ import java.util.Map; final class DockObserver extends SystemService { private static final String TAG = "DockObserver"; - private static final int MSG_DOCK_STATE_CHANGED = 0; - private final PowerManager mPowerManager; private final PowerManager.WakeLock mWakeLock; @@ -66,11 +64,16 @@ final class DockObserver extends SystemService { private boolean mSystemReady; + @GuardedBy("mLock") private int mActualDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + @GuardedBy("mLock") private int mReportedDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + + @GuardedBy("mLock") private int mPreviousDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED; + @GuardedBy("mLock") private boolean mUpdatesStopped; private final boolean mKeepDreamingWhenUnplugging; @@ -182,18 +185,24 @@ final class DockObserver extends SystemService { ExtconInfo.EXTCON_DOCK }); - if (!infos.isEmpty()) { - ExtconInfo info = infos.get(0); - Slog.i(TAG, "Found extcon info devPath: " + info.getDevicePath() - + ", statePath: " + info.getStatePath()); - - // set initial status - setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath())); - mPreviousDockState = mActualDockState; - - mExtconUEventObserver.startObserving(info); - } else { - Slog.i(TAG, "No extcon dock device found in this kernel."); + synchronized (mLock) { + if (!infos.isEmpty()) { + ExtconInfo info = infos.get(0); + Slog.i( + TAG, + "Found extcon info devPath: " + + info.getDevicePath() + + ", statePath: " + + info.getStatePath()); + + // set initial status + setDockStateFromProviderLocked(ExtconStateProvider.fromFile(info.getStatePath())); + mPreviousDockState = mActualDockState; + + mExtconUEventObserver.startObserving(info); + } else { + Slog.i(TAG, "No extcon dock device found in this kernel."); + } } mDockObserverLocalService = new DockObserverLocalService(); @@ -223,13 +232,15 @@ final class DockObserver extends SystemService { } } + @GuardedBy("mLock") private void updateIfDockedLocked() { // don't bother broadcasting undocked here if (mReportedDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) { - updateLocked(); + postWakefulDockStateChange(); } } + @GuardedBy("mLock") private void setActualDockStateLocked(int newState) { mActualDockState = newState; if (!mUpdatesStopped) { @@ -237,6 +248,7 @@ final class DockObserver extends SystemService { } } + @GuardedBy("mLock") private void setDockStateLocked(int newState) { if (newState != mReportedDockState) { mReportedDockState = newState; @@ -246,10 +258,12 @@ final class DockObserver extends SystemService { if (mSystemReady) { // Wake up immediately when docked or undocked unless prohibited from doing so. if (allowWakeFromDock()) { - mPowerManager.wakeUp(SystemClock.uptimeMillis(), + mPowerManager.wakeUp( + SystemClock.uptimeMillis(), + PowerManager.WAKE_REASON_DOCK, "android.server:DOCK"); } - updateLocked(); + postWakefulDockStateChange(); } } } @@ -263,9 +277,8 @@ final class DockObserver extends SystemService { Settings.Global.THEATER_MODE_ON, 0) == 0); } - private void updateLocked() { - mWakeLock.acquire(); - mHandler.sendEmptyMessage(MSG_DOCK_STATE_CHANGED); + private void postWakefulDockStateChange() { + mHandler.post(mWakeLock.wrap(this::handleDockStateChange)); } private void handleDockStateChange() { @@ -348,17 +361,7 @@ final class DockObserver extends SystemService { } } - private final Handler mHandler = new Handler(true /*async*/) { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_DOCK_STATE_CHANGED: - handleDockStateChange(); - mWakeLock.release(); - break; - } - } - }; + private final Handler mHandler = new Handler(true /*async*/); private int getDockedStateExtraValue(ExtconStateProvider state) { for (ExtconStateConfig config : mExtconStateConfigs) { @@ -386,6 +389,7 @@ final class DockObserver extends SystemService { } } + @GuardedBy("mLock") private void setDockStateFromProviderLocked(ExtconStateProvider provider) { int state = Intent.EXTRA_DOCK_STATE_UNDOCKED; if ("1".equals(provider.getValue("DOCK"))) { diff --git a/services/core/java/com/android/server/am/CachedAppOptimizer.java b/services/core/java/com/android/server/am/CachedAppOptimizer.java index d335529a006a..ce526e510053 100644 --- a/services/core/java/com/android/server/am/CachedAppOptimizer.java +++ b/services/core/java/com/android/server/am/CachedAppOptimizer.java @@ -572,6 +572,7 @@ public class CachedAppOptimizer { public long mTotalAnonMemFreedKBs; public long mSumOrigAnonRss; public double mMaxCompactEfficiency; + public double mMaxSwapEfficiency; // Cpu time public long mTotalCpuTimeMillis; @@ -586,6 +587,10 @@ public class CachedAppOptimizer { if (compactEfficiency > mMaxCompactEfficiency) { mMaxCompactEfficiency = compactEfficiency; } + final double swapEfficiency = anonRssSaved / (double) origAnonRss; + if (swapEfficiency > mMaxSwapEfficiency) { + mMaxSwapEfficiency = swapEfficiency; + } mTotalDeltaAnonRssKBs += anonRssSaved; mTotalZramConsumedKBs += zramConsumed; mTotalAnonMemFreedKBs += memFreed; @@ -628,7 +633,11 @@ public class CachedAppOptimizer { pw.println(" -----Memory Stats----"); pw.println(" Total Delta Anon RSS (KB) : " + mTotalDeltaAnonRssKBs); pw.println(" Total Physical ZRAM Consumed (KB): " + mTotalZramConsumedKBs); + // Anon Mem Freed = Delta Anon RSS - ZRAM Consumed pw.println(" Total Anon Memory Freed (KB): " + mTotalAnonMemFreedKBs); + pw.println(" Avg Swap Efficiency (KB) (Delta Anon RSS/Orig Anon RSS): " + + (mTotalDeltaAnonRssKBs / (double) mSumOrigAnonRss)); + pw.println(" Max Swap Efficiency: " + mMaxSwapEfficiency); // This tells us how much anon memory we were able to free thanks to running // compaction pw.println(" Avg Compaction Efficiency (Anon Freed/Anon RSS): " @@ -808,8 +817,9 @@ public class CachedAppOptimizer { pw.println(" Tracking last compaction stats for " + mLastCompactionStats.size() + " processes."); pw.println("Last Compaction per process stats:"); - pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs," - + "CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,oomAdjReason)"); + pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs" + + ",SwapEfficiency,CompactEfficiency,CompactCost(ms/MB),procState,oomAdj," + + "oomAdjReason)"); for (Map.Entry<Integer, SingleCompactionStats> entry : mLastCompactionStats.entrySet()) { SingleCompactionStats stats = entry.getValue(); @@ -818,7 +828,8 @@ public class CachedAppOptimizer { pw.println(); pw.println("Last 20 Compactions Stats:"); pw.println(" (ProcessName,Source,DeltaAnonRssKBs,ZramConsumedKBs,AnonMemFreedKBs," - + "CompactEfficiency,CompactCost(ms/MB),procState,oomAdj,oomAdjReason)"); + + "SwapEfficiency,CompactEfficiency,CompactCost(ms/MB),procState,oomAdj," + + "oomAdjReason)"); for (SingleCompactionStats stats : mCompactionStatsHistory) { stats.dump(pw); } @@ -1779,6 +1790,8 @@ public class CachedAppOptimizer { double getCompactEfficiency() { return mAnonMemFreedKBs / (double) mOrigAnonRss; } + double getSwapEfficiency() { return mDeltaAnonRssKBs / (double) mOrigAnonRss; } + double getCompactCost() { // mCpuTimeMillis / (anonMemFreedKBs/1024) and metric is in (ms/MB) return mCpuTimeMillis / (double) mAnonMemFreedKBs * 1024; @@ -1791,7 +1804,8 @@ public class CachedAppOptimizer { @NeverCompile void dump(PrintWriter pw) { pw.println(" (" + mProcessName + "," + mSourceType.name() + "," + mDeltaAnonRssKBs - + "," + mZramConsumedKBs + "," + mAnonMemFreedKBs + "," + getCompactEfficiency() + + "," + mZramConsumedKBs + "," + mAnonMemFreedKBs + "," + + getSwapEfficiency() + "," + getCompactEfficiency() + "," + getCompactCost() + "," + mProcState + "," + mOomAdj + "," + OomAdjuster.oomAdjReasonToString(mOomAdjReason) + ")"); } diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java index e753f273eb9b..4301c93cbed1 100644 --- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java +++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java @@ -87,7 +87,6 @@ import android.service.quicksettings.TileService; import android.text.TextUtils; import android.util.ArrayMap; import android.util.IndentingPrintWriter; -import android.util.IntArray; import android.util.Pair; import android.util.Slog; import android.util.SparseArray; @@ -125,7 +124,6 @@ import com.android.server.policy.GlobalActionsProvider; import com.android.server.power.ShutdownCheckPoints; import com.android.server.power.ShutdownThread; import com.android.server.wm.ActivityTaskManagerInternal; -import com.android.systemui.shared.Flags; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -343,19 +341,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D @Override public void onDisplayAdded(int displayId) { - if (Flags.statusBarConnectedDisplays()) { - synchronized (mLock) { - mDisplayUiState.put(displayId, new UiState()); - } + synchronized (mLock) { + mDisplayUiState.put(displayId, new UiState()); } } @Override public void onDisplayRemoved(int displayId) { - if (Flags.statusBarConnectedDisplays()) { - synchronized (mLock) { - mDisplayUiState.remove(displayId); - } + synchronized (mLock) { + mDisplayUiState.remove(displayId); } } @@ -1366,66 +1360,53 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D return mTracingEnabled; } + // TODO(b/117478341): make it aware of multi-display if needed. @Override public void disable(int what, IBinder token, String pkg) { disableForUser(what, token, pkg, mCurrentUserId); } - /** - * Disable additional status bar features for user for all displays. Pass the bitwise-or of the - * {@code #DISABLE_*} flags. To re-enable everything, pass {@code #DISABLE_NONE}. - * - * Warning: Only pass {@code #DISABLE_*} flags into this function, do not use - * {@code #DISABLE2_*} flags. - */ + // TODO(b/117478341): make it aware of multi-display if needed. @Override public void disableForUser(int what, IBinder token, String pkg, int userId) { enforceStatusBar(); enforceValidCallingUser(); synchronized (mLock) { - IntArray displayIds = new IntArray(); - for (int i = 0; i < mDisplayUiState.size(); i++) { - displayIds.add(mDisplayUiState.keyAt(i)); - } - disableLocked(displayIds, userId, what, token, pkg, 1); + disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 1); } } + // TODO(b/117478341): make it aware of multi-display if needed. /** - * Disable additional status bar features. Pass the bitwise-or of the {@code #DISABLE2_*} flags. - * To re-enable everything, pass {@code #DISABLE2_NONE}. + * Disable additional status bar features. Pass the bitwise-or of the DISABLE2_* flags. + * To re-enable everything, pass {@link #DISABLE2_NONE}. * - * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use - * {@code #DISABLE_*} flags. + * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags. */ @Override public void disable2(int what, IBinder token, String pkg) { disable2ForUser(what, token, pkg, mCurrentUserId); } + // TODO(b/117478341): make it aware of multi-display if needed. /** - * Disable additional status bar features for a given user for all displays. Pass the bitwise-or - * of the {@code #DISABLE2_*} flags. To re-enable everything, pass {@code #DISABLE2_NONE}. + * Disable additional status bar features for a given user. Pass the bitwise-or of the + * DISABLE2_* flags. To re-enable everything, pass {@link #DISABLE_NONE}. * - * Warning: Only pass {@code #DISABLE2_*} flags into this function, do not use - * {@code #DISABLE_*} flags. + * Warning: Only pass DISABLE2_* flags into this function, do not use DISABLE_* flags. */ @Override public void disable2ForUser(int what, IBinder token, String pkg, int userId) { enforceStatusBar(); synchronized (mLock) { - IntArray displayIds = new IntArray(); - for (int i = 0; i < mDisplayUiState.size(); i++) { - displayIds.add(mDisplayUiState.keyAt(i)); - } - disableLocked(displayIds, userId, what, token, pkg, 2); + disableLocked(DEFAULT_DISPLAY, userId, what, token, pkg, 2); } } - private void disableLocked(IntArray displayIds, int userId, int what, IBinder token, - String pkg, int whichFlag) { + private void disableLocked(int displayId, int userId, int what, IBinder token, String pkg, + int whichFlag) { // It's important that the the callback and the call to mBar get done // in the same order when multiple threads are calling this function // so they are paired correctly. The messages on the handler will be @@ -1435,27 +1416,18 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D // Ensure state for the current user is applied, even if passed a non-current user. final int net1 = gatherDisableActionsLocked(mCurrentUserId, 1); final int net2 = gatherDisableActionsLocked(mCurrentUserId, 2); - boolean shouldCallNotificationOnSetDisabled = false; - IStatusBar bar = mBar; - for (int displayId : displayIds.toArray()) { - final UiState state = getUiState(displayId); - if (!state.disableEquals(net1, net2)) { - shouldCallNotificationOnSetDisabled = true; - state.setDisabled(net1, net2); - if (bar != null) { - try { - // TODO(b/388244660): Create IStatusBar#disableForAllDisplays to avoid - // multiple IPC calls. - bar.disable(displayId, net1, net2); - } catch (RemoteException ex) { - Slog.e(TAG, "Unable to disable Status bar.", ex); - } + final UiState state = getUiState(displayId); + if (!state.disableEquals(net1, net2)) { + state.setDisabled(net1, net2); + mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1)); + IStatusBar bar = mBar; + if (bar != null) { + try { + bar.disable(displayId, net1, net2); + } catch (RemoteException ex) { } } } - if (shouldCallNotificationOnSetDisabled) { - mHandler.post(() -> mNotificationDelegate.onSetDisabled(net1)); - } } /** @@ -1610,8 +1582,7 @@ public class StatusBarManagerService extends IStatusBarService.Stub implements D if (SPEW) Slog.d(TAG, "setDisableFlags(0x" + Integer.toHexString(flags) + ")"); synchronized (mLock) { - disableLocked(IntArray.wrap(new int[]{displayId}), mCurrentUserId, flags, - mSysUiVisToken, cause, 1); + disableLocked(displayId, mCurrentUserId, flags, mSysUiVisToken, cause, 1); } } diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index cf111cdbcc6a..ddb9f178cb8b 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -2988,38 +2988,45 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { throw new SecurityException("Requires permission " + android.Manifest.permission.DEVICE_POWER); } - - synchronized (mGlobalLock) { - final long ident = Binder.clearCallingIdentity(); - if (mKeyguardShown != keyguardShowing) { - mKeyguardShown = keyguardShowing; - final Message msg = PooledLambda.obtainMessage( - ActivityManagerInternal::reportCurKeyguardUsageEvent, mAmInternal, - keyguardShowing); - mH.sendMessage(msg); - } - // Always reset the state regardless of keyguard-showing change, because that means the - // unlock is either completed or canceled. - if ((mDemoteTopAppReasons & DEMOTE_TOP_REASON_DURING_UNLOCKING) != 0) { - mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING; - // The scheduling group of top process was demoted by unlocking, so recompute - // to restore its real top priority if possible. - if (mTopApp != null) { - mTopApp.scheduleUpdateOomAdj(); - } + final long ident = Binder.clearCallingIdentity(); + try { + synchronized (mGlobalLock) { + setLockScreenShownLocked(keyguardShowing, aodShowing); } - try { - Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "setLockScreenShown"); - mRootWindowContainer.forAllDisplays(displayContent -> { - mKeyguardController.setKeyguardShown(displayContent.getDisplayId(), - keyguardShowing, aodShowing); - }); - maybeHideLockedProfileActivityLocked(); - } finally { - Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); - Binder.restoreCallingIdentity(ident); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @GuardedBy("mGlobalLock") + void setLockScreenShownLocked(boolean keyguardShowing, boolean aodShowing) { + if (mKeyguardShown != keyguardShowing) { + mKeyguardShown = keyguardShowing; + final Message msg = PooledLambda.obtainMessage( + ActivityManagerInternal::reportCurKeyguardUsageEvent, mAmInternal, + keyguardShowing); + mH.sendMessage(msg); + } + // Always reset the state regardless of keyguard-showing change, because that means the + // unlock is either completed or canceled. + if ((mDemoteTopAppReasons & DEMOTE_TOP_REASON_DURING_UNLOCKING) != 0) { + mDemoteTopAppReasons &= ~DEMOTE_TOP_REASON_DURING_UNLOCKING; + // The scheduling group of top process was demoted by unlocking, so recompute + // to restore its real top priority if possible. + if (mTopApp != null) { + mTopApp.scheduleUpdateOomAdj(); } } + try { + Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "setLockScreenShown"); + mRootWindowContainer.forAllDisplays(displayContent -> { + mKeyguardController.setKeyguardShown(displayContent.getDisplayId(), + keyguardShowing, aodShowing); + }); + maybeHideLockedProfileActivityLocked(); + } finally { + Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); + } mH.post(() -> { for (int i = mScreenObservers.size() - 1; i >= 0; i--) { diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java index 060f2e803ec9..b4c2c0173767 100644 --- a/services/core/java/com/android/server/wm/WindowOrganizerController.java +++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java @@ -1865,7 +1865,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub if (keyguardState != null) { boolean keyguardShowing = keyguardState.getKeyguardShowing(); boolean aodShowing = keyguardState.getAodShowing(); - mService.setLockScreenShown(keyguardShowing, aodShowing); + mService.setLockScreenShownLocked(keyguardShowing, aodShowing); } return effects; } diff --git a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java index 263ada8b36f6..148c96850d34 100644 --- a/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java +++ b/services/tests/servicestests/src/com/android/server/statusbar/StatusBarManagerServiceTest.java @@ -69,7 +69,6 @@ import android.os.Binder; import android.os.Looper; import android.os.RemoteException; import android.os.UserHandle; -import android.platform.test.annotations.EnableFlags; import android.service.quicksettings.TileService; import android.testing.TestableContext; @@ -80,7 +79,6 @@ import com.android.internal.statusbar.IStatusBar; import com.android.server.LocalServices; import com.android.server.policy.GlobalActionsProvider; import com.android.server.wm.ActivityTaskManagerInternal; -import com.android.systemui.shared.Flags; import libcore.junit.util.compat.CoreCompatChangeRule; @@ -107,7 +105,6 @@ public class StatusBarManagerServiceTest { TEST_SERVICE); private static final CharSequence APP_NAME = "AppName"; private static final CharSequence TILE_LABEL = "Tile label"; - private static final int SECONDARY_DISPLAY_ID = 2; @Rule public final TestableContext mContext = @@ -752,29 +749,6 @@ public class StatusBarManagerServiceTest { } @Test - @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS) - public void testDisableForAllDisplays() throws Exception { - int user1Id = 0; - mockUidCheck(); - mockCurrentUserCheck(user1Id); - - mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID); - - int expectedFlags = DISABLE_MASK & DISABLE_BACK; - String pkg = mContext.getPackageName(); - - // before disabling - assertEquals(DISABLE_NONE, - mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]); - - // disable - mStatusBarManagerService.disable(expectedFlags, mMockStatusBar, pkg); - - verify(mMockStatusBar).disable(0, expectedFlags, 0); - verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, expectedFlags, 0); - } - - @Test public void testSetHomeDisabled() throws Exception { int expectedFlags = DISABLE_MASK & DISABLE_HOME; String pkg = mContext.getPackageName(); @@ -877,29 +851,6 @@ public class StatusBarManagerServiceTest { } @Test - @EnableFlags(Flags.FLAG_STATUS_BAR_CONNECTED_DISPLAYS) - public void testDisable2ForAllDisplays() throws Exception { - int user1Id = 0; - mockUidCheck(); - mockCurrentUserCheck(user1Id); - - mStatusBarManagerService.onDisplayAdded(SECONDARY_DISPLAY_ID); - - int expectedFlags = DISABLE2_MASK & DISABLE2_NOTIFICATION_SHADE; - String pkg = mContext.getPackageName(); - - // before disabling - assertEquals(DISABLE_NONE, - mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]); - - // disable - mStatusBarManagerService.disable2(expectedFlags, mMockStatusBar, pkg); - - verify(mMockStatusBar).disable(0, 0, expectedFlags); - verify(mMockStatusBar).disable(SECONDARY_DISPLAY_ID, 0, expectedFlags); - } - - @Test public void testSetQuickSettingsDisabled2() throws Exception { int expectedFlags = DISABLE2_MASK & DISABLE2_QUICK_SETTINGS; String pkg = mContext.getPackageName(); @@ -1141,7 +1092,6 @@ public class StatusBarManagerServiceTest { // disable mStatusBarManagerService.disableForUser(expectedUser1Flags, mMockStatusBar, pkg, user1Id); mStatusBarManagerService.disableForUser(expectedUser2Flags, mMockStatusBar, pkg, user2Id); - // check that right flag is disabled assertEquals(expectedUser1Flags, mStatusBarManagerService.getDisableFlags(mMockStatusBar, user1Id)[0]); diff --git a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp index a0e047759dab..1fb18a6bb391 100644 --- a/tests/CompanionDeviceMultiDeviceTests/host/Android.bp +++ b/tests/CompanionDeviceMultiDeviceTests/host/Android.bp @@ -39,13 +39,4 @@ python_test_host { device_common_data: [ ":cdm_snippet_legacy", ], - version: { - py2: { - enabled: false, - }, - py3: { - enabled: true, - embedded_launcher: true, - }, - }, } diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt index 03d6ce88ac0c..18f44ddff086 100644 --- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt +++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt @@ -51,8 +51,8 @@ constructor( instrumentation.targetContext.contentResolver, Settings.System.HIDE_ROTATION_LOCK_TOGGLE_FOR_ACCESSIBILITY, 0, - UserHandle.USER_CURRENT - ); + UserHandle.USER_CURRENT_OR_SELF + ) } private val logTag = this::class.java.simpleName |