Loading packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/DualShadeEducationalTooltips.kt 0 → 100644 +107 −0 Original line number Diff line number Diff line /* * 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. */ @file:OptIn(ExperimentalMaterial3Api::class) package com.android.systemui.scene.ui.composable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.RichTooltip import androidx.compose.material3.Text import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.android.compose.modifiers.height import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.scene.ui.viewmodel.DualShadeEducationalTooltipsViewModel @Composable fun DualShadeEducationalTooltips(viewModelFactory: DualShadeEducationalTooltipsViewModel.Factory) { val context = LocalContext.current val viewModel = rememberViewModel(traceName = "DualShadeEducationalTooltips") { viewModelFactory.create(context) } val visibleTooltip = viewModel.visibleTooltip ?: return val anchorBottomY = visibleTooltip.anchorBottomY // This Box represents the bounds of the top edge that the user can swipe down on to reveal // either of the dual shade overlays. It's used as a convenient way to position the anchor for // each of the tooltips that can be shown. As such, this Box is the same size as the status bar. Box( contentAlignment = if (visibleTooltip.isAlignedToStart) { Alignment.CenterStart } else { Alignment.CenterEnd }, modifier = Modifier.fillMaxWidth().height { anchorBottomY }.padding(horizontal = 24.dp), ) { AnchoredTooltip( text = visibleTooltip.text, onShown = visibleTooltip.onShown, onDismissed = visibleTooltip.onDismissed, ) } } @Composable private fun AnchoredTooltip( text: String, onShown: () -> Unit, onDismissed: () -> Unit, modifier: Modifier = Modifier, ) { val tooltipState = rememberTooltipState(initialIsVisible = true, isPersistent = true) LaunchedEffect(Unit) { onShown() } TooltipBox( positionProvider = TooltipDefaults.rememberTooltipPositionProvider(), tooltip = { RichTooltip( colors = TooltipDefaults.richTooltipColors( containerColor = LocalAndroidColorScheme.current.tertiaryFixed, contentColor = LocalAndroidColorScheme.current.onTertiaryFixed, ), caretSize = TooltipDefaults.caretSize, shadowElevation = 2.dp, ) { Text(text = text, modifier = Modifier.padding(8.dp)) } }, state = tooltipState, onDismissRequest = onDismissed, modifier = modifier, ) { Spacer(modifier = Modifier.width(48.dp).fillMaxHeight()) } } packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +3 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,8 @@ * limitations under the License. */ @file:OptIn(ExperimentalMaterial3Api::class) package com.android.systemui.scene.ui.composable import android.os.Build Loading @@ -22,6 +24,7 @@ import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect Loading packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/DualShadeEducationalTooltipsViewModelTest.kt 0 → 100644 +212 −0 Original line number Diff line number Diff line /* * 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.scene.ui.viewmodel import android.content.applicationContext import android.content.pm.UserInfo import androidx.test.filters.SmallTest import com.android.compose.animation.scene.OverlayKey import com.android.systemui.SysuiTestCase import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.advanceTimeBy import com.android.systemui.kosmos.currentValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.DualShadeEducationInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.shade.domain.interactor.disableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.user.domain.interactor.selectedUserInteractor import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @EnableSceneContainer class DualShadeEducationalTooltipsViewModelTest( private val forOverlay: OverlayKey, private val tooltipText: String, ) : SysuiTestCase() { private val kosmos = testKosmos() private lateinit var underTest: DualShadeEducationalTooltipsViewModel @Before fun setUp() { kosmos.enableDualShade() kosmos.fakeUserRepository.setUserInfos(USER_INFOS) overrideResource(R.string.dual_shade_educational_tooltip_notifs, NOTIFS_TOOLTIP) overrideResource(R.string.dual_shade_educational_tooltip_qs, QS_TOOLTIP) underTest = kosmos.dualShadeEducationalTooltipsViewModelFactory.create(kosmos.applicationContext) underTest.activateIn(kosmos.testScope) kosmos.runCurrent() } @Test fun visibleTooltip_initiallyNull() = kosmos.runTest { assertThat(underTest.visibleTooltip).isNull() } @Test fun visibleTooltip_happyPath() = kosmos.runTest { val otherOverlay = otherOverlay(forOverlay) showOverlay(otherOverlay) // No tooltip before the delay. assertNoTooltip() advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS - 1) // No tooltip because didn't wait quite long enough yet. assertNoTooltip() advanceTimeBy(1) // Expected tooltip after the full delay. val tooltip = assertVisibleTooltip(tooltipText) // UI reports impression and dismissal. tooltip.onShown() tooltip.onDismissed() assertNoTooltip() // Hide and reshow overlay to try and trigger it again it shouldn't show again because // it was already shown once. hideOverlay(otherOverlay) assertNoTooltip() showOverlay(otherOverlay) advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS) // The tooltip should still be gone as it was already shown to the user. assertNoTooltip() } @Test fun visibleTooltip_otherOverlayHiddenBeforeDelay_noTooltip() = kosmos.runTest { val otherOverlay = otherOverlay(forOverlay) showOverlay(otherOverlay) // No tooltip before the delay. assertNoTooltip() advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS - 1) // No tooltip because didn't wait quite long enough yet. assertNoTooltip() // Overlay hidden before the delay elapses. hideOverlay(otherOverlay) assertNoTooltip() advanceTimeBy(1) // Waited the entire delay, but the overlay was already hidden. assertNoTooltip() } @Test fun visibleTooltip_notDualShadeMode_noTooltip() = kosmos.runTest { disableDualShade() showOverlay(otherOverlay(forOverlay)) advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS) assertNoTooltip() } @Test fun visibleTooltip_reshowsTooltip_afterUserChanged() = kosmos.runTest { val otherOverlay = otherOverlay(forOverlay) showOverlay(otherOverlay) advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS) val tooltip = assertVisibleTooltip(tooltipText) tooltip.onShown() tooltip.onDismissed() assertNoTooltip() hideOverlay(otherOverlay) selectUser(USER_INFOS[1]) showOverlay(otherOverlay) advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS) // New user, tooltip shown again. assertVisibleTooltip(tooltipText) } /** * Returns the complementary overlay for [forOverlay]; the one that, when shown, the tooltip * will show for [forOverlay]. */ private fun otherOverlay(forOverlay: OverlayKey): OverlayKey { return when (forOverlay) { Overlays.NotificationsShade -> Overlays.QuickSettingsShade Overlays.QuickSettingsShade -> Overlays.NotificationsShade else -> error("Test isn't expecting forOverlay of ${forOverlay.debugName}") } } private fun Kosmos.showOverlay(overlay: OverlayKey) { sceneInteractor.showOverlay(overlay, "") assertThat(currentValue(sceneInteractor.currentOverlays)).contains(overlay) } private fun Kosmos.hideOverlay(overlay: OverlayKey) { sceneInteractor.hideOverlay(overlay, "") assertThat(currentValue(sceneInteractor.currentOverlays)).doesNotContain(overlay) } private fun Kosmos.assertVisibleTooltip(text: String): DualShadeEducationalTooltipViewModel { runCurrent() assertThat(underTest.visibleTooltip).isNotNull() assertThat(underTest.visibleTooltip?.text).isEqualTo(text) return underTest.visibleTooltip!! } private fun Kosmos.assertNoTooltip() { runCurrent() assertThat(underTest.visibleTooltip).isNull() } private suspend fun Kosmos.selectUser(userInfo: UserInfo) { fakeUserRepository.setSelectedUserInfo(userInfo) assertThat(selectedUserInteractor.getSelectedUserId()).isEqualTo(userInfo.id) } companion object { private const val NOTIFS_TOOLTIP = "notifs_tooltip" private const val QS_TOOLTIP = "qs_tooltip" private val USER_INFOS = listOf(UserInfo(10, "Initial user", 0), UserInfo(11, "Other user", 0)) @JvmStatic @Parameters(name = "{0}, {1}") fun testParameters(): List<Array<Any>> { return listOf( arrayOf(Overlays.NotificationsShade, NOTIFS_TOOLTIP), arrayOf(Overlays.QuickSettingsShade, QS_TOOLTIP), ) } } } packages/SystemUI/res/values-ldrtl/strings.xml +12 −0 Original line number Diff line number Diff line Loading @@ -19,4 +19,16 @@ <resources> <!-- Recents: Text that shows above the navigation bar after launching several apps. [CHAR LIMIT=NONE] --> <string name="recents_quick_scrub_onboarding">Drag left to quickly switch apps</string> <!-- Content of user education tooltip shown to teach the user that they can swipe down from the top right edge of the display to expand the notification shade panel. --> <string name="dual_shade_educational_tooltip_notifs">Swipe from the top right to open Notifications</string> <!-- Content of user education tooltip shown to teach the user that they can swipe down from the top left edge of the display to expand the Quick Settings shade panel. --> <string name="dual_shade_educational_tooltip_qs">Swipe from the top left to open Quick Settings</string> </resources> packages/SystemUI/res/values/strings.xml +12 −0 Original line number Diff line number Diff line Loading @@ -4233,4 +4233,16 @@ <!-- Description for the Underlay close button. The Underlay view appears on the bottom of the screen and shows some AI hints. The button will dismiss the underlay view. [CHAR LIMIT=NONE] --> <string name="underlay_close_button_content_description">Close</string> <!-- Content of user education tooltip shown to teach the user that they can swipe down from the top left edge of the display to expand the notification shade panel. --> <string name="dual_shade_educational_tooltip_notifs">Swipe from the top left to open Notifications</string> <!-- Content of user education tooltip shown to teach the user that they can swipe down from the top right edge of the display to expand the Quick Settings shade panel. --> <string name="dual_shade_educational_tooltip_qs">Swipe from the top right to open Quick Settings</string> </resources> Loading
packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/DualShadeEducationalTooltips.kt 0 → 100644 +107 −0 Original line number Diff line number Diff line /* * 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. */ @file:OptIn(ExperimentalMaterial3Api::class) package com.android.systemui.scene.ui.composable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.RichTooltip import androidx.compose.material3.Text import androidx.compose.material3.TooltipBox import androidx.compose.material3.TooltipDefaults import androidx.compose.material3.rememberTooltipState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.unit.dp import com.android.compose.modifiers.height import com.android.compose.theme.LocalAndroidColorScheme import com.android.systemui.lifecycle.rememberViewModel import com.android.systemui.scene.ui.viewmodel.DualShadeEducationalTooltipsViewModel @Composable fun DualShadeEducationalTooltips(viewModelFactory: DualShadeEducationalTooltipsViewModel.Factory) { val context = LocalContext.current val viewModel = rememberViewModel(traceName = "DualShadeEducationalTooltips") { viewModelFactory.create(context) } val visibleTooltip = viewModel.visibleTooltip ?: return val anchorBottomY = visibleTooltip.anchorBottomY // This Box represents the bounds of the top edge that the user can swipe down on to reveal // either of the dual shade overlays. It's used as a convenient way to position the anchor for // each of the tooltips that can be shown. As such, this Box is the same size as the status bar. Box( contentAlignment = if (visibleTooltip.isAlignedToStart) { Alignment.CenterStart } else { Alignment.CenterEnd }, modifier = Modifier.fillMaxWidth().height { anchorBottomY }.padding(horizontal = 24.dp), ) { AnchoredTooltip( text = visibleTooltip.text, onShown = visibleTooltip.onShown, onDismissed = visibleTooltip.onDismissed, ) } } @Composable private fun AnchoredTooltip( text: String, onShown: () -> Unit, onDismissed: () -> Unit, modifier: Modifier = Modifier, ) { val tooltipState = rememberTooltipState(initialIsVisible = true, isPersistent = true) LaunchedEffect(Unit) { onShown() } TooltipBox( positionProvider = TooltipDefaults.rememberTooltipPositionProvider(), tooltip = { RichTooltip( colors = TooltipDefaults.richTooltipColors( containerColor = LocalAndroidColorScheme.current.tertiaryFixed, contentColor = LocalAndroidColorScheme.current.onTertiaryFixed, ), caretSize = TooltipDefaults.caretSize, shadowElevation = 2.dp, ) { Text(text = text, modifier = Modifier.padding(8.dp)) } }, state = tooltipState, onDismissRequest = onDismissed, modifier = modifier, ) { Spacer(modifier = Modifier.width(48.dp).fillMaxHeight()) } }
packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt +3 −0 Original line number Diff line number Diff line Loading @@ -14,6 +14,8 @@ * limitations under the License. */ @file:OptIn(ExperimentalMaterial3Api::class) package com.android.systemui.scene.ui.composable import android.os.Build Loading @@ -22,6 +24,7 @@ import androidx.compose.foundation.gestures.awaitEachGesture import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect Loading
packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/DualShadeEducationalTooltipsViewModelTest.kt 0 → 100644 +212 −0 Original line number Diff line number Diff line /* * 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.scene.ui.viewmodel import android.content.applicationContext import android.content.pm.UserInfo import androidx.test.filters.SmallTest import com.android.compose.animation.scene.OverlayKey import com.android.systemui.SysuiTestCase import com.android.systemui.flags.EnableSceneContainer import com.android.systemui.kosmos.Kosmos import com.android.systemui.kosmos.advanceTimeBy import com.android.systemui.kosmos.currentValue import com.android.systemui.kosmos.runCurrent import com.android.systemui.kosmos.runTest import com.android.systemui.kosmos.testScope import com.android.systemui.lifecycle.activateIn import com.android.systemui.res.R import com.android.systemui.scene.domain.interactor.DualShadeEducationInteractor import com.android.systemui.scene.domain.interactor.sceneInteractor import com.android.systemui.scene.shared.model.Overlays import com.android.systemui.shade.domain.interactor.disableDualShade import com.android.systemui.shade.domain.interactor.enableDualShade import com.android.systemui.testKosmos import com.android.systemui.user.data.repository.fakeUserRepository import com.android.systemui.user.domain.interactor.selectedUserInteractor import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import platform.test.runner.parameterized.ParameterizedAndroidJunit4 import platform.test.runner.parameterized.Parameters @SmallTest @RunWith(ParameterizedAndroidJunit4::class) @EnableSceneContainer class DualShadeEducationalTooltipsViewModelTest( private val forOverlay: OverlayKey, private val tooltipText: String, ) : SysuiTestCase() { private val kosmos = testKosmos() private lateinit var underTest: DualShadeEducationalTooltipsViewModel @Before fun setUp() { kosmos.enableDualShade() kosmos.fakeUserRepository.setUserInfos(USER_INFOS) overrideResource(R.string.dual_shade_educational_tooltip_notifs, NOTIFS_TOOLTIP) overrideResource(R.string.dual_shade_educational_tooltip_qs, QS_TOOLTIP) underTest = kosmos.dualShadeEducationalTooltipsViewModelFactory.create(kosmos.applicationContext) underTest.activateIn(kosmos.testScope) kosmos.runCurrent() } @Test fun visibleTooltip_initiallyNull() = kosmos.runTest { assertThat(underTest.visibleTooltip).isNull() } @Test fun visibleTooltip_happyPath() = kosmos.runTest { val otherOverlay = otherOverlay(forOverlay) showOverlay(otherOverlay) // No tooltip before the delay. assertNoTooltip() advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS - 1) // No tooltip because didn't wait quite long enough yet. assertNoTooltip() advanceTimeBy(1) // Expected tooltip after the full delay. val tooltip = assertVisibleTooltip(tooltipText) // UI reports impression and dismissal. tooltip.onShown() tooltip.onDismissed() assertNoTooltip() // Hide and reshow overlay to try and trigger it again it shouldn't show again because // it was already shown once. hideOverlay(otherOverlay) assertNoTooltip() showOverlay(otherOverlay) advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS) // The tooltip should still be gone as it was already shown to the user. assertNoTooltip() } @Test fun visibleTooltip_otherOverlayHiddenBeforeDelay_noTooltip() = kosmos.runTest { val otherOverlay = otherOverlay(forOverlay) showOverlay(otherOverlay) // No tooltip before the delay. assertNoTooltip() advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS - 1) // No tooltip because didn't wait quite long enough yet. assertNoTooltip() // Overlay hidden before the delay elapses. hideOverlay(otherOverlay) assertNoTooltip() advanceTimeBy(1) // Waited the entire delay, but the overlay was already hidden. assertNoTooltip() } @Test fun visibleTooltip_notDualShadeMode_noTooltip() = kosmos.runTest { disableDualShade() showOverlay(otherOverlay(forOverlay)) advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS) assertNoTooltip() } @Test fun visibleTooltip_reshowsTooltip_afterUserChanged() = kosmos.runTest { val otherOverlay = otherOverlay(forOverlay) showOverlay(otherOverlay) advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS) val tooltip = assertVisibleTooltip(tooltipText) tooltip.onShown() tooltip.onDismissed() assertNoTooltip() hideOverlay(otherOverlay) selectUser(USER_INFOS[1]) showOverlay(otherOverlay) advanceTimeBy(DualShadeEducationInteractor.TOOLTIP_APPEARANCE_DELAY_MS) // New user, tooltip shown again. assertVisibleTooltip(tooltipText) } /** * Returns the complementary overlay for [forOverlay]; the one that, when shown, the tooltip * will show for [forOverlay]. */ private fun otherOverlay(forOverlay: OverlayKey): OverlayKey { return when (forOverlay) { Overlays.NotificationsShade -> Overlays.QuickSettingsShade Overlays.QuickSettingsShade -> Overlays.NotificationsShade else -> error("Test isn't expecting forOverlay of ${forOverlay.debugName}") } } private fun Kosmos.showOverlay(overlay: OverlayKey) { sceneInteractor.showOverlay(overlay, "") assertThat(currentValue(sceneInteractor.currentOverlays)).contains(overlay) } private fun Kosmos.hideOverlay(overlay: OverlayKey) { sceneInteractor.hideOverlay(overlay, "") assertThat(currentValue(sceneInteractor.currentOverlays)).doesNotContain(overlay) } private fun Kosmos.assertVisibleTooltip(text: String): DualShadeEducationalTooltipViewModel { runCurrent() assertThat(underTest.visibleTooltip).isNotNull() assertThat(underTest.visibleTooltip?.text).isEqualTo(text) return underTest.visibleTooltip!! } private fun Kosmos.assertNoTooltip() { runCurrent() assertThat(underTest.visibleTooltip).isNull() } private suspend fun Kosmos.selectUser(userInfo: UserInfo) { fakeUserRepository.setSelectedUserInfo(userInfo) assertThat(selectedUserInteractor.getSelectedUserId()).isEqualTo(userInfo.id) } companion object { private const val NOTIFS_TOOLTIP = "notifs_tooltip" private const val QS_TOOLTIP = "qs_tooltip" private val USER_INFOS = listOf(UserInfo(10, "Initial user", 0), UserInfo(11, "Other user", 0)) @JvmStatic @Parameters(name = "{0}, {1}") fun testParameters(): List<Array<Any>> { return listOf( arrayOf(Overlays.NotificationsShade, NOTIFS_TOOLTIP), arrayOf(Overlays.QuickSettingsShade, QS_TOOLTIP), ) } } }
packages/SystemUI/res/values-ldrtl/strings.xml +12 −0 Original line number Diff line number Diff line Loading @@ -19,4 +19,16 @@ <resources> <!-- Recents: Text that shows above the navigation bar after launching several apps. [CHAR LIMIT=NONE] --> <string name="recents_quick_scrub_onboarding">Drag left to quickly switch apps</string> <!-- Content of user education tooltip shown to teach the user that they can swipe down from the top right edge of the display to expand the notification shade panel. --> <string name="dual_shade_educational_tooltip_notifs">Swipe from the top right to open Notifications</string> <!-- Content of user education tooltip shown to teach the user that they can swipe down from the top left edge of the display to expand the Quick Settings shade panel. --> <string name="dual_shade_educational_tooltip_qs">Swipe from the top left to open Quick Settings</string> </resources>
packages/SystemUI/res/values/strings.xml +12 −0 Original line number Diff line number Diff line Loading @@ -4233,4 +4233,16 @@ <!-- Description for the Underlay close button. The Underlay view appears on the bottom of the screen and shows some AI hints. The button will dismiss the underlay view. [CHAR LIMIT=NONE] --> <string name="underlay_close_button_content_description">Close</string> <!-- Content of user education tooltip shown to teach the user that they can swipe down from the top left edge of the display to expand the notification shade panel. --> <string name="dual_shade_educational_tooltip_notifs">Swipe from the top left to open Notifications</string> <!-- Content of user education tooltip shown to teach the user that they can swipe down from the top right edge of the display to expand the Quick Settings shade panel. --> <string name="dual_shade_educational_tooltip_qs">Swipe from the top right to open Quick Settings</string> </resources>