/* * Copyright (C) 2023 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.systemui.accessibility.fontscaling import android.content.res.Configuration import android.os.Handler import android.provider.Settings import android.testing.TestableLooper import android.view.LayoutInflater import android.view.ViewGroup import android.widget.Button import android.widget.SeekBar import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.filters.SmallTest import com.android.systemui.SysuiTestCase import com.android.systemui.animation.DialogTransitionAnimator import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView import com.android.systemui.common.ui.view.SeekBarWithIconButtonsView.OnSeekBarWithIconButtonsChangeListener import com.android.systemui.model.SysUiState import com.android.systemui.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.statusbar.phone.SystemUIDialog import com.android.systemui.statusbar.phone.SystemUIDialog.DEFAULT_DISMISS_ON_DEVICE_LOCK import com.android.systemui.statusbar.phone.SystemUIDialogManager import com.android.systemui.util.concurrency.FakeExecutor import com.android.systemui.util.settings.FakeSettings import com.android.systemui.util.settings.SecureSettings import com.android.systemui.util.settings.SystemSettings import com.android.systemui.util.time.FakeSystemClock import com.google.common.truth.Truth.assertThat import org.junit.Before import org.junit.Test import org.junit.runner.RunWith import org.mockito.ArgumentMatchers.any import org.mockito.ArgumentMatchers.anyBoolean import org.mockito.ArgumentMatchers.anyLong import org.mockito.Mock import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.MockitoAnnotations import org.mockito.Mockito.`when` as whenever private const val ON: Int = 1 private const val OFF: Int = 0 /** Tests for [FontScalingDialogDelegate]. */ @SmallTest @RunWith(AndroidJUnit4::class) @TestableLooper.RunWithLooper(setAsMainLooper = true) class FontScalingDialogDelegateTest : SysuiTestCase() { private lateinit var fontScalingDialogDelegate: FontScalingDialogDelegate private lateinit var dialog: SystemUIDialog private lateinit var systemSettings: SystemSettings private lateinit var secureSettings: SecureSettings private lateinit var systemClock: FakeSystemClock private lateinit var backgroundDelayableExecutor: FakeExecutor private lateinit var testableLooper: TestableLooper private val fontSizeValueArray: Array = mContext .getResources() .getStringArray(com.android.settingslib.R.array.entryvalues_font_size) @Mock private lateinit var dialogManager: SystemUIDialogManager @Mock private lateinit var dialogFactory: SystemUIDialog.Factory @Mock private lateinit var userTracker: UserTracker @Mock private lateinit var sysuiState: SysUiState @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) val mainHandler = Handler(testableLooper.looper) systemSettings = FakeSettings() // Guarantee that the systemSettings always starts with the default font scale. systemSettings.putFloatForUser(Settings.System.FONT_SCALE, 1.0f, userTracker.userId) secureSettings = FakeSettings() systemClock = FakeSystemClock() backgroundDelayableExecutor = FakeExecutor(systemClock) whenever(sysuiState.setFlag(anyLong(), anyBoolean())).thenReturn(sysuiState) fontScalingDialogDelegate = spy( FontScalingDialogDelegate( mContext, dialogFactory, LayoutInflater.from(mContext), systemSettings, secureSettings, systemClock, userTracker, mainHandler, backgroundDelayableExecutor ) ) dialog = SystemUIDialog( mContext, 0, DEFAULT_DISMISS_ON_DEVICE_LOCK, dialogManager, sysuiState, fakeBroadcastDispatcher, mDialogTransitionAnimator, fontScalingDialogDelegate ) whenever(dialogFactory.create(any(), any())).thenReturn(dialog) } @Test fun showTheDialog_seekbarIsShowingCorrectProgress() { dialog.show() val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!! val progress: Int = seekBar.getProgress() val currentScale = systemSettings.getFloatForUser( Settings.System.FONT_SCALE, /* def= */ 1.0f, userTracker.userId ) assertThat(currentScale).isEqualTo(fontSizeValueArray[progress].toFloat()) dialog.dismiss() } @Test fun progressIsZero_clickIconEnd_seekBarProgressIncreaseOne_fontSizeScaled() { dialog.show() val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!! val seekBarWithIconButtonsView: SeekBarWithIconButtonsView = dialog.findViewById(R.id.font_scaling_slider)!! val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!! seekBarWithIconButtonsView.setProgress(0) backgroundDelayableExecutor.runAllReady() backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() iconEndFrame.performClick() backgroundDelayableExecutor.runAllReady() backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() val currentScale = systemSettings.getFloatForUser( Settings.System.FONT_SCALE, /* def= */ 1.0f, userTracker.userId ) assertThat(seekBar.getProgress()).isEqualTo(1) assertThat(currentScale).isEqualTo(fontSizeValueArray[1].toFloat()) dialog.dismiss() } @Test fun progressIsMax_clickIconStart_seekBarProgressDecreaseOne_fontSizeScaled() { dialog.show() val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!! val seekBarWithIconButtonsView: SeekBarWithIconButtonsView = dialog.findViewById(R.id.font_scaling_slider)!! val seekBar: SeekBar = dialog.findViewById(R.id.seekbar)!! seekBarWithIconButtonsView.setProgress(fontSizeValueArray.size - 1) backgroundDelayableExecutor.runAllReady() backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() iconStartFrame.performClick() backgroundDelayableExecutor.runAllReady() backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() val currentScale = systemSettings.getFloatForUser( Settings.System.FONT_SCALE, /* def= */ 1.0f, userTracker.userId ) assertThat(seekBar.getProgress()).isEqualTo(fontSizeValueArray.size - 2) assertThat(currentScale) .isEqualTo(fontSizeValueArray[fontSizeValueArray.size - 2].toFloat()) dialog.dismiss() } @Test fun progressChanged_keyWasNotSetBefore_fontScalingHasBeenChangedIsOn() { dialog.show() val iconStartFrame: ViewGroup = dialog.findViewById(R.id.icon_start_frame)!! secureSettings.putIntForUser( Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, OFF, userTracker.userId ) // Default seekbar progress for font size is 1, click start icon to decrease the progress iconStartFrame.performClick() backgroundDelayableExecutor.runAllReady() backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() val currentSettings = secureSettings.getIntForUser( Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, /* def = */ OFF, userTracker.userId ) assertThat(currentSettings).isEqualTo(ON) dialog.dismiss() } @Test fun dragSeekbar_systemFontSizeSettingsDoesNotChange() { dialog.show() val slider = dialog.findViewById(R.id.font_scaling_slider)!! val changeListener = slider.onSeekBarWithIconButtonsChangeListener val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!! // Default seekbar progress for font size is 1, simulate dragging to 0 without // releasing the finger. changeListener.onStartTrackingTouch(seekBar) // Update seekbar progress. This will trigger onProgressChanged in the // OnSeekBarChangeListener and the seekbar could get updated progress value // in onStopTrackingTouch. seekBar.progress = 0 backgroundDelayableExecutor.runAllReady() backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() // Verify that the scale of font size remains the default value 1.0f. var systemScale = systemSettings.getFloatForUser( Settings.System.FONT_SCALE, /* def= */ 1.0f, userTracker.userId ) assertThat(systemScale).isEqualTo(1.0f) // Simulate releasing the finger from the seekbar. changeListener.onStopTrackingTouch(seekBar) backgroundDelayableExecutor.runAllReady() backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() // SeekBar interaction is finalized. changeListener.onUserInteractionFinalized( seekBar, OnSeekBarWithIconButtonsChangeListener.ControlUnitType.SLIDER ) backgroundDelayableExecutor.runAllReady() backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() // Verify that the scale of font size has been updated. systemScale = systemSettings.getFloatForUser( Settings.System.FONT_SCALE, /* def= */ 1.0f, userTracker.userId ) assertThat(systemScale).isEqualTo(fontSizeValueArray[0].toFloat()) dialog.dismiss() } @Test fun dragSeekBar_createTextPreview() { dialog.show() val slider = dialog.findViewById(R.id.font_scaling_slider)!! val changeListener = slider.onSeekBarWithIconButtonsChangeListener val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!! // Default seekbar progress for font size is 1, simulate dragging to 0 without // releasing the finger changeListener.onStartTrackingTouch(seekBar) changeListener.onProgressChanged(seekBar, /* progress= */ 0, /* fromUser= */ false) backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() verify(fontScalingDialogDelegate).createTextPreview(/* index= */ 0) dialog.dismiss() } @Test fun changeFontSize_buttonIsDisabledBeforeFontSizeChangeFinishes() { dialog.show() val iconEndFrame: ViewGroup = dialog.findViewById(R.id.icon_end_frame)!! val doneButton: Button = dialog.findViewById(com.android.internal.R.id.button1)!! iconEndFrame.performClick() backgroundDelayableExecutor.runAllReady() backgroundDelayableExecutor.advanceClockToNext() backgroundDelayableExecutor.runAllReady() // Verify that the button is disabled before receiving onConfigurationChanged assertThat(doneButton.isEnabled).isFalse() val config = Configuration() config.fontScale = 1.15f dialog.onConfigurationChanged(config) testableLooper.processAllMessages() assertThat(doneButton.isEnabled).isTrue() } }