Merge "Move trigger for dialog to change controls settings" into tm-qpr-dev am: 7dc6120c1a
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/20621516 Change-Id: I2ab447acb6a9aa85b62590cfbb316a655eb43ec8 Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
This commit is contained in:
@@ -19,7 +19,7 @@ package com.android.systemui.controls.dagger
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.android.internal.widget.LockPatternUtils
|
import com.android.internal.widget.LockPatternUtils
|
||||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
|
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.ControlsController
|
||||||
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
|
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
|
||||||
import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
|
import com.android.systemui.controls.controller.ControlsTileResourceConfigurationImpl
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ import android.app.Activity
|
|||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import com.android.systemui.controls.ControlsMetricsLogger
|
import com.android.systemui.controls.ControlsMetricsLogger
|
||||||
import com.android.systemui.controls.ControlsMetricsLoggerImpl
|
import com.android.systemui.controls.ControlsMetricsLoggerImpl
|
||||||
import com.android.systemui.controls.ControlsSettingsRepository
|
import com.android.systemui.controls.settings.ControlsSettingsRepository
|
||||||
import com.android.systemui.controls.ControlsSettingsRepositoryImpl
|
import com.android.systemui.controls.settings.ControlsSettingsRepositoryImpl
|
||||||
import com.android.systemui.controls.controller.ControlsBindingController
|
import com.android.systemui.controls.controller.ControlsBindingController
|
||||||
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
|
import com.android.systemui.controls.controller.ControlsBindingControllerImpl
|
||||||
import com.android.systemui.controls.controller.ControlsController
|
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.ControlsListingControllerImpl
|
||||||
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
|
import com.android.systemui.controls.management.ControlsProviderSelectorActivity
|
||||||
import com.android.systemui.controls.management.ControlsRequestDialog
|
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.ControlActionCoordinator
|
||||||
import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
|
import com.android.systemui.controls.ui.ControlActionCoordinatorImpl
|
||||||
import com.android.systemui.controls.ui.ControlsActivity
|
import com.android.systemui.controls.ui.ControlsActivity
|
||||||
@@ -89,6 +91,11 @@ abstract class ControlsModule {
|
|||||||
manager: ControlsSettingsRepositoryImpl
|
manager: ControlsSettingsRepositoryImpl
|
||||||
): ControlsSettingsRepository
|
): ControlsSettingsRepository
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun provideDialogManager(
|
||||||
|
manager: ControlsSettingsDialogManagerImpl
|
||||||
|
): ControlsSettingsDialogManager
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
|
abstract fun provideMetricsLogger(logger: ControlsMetricsLoggerImpl): ControlsMetricsLogger
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.systemui.controls
|
package com.android.systemui.controls.settings
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.systemui.controls
|
package com.android.systemui.controls.settings
|
||||||
|
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
|
||||||
@@ -19,15 +19,12 @@ package com.android.systemui.controls.ui
|
|||||||
import android.annotation.AnyThread
|
import android.annotation.AnyThread
|
||||||
import android.annotation.MainThread
|
import android.annotation.MainThread
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.content.pm.ResolveInfo
|
import android.content.pm.ResolveInfo
|
||||||
import android.os.UserHandle
|
|
||||||
import android.os.VibrationEffect
|
import android.os.VibrationEffect
|
||||||
import android.provider.Settings.Secure
|
|
||||||
import android.service.controls.Control
|
import android.service.controls.Control
|
||||||
import android.service.controls.actions.BooleanAction
|
import android.service.controls.actions.BooleanAction
|
||||||
import android.service.controls.actions.CommandAction
|
import android.service.controls.actions.CommandAction
|
||||||
@@ -35,39 +32,36 @@ import android.service.controls.actions.FloatAction
|
|||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import com.android.internal.annotations.VisibleForTesting
|
import com.android.internal.annotations.VisibleForTesting
|
||||||
import com.android.systemui.R
|
|
||||||
import com.android.systemui.broadcast.BroadcastSender
|
import com.android.systemui.broadcast.BroadcastSender
|
||||||
import com.android.systemui.controls.ControlsMetricsLogger
|
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.SysUISingleton
|
||||||
import com.android.systemui.dagger.qualifiers.Main
|
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.plugins.ActivityStarter
|
||||||
import com.android.systemui.settings.UserContextProvider
|
|
||||||
import com.android.systemui.statusbar.VibratorHelper
|
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.statusbar.policy.KeyguardStateController
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
import com.android.systemui.util.settings.SecureSettings
|
|
||||||
import com.android.wm.shell.TaskViewFactory
|
import com.android.wm.shell.TaskViewFactory
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@SysUISingleton
|
@SysUISingleton
|
||||||
class ControlActionCoordinatorImpl @Inject constructor(
|
class ControlActionCoordinatorImpl @Inject constructor(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val bgExecutor: DelayableExecutor,
|
private val bgExecutor: DelayableExecutor,
|
||||||
@Main private val uiExecutor: DelayableExecutor,
|
@Main private val uiExecutor: DelayableExecutor,
|
||||||
private val activityStarter: ActivityStarter,
|
private val activityStarter: ActivityStarter,
|
||||||
private val broadcastSender: BroadcastSender,
|
private val broadcastSender: BroadcastSender,
|
||||||
private val keyguardStateController: KeyguardStateController,
|
private val keyguardStateController: KeyguardStateController,
|
||||||
private val taskViewFactory: Optional<TaskViewFactory>,
|
private val taskViewFactory: Optional<TaskViewFactory>,
|
||||||
private val controlsMetricsLogger: ControlsMetricsLogger,
|
private val controlsMetricsLogger: ControlsMetricsLogger,
|
||||||
private val vibrator: VibratorHelper,
|
private val vibrator: VibratorHelper,
|
||||||
private val secureSettings: SecureSettings,
|
private val controlsSettingsRepository: ControlsSettingsRepository,
|
||||||
private val userContextProvider: UserContextProvider,
|
private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
|
||||||
private val controlsSettingsRepository: ControlsSettingsRepository,
|
private val featureFlags: FeatureFlags,
|
||||||
) : ControlActionCoordinator {
|
) : ControlActionCoordinator {
|
||||||
private var dialog: Dialog? = null
|
private var dialog: Dialog? = null
|
||||||
private var pendingAction: Action? = null
|
private var pendingAction: Action? = null
|
||||||
@@ -76,16 +70,16 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
|||||||
get() = !keyguardStateController.isUnlocked()
|
get() = !keyguardStateController.isUnlocked()
|
||||||
private val allowTrivialControls: Boolean
|
private val allowTrivialControls: Boolean
|
||||||
get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
|
get() = controlsSettingsRepository.allowActionOnTrivialControlsInLockscreen.value
|
||||||
private val showDeviceControlsInLockscreen: Boolean
|
|
||||||
get() = controlsSettingsRepository.canShowControlsInLockscreen.value
|
|
||||||
override lateinit var activityContext: Context
|
override lateinit var activityContext: Context
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
|
private const val RESPONSE_TIMEOUT_IN_MILLIS = 3000L
|
||||||
private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun closeDialogs() {
|
override fun closeDialogs() {
|
||||||
|
if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
|
||||||
|
controlsSettingsDialogManager.closeDialog()
|
||||||
|
}
|
||||||
val isActivityFinishing =
|
val isActivityFinishing =
|
||||||
(activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
|
(activityContext as? Activity)?.let { it.isFinishing || it.isDestroyed }
|
||||||
if (isActivityFinishing == true) {
|
if (isActivityFinishing == true) {
|
||||||
@@ -253,71 +247,9 @@ class ControlActionCoordinatorImpl @Inject constructor(
|
|||||||
if (action.authIsRequired) {
|
if (action.authIsRequired) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val prefs = userContextProvider.userContext.getSharedPreferences(
|
if (!featureFlags.isEnabled(Flags.USE_APP_PANELS)) {
|
||||||
PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
|
controlsSettingsDialogManager.maybeShowDialog(activityContext) {}
|
||||||
val attempts = prefs.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)
|
|
||||||
if (attempts >= MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG ||
|
|
||||||
(showDeviceControlsInLockscreen && allowTrivialControls)) {
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
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
|
@VisibleForTesting
|
||||||
|
|||||||
@@ -32,8 +32,10 @@ import androidx.activity.ComponentActivity
|
|||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.broadcast.BroadcastDispatcher
|
import com.android.systemui.broadcast.BroadcastDispatcher
|
||||||
import com.android.systemui.controls.management.ControlsAnimations
|
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.FeatureFlags
|
||||||
import com.android.systemui.flags.Flags
|
import com.android.systemui.flags.Flags
|
||||||
|
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -47,7 +49,9 @@ class ControlsActivity @Inject constructor(
|
|||||||
private val uiController: ControlsUiController,
|
private val uiController: ControlsUiController,
|
||||||
private val broadcastDispatcher: BroadcastDispatcher,
|
private val broadcastDispatcher: BroadcastDispatcher,
|
||||||
private val dreamManager: IDreamManager,
|
private val dreamManager: IDreamManager,
|
||||||
private val featureFlags: FeatureFlags
|
private val featureFlags: FeatureFlags,
|
||||||
|
private val controlsSettingsDialogManager: ControlsSettingsDialogManager,
|
||||||
|
private val keyguardStateController: KeyguardStateController
|
||||||
) : ComponentActivity() {
|
) : ComponentActivity() {
|
||||||
|
|
||||||
private lateinit var parent: ViewGroup
|
private lateinit var parent: ViewGroup
|
||||||
@@ -92,7 +96,13 @@ class ControlsActivity @Inject constructor(
|
|||||||
|
|
||||||
parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
|
parent = requireViewById<ViewGroup>(R.id.global_actions_controls)
|
||||||
parent.alpha = 0f
|
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()
|
ControlsAnimations.enterAnimation(parent).start()
|
||||||
}
|
}
|
||||||
@@ -124,6 +134,7 @@ class ControlsActivity @Inject constructor(
|
|||||||
mExitToDream = false
|
mExitToDream = false
|
||||||
|
|
||||||
uiController.hide()
|
uiController.hide()
|
||||||
|
controlsSettingsDialogManager.closeDialog()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ import com.android.systemui.Dumpable
|
|||||||
import com.android.systemui.R
|
import com.android.systemui.R
|
||||||
import com.android.systemui.controls.ControlsMetricsLogger
|
import com.android.systemui.controls.ControlsMetricsLogger
|
||||||
import com.android.systemui.controls.ControlsServiceInfo
|
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.CustomIconCache
|
||||||
import com.android.systemui.controls.controller.ControlsController
|
import com.android.systemui.controls.controller.ControlsController
|
||||||
import com.android.systemui.controls.controller.StructureInfo
|
import com.android.systemui.controls.controller.StructureInfo
|
||||||
|
|||||||
@@ -68,7 +68,6 @@ public class DeviceControlsControllerImpl @Inject constructor(
|
|||||||
|
|
||||||
internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
|
internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
|
||||||
const val PREFS_CONTROLS_FILE = "controls_prefs"
|
const val PREFS_CONTROLS_FILE = "controls_prefs"
|
||||||
internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
|
|
||||||
private const val SEEDING_MAX = 2
|
private const val SEEDING_MAX = 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -16,21 +16,19 @@
|
|||||||
|
|
||||||
package com.android.systemui.controls.ui
|
package com.android.systemui.controls.ui
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.test.suitebuilder.annotation.SmallTest
|
import android.test.suitebuilder.annotation.SmallTest
|
||||||
import android.testing.AndroidTestingRunner
|
import android.testing.AndroidTestingRunner
|
||||||
import com.android.systemui.SysuiTestCase
|
import com.android.systemui.SysuiTestCase
|
||||||
import com.android.systemui.broadcast.BroadcastSender
|
import com.android.systemui.broadcast.BroadcastSender
|
||||||
import com.android.systemui.controls.ControlsMetricsLogger
|
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.plugins.ActivityStarter
|
||||||
import com.android.systemui.settings.UserContextProvider
|
|
||||||
import com.android.systemui.statusbar.VibratorHelper
|
import com.android.systemui.statusbar.VibratorHelper
|
||||||
import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
|
|
||||||
import com.android.systemui.statusbar.policy.KeyguardStateController
|
import com.android.systemui.statusbar.policy.KeyguardStateController
|
||||||
import com.android.systemui.util.concurrency.DelayableExecutor
|
import com.android.systemui.util.concurrency.DelayableExecutor
|
||||||
import com.android.systemui.util.settings.SecureSettings
|
|
||||||
import com.android.wm.shell.TaskViewFactory
|
import com.android.wm.shell.TaskViewFactory
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
@@ -40,8 +38,8 @@ import org.mockito.Mock
|
|||||||
import org.mockito.Mockito
|
import org.mockito.Mockito
|
||||||
import org.mockito.Mockito.`when`
|
import org.mockito.Mockito.`when`
|
||||||
import org.mockito.Mockito.anyBoolean
|
import org.mockito.Mockito.anyBoolean
|
||||||
|
import org.mockito.Mockito.doNothing
|
||||||
import org.mockito.Mockito.doReturn
|
import org.mockito.Mockito.doReturn
|
||||||
import org.mockito.Mockito.mock
|
|
||||||
import org.mockito.Mockito.never
|
import org.mockito.Mockito.never
|
||||||
import org.mockito.Mockito.reset
|
import org.mockito.Mockito.reset
|
||||||
import org.mockito.Mockito.spy
|
import org.mockito.Mockito.spy
|
||||||
@@ -71,9 +69,9 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
|
|||||||
@Mock
|
@Mock
|
||||||
private lateinit var metricsLogger: ControlsMetricsLogger
|
private lateinit var metricsLogger: ControlsMetricsLogger
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var secureSettings: SecureSettings
|
private lateinit var featureFlags: FeatureFlags
|
||||||
@Mock
|
@Mock
|
||||||
private lateinit var userContextProvider: UserContextProvider
|
private lateinit var controlsSettingsDialogManager: ControlsSettingsDialogManager
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun <T> any(): T = Mockito.any<T>()
|
fun <T> any(): T = Mockito.any<T>()
|
||||||
@@ -103,23 +101,16 @@ class ControlActionCoordinatorImplTest : SysuiTestCase() {
|
|||||||
taskViewFactory,
|
taskViewFactory,
|
||||||
metricsLogger,
|
metricsLogger,
|
||||||
vibratorHelper,
|
vibratorHelper,
|
||||||
secureSettings,
|
controlsSettingsRepository,
|
||||||
userContextProvider,
|
controlsSettingsDialogManager,
|
||||||
controlsSettingsRepository
|
featureFlags
|
||||||
))
|
))
|
||||||
|
coordinator.activityContext = mContext
|
||||||
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)
|
|
||||||
|
|
||||||
`when`(cvh.cws.ci.controlId).thenReturn(ID)
|
`when`(cvh.cws.ci.controlId).thenReturn(ID)
|
||||||
`when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
|
`when`(cvh.cws.control?.isAuthRequired()).thenReturn(true)
|
||||||
|
`when`(featureFlags.isEnabled(Flags.USE_APP_PANELS)).thenReturn(false)
|
||||||
|
|
||||||
action = spy(coordinator.Action(ID, {}, false, true))
|
action = spy(coordinator.Action(ID, {}, false, true))
|
||||||
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
|
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())
|
doReturn(action).`when`(coordinator).createAction(any(), any(), anyBoolean(), anyBoolean())
|
||||||
|
|
||||||
`when`(keyguardStateController.isShowing()).thenReturn(true)
|
`when`(keyguardStateController.isShowing()).thenReturn(true)
|
||||||
`when`(keyguardStateController.isUnlocked()).thenReturn(false)
|
|
||||||
|
|
||||||
coordinator.toggle(cvh, "", true)
|
coordinator.toggle(cvh, "", true)
|
||||||
|
|
||||||
verify(coordinator).bouncerOrRun(action)
|
verify(coordinator).bouncerOrRun(action)
|
||||||
|
verify(controlsSettingsDialogManager).maybeShowDialog(any(), any())
|
||||||
verify(action).invoke()
|
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
|
@Test
|
||||||
fun testToggleDoesNotRunsWhenLockedAndAuthRequired() {
|
fun testToggleDoesNotRunsWhenLockedAndAuthRequired() {
|
||||||
action = spy(coordinator.Action(ID, {}, false, true))
|
action = spy(coordinator.Action(ID, {}, false, true))
|
||||||
|
|||||||
@@ -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_NOT_REQUIRED
|
||||||
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
|
import com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT
|
||||||
import com.android.systemui.SysuiTestCase
|
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.ControlsController
|
||||||
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
|
import com.android.systemui.controls.controller.ControlsTileResourceConfiguration
|
||||||
import com.android.systemui.controls.management.ControlsListingController
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
|
|||||||
@@ -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()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.systemui.controls
|
package com.android.systemui.controls.settings
|
||||||
|
|
||||||
import android.content.pm.UserInfo
|
import android.content.pm.UserInfo
|
||||||
import android.provider.Settings
|
import android.provider.Settings
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.systemui.controls
|
package com.android.systemui.controls.settings
|
||||||
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
@@ -35,10 +35,10 @@ import com.android.systemui.SysuiTestCase
|
|||||||
import com.android.systemui.controls.ControlsMetricsLogger
|
import com.android.systemui.controls.ControlsMetricsLogger
|
||||||
import com.android.systemui.controls.ControlsServiceInfo
|
import com.android.systemui.controls.ControlsServiceInfo
|
||||||
import com.android.systemui.controls.CustomIconCache
|
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.ControlsController
|
||||||
import com.android.systemui.controls.controller.StructureInfo
|
import com.android.systemui.controls.controller.StructureInfo
|
||||||
import com.android.systemui.controls.management.ControlsListingController
|
import com.android.systemui.controls.management.ControlsListingController
|
||||||
|
import com.android.systemui.controls.settings.FakeControlsSettingsRepository
|
||||||
import com.android.systemui.dump.DumpManager
|
import com.android.systemui.dump.DumpManager
|
||||||
import com.android.systemui.plugins.ActivityStarter
|
import com.android.systemui.plugins.ActivityStarter
|
||||||
import com.android.systemui.settings.UserFileManager
|
import com.android.systemui.settings.UserFileManager
|
||||||
|
|||||||
Reference in New Issue
Block a user