Merge "Move trigger for dialog to change controls settings" into tm-qpr-dev

This commit is contained in:
Fabian Kozynski
2022-12-08 20:59:52 +00:00
committed by Android (Google) Code Review
15 changed files with 640 additions and 125 deletions

View File

@@ -19,7 +19,7 @@ package com.android.systemui.controls.dagger
import android.content.Context
import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.controls.ControlsSettingsRepository
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl

View File

@@ -20,8 +20,8 @@ import android.app.Activity
import android.content.pm.PackageManager
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsMetricsLoggerImpl
import com.android.systemui.controls.ControlsSettingsRepository
import com.android.systemui.controls.ControlsSettingsRepositoryImpl
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.controls.settings.ControlsSettingsRepositoryImpl
import com.android.systemui.controls.controller.ControlsBindingController
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
import com.android.systemui.controls.controller.ControlsController
@@ -34,6 +34,8 @@ import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.management.ControlsListingControllerImpl
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
import com.android.systemui.controls.management.ControlsRequestDialog
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.controls.settings.ControlsSettingsDialogManagerImpl
import com.android.systemui.controls.ui.ControlActionCoordinator
import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
import com.android.systemui.controls.ui.ControlsActivity
@@ -89,6 +91,11 @@ abstract class ControlsModule {
manager: ControlsSettingsRepositoryImpl
): ControlsSettingsRepository
@Binds
abstract fun provideDialogManager(
manager: ControlsSettingsDialogManagerImpl
): ControlsSettingsDialogManager
@Binds
abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger

View File

@@ -0,0 +1,231 @@
/*
* Copyright (C) 2022 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.controls.settings
import android.app.AlertDialog
import android.content.Context
import android.content.Context.MODE_PRIVATE
import android.content.DialogInterface
import android.content.SharedPreferences
import android.provider.Settings
import androidx.annotation.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG
import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.util.settings.SecureSettings
import javax.inject.Inject
/**
* Manager to display a dialog to prompt user to enable controls related Settings:
*
* * [Settings.Secure.LOCKSCREEN_SHOW_CONTROLS]
* * [Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS]
*/
interface ControlsSettingsDialogManager {
/**
* Shows the corresponding dialog. In order for a dialog to appear, the following must be true
*
* * At least one of the Settings in [ControlsSettingsRepository] are `false`.
* * The dialog has not been seen by the user too many times (as defined by
* [MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG]).
*
* When the dialogs are shown, the following outcomes are possible:
* * User cancels the dialog by clicking outside or going back: we register that the dialog was
* seen but the settings don't change.
* * User responds negatively to the dialog: we register that the user doesn't want to change
* the settings (dialog will not appear again) and the settings don't change.
* * User responds positively to the dialog: the settings are set to `true` and the dialog will
* not appear again.
* * SystemUI closes the dialogs (for example, the activity showing it is closed). In this case,
* we don't modify anything.
*
* Of those four scenarios, only the first three will cause [onAttemptCompleted] to be called.
* It will also be called if the dialogs are not shown.
*/
fun maybeShowDialog(activityContext: Context, onAttemptCompleted: () -> Unit)
/**
* Closes the dialog without registering anything from the user. The state of the settings after
* this is called will be the same as before the dialogs were shown.
*/
fun closeDialog()
companion object {
@VisibleForTesting internal const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
@VisibleForTesting
internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
}
}
@SysUISingleton
class ControlsSettingsDialogManagerImpl
@VisibleForTesting
internal constructor(
private val secureSettings: SecureSettings,
private val userFileManager: UserFileManager,
private val controlsSettingsRepository: ControlsSettingsRepository,
private val userTracker: UserTracker,
private val activityStarter: ActivityStarter,
private val dialogProvider: (context: Context, theme: Int) -> AlertDialog
) : ControlsSettingsDialogManager {
@Inject
constructor(
secureSettings: SecureSettings,
userFileManager: UserFileManager,
controlsSettingsRepository: ControlsSettingsRepository,
userTracker: UserTracker,
activityStarter: ActivityStarter
) : this(
secureSettings,
userFileManager,
controlsSettingsRepository,
userTracker,
activityStarter,
{ context, theme -> SettingsDialog(context, theme) }
)
private var dialog: AlertDialog? = null
private set
private val showDeviceControlsInLockscreen: Boolean
get() = controlsSettingsRepository.canShowControlsInLockscreen.value
private val allowTrivialControls: Boolean
get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
override fun maybeShowDialog(activityContext: Context, onAttemptCompleted: () -> Unit) {
closeDialog()
val prefs =
userFileManager.getSharedPreferences(
DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
MODE_PRIVATE,
userTracker.userId
)
val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
if (
attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
(showDeviceControlsInLockscreen && allowTrivialControls)
) {
onAttemptCompleted()
return
}
val listener = DialogListener(prefs, attempts, onAttemptCompleted)
val d =
dialogProvider(activityContext, R.style.Theme_SystemUI_Dialog).apply {
setIcon(R.drawable.ic_warning)
setOnCancelListener(listener)
setNeutralButton(R.string.controls_settings_dialog_neutral_button, listener)
setPositiveButton(R.string.controls_settings_dialog_positive_button, listener)
if (showDeviceControlsInLockscreen) {
setTitle(R.string.controls_settings_trivial_controls_dialog_title)
setMessage(R.string.controls_settings_trivial_controls_dialog_message)
} else {
setTitle(R.string.controls_settings_show_controls_dialog_title)
setMessage(R.string.controls_settings_show_controls_dialog_message)
}
}
SystemUIDialog.registerDismissListener(d) { dialog = null }
SystemUIDialog.setDialogSize(d)
SystemUIDialog.setShowForAllUsers(d, true)
dialog = d
d.show()
}
private fun turnOnSettingSecurely(settings: List<String>) {
val action =
ActivityStarter.OnDismissAction {
settings.forEach { setting ->
secureSettings.putIntForUser(setting, 1, userTracker.userId)
}
true
}
activityStarter.dismissKeyguardThenExecute(
action,
/* cancel */ null,
/* afterKeyguardGone */ true
)
}
override fun closeDialog() {
dialog?.dismiss()
}
private inner class DialogListener(
private val prefs: SharedPreferences,
private val attempts: Int,
private val onComplete: () -> Unit
) : DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
override fun onClick(dialog: DialogInterface?, which: Int) {
if (dialog == null) return
if (which == DialogInterface.BUTTON_POSITIVE) {
val settings = mutableListOf(Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS)
if (!showDeviceControlsInLockscreen) {
settings.add(Settings.Secure.LOCKSCREEN_SHOW_CONTROLS)
}
turnOnSettingSecurely(settings)
}
if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
prefs
.edit()
.putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
.apply()
}
onComplete()
}
override fun onCancel(dialog: DialogInterface?) {
if (dialog == null) return
if (attempts < MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, attempts + 1).apply()
}
onComplete()
}
}
private fun AlertDialog.setNeutralButton(
msgId: Int,
listener: DialogInterface.OnClickListener
) {
setButton(DialogInterface.BUTTON_NEUTRAL, context.getText(msgId), listener)
}
private fun AlertDialog.setPositiveButton(
msgId: Int,
listener: DialogInterface.OnClickListener
) {
setButton(DialogInterface.BUTTON_POSITIVE, context.getText(msgId), listener)
}
private fun AlertDialog.setMessage(msgId: Int) {
setMessage(context.getText(msgId))
}
/** This is necessary because the constructors are `protected`. */
private class SettingsDialog(context: Context, theme: Int) : AlertDialog(context, theme)
}

View File

@@ -15,7 +15,7 @@
*
*/
package com.android.systemui.controls
package com.android.systemui.controls.settings
import kotlinx.coroutines.flow.StateFlow

View File

@@ -15,7 +15,7 @@
*
*/
package com.android.systemui.controls
package com.android.systemui.controls.settings
import android.provider.Settings
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow

View File

@@ -19,15 +19,12 @@ package com.android.systemui.controls.ui
import android.annotation.AnyThread
import android.annotation.MainThread
import android.app.Activity
import android.app.AlertDialog
import android.app.Dialog
import android.app.PendingIntent
import android.content.Context
import android.content.pm.PackageManager
import android.content.pm.ResolveInfo
import android.os.UserHandle
import android.os.VibrationEffect
import android.provider.Settings.Secure
import android.service.controls.Control
import android.service.controls.actions.BooleanAction
import android.service.controls.actions.CommandAction
@@ -35,39 +32,36 @@ import android.service.controls.actions.FloatAction
import android.util.Log
import android.view.HapticFeedbackConstants
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsSettingsRepository
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.dagger.SysUISingleton
import com.android.systemui.dagger.qualifiers.Main
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.phone.SystemUIDialog
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_CONTROLS_FILE
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.TaskViewFactory
import java.util.Optional
import javax.inject.Inject
@SysUISingleton
class ControlActionCoordinatorImpl @Inject constructor(
private val context: Context,
private val bgExecutor: DelayableExecutor,
@Main private val uiExecutor: DelayableExecutor,
private val activityStarter: ActivityStarter,
private val broadcastSender: BroadcastSender,
private val keyguardStateController: KeyguardStateController,
private val taskViewFactory: Optional<TaskViewFactory>,
private val controlsMetricsLogger: ControlsMetricsLogger,
private val vibrator: VibratorHelper,
private val secureSettings: SecureSettings,
private val userContextProvider: UserContextProvider,
private val controlsSettingsRepository: ControlsSettingsRepository,
private val context: Context,
private val bgExecutor: DelayableExecutor,
@Main private val uiExecutor: DelayableExecutor,
private val activityStarter: ActivityStarter,
private val broadcastSender: BroadcastSender,
private val keyguardStateController: KeyguardStateController,
private val taskViewFactory: Optional<TaskViewFactory>,
private val controlsMetricsLogger: ControlsMetricsLogger,
private val vibrator: VibratorHelper,
private val controlsSettingsRepository: ControlsSettingsRepository,
private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
private val featureFlags: FeatureFlags,
) : ControlActionCoordinator {
private var dialog: Dialog? = null
private var pendingAction: Action? = null
@@ -76,16 +70,16 @@ class ControlActionCoordinatorImpl @Inject constructor(
get() = !keyguardStateController.isUnlocked()
private val allowTrivialControls: Boolean
get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
private val showDeviceControlsInLockscreen: Boolean
get() = controlsSettingsRepository.canShowControlsInLockscreen.value
override lateinit var activityContext: Context
companion object {
private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
}
override fun closeDialogs() {
if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
controlsSettingsDialogManager.closeDialog()
}
val isActivityFinishing =
(activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
if (isActivityFinishing == true) {
@@ -253,71 +247,9 @@ class ControlActionCoordinatorImpl @Inject constructor(
if (action.authIsRequired) {
return
}
val prefs = userContextProvider.userContext.getSharedPreferences(
PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
if (attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
(showDeviceControlsInLockscreen && allowTrivialControls)) {
return
if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
controlsSettingsDialogManager.maybeShowDialog(activityContext) {}
}
val builder = AlertDialog
.Builder(activityContext, R.style.Theme_SystemUI_Dialog)
.setIcon(R.drawable.ic_warning)
.setOnCancelListener {
if (attempts < MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, attempts + 1)
.commit()
}
true
}
.setNeutralButton(R.string.controls_settings_dialog_neutral_button) { _, _ ->
if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
.commit()
}
true
}
if (showDeviceControlsInLockscreen) {
dialog = builder
.setTitle(R.string.controls_settings_trivial_controls_dialog_title)
.setMessage(R.string.controls_settings_trivial_controls_dialog_message)
.setPositiveButton(R.string.controls_settings_dialog_positive_button) { _, _ ->
if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
.commit()
}
secureSettings.putIntForUser(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS, 1,
UserHandle.USER_CURRENT)
true
}
.create()
} else {
dialog = builder
.setTitle(R.string.controls_settings_show_controls_dialog_title)
.setMessage(R.string.controls_settings_show_controls_dialog_message)
.setPositiveButton(R.string.controls_settings_dialog_positive_button) { _, _ ->
if (attempts != MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) {
prefs.edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS,
MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
.commit()
}
secureSettings.putIntForUser(Secure.LOCKSCREEN_SHOW_CONTROLS,
1, UserHandle.USER_CURRENT)
secureSettings.putIntForUser(Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS,
1, UserHandle.USER_CURRENT)
true
}
.create()
}
SystemUIDialog.registerDismissListener(dialog)
SystemUIDialog.setDialogSize(dialog)
dialog?.create()
dialog?.show()
}
@VisibleForTesting

View File

@@ -32,8 +32,10 @@ import androidx.activity.ComponentActivity
import com.android.systemui.R
import com.android.systemui.broadcast.BroadcastDispatcher
import com.android.systemui.controls.management.ControlsAnimations
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.statusbar.policy.KeyguardStateController
import javax.inject.Inject
/**
@@ -47,7 +49,9 @@ class ControlsActivity @Inject constructor(
private val uiController: ControlsUiController,
private val broadcastDispatcher: BroadcastDispatcher,
private val dreamManager: IDreamManager,
private val featureFlags: FeatureFlags
private val featureFlags: FeatureFlags,
private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
private val keyguardStateController: KeyguardStateController
) : ComponentActivity() {
private lateinit var parent: ViewGroup
@@ -92,7 +96,13 @@ class ControlsActivity @Inject constructor(
parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
parent.alpha = 0f
uiController.show(parent, { finishOrReturnToDream() }, this)
if (featureFlags.isEnabled(Flags.USE_APP_PANELS) && !keyguardStateController.isUnlocked) {
controlsSettingsDialogManager.maybeShowDialog(this) {
uiController.show(parent, { finishOrReturnToDream() }, this)
}
} else {
uiController.show(parent, { finishOrReturnToDream() }, this)
}
ControlsAnimations.enterAnimation(parent).start()
}
@@ -124,6 +134,7 @@ class ControlsActivity @Inject constructor(
mExitToDream = false
uiController.hide()
controlsSettingsDialogManager.closeDialog()
}
override fun onDestroy() {

View File

@@ -49,7 +49,7 @@ import com.android.systemui.Dumpable
import com.android.systemui.R
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.ControlsSettingsRepository
import com.android.systemui.controls.settings.ControlsSettingsRepository
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo

View File

@@ -68,7 +68,6 @@ public class DeviceControlsControllerImpl @Inject constructor(
internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
const val PREFS_CONTROLS_FILE = "controls_prefs"
internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
private const val SEEDING_MAX = 2
}

View File

@@ -16,21 +16,19 @@
package com.android.systemui.controls.ui
import android.content.Context
import android.content.SharedPreferences
import android.test.suitebuilder.annotation.SmallTest
import android.testing.AndroidTestingRunner
import com.android.systemui.SysuiTestCase
import com.android.systemui.broadcast.BroadcastSender
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.FakeControlsSettingsRepository
import com.android.systemui.controls.settings.ControlsSettingsDialogManager
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserContextProvider
import com.android.systemui.statusbar.VibratorHelper
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.statusbar.policy.KeyguardStateController
import com.android.systemui.util.concurrency.DelayableExecutor
import com.android.systemui.util.settings.SecureSettings
import com.android.wm.shell.TaskViewFactory
import org.junit.Before
import org.junit.Test
@@ -40,8 +38,8 @@ import org.mockito.Mock
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.Mockito.anyBoolean
import org.mockito.Mockito.doNothing
import org.mockito.Mockito.doReturn
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.reset
import org.mockito.Mockito.spy
@@ -71,9 +69,9 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
@Mock
private lateinit var metricsLogger: ControlsMetricsLogger
@Mock
private lateinit var secureSettings: SecureSettings
private lateinit var featureFlags: FeatureFlags
@Mock
private lateinit var userContextProvider: UserContextProvider
private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
companion object {
fun <T> any(): T = Mockito.any<T>()
@@ -103,23 +101,16 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
taskViewFactory,
metricsLogger,
vibratorHelper,
secureSettings,
userContextProvider,
controlsSettingsRepository
controlsSettingsRepository,
controlsSettingsDialogManager,
featureFlags
))
val userContext = mock(Context::class.java)
val pref = mock(SharedPreferences::class.java)
`when`(userContextProvider.userContext).thenReturn(userContext)
`when`(userContext.getSharedPreferences(
DeviceControlsControllerImpl.PREFS_CONTROLS_FILE, Context.MODE_PRIVATE))
.thenReturn(pref)
// Just return 2 so we don't test any Dialog logic which requires a launched activity.
`when`(pref.getInt(DeviceControlsControllerImpl.PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
.thenReturn(2)
coordinator.activityContext = mContext
`when`(cvh.cws.ci.controlId).thenReturn(ID)
`when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
`when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(false)
action = spy(coordinator.Action(ID, {}, false, true))
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
}
@@ -160,14 +151,30 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
`when`(keyguardStateController.isShowing()).thenReturn(true)
`when`(keyguardStateController.isUnlocked()).thenReturn(false)
coordinator.toggle(cvh, "", true)
verify(coordinator).bouncerOrRun(action)
verify(controlsSettingsDialogManager).maybeShowDialog(any(), any())
verify(action).invoke()
}
@Test
fun testToggleWhenLockedDoesNotTriggerDialog_featureFlagEnabled() {
`when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(true)
action = spy(coordinator.Action(ID, {}, false, false))
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
`when`(keyguardStateController.isShowing()).thenReturn(true)
`when`(keyguardStateController.isUnlocked()).thenReturn(false)
doNothing().`when`(controlsSettingsDialogManager).maybeShowDialog(any(), any())
coordinator.toggle(cvh, "", true)
verify(coordinator).bouncerOrRun(action)
verify(controlsSettingsDialogManager, never()).maybeShowDialog(any(), any())
}
@Test
fun testToggleDoesNotRunsWhenLockedAndAuthRequired() {
action = spy(coordinator.Action(ID, {}, false, true))

View File

@@ -22,7 +22,7 @@ import com.android.internal.widget.LockPatternUtils
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.FakeControlsSettingsRepository
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
import com.android.systemui.controls.management.ControlsListingController

View File

@@ -0,0 +1,328 @@
/*
* Copyright (C) 2022 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.controls.settings
import android.content.DialogInterface
import android.content.SharedPreferences
import android.database.ContentObserver
import android.provider.Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS
import android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS
import android.testing.AndroidTestingRunner
import android.testing.TestableLooper
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager
import com.android.systemui.settings.UserTracker
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
import com.android.systemui.util.FakeSharedPreferences
import com.android.systemui.util.TestableAlertDialog
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.nullable
import com.android.systemui.util.settings.FakeSettings
import com.google.common.truth.Truth.assertThat
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentMatchers.anyBoolean
import org.mockito.Mock
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.never
import org.mockito.Mockito.verify
import org.mockito.Mockito.`when`
import org.mockito.MockitoAnnotations
@SmallTest
@RunWith(AndroidTestingRunner::class)
@TestableLooper.RunWithLooper
class ControlsSettingsDialogManagerImplTest : SysuiTestCase() {
companion object {
private const val SETTING_SHOW = LOCKSCREEN_SHOW_CONTROLS
private const val SETTING_ACTION = LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS
private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
}
@Mock private lateinit var userFileManager: UserFileManager
@Mock private lateinit var userTracker: UserTracker
@Mock private lateinit var activityStarter: ActivityStarter
@Mock private lateinit var completedRunnable: () -> Unit
private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository
private lateinit var sharedPreferences: FakeSharedPreferences
private lateinit var secureSettings: FakeSettings
private lateinit var underTest: ControlsSettingsDialogManagerImpl
private var dialog: TestableAlertDialog? = null
@Before
fun setUp() {
MockitoAnnotations.initMocks(this)
controlsSettingsRepository = FakeControlsSettingsRepository()
sharedPreferences = FakeSharedPreferences()
secureSettings = FakeSettings()
`when`(userTracker.userId).thenReturn(0)
secureSettings.userId = userTracker.userId
`when`(
userFileManager.getSharedPreferences(
eq(DeviceControlsControllerImpl.PREFS_CONTROLS_FILE),
anyInt(),
anyInt()
)
)
.thenReturn(sharedPreferences)
`when`(activityStarter.dismissKeyguardThenExecute(any(), nullable(), anyBoolean()))
.thenAnswer { (it.arguments[0] as ActivityStarter.OnDismissAction).onDismiss() }
attachRepositoryToSettings()
underTest =
ControlsSettingsDialogManagerImpl(
secureSettings,
userFileManager,
controlsSettingsRepository,
userTracker,
activityStarter
) { context, _ -> TestableAlertDialog(context).also { dialog = it } }
}
@After
fun tearDown() {
underTest.closeDialog()
}
@Test
fun dialogNotShownIfPrefsAtMaximum() {
sharedPreferences.putAttempts(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
underTest.maybeShowDialog(context, completedRunnable)
assertThat(dialog?.isShowing ?: false).isFalse()
verify(completedRunnable).invoke()
}
@Test
fun dialogNotShownIfSettingsAreTrue() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, true)
underTest.maybeShowDialog(context, completedRunnable)
assertThat(dialog?.isShowing ?: false).isFalse()
verify(completedRunnable).invoke()
}
@Test
fun dialogShownIfAllowTrivialControlsFalse() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
assertThat(dialog?.isShowing ?: false).isTrue()
}
@Test
fun dialogDispossedAfterClosing() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
underTest.closeDialog()
assertThat(dialog?.isShowing ?: false).isFalse()
}
@Test
fun dialogNeutralButtonDoesntChangeSetting() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
clickButton(DialogInterface.BUTTON_NEUTRAL)
assertThat(secureSettings.getBool(SETTING_ACTION, false)).isFalse()
}
@Test
fun dialogNeutralButtonPutsMaxAttempts() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
clickButton(DialogInterface.BUTTON_NEUTRAL)
assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
.isEqualTo(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
}
@Test
fun dialogNeutralButtonCallsOnComplete() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
clickButton(DialogInterface.BUTTON_NEUTRAL)
verify(completedRunnable).invoke()
}
@Test
fun dialogPositiveButtonChangesSetting() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
clickButton(DialogInterface.BUTTON_POSITIVE)
assertThat(secureSettings.getBool(SETTING_ACTION, false)).isTrue()
}
@Test
fun dialogPositiveButtonPutsMaxAttempts() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
clickButton(DialogInterface.BUTTON_POSITIVE)
assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
.isEqualTo(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG)
}
@Test
fun dialogPositiveButtonCallsOnComplete() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
clickButton(DialogInterface.BUTTON_POSITIVE)
verify(completedRunnable).invoke()
}
@Test
fun dialogCancelDoesntChangeSetting() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
dialog?.cancel()
assertThat(secureSettings.getBool(SETTING_ACTION, false)).isFalse()
}
@Test
fun dialogCancelPutsOneExtraAttempt() {
val attempts = 0
sharedPreferences.putAttempts(attempts)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
dialog?.cancel()
assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0))
.isEqualTo(attempts + 1)
}
@Test
fun dialogCancelCallsOnComplete() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
dialog?.cancel()
verify(completedRunnable).invoke()
}
@Test
fun closeDialogDoesNotCallOnComplete() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, true)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
underTest.closeDialog()
verify(completedRunnable, never()).invoke()
}
@Test
fun dialogPositiveWithBothSettingsFalseTogglesBothSettings() {
sharedPreferences.putAttempts(0)
secureSettings.putBool(SETTING_SHOW, false)
secureSettings.putBool(SETTING_ACTION, false)
underTest.maybeShowDialog(context, completedRunnable)
clickButton(DialogInterface.BUTTON_POSITIVE)
assertThat(secureSettings.getBool(SETTING_SHOW)).isTrue()
assertThat(secureSettings.getBool(SETTING_ACTION)).isTrue()
}
private fun clickButton(which: Int) {
dialog?.clickButton(which)
}
private fun attachRepositoryToSettings() {
secureSettings.registerContentObserver(
SETTING_SHOW,
object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
controlsSettingsRepository.setCanShowControlsInLockscreen(
secureSettings.getBool(SETTING_SHOW, false)
)
}
}
)
secureSettings.registerContentObserver(
SETTING_ACTION,
object : ContentObserver(null) {
override fun onChange(selfChange: Boolean) {
controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen(
secureSettings.getBool(SETTING_ACTION, false)
)
}
}
)
}
private fun SharedPreferences.putAttempts(value: Int) {
edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, value).commit()
}
}

View File

@@ -15,7 +15,7 @@
*
*/
package com.android.systemui.controls
package com.android.systemui.controls.settings
import android.content.pm.UserInfo
import android.provider.Settings

View File

@@ -15,7 +15,7 @@
*
*/
package com.android.systemui.controls
package com.android.systemui.controls.settings
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow

View File

@@ -35,10 +35,10 @@ import com.android.systemui.SysuiTestCase
import com.android.systemui.controls.ControlsMetricsLogger
import com.android.systemui.controls.ControlsServiceInfo
import com.android.systemui.controls.CustomIconCache
import com.android.systemui.controls.FakeControlsSettingsRepository
import com.android.systemui.controls.controller.ControlsController
import com.android.systemui.controls.controller.StructureInfo
import com.android.systemui.controls.management.ControlsListingController
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
import com.android.systemui.dump.DumpManager
import com.android.systemui.plugins.ActivityStarter
import com.android.systemui.settings.UserFileManager