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