diff options
7 files changed, 301 insertions, 1 deletions
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig index c82c63c7c78b..2626ded20f40 100644 --- a/packages/SystemUI/aconfig/systemui.aconfig +++ b/packages/SystemUI/aconfig/systemui.aconfig @@ -1812,3 +1812,10 @@ flag { description: "Applies GSF font styles to Quick Settings surfaces." bug: "379364381" } + +flag { + name: "glanceable_hub_shortcut_button" + namespace: "systemui" + description: "Adds a shortcut button to lockscreen to show glanceable hub." + bug: "378173531" +} diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt new file mode 100644 index 000000000000..77c615cce287 --- /dev/null +++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfigTest.kt @@ -0,0 +1,151 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import android.platform.test.annotations.DisableFlags +import android.platform.test.annotations.EnableFlags +import android.platform.test.flag.junit.FlagsParameterization +import androidx.test.filters.SmallTest +import com.android.systemui.Flags +import com.android.systemui.SysuiTestCase +import com.android.systemui.communal.data.repository.communalSceneRepository +import com.android.systemui.communal.domain.interactor.communalInteractor +import com.android.systemui.communal.domain.interactor.setCommunalEnabled +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.coroutines.collectLastValue +import com.android.systemui.flags.andSceneContainer +import com.android.systemui.kosmos.testScope +import com.android.systemui.scene.data.repository.sceneContainerRepository +import com.android.systemui.scene.domain.interactor.sceneInteractor +import com.android.systemui.scene.shared.model.Scenes +import com.android.systemui.testKosmos +import com.google.common.truth.Truth.assertThat +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.runCurrent +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.MockitoAnnotations +import platform.test.runner.parameterized.ParameterizedAndroidJunit4 +import platform.test.runner.parameterized.Parameters + +@SmallTest +@OptIn(ExperimentalCoroutinesApi::class) +@EnableFlags(Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON) +@RunWith(ParameterizedAndroidJunit4::class) +class GlanceableHubQuickAffordanceConfigTest(flags: FlagsParameterization?) : SysuiTestCase() { + private val kosmos = testKosmos() + private val testScope = kosmos.testScope + + private lateinit var underTest: GlanceableHubQuickAffordanceConfig + + init { + mSetFlagsRule.setFlagsParameterization(flags!!) + } + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + underTest = + GlanceableHubQuickAffordanceConfig( + context = context, + communalInteractor = kosmos.communalInteractor, + communalSceneRepository = kosmos.communalSceneRepository, + sceneInteractor = kosmos.sceneInteractor, + ) + } + + @Test + fun lockscreenState_whenGlanceableHubEnabled_returnsVisible() = + testScope.runTest { + kosmos.setCommunalEnabled(true) + runCurrent() + + val lockScreenState by collectLastValue(underTest.lockScreenState) + + assertTrue(lockScreenState is KeyguardQuickAffordanceConfig.LockScreenState.Visible) + } + + @Test + fun lockscreenState_whenGlanceableHubDisabled_returnsHidden() = + testScope.runTest { + kosmos.setCommunalEnabled(false) + val lockScreenState by collectLastValue(underTest.lockScreenState) + runCurrent() + + assertTrue(lockScreenState is KeyguardQuickAffordanceConfig.LockScreenState.Hidden) + } + + @Test + fun pickerScreenState_whenGlanceableHubEnabled_returnsDefault() = + testScope.runTest { + kosmos.setCommunalEnabled(true) + runCurrent() + + assertThat(underTest.getPickerScreenState()) + .isEqualTo(KeyguardQuickAffordanceConfig.PickerScreenState.Default()) + } + + @Test + fun pickerScreenState_whenGlanceableHubDisabled_returnsDisabled() = + testScope.runTest { + kosmos.setCommunalEnabled(false) + runCurrent() + + assertThat( + underTest.getPickerScreenState() + is KeyguardQuickAffordanceConfig.PickerScreenState.Disabled + ) + } + + @Test + @DisableFlags(Flags.FLAG_SCENE_CONTAINER) + fun onTriggered_changesSceneToCommunal() = + testScope.runTest { + underTest.onTriggered(expandable = null) + runCurrent() + + assertThat(kosmos.communalSceneRepository.currentScene.value) + .isEqualTo(CommunalScenes.Communal) + } + + @Test + @EnableFlags(Flags.FLAG_SCENE_CONTAINER) + fun testTransitionToGlanceableHub_sceneContainer() = + testScope.runTest { + underTest.onTriggered(expandable = null) + runCurrent() + + assertThat(kosmos.sceneContainerRepository.currentScene.value) + .isEqualTo(Scenes.Communal) + } + + companion object { + @JvmStatic + @Parameters(name = "{0}") + fun getParams(): List<FlagsParameterization> { + return FlagsParameterization.allCombinationsOf( + Flags.FLAG_GLANCEABLE_HUB_SHORTCUT_BUTTON + ) + .andSceneContainer() + } + } +} diff --git a/packages/SystemUI/res/drawable/ic_widgets.xml b/packages/SystemUI/res/drawable/ic_widgets.xml new file mode 100644 index 000000000000..9e05809bfb33 --- /dev/null +++ b/packages/SystemUI/res/drawable/ic_widgets.xml @@ -0,0 +1,26 @@ +<!-- + ~ Copyright (C) 2024 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); + ~ you may not use this file except in compliance with the License. + ~ You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + ~ See the License for the specific language governing permissions and + ~ limitations under the License. + --> + +<vector xmlns:android="http://schemas.android.com/apk/res/android" + android:width="24dp" + android:height="24dp" + android:tint="?attr/colorControlNormal" + android:viewportHeight="960" + android:viewportWidth="960"> + <path + android:fillColor="@android:color/black" + android:pathData="M666,520L440,294L666,68L892,294L666,520ZM120,440L120,120L440,120L440,440L120,440ZM520,840L520,520L840,520L840,840L520,840ZM120,840L120,520L440,520L440,840L120,840ZM200,360L360,360L360,200L200,200L200,360ZM667,408L780,295L667,182L554,295L667,408ZM600,760L760,760L760,600L600,600L600,760ZM200,760L360,760L360,600L200,600L200,760ZM360,360L360,360L360,360L360,360L360,360ZM554,295L554,295L554,295L554,295L554,295ZM360,600L360,600L360,600L360,600L360,600ZM600,600L600,600L600,600L600,600L600,600Z" /> +</vector> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c3f4222a5eb8..24cbdab5efb7 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1336,6 +1336,10 @@ <string name="communal_widgets_disclaimer_text">To open an app using a widget, you\u2019ll need to verify it\u2019s you. Also, keep in mind that anyone can view them, even when your tablet\u2019s locked. Some widgets may not have been intended for your lock screen and may be unsafe to add here.</string> <!-- Button for user to verify they understand the information presented. [CHAR LIMIT=50] --> <string name="communal_widgets_disclaimer_button">Got it</string> + <!-- Lockscreen affordance to open glanceable hub. [CHAR LIMIT=20] --> + <string name="glanceable_hub_lockscreen_affordance_label">Widgets</string> + <!-- Text explaining that the glanceable hub affordance is disabled. [CHAR LIMIT=NONE] --> + <string name="glanceable_hub_lockscreen_affordance_disabled_text">To add Widgets on the lock screen as a shortcut, make sure it is enabled in settings.</string> <!-- Related to user switcher --><skip/> diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt index 80675d373b8e..a45204d41718 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/BuiltInKeyguardQuickAffordanceKeys.kt @@ -28,6 +28,7 @@ object BuiltInKeyguardQuickAffordanceKeys { const val CREATE_NOTE = "create_note" const val DO_NOT_DISTURB = "do_not_disturb" const val FLASHLIGHT = "flashlight" + const val GLANCEABLE_HUB = "glanceable_hub" const val HOME_CONTROLS = "home" const val MUTE = "mute" const val QR_CODE_SCANNER = "qr_code_scanner" diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt new file mode 100644 index 000000000000..0b78be128c69 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/GlanceableHubQuickAffordanceConfig.kt @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.systemui.keyguard.data.quickaffordance + +import android.content.Context +import android.util.Log +import com.android.systemui.Flags.glanceableHubShortcutButton +import com.android.systemui.animation.Expandable +import com.android.systemui.common.shared.model.ContentDescription +import com.android.systemui.common.shared.model.Icon +import com.android.systemui.communal.data.repository.CommunalSceneRepository +import com.android.systemui.communal.domain.interactor.CommunalInteractor +import com.android.systemui.communal.shared.model.CommunalScenes +import com.android.systemui.dagger.SysUISingleton +import com.android.systemui.dagger.qualifiers.Application +import com.android.systemui.res.R +import com.android.systemui.scene.domain.interactor.SceneInteractor +import com.android.systemui.scene.shared.flag.SceneContainerFlag +import com.android.systemui.scene.shared.model.Scenes +import javax.inject.Inject +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow + +/** Lockscreen affordance that opens the glanceable hub. */ +@SysUISingleton +class GlanceableHubQuickAffordanceConfig +@Inject +constructor( + @Application private val context: Context, + private val communalSceneRepository: CommunalSceneRepository, + private val communalInteractor: CommunalInteractor, + private val sceneInteractor: SceneInteractor, +) : KeyguardQuickAffordanceConfig { + + private val pickerNameResourceId = R.string.glanceable_hub_lockscreen_affordance_label + + override val key: String = BuiltInKeyguardQuickAffordanceKeys.GLANCEABLE_HUB + + override fun pickerName(): String = context.getString(pickerNameResourceId) + + override val pickerIconResourceId: Int + get() = R.drawable.ic_widgets + + override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> + get() = flow { + emit( + // TODO(b/378113263): Gate on getV2FlagEnabled() when ready. + if (!glanceableHubShortcutButton()) { + Log.i(TAG, "Button hidden on lockscreen: flag not enabled.") + KeyguardQuickAffordanceConfig.LockScreenState.Hidden + } else if (!communalInteractor.isCommunalEnabled.value) { + Log.i(TAG, "Button hidden on lockscreen: hub not enabled in settings.") + KeyguardQuickAffordanceConfig.LockScreenState.Hidden + } else { + KeyguardQuickAffordanceConfig.LockScreenState.Visible( + icon = + Icon.Resource( + pickerIconResourceId, + ContentDescription.Resource(pickerNameResourceId), + ) + ) + } + ) + } + + override suspend fun getPickerScreenState(): KeyguardQuickAffordanceConfig.PickerScreenState { + // TODO(b/378113263): Gate on getV2FlagEnabled() when ready. + return if (!glanceableHubShortcutButton()) { + Log.i(TAG, "Button unavailable in picker: flag not enabled.") + KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice + } else if (!communalInteractor.isCommunalEnabled.value) { + Log.i(TAG, "Button disabled in picker: hub not enabled in settings.") + KeyguardQuickAffordanceConfig.PickerScreenState.Disabled( + context.getString(R.string.glanceable_hub_lockscreen_affordance_disabled_text) + ) + } else { + KeyguardQuickAffordanceConfig.PickerScreenState.Default() + } + } + + override fun onTriggered( + expandable: Expandable? + ): KeyguardQuickAffordanceConfig.OnTriggeredResult { + if (SceneContainerFlag.isEnabled) { + sceneInteractor.changeScene(Scenes.Communal, "lockscreen to communal from shortcut") + } else { + communalSceneRepository.changeScene(CommunalScenes.Communal, null) + } + return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled + } + + companion object { + private const val TAG = "GlanceableHubQuickAffordanceConfig" + } +} diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt index 45561959a7df..8c6fdb989daf 100644 --- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt +++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt @@ -26,7 +26,7 @@ import dagger.multibindings.ElementsIntoSet interface KeyguardDataQuickAffordanceModule { @Binds fun providerClientFactory( - impl: KeyguardQuickAffordanceProviderClientFactoryImpl, + impl: KeyguardQuickAffordanceProviderClientFactoryImpl ): KeyguardQuickAffordanceProviderClientFactory companion object { @@ -36,6 +36,7 @@ interface KeyguardDataQuickAffordanceModule { camera: CameraQuickAffordanceConfig, doNotDisturb: DoNotDisturbQuickAffordanceConfig, flashlight: FlashlightQuickAffordanceConfig, + glanceableHub: GlanceableHubQuickAffordanceConfig, home: HomeControlsKeyguardQuickAffordanceConfig, mute: MuteQuickAffordanceConfig, quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig, @@ -46,6 +47,7 @@ interface KeyguardDataQuickAffordanceModule { camera, doNotDisturb, flashlight, + glanceableHub, home, mute, quickAccessWallet, |