1 /* <lambda>null2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 18 package com.android.systemui.controls.settings 19 20 import android.content.DialogInterface 21 import android.content.SharedPreferences 22 import android.database.ContentObserver 23 import android.provider.Settings.Secure.LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS 24 import android.provider.Settings.Secure.LOCKSCREEN_SHOW_CONTROLS 25 import android.testing.TestableLooper 26 import androidx.test.ext.junit.runners.AndroidJUnit4 27 import androidx.test.filters.SmallTest 28 import com.android.systemui.SysuiTestCase 29 import com.android.systemui.controls.settings.ControlsSettingsDialogManager.Companion.PREFS_SETTINGS_DIALOG_ATTEMPTS 30 import com.android.systemui.plugins.ActivityStarter 31 import com.android.systemui.settings.UserFileManager 32 import com.android.systemui.settings.UserTracker 33 import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl 34 import com.android.systemui.util.FakeSharedPreferences 35 import com.android.systemui.util.TestableAlertDialog 36 import com.android.systemui.util.mockito.any 37 import com.android.systemui.util.mockito.eq 38 import com.android.systemui.util.mockito.nullable 39 import com.android.systemui.util.settings.FakeSettings 40 import com.google.common.truth.Truth.assertThat 41 import org.junit.After 42 import org.junit.Before 43 import org.junit.Test 44 import org.junit.runner.RunWith 45 import org.mockito.ArgumentMatchers.anyBoolean 46 import org.mockito.Mock 47 import org.mockito.Mockito.anyInt 48 import org.mockito.Mockito.doAnswer 49 import org.mockito.Mockito.never 50 import org.mockito.Mockito.verify 51 import org.mockito.Mockito.`when` 52 import org.mockito.MockitoAnnotations 53 54 @SmallTest 55 @RunWith(AndroidJUnit4::class) 56 @TestableLooper.RunWithLooper 57 class ControlsSettingsDialogManagerImplTest : SysuiTestCase() { 58 59 companion object { 60 private const val SETTING_SHOW = LOCKSCREEN_SHOW_CONTROLS 61 private const val SETTING_ACTION = LOCKSCREEN_ALLOW_TRIVIAL_CONTROLS 62 private const val MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG = 2 63 } 64 65 @Mock private lateinit var userFileManager: UserFileManager 66 @Mock private lateinit var userTracker: UserTracker 67 @Mock private lateinit var activityStarter: ActivityStarter 68 @Mock private lateinit var completedRunnable: () -> Unit 69 70 private lateinit var controlsSettingsRepository: FakeControlsSettingsRepository 71 private lateinit var sharedPreferences: FakeSharedPreferences 72 private lateinit var secureSettings: FakeSettings 73 74 private lateinit var underTest: ControlsSettingsDialogManagerImpl 75 76 private var dialog: TestableAlertDialog? = null 77 78 @Before 79 fun setUp() { 80 MockitoAnnotations.initMocks(this) 81 82 controlsSettingsRepository = FakeControlsSettingsRepository() 83 sharedPreferences = FakeSharedPreferences() 84 secureSettings = FakeSettings() 85 86 `when`(userTracker.userId).thenReturn(0) 87 secureSettings.userId = userTracker.userId 88 `when`( 89 userFileManager.getSharedPreferences( 90 eq(DeviceControlsControllerImpl.PREFS_CONTROLS_FILE), 91 anyInt(), 92 anyInt() 93 ) 94 ) 95 .thenReturn(sharedPreferences) 96 97 `when`(activityStarter.dismissKeyguardThenExecute(any(), nullable(), anyBoolean())) 98 .thenAnswer { (it.arguments[0] as ActivityStarter.OnDismissAction).onDismiss() } 99 100 attachRepositoryToSettings() 101 underTest = 102 ControlsSettingsDialogManagerImpl( 103 secureSettings, 104 userFileManager, 105 controlsSettingsRepository, 106 userTracker, 107 activityStarter 108 ) { context, _ -> 109 TestableAlertDialog(context).also { dialog = it } 110 } 111 } 112 113 @After 114 fun tearDown() { 115 underTest.closeDialog() 116 } 117 118 @Test 119 fun dialogNotShownIfPrefsAtMaximum() { 120 sharedPreferences.putAttempts(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) 121 122 underTest.maybeShowDialog(context, completedRunnable) 123 124 assertThat(dialog?.isShowing ?: false).isFalse() 125 verify(completedRunnable).invoke() 126 } 127 128 @Test 129 fun dialogNotShownIfSettingsAreTrue() { 130 sharedPreferences.putAttempts(0) 131 secureSettings.putBool(SETTING_SHOW, true) 132 secureSettings.putBool(SETTING_ACTION, true) 133 134 underTest.maybeShowDialog(context, completedRunnable) 135 136 assertThat(dialog?.isShowing ?: false).isFalse() 137 verify(completedRunnable).invoke() 138 } 139 140 @Test 141 fun dialogShownIfAllowTrivialControlsFalse() { 142 sharedPreferences.putAttempts(0) 143 secureSettings.putBool(SETTING_SHOW, true) 144 secureSettings.putBool(SETTING_ACTION, false) 145 146 underTest.maybeShowDialog(context, completedRunnable) 147 148 assertThat(dialog?.isShowing ?: false).isTrue() 149 } 150 151 @Test 152 fun dialogDispossedAfterClosing() { 153 sharedPreferences.putAttempts(0) 154 secureSettings.putBool(SETTING_SHOW, true) 155 secureSettings.putBool(SETTING_ACTION, false) 156 157 underTest.maybeShowDialog(context, completedRunnable) 158 underTest.closeDialog() 159 160 assertThat(dialog?.isShowing ?: false).isFalse() 161 } 162 163 @Test 164 fun dialogNeutralButtonDoesntChangeSetting() { 165 sharedPreferences.putAttempts(0) 166 secureSettings.putBool(SETTING_SHOW, true) 167 secureSettings.putBool(SETTING_ACTION, false) 168 169 underTest.maybeShowDialog(context, completedRunnable) 170 clickButton(DialogInterface.BUTTON_NEUTRAL) 171 172 assertThat(secureSettings.getBool(SETTING_ACTION, false)).isFalse() 173 } 174 175 @Test 176 fun dialogNeutralButtonPutsMaxAttempts() { 177 sharedPreferences.putAttempts(0) 178 secureSettings.putBool(SETTING_SHOW, true) 179 secureSettings.putBool(SETTING_ACTION, false) 180 181 underTest.maybeShowDialog(context, completedRunnable) 182 clickButton(DialogInterface.BUTTON_NEUTRAL) 183 184 assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)) 185 .isEqualTo(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) 186 } 187 188 @Test 189 fun dialogNeutralButtonCallsOnComplete() { 190 sharedPreferences.putAttempts(0) 191 secureSettings.putBool(SETTING_SHOW, true) 192 secureSettings.putBool(SETTING_ACTION, false) 193 194 underTest.maybeShowDialog(context, completedRunnable) 195 clickButton(DialogInterface.BUTTON_NEUTRAL) 196 197 verify(completedRunnable).invoke() 198 } 199 200 @Test 201 fun dialogPositiveButtonChangesSetting() { 202 sharedPreferences.putAttempts(0) 203 secureSettings.putBool(SETTING_SHOW, true) 204 secureSettings.putBool(SETTING_ACTION, false) 205 206 underTest.maybeShowDialog(context, completedRunnable) 207 clickButton(DialogInterface.BUTTON_POSITIVE) 208 209 assertThat(secureSettings.getBool(SETTING_ACTION, false)).isTrue() 210 } 211 212 @Test 213 fun dialogPositiveButtonPutsMaxAttempts() { 214 sharedPreferences.putAttempts(0) 215 secureSettings.putBool(SETTING_SHOW, true) 216 secureSettings.putBool(SETTING_ACTION, false) 217 218 underTest.maybeShowDialog(context, completedRunnable) 219 clickButton(DialogInterface.BUTTON_POSITIVE) 220 221 assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)) 222 .isEqualTo(MAX_NUMBER_ATTEMPTS_CONTROLS_DIALOG) 223 } 224 225 @Test 226 fun dialogPositiveButtonCallsOnComplete() { 227 sharedPreferences.putAttempts(0) 228 secureSettings.putBool(SETTING_SHOW, true) 229 secureSettings.putBool(SETTING_ACTION, false) 230 231 underTest.maybeShowDialog(context, completedRunnable) 232 clickButton(DialogInterface.BUTTON_POSITIVE) 233 234 verify(completedRunnable).invoke() 235 } 236 237 @Test 238 fun dialogPositiveButtonWhenCalledOnCompleteSettingIsTrue() { 239 sharedPreferences.putAttempts(0) 240 secureSettings.putBool(SETTING_SHOW, true) 241 secureSettings.putBool(SETTING_ACTION, false) 242 243 doAnswer { assertThat(secureSettings.getBool(SETTING_ACTION, false)).isTrue() } 244 .`when`(completedRunnable) 245 .invoke() 246 247 underTest.maybeShowDialog(context, completedRunnable) 248 clickButton(DialogInterface.BUTTON_POSITIVE) 249 250 verify(completedRunnable).invoke() 251 } 252 253 @Test 254 fun dialogPositiveCancelKeyguardStillCallsOnComplete() { 255 `when`(activityStarter.dismissKeyguardThenExecute(any(), nullable(), anyBoolean())) 256 .thenAnswer { (it.arguments[1] as Runnable).run() } 257 sharedPreferences.putAttempts(0) 258 secureSettings.putBool(SETTING_SHOW, true) 259 secureSettings.putBool(SETTING_ACTION, false) 260 261 underTest.maybeShowDialog(context, completedRunnable) 262 clickButton(DialogInterface.BUTTON_POSITIVE) 263 264 verify(completedRunnable).invoke() 265 } 266 267 @Test 268 fun dialogCancelDoesntChangeSetting() { 269 sharedPreferences.putAttempts(0) 270 secureSettings.putBool(SETTING_SHOW, true) 271 secureSettings.putBool(SETTING_ACTION, false) 272 273 underTest.maybeShowDialog(context, completedRunnable) 274 dialog?.cancel() 275 276 assertThat(secureSettings.getBool(SETTING_ACTION, false)).isFalse() 277 } 278 279 @Test 280 fun dialogCancelPutsOneExtraAttempt() { 281 val attempts = 0 282 sharedPreferences.putAttempts(attempts) 283 secureSettings.putBool(SETTING_SHOW, true) 284 secureSettings.putBool(SETTING_ACTION, false) 285 286 underTest.maybeShowDialog(context, completedRunnable) 287 dialog?.cancel() 288 289 assertThat(sharedPreferences.getInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, 0)) 290 .isEqualTo(attempts + 1) 291 } 292 293 @Test 294 fun dialogCancelCallsOnComplete() { 295 sharedPreferences.putAttempts(0) 296 secureSettings.putBool(SETTING_SHOW, true) 297 secureSettings.putBool(SETTING_ACTION, false) 298 299 underTest.maybeShowDialog(context, completedRunnable) 300 dialog?.cancel() 301 302 verify(completedRunnable).invoke() 303 } 304 305 @Test 306 fun closeDialogDoesNotCallOnComplete() { 307 sharedPreferences.putAttempts(0) 308 secureSettings.putBool(SETTING_SHOW, true) 309 secureSettings.putBool(SETTING_ACTION, false) 310 311 underTest.maybeShowDialog(context, completedRunnable) 312 underTest.closeDialog() 313 314 verify(completedRunnable, never()).invoke() 315 } 316 317 @Test 318 fun dialogPositiveWithBothSettingsFalseTogglesBothSettings() { 319 sharedPreferences.putAttempts(0) 320 secureSettings.putBool(SETTING_SHOW, false) 321 secureSettings.putBool(SETTING_ACTION, false) 322 323 underTest.maybeShowDialog(context, completedRunnable) 324 clickButton(DialogInterface.BUTTON_POSITIVE) 325 326 assertThat(secureSettings.getBool(SETTING_SHOW)).isTrue() 327 assertThat(secureSettings.getBool(SETTING_ACTION)).isTrue() 328 } 329 330 private fun clickButton(which: Int) { 331 dialog?.clickButton(which) 332 } 333 334 private fun attachRepositoryToSettings() { 335 secureSettings.registerContentObserverSync( 336 SETTING_SHOW, 337 object : ContentObserver(null) { 338 override fun onChange(selfChange: Boolean) { 339 controlsSettingsRepository.setCanShowControlsInLockscreen( 340 secureSettings.getBool(SETTING_SHOW, false) 341 ) 342 } 343 } 344 ) 345 346 secureSettings.registerContentObserverSync( 347 SETTING_ACTION, 348 object : ContentObserver(null) { 349 override fun onChange(selfChange: Boolean) { 350 controlsSettingsRepository.setAllowActionOnTrivialControlsInLockscreen( 351 secureSettings.getBool(SETTING_ACTION, false) 352 ) 353 } 354 } 355 ) 356 } 357 358 private fun SharedPreferences.putAttempts(value: Int) { 359 edit().putInt(PREFS_SETTINGS_DIALOG_ATTEMPTS, value).commit() 360 } 361 } 362