1 /*
2  * Copyright (C) 2023 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 package com.android.systemui.accessibility.fontscaling
17 
18 import android.content.res.Configuration
19 import android.os.Handler
20 import android.provider.Settings
21 import android.testing.TestableLooper
22 import android.view.LayoutInflater
23 import android.view.ViewGroup
24 import android.widget.Button
25 import android.widget.SeekBar
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.animation.DialogTransitionAnimator
30 import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView
31 import com.android.systemui.model.SysUiState
32 import com.android.systemui.res.R
33 import com.android.systemui.settings.UserTracker
34 import com.android.systemui.statusbar.phone.SystemUIDialog
35 import com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK
36 import com.android.systemui.statusbar.phone.SystemUIDialogManager
37 import com.android.systemui.util.concurrency.FakeExecutor
38 import com.android.systemui.util.settings.FakeSettings
39 import com.android.systemui.util.settings.SecureSettings
40 import com.android.systemui.util.settings.SystemSettings
41 import com.android.systemui.util.time.FakeSystemClock
42 import com.google.common.truth.Truth.assertThat
43 import org.junit.Before
44 import org.junit.Test
45 import org.junit.runner.RunWith
46 import org.mockito.ArgumentMatchers.any
47 import org.mockito.ArgumentMatchers.anyBoolean
48 import org.mockito.ArgumentMatchers.anyLong
49 import org.mockito.Mock
50 import org.mockito.Mockito.spy
51 import org.mockito.Mockito.verify
52 import org.mockito.Mockito.`when` as whenever
53 import org.mockito.MockitoAnnotations
54 
55 private const val ON: Int = 1
56 private const val OFF: Int = 0
57 
58 /** Tests for [FontScalingDialogDelegate]. */
59 @SmallTest
60 @RunWith(AndroidJUnit4::class)
61 @TestableLooper.RunWithLooper(setAsMainLooper = true)
62 class FontScalingDialogDelegateTest : SysuiTestCase() {
63     private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate
64     private lateinit var dialog: SystemUIDialog
65     private lateinit var systemSettings: SystemSettings
66     private lateinit var secureSettings: SecureSettings
67     private lateinit var systemClock: FakeSystemClock
68     private lateinit var backgroundDelayableExecutor: FakeExecutor
69     private lateinit var testableLooper: TestableLooper
70     private val fontSizeValueArray: Array<String> =
71         mContext
72             .getResources()
73             .getStringArray(com.android.settingslib.R.array.entryvalues_font_size)
74 
75     @Mock private lateinit var dialogManager: SystemUIDialogManager
76     @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
77     @Mock private lateinit var userTracker: UserTracker
78     @Mock private lateinit var sysuiState: SysUiState
79     @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
80 
81     @Before
setUpnull82     fun setUp() {
83         MockitoAnnotations.initMocks(this)
84         testableLooper = TestableLooper.get(this)
85         val mainHandler = Handler(testableLooper.looper)
86         systemSettings = FakeSettings()
87         // Guarantee that the systemSettings always starts with the default font scale.
88         systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId)
89         secureSettings = FakeSettings()
90         systemClock = FakeSystemClock()
91         backgroundDelayableExecutor = FakeExecutor(systemClock)
92         whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState)
93 
94         fontScalingDialogDelegate =
95             spy(
96                 FontScalingDialogDelegate(
97                     mContext,
98                     dialogFactory,
99                     LayoutInflater.from(mContext),
100                     systemSettings,
101                     secureSettings,
102                     systemClock,
103                     userTracker,
104                     mainHandler,
105                     backgroundDelayableExecutor,
106                 )
107             )
108 
109         dialog =
110             SystemUIDialog(
111                 mContext,
112                 0,
113                 DEFAULT_DISMISS_ON_DEVICE_LOCK,
114                 dialogManager,
115                 sysuiState,
116                 fakeBroadcastDispatcher,
117                 mDialogTransitionAnimator,
118                 fontScalingDialogDelegate,
119             )
120 
121         whenever(dialogFactory.create(any(), any())).thenReturn(dialog)
122     }
123 
124     @Test
showTheDialog_seekbarIsShowingCorrectProgressnull125     fun showTheDialog_seekbarIsShowingCorrectProgress() {
126         dialog.show()
127 
128         val seekBar: SeekBar = dialog.findViewById<SeekBar>(R.id.seekbar)!!
129         val progress: Int = seekBar.getProgress()
130         val currentScale =
131             systemSettings.getFloatForUser(
132                 Settings.System.FONT_SCALE,
133                 /* def= */ 1.0f,
134                 userTracker.userId,
135             )
136 
137         assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat())
138 
139         dialog.dismiss()
140     }
141 
142     @Test
progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScalednull143     fun progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScaled() {
144         dialog.show()
145 
146         val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!!
147         val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
148             dialog.findViewById(R.id.font_scaling_slider)!!
149         val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!!
150 
151         seekBarWithIconButtonsView.setProgress(0)
152         backgroundDelayableExecutor.runAllReady()
153         backgroundDelayableExecutor.advanceClockToNext()
154         backgroundDelayableExecutor.runAllReady()
155 
156         iconEndFrame.performClick()
157         backgroundDelayableExecutor.runAllReady()
158         backgroundDelayableExecutor.advanceClockToNext()
159         backgroundDelayableExecutor.runAllReady()
160 
161         val currentScale =
162             systemSettings.getFloatForUser(
163                 Settings.System.FONT_SCALE,
164                 /* def= */ 1.0f,
165                 userTracker.userId,
166             )
167         assertThat(seekBar.getProgress()).isEqualTo(1)
168         assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat())
169 
170         dialog.dismiss()
171     }
172 
173     @Test
progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScalednull174     fun progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScaled() {
175         dialog.show()
176 
177         val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!!
178         val seekBarWithIconButtonsView: SeekBarWithIconButtonsView =
179             dialog.findViewById(R.id.font_scaling_slider)!!
180         val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!!
181 
182         seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1)
183         backgroundDelayableExecutor.runAllReady()
184         backgroundDelayableExecutor.advanceClockToNext()
185         backgroundDelayableExecutor.runAllReady()
186 
187         iconStartFrame.performClick()
188         backgroundDelayableExecutor.runAllReady()
189         backgroundDelayableExecutor.advanceClockToNext()
190         backgroundDelayableExecutor.runAllReady()
191 
192         val currentScale =
193             systemSettings.getFloatForUser(
194                 Settings.System.FONT_SCALE,
195                 /* def= */ 1.0f,
196                 userTracker.userId,
197             )
198         assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2)
199         assertThat(currentScale)
200             .isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat())
201 
202         dialog.dismiss()
203     }
204 
205     @Test
progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOnnull206     fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() {
207         dialog.show()
208 
209         val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!!
210         secureSettings.putIntForUser(
211             Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
212             OFF,
213             userTracker.userId,
214         )
215 
216         // Default seekbar progress for font size is 1, click start icon to decrease the progress
217         iconStartFrame.performClick()
218         backgroundDelayableExecutor.runAllReady()
219         backgroundDelayableExecutor.advanceClockToNext()
220         backgroundDelayableExecutor.runAllReady()
221 
222         val currentSettings =
223             secureSettings.getIntForUser(
224                 Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
225                 /* def = */ OFF,
226                 userTracker.userId,
227             )
228         assertThat(currentSettings).isEqualTo(ON)
229 
230         dialog.dismiss()
231     }
232 
233     @Test
dragSeekbar_systemFontSizeSettingsDoesNotChangenull234     fun dragSeekbar_systemFontSizeSettingsDoesNotChange() {
235         dialog.show()
236 
237         val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!!
238         val seekBarListener = slider.getSeekBarChangeListener()
239 
240         val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
241 
242         // Default seekbar progress for font size is 1, simulate dragging to 0 without
243         // releasing the finger.
244         seekBarListener.onStartTrackingTouch(seekBar)
245         // Update seekbar progress. This will trigger onProgressChanged in the
246         // OnSeekBarChangeListener and the seekbar could get updated progress value
247         // in onStopTrackingTouch.
248         seekBar.progress = 0
249         backgroundDelayableExecutor.runAllReady()
250         backgroundDelayableExecutor.advanceClockToNext()
251         backgroundDelayableExecutor.runAllReady()
252 
253         // Verify that the scale of font size remains the default value 1.0f.
254         var systemScale =
255             systemSettings.getFloatForUser(
256                 Settings.System.FONT_SCALE,
257                 /* def= */ 1.0f,
258                 userTracker.userId,
259             )
260         assertThat(systemScale).isEqualTo(1.0f)
261 
262         // Simulate releasing the finger from the seekbar.
263         seekBarListener.onStopTrackingTouch(seekBar)
264         backgroundDelayableExecutor.runAllReady()
265         backgroundDelayableExecutor.advanceClockToNext()
266         backgroundDelayableExecutor.runAllReady()
267 
268         // Verify that the scale of font size has been updated.
269         systemScale =
270             systemSettings.getFloatForUser(
271                 Settings.System.FONT_SCALE,
272                 /* def= */ 1.0f,
273                 userTracker.userId,
274             )
275         assertThat(systemScale).isEqualTo(fontSizeValueArray[0].toFloat())
276 
277         dialog.dismiss()
278     }
279 
280     @Test
dragSeekBar_createTextPreviewnull281     fun dragSeekBar_createTextPreview() {
282         dialog.show()
283         val slider = dialog.findViewById<SeekBarWithIconButtonsView>(R.id.font_scaling_slider)!!
284         val changeListener = slider.onSeekBarWithIconButtonsChangeListener
285 
286         val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!!
287 
288         // Default seekbar progress for font size is 1, simulate dragging to 0 without
289         // releasing the finger
290         changeListener.onStartTrackingTouch(seekBar)
291         changeListener.onProgressChanged(seekBar, /* progress= */ 0, /* fromUser= */ false)
292         backgroundDelayableExecutor.advanceClockToNext()
293         backgroundDelayableExecutor.runAllReady()
294 
295         verify(fontScalingDialogDelegate).createTextPreview(/* index= */ 0)
296         dialog.dismiss()
297     }
298 
299     @Test
changeFontSize_buttonIsDisabledBeforeFontSizeChangeFinishesnull300     fun changeFontSize_buttonIsDisabledBeforeFontSizeChangeFinishes() {
301         dialog.show()
302 
303         val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!!
304         val doneButton: Button = dialog.findViewById(com.android.internal.R.id.button1)!!
305 
306         iconEndFrame.performClick()
307         backgroundDelayableExecutor.runAllReady()
308         backgroundDelayableExecutor.advanceClockToNext()
309         backgroundDelayableExecutor.runAllReady()
310 
311         // Verify that the button is disabled before receiving onConfigurationChanged
312         assertThat(doneButton.isEnabled).isFalse()
313 
314         val config = Configuration()
315         config.fontScale = 1.15f
316         dialog.onConfigurationChanged(config)
317         testableLooper.processAllMessages()
318         assertThat(doneButton.isEnabled).isTrue()
319     }
320 }
321