summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt424
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt6
2 files changed, 246 insertions, 184 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index 64fa33ce8118..eff3e76f43be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -33,6 +33,7 @@ import com.android.systemui.plugins.FalsingManager
import com.android.systemui.qs.dagger.QSFlagsModule.PM_LITE_ENABLED
import com.android.systemui.qs.footer.data.model.UserSwitcherStatusModel
import com.android.systemui.qs.footer.domain.interactor.FooterActionsInteractor
+import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
import com.android.systemui.res.R
import com.android.systemui.util.icuMessageFormat
import javax.inject.Inject
@@ -47,17 +48,35 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
+private const val TAG = "FooterActionsViewModel"
+
/** A ViewModel for the footer actions. */
class FooterActionsViewModel(
- @Application appContext: Context,
- private val footerActionsInteractor: FooterActionsInteractor,
- private val falsingManager: FalsingManager,
- private val globalActionsDialogLite: GlobalActionsDialogLite,
- showPowerButton: Boolean,
-) {
- /** The context themed with the Quick Settings colors. */
- private val context = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings)
+ /** The model for the security button. */
+ val security: Flow<FooterActionsSecurityButtonViewModel?>,
+
+ /** The model for the foreground services button. */
+ val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?>,
+ /** The model for the user switcher button. */
+ val userSwitcher: Flow<FooterActionsButtonViewModel?>,
+
+ /** The model for the settings button. */
+ val settings: FooterActionsButtonViewModel,
+
+ /** The model for the power button. */
+ val power: FooterActionsButtonViewModel?,
+
+ /**
+ * Observe the device monitoring dialog requests and show the dialog accordingly. This function
+ * will suspend indefinitely and will need to be cancelled to stop observing.
+ *
+ * Important: [quickSettingsContext] must be the [Context] associated to the
+ * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this
+ * function must be cancelled when that fragment is destroyed.
+ */
+ val observeDeviceMonitoringDialogRequests: suspend (quickSettingsContext: Context) -> Unit,
+) {
/**
* Whether the UI rendering this ViewModel should be visible. Note that even when this is false,
* the UI should still participate to the layout it is included in (i.e. in the View world it
@@ -74,107 +93,6 @@ class FooterActionsViewModel(
private val _backgroundAlpha = MutableStateFlow(1f)
val backgroundAlpha: StateFlow<Float> = _backgroundAlpha.asStateFlow()
- /** The model for the security button. */
- val security: Flow<FooterActionsSecurityButtonViewModel?> =
- footerActionsInteractor.securityButtonConfig
- .map { config ->
- val (icon, text, isClickable) = config ?: return@map null
- FooterActionsSecurityButtonViewModel(
- icon,
- text,
- if (isClickable) this::onSecurityButtonClicked else null,
- )
- }
- .distinctUntilChanged()
-
- /** The model for the foreground services button. */
- val foregroundServices: Flow<FooterActionsForegroundServicesButtonViewModel?> =
- combine(
- footerActionsInteractor.foregroundServicesCount,
- footerActionsInteractor.hasNewForegroundServices,
- security,
- ) { foregroundServicesCount, hasNewChanges, securityModel ->
- if (foregroundServicesCount <= 0) {
- return@combine null
- }
-
- val text =
- icuMessageFormat(
- context.resources,
- R.string.fgs_manager_footer_label,
- foregroundServicesCount,
- )
- FooterActionsForegroundServicesButtonViewModel(
- foregroundServicesCount,
- text = text,
- displayText = securityModel == null,
- hasNewChanges = hasNewChanges,
- this::onForegroundServiceButtonClicked,
- )
- }
- .distinctUntilChanged()
-
- /** The model for the user switcher button. */
- val userSwitcher: Flow<FooterActionsButtonViewModel?> =
- footerActionsInteractor.userSwitcherStatus
- .map { userSwitcherStatus ->
- when (userSwitcherStatus) {
- UserSwitcherStatusModel.Disabled -> null
- is UserSwitcherStatusModel.Enabled -> {
- if (userSwitcherStatus.currentUserImage == null) {
- Log.e(
- TAG,
- "Skipped the addition of user switcher button because " +
- "currentUserImage is missing",
- )
- return@map null
- }
-
- userSwitcherButton(userSwitcherStatus)
- }
- }
- }
- .distinctUntilChanged()
-
- /** The model for the settings button. */
- val settings: FooterActionsButtonViewModel =
- FooterActionsButtonViewModel(
- id = R.id.settings_button_container,
- Icon.Resource(
- R.drawable.ic_settings,
- ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
- ),
- iconTint =
- Utils.getColorAttrDefaultColor(
- context,
- R.attr.onShadeInactiveVariant,
- ),
- backgroundColor = R.attr.shadeInactive,
- this::onSettingsButtonClicked,
- )
-
- /** The model for the power button. */
- val power: FooterActionsButtonViewModel? =
- if (showPowerButton) {
- FooterActionsButtonViewModel(
- id = R.id.pm_lite,
- Icon.Resource(
- android.R.drawable.ic_lock_power_off,
- ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
- ),
- iconTint =
- Utils.getColorAttrDefaultColor(
- context,
- R.attr.onShadeActive,
- ),
- backgroundColor = R.attr.shadeActive,
- this::onPowerButtonClicked,
- )
- } else {
- null
- }
-
- /** Called when the visibility of the UI rendering this model should be changed. */
fun onVisibilityChangeRequested(visible: Boolean) {
_isVisible.value = visible
}
@@ -195,14 +113,52 @@ class FooterActionsViewModel(
}
}
- /**
- * Observe the device monitoring dialog requests and show the dialog accordingly. This function
- * will suspend indefinitely and will need to be cancelled to stop observing.
- *
- * Important: [quickSettingsContext] must be the [Context] associated to the
- * [Quick Settings fragment][com.android.systemui.qs.QSFragmentLegacy], and the call to this
- * function must be cancelled when that fragment is destroyed.
- */
+ @SysUISingleton
+ class Factory
+ @Inject
+ constructor(
+ @Application private val context: Context,
+ private val falsingManager: FalsingManager,
+ private val footerActionsInteractor: FooterActionsInteractor,
+ private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>,
+ @Named(PM_LITE_ENABLED) private val showPowerButton: Boolean,
+ ) {
+ /** Create a [FooterActionsViewModel] bound to the lifecycle of [lifecycleOwner]. */
+ fun create(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
+ val globalActionsDialogLite = globalActionsDialogLiteProvider.get()
+ if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) {
+ // This should usually not happen, but let's make sure we already destroy
+ // globalActionsDialogLite.
+ globalActionsDialogLite.destroy()
+ } else {
+ // Destroy globalActionsDialogLite when the lifecycle is destroyed.
+ lifecycleOwner.lifecycle.addObserver(
+ object : DefaultLifecycleObserver {
+ override fun onDestroy(owner: LifecycleOwner) {
+ globalActionsDialogLite.destroy()
+ }
+ }
+ )
+ }
+
+ return FooterActionsViewModel(
+ context,
+ footerActionsInteractor,
+ falsingManager,
+ globalActionsDialogLite,
+ showPowerButton,
+ )
+ }
+ }
+}
+
+fun FooterActionsViewModel(
+ @Application appContext: Context,
+ footerActionsInteractor: FooterActionsInteractor,
+ falsingManager: FalsingManager,
+ globalActionsDialogLite: GlobalActionsDialogLite,
+ showPowerButton: Boolean,
+): FooterActionsViewModel {
suspend fun observeDeviceMonitoringDialogRequests(quickSettingsContext: Context) {
footerActionsInteractor.deviceMonitoringDialogRequests.collect {
footerActionsInteractor.showDeviceMonitoringDialog(
@@ -212,7 +168,7 @@ class FooterActionsViewModel(
}
}
- private fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) {
+ fun onSecurityButtonClicked(quickSettingsContext: Context, expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
@@ -220,7 +176,7 @@ class FooterActionsViewModel(
footerActionsInteractor.showDeviceMonitoringDialog(quickSettingsContext, expandable)
}
- private fun onForegroundServiceButtonClicked(expandable: Expandable) {
+ fun onForegroundServiceButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
@@ -228,7 +184,7 @@ class FooterActionsViewModel(
footerActionsInteractor.showForegroundServicesDialog(expandable)
}
- private fun onUserSwitcherClicked(expandable: Expandable) {
+ fun onUserSwitcherClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
@@ -236,7 +192,7 @@ class FooterActionsViewModel(
footerActionsInteractor.showUserSwitcher(expandable)
}
- private fun onSettingsButtonClicked(expandable: Expandable) {
+ fun onSettingsButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
@@ -244,7 +200,7 @@ class FooterActionsViewModel(
footerActionsInteractor.showSettings(expandable)
}
- private fun onPowerButtonClicked(expandable: Expandable) {
+ fun onPowerButtonClicked(expandable: Expandable) {
if (falsingManager.isFalseTap(FalsingManager.LOW_PENALTY)) {
return
}
@@ -252,71 +208,179 @@ class FooterActionsViewModel(
footerActionsInteractor.showPowerMenuDialog(globalActionsDialogLite, expandable)
}
- private fun userSwitcherButton(
- status: UserSwitcherStatusModel.Enabled
- ): FooterActionsButtonViewModel {
- val icon = status.currentUserImage!!
-
- return FooterActionsButtonViewModel(
- id = R.id.multi_user_switch,
- icon =
- Icon.Loaded(
- icon,
- ContentDescription.Loaded(
- userSwitcherContentDescription(status.currentUserName)
- ),
- ),
- iconTint = null,
- backgroundColor = R.attr.shadeInactive,
- onClick = this::onUserSwitcherClicked,
- )
- }
+ val qsThemedContext = ContextThemeWrapper(appContext, R.style.Theme_SystemUI_QuickSettings)
- private fun userSwitcherContentDescription(currentUser: String?): String? {
- return currentUser?.let { user ->
- context.getString(R.string.accessibility_quick_settings_user, user)
- }
- }
+ val security =
+ footerActionsInteractor.securityButtonConfig
+ .map { config ->
+ config?.let { securityButtonViewModel(it, ::onSecurityButtonClicked) }
+ }
+ .distinctUntilChanged()
- @SysUISingleton
- class Factory
- @Inject
- constructor(
- @Application private val context: Context,
- private val falsingManager: FalsingManager,
- private val footerActionsInteractor: FooterActionsInteractor,
- private val globalActionsDialogLiteProvider: Provider<GlobalActionsDialogLite>,
- @Named(PM_LITE_ENABLED) private val showPowerButton: Boolean,
- ) {
- /** Create a [FooterActionsViewModel] bound to the lifecycle of [lifecycleOwner]. */
- fun create(lifecycleOwner: LifecycleOwner): FooterActionsViewModel {
- val globalActionsDialogLite = globalActionsDialogLiteProvider.get()
- if (lifecycleOwner.lifecycle.currentState == Lifecycle.State.DESTROYED) {
- // This should usually not happen, but let's make sure we already destroy
- // globalActionsDialogLite.
- globalActionsDialogLite.destroy()
- } else {
- // Destroy globalActionsDialogLite when the lifecycle is destroyed.
- lifecycleOwner.lifecycle.addObserver(
- object : DefaultLifecycleObserver {
- override fun onDestroy(owner: LifecycleOwner) {
- globalActionsDialogLite.destroy()
+ val foregroundServices =
+ combine(
+ footerActionsInteractor.foregroundServicesCount,
+ footerActionsInteractor.hasNewForegroundServices,
+ security,
+ ) { foregroundServicesCount, hasNewChanges, securityModel ->
+ if (foregroundServicesCount <= 0) {
+ return@combine null
+ }
+
+ foregroundServicesButtonViewModel(
+ qsThemedContext,
+ foregroundServicesCount,
+ securityModel,
+ hasNewChanges,
+ ::onForegroundServiceButtonClicked,
+ )
+ }
+ .distinctUntilChanged()
+
+ val userSwitcher =
+ footerActionsInteractor.userSwitcherStatus
+ .map { userSwitcherStatus ->
+ when (userSwitcherStatus) {
+ UserSwitcherStatusModel.Disabled -> null
+ is UserSwitcherStatusModel.Enabled -> {
+ if (userSwitcherStatus.currentUserImage == null) {
+ Log.e(
+ TAG,
+ "Skipped the addition of user switcher button because " +
+ "currentUserImage is missing",
+ )
+ return@map null
}
+
+ userSwitcherButtonViewModel(
+ qsThemedContext,
+ userSwitcherStatus,
+ ::onUserSwitcherClicked
+ )
}
- )
+ }
}
+ .distinctUntilChanged()
- return FooterActionsViewModel(
- context,
- footerActionsInteractor,
- falsingManager,
- globalActionsDialogLite,
- showPowerButton,
- )
+ val settings = settingsButtonViewModel(qsThemedContext, ::onSettingsButtonClicked)
+ val power =
+ if (showPowerButton) {
+ powerButtonViewModel(qsThemedContext, ::onPowerButtonClicked)
+ } else {
+ null
}
- }
- companion object {
- private const val TAG = "FooterActionsViewModel"
+ return FooterActionsViewModel(
+ security = security,
+ foregroundServices = foregroundServices,
+ userSwitcher = userSwitcher,
+ settings = settings,
+ power = power,
+ observeDeviceMonitoringDialogRequests = ::observeDeviceMonitoringDialogRequests,
+ )
+}
+
+fun securityButtonViewModel(
+ config: SecurityButtonConfig,
+ onSecurityButtonClicked: (Context, Expandable) -> Unit,
+): FooterActionsSecurityButtonViewModel {
+ val (icon, text, isClickable) = config
+ return FooterActionsSecurityButtonViewModel(
+ icon,
+ text,
+ if (isClickable) onSecurityButtonClicked else null,
+ )
+}
+
+fun foregroundServicesButtonViewModel(
+ qsThemedContext: Context,
+ foregroundServicesCount: Int,
+ securityModel: FooterActionsSecurityButtonViewModel?,
+ hasNewChanges: Boolean,
+ onForegroundServiceButtonClicked: (Expandable) -> Unit,
+): FooterActionsForegroundServicesButtonViewModel {
+ val text =
+ icuMessageFormat(
+ qsThemedContext.resources,
+ R.string.fgs_manager_footer_label,
+ foregroundServicesCount,
+ )
+
+ return FooterActionsForegroundServicesButtonViewModel(
+ foregroundServicesCount,
+ text = text,
+ displayText = securityModel == null,
+ hasNewChanges = hasNewChanges,
+ onForegroundServiceButtonClicked,
+ )
+}
+
+fun userSwitcherButtonViewModel(
+ qsThemedContext: Context,
+ status: UserSwitcherStatusModel.Enabled,
+ onUserSwitcherClicked: (Expandable) -> Unit,
+): FooterActionsButtonViewModel {
+ val icon = status.currentUserImage!!
+ return FooterActionsButtonViewModel(
+ id = R.id.multi_user_switch,
+ icon =
+ Icon.Loaded(
+ icon,
+ ContentDescription.Loaded(
+ userSwitcherContentDescription(qsThemedContext, status.currentUserName)
+ ),
+ ),
+ iconTint = null,
+ backgroundColor = R.attr.shadeInactive,
+ onClick = onUserSwitcherClicked,
+ )
+}
+
+private fun userSwitcherContentDescription(
+ qsThemedContext: Context,
+ currentUser: String?
+): String? {
+ return currentUser?.let { user ->
+ qsThemedContext.getString(R.string.accessibility_quick_settings_user, user)
}
}
+
+fun settingsButtonViewModel(
+ qsThemedContext: Context,
+ onSettingsButtonClicked: (Expandable) -> Unit,
+): FooterActionsButtonViewModel {
+ return FooterActionsButtonViewModel(
+ id = R.id.settings_button_container,
+ Icon.Resource(
+ R.drawable.ic_settings,
+ ContentDescription.Resource(R.string.accessibility_quick_settings_settings)
+ ),
+ iconTint =
+ Utils.getColorAttrDefaultColor(
+ qsThemedContext,
+ R.attr.onShadeInactiveVariant,
+ ),
+ backgroundColor = R.attr.shadeInactive,
+ onSettingsButtonClicked,
+ )
+}
+
+fun powerButtonViewModel(
+ qsThemedContext: Context,
+ onPowerButtonClicked: (Expandable) -> Unit,
+): FooterActionsButtonViewModel {
+ return FooterActionsButtonViewModel(
+ id = R.id.pm_lite,
+ Icon.Resource(
+ android.R.drawable.ic_lock_power_off,
+ ContentDescription.Resource(R.string.accessibility_quick_settings_power_menu)
+ ),
+ iconTint =
+ Utils.getColorAttrDefaultColor(
+ qsThemedContext,
+ R.attr.onShadeActive,
+ ),
+ backgroundColor = R.attr.shadeActive,
+ onPowerButtonClicked,
+ )
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
index 4ada44c40f49..8e8a0c81e73d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModelTest.kt
@@ -25,7 +25,6 @@ import android.view.ContextThemeWrapper
import androidx.test.filters.SmallTest
import com.android.settingslib.Utils
import com.android.settingslib.drawable.UserIconDrawable
-import com.android.systemui.res.R
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.common.shared.model.ContentDescription
@@ -35,6 +34,7 @@ import com.android.systemui.qs.FakeFgsManagerController
import com.android.systemui.qs.QSSecurityFooterUtils
import com.android.systemui.qs.footer.FooterActionsTestUtils
import com.android.systemui.qs.footer.domain.model.SecurityButtonConfig
+import com.android.systemui.res.R
import com.android.systemui.security.data.model.SecurityModel
import com.android.systemui.settings.FakeUserTracker
import com.android.systemui.statusbar.policy.FakeSecurityController
@@ -372,9 +372,7 @@ class FooterActionsViewModelTest : SysuiTestCase() {
),
)
- val job = launch {
- underTest.observeDeviceMonitoringDialogRequests(quickSettingsContext = mock())
- }
+ val job = launch { underTest.observeDeviceMonitoringDialogRequests(mock()) }
advanceUntilIdle()
assertThat(nDialogRequests).isEqualTo(3)