/* * 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.res.R import com.android.systemui.settings.UserTracker import com.android.systemui.shade.domain.interactor.FakeShadeDialogContextInteractor 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.Mock import org.mockito.Mockito.spy import org.mockito.Mockito.verify import org.mockito.Mockito.`when` as whenever import org.mockito.MockitoAnnotations 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 mDialogTransitionAnimator: DialogTransitionAnimator @Before fun setUp() { MockitoAnnotations.initMocks(this) testableLooper = TestableLooper.get(this) val mainHandler = Handler(testableLooper.looper) val fakeSettings = FakeSettings() 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) fontScalingDialogDelegate = spy( FontScalingDialogDelegate( mContext, dialogFactory, LayoutInflater.from(mContext), systemSettings, secureSettings, systemClock, userTracker, mainHandler, backgroundDelayableExecutor, FakeShadeDialogContextInteractor(mContext), ) ) dialog = SystemUIDialog( mContext, 0, DEFAULT_DISMISS_ON_DEVICE_LOCK, dialogManager, 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.putStringForUser( Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, OFF.toString(), 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.getStringForUser( Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED, userTracker.userId, ) assertThat(currentSettings).isEqualTo(ON.toString()) dialog.dismiss() } @Test fun dragSeekbar_systemFontSizeSettingsDoesNotChange() { dialog.show() val slider = dialog.findViewById(R.id.font_scaling_slider)!! val seekBarListener = slider.getSeekBarChangeListener() val seekBar: SeekBar = slider.findViewById(R.id.seekbar)!! // Default seekbar progress for font size is 1, simulate dragging to 0 without // releasing the finger. seekBarListener.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. seekBarListener.onStopTrackingTouch(seekBar) 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() dialog.dismiss() } }