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