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