From 4681ef2b47f245dacebcc56b1cb8ed2d9a441109 Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Fri, 21 Jan 2022 00:53:32 +0800 Subject: [PATCH 1/6] =?UTF-8?q?New=20feature=20=E2=80=9CText=20and=20readi?= =?UTF-8?q?ng=20options=E2=80=9D=20for=20SetupWizard,=20Wallpaper,=20and?= =?UTF-8?q?=20Settings=20(10/n).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create the font size LabeledSeekBarPreference and add the entry 1) It's integrated with the system font scale configurations. 2) Create the new PreviewSizeSeekBarController component for controlling the LabeledSeekBarPreference of the display/font size. 3) Create the new PreviewSizeData component to store the configurations related to the display/font size features. Bug: 211503117 Test: make RunSettingsRoboTests ROBOTEST_FILTER=FontSizeDataTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=PreviewSizeSeekBarControllerTest Change-Id: I0c1cf6d0425c5c8b61cc8ed0c9fedadf7a65bd27 --- .../accessibility_text_reading_options.xml | 10 ++ .../settings/accessibility/FontSizeData.java | 58 +++++++++ .../accessibility/PreviewSizeData.java | 71 +++++++++++ .../PreviewSizeSeekBarController.java | 113 ++++++++++++++++++ .../TextReadingPreferenceFragment.java | 16 +++ .../accessibility/FontSizeDataTest.java | 55 +++++++++ .../PreviewSizeSeekBarControllerTest.java | 85 +++++++++++++ 7 files changed, 408 insertions(+) create mode 100644 src/com/android/settings/accessibility/FontSizeData.java create mode 100644 src/com/android/settings/accessibility/PreviewSizeData.java create mode 100644 src/com/android/settings/accessibility/PreviewSizeSeekBarController.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/FontSizeDataTest.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java diff --git a/res/xml/accessibility_text_reading_options.xml b/res/xml/accessibility_text_reading_options.xml index 4bc93173e0c..49f697328c4 100644 --- a/res/xml/accessibility_text_reading_options.xml +++ b/res/xml/accessibility_text_reading_options.xml @@ -21,6 +21,16 @@ android:persistent="false" android:title="@string/accessibility_text_reading_options_title"> + + { + private static final float FONT_SCALE_DEF_VALUE = 1.0f; + + FontSizeData(Context context) { + super(context); + + final Resources resources = getContext().getResources(); + final ContentResolver resolver = getContext().getContentResolver(); + final List strEntryValues = + Arrays.asList(resources.getStringArray(R.array.entryvalues_font_size)); + setDefaultValue(FONT_SCALE_DEF_VALUE); + final float currentScale = + Settings.System.getFloat(resolver, Settings.System.FONT_SCALE, getDefaultValue()); + setInitialIndex(fontSizeValueToIndex(currentScale, strEntryValues.toArray(new String[0]))); + setValues(strEntryValues.stream().map(Float::valueOf).collect(Collectors.toList())); + } + + @Override + void commit(int currentProgress) { + final ContentResolver resolver = getContext().getContentResolver(); + Settings.System.putFloat(resolver, Settings.System.FONT_SCALE, + getValues().get(currentProgress)); + } +} diff --git a/src/com/android/settings/accessibility/PreviewSizeData.java b/src/com/android/settings/accessibility/PreviewSizeData.java new file mode 100644 index 00000000000..5d4204e2e56 --- /dev/null +++ b/src/com/android/settings/accessibility/PreviewSizeData.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2022 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.settings.accessibility; + +import android.content.Context; + +import androidx.annotation.NonNull; + +import java.util.List; + +/** + * Abstract data class for storing and fetching the configurations related to the preview of the + * text and reading options. + */ +abstract class PreviewSizeData { + private final Context mContext; + private int mInitialIndex; + private T mDefaultValue; + private List mValues; + + PreviewSizeData(@NonNull Context context) { + mContext = context; + } + + Context getContext() { + return mContext; + } + + List getValues() { + return mValues; + } + + void setValues(List values) { + mValues = values; + } + + T getDefaultValue() { + return mDefaultValue; + } + + void setDefaultValue(T defaultValue) { + mDefaultValue = defaultValue; + } + + int getInitialIndex() { + return mInitialIndex; + } + + void setInitialIndex(int initialIndex) { + mInitialIndex = initialIndex; + } + + /** + * Persists the selected size. + */ + abstract void commit(int currentProgress); +} diff --git a/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java new file mode 100644 index 00000000000..be10c77f0d1 --- /dev/null +++ b/src/com/android/settings/accessibility/PreviewSizeSeekBarController.java @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2022 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.settings.accessibility; + +import android.content.Context; +import android.widget.SeekBar; + +import androidx.annotation.NonNull; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.widget.LabeledSeekBarPreference; + +/** + * The controller of {@link LabeledSeekBarPreference} that listens to display size and font size + * settings changes and updates preview size threshold smoothly. + */ +class PreviewSizeSeekBarController extends BasePreferenceController { + private final PreviewSizeData mSizeData; + private boolean mSeekByTouch; + private ProgressInteractionListener mInteractionListener; + + private final SeekBar.OnSeekBarChangeListener mSeekBarChangeListener = + new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + mInteractionListener.notifyPreferenceChanged(); + + if (!mSeekByTouch && mInteractionListener != null) { + mInteractionListener.onProgressChanged(); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) { + mSeekByTouch = true; + } + + @Override + public void onStopTrackingTouch(SeekBar seekBar) { + mSeekByTouch = false; + + if (mInteractionListener != null) { + mInteractionListener.onEndTrackingTouch(); + } + } + }; + + PreviewSizeSeekBarController(Context context, String preferenceKey, + @NonNull PreviewSizeData sizeData) { + super(context, preferenceKey); + mSizeData = sizeData; + } + + void setInteractionListener(ProgressInteractionListener interactionListener) { + mInteractionListener = interactionListener; + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + final int dataSize = mSizeData.getValues().size(); + final int initialIndex = mSizeData.getInitialIndex(); + final LabeledSeekBarPreference seekBarPreference = + screen.findPreference(getPreferenceKey()); + + seekBarPreference.setMax(dataSize - 1); + seekBarPreference.setProgress(initialIndex); + seekBarPreference.setContinuousUpdates(true); + seekBarPreference.setOnSeekBarChangeListener(mSeekBarChangeListener); + } + + /** + * Interface for callbacks when users interact with the seek bar. + */ + interface ProgressInteractionListener { + + /** + * Called when the progress is changed. + */ + void notifyPreferenceChanged(); + + /** + * Called when the progress is changed without tracking touch. + */ + void onProgressChanged(); + + /** + * Called when the seek bar is end tracking. + */ + void onEndTrackingTouch(); + } +} diff --git a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java index 0e8457beaca..9bf5a146754 100644 --- a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java +++ b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java @@ -17,12 +17,17 @@ package com.android.settings.accessibility; import android.app.settings.SettingsEnums; +import android.content.Context; import com.android.settings.R; import com.android.settings.dashboard.DashboardFragment; import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.search.SearchIndexable; +import java.util.ArrayList; +import java.util.List; + /** * Accessibility settings for adjusting the system features which are related to the reading. For * example, bold text, high contrast text, display size, font size and so on. @@ -30,6 +35,7 @@ import com.android.settingslib.search.SearchIndexable; @SearchIndexable(forTarget = SearchIndexable.ALL & ~SearchIndexable.ARC) public class TextReadingPreferenceFragment extends DashboardFragment { private static final String TAG = "TextReadingPreferenceFragment"; + private static final String FONT_SIZE_KEY = "font_size"; @Override protected int getPreferenceScreenResId() { @@ -46,6 +52,16 @@ public class TextReadingPreferenceFragment extends DashboardFragment { return SettingsEnums.ACCESSIBILITY_TEXT_READING_OPTIONS; } + @Override + protected List createPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + final FontSizeData fontSizeData = new FontSizeData(context); + + controllers.add(new PreviewSizeSeekBarController(context, FONT_SIZE_KEY, fontSizeData)); + + return controllers; + } + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider(R.xml.accessibility_text_reading_options); } diff --git a/tests/robotests/src/com/android/settings/accessibility/FontSizeDataTest.java b/tests/robotests/src/com/android/settings/accessibility/FontSizeDataTest.java new file mode 100644 index 00000000000..7e357140811 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/FontSizeDataTest.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2022 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.settings.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; +import android.provider.Settings; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** + * Tests for {@link FontSizeData}. + */ +@RunWith(RobolectricTestRunner.class) +public class FontSizeDataTest { + private final Context mContext = ApplicationProvider.getApplicationContext(); + private FontSizeData mFontSizeData; + + @Before + public void setUp() { + mFontSizeData = new FontSizeData(mContext); + } + + @Test + public void commit_success() { + final int progress = 3; + + mFontSizeData.commit(progress); + final float currentScale = + Settings.System.getFloat(mContext.getContentResolver(), Settings.System.FONT_SCALE, + /* def= */ 1.0f); + + assertThat(currentScale).isEqualTo(mFontSizeData.getValues().get(progress)); + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java new file mode 100644 index 00000000000..a67b001f85c --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/PreviewSizeSeekBarControllerTest.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2022 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.settings.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; + +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.widget.LabeledSeekBarPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +/** + * Tests for {@link PreviewSizeSeekBarController}. + */ +@RunWith(RobolectricTestRunner.class) +public class PreviewSizeSeekBarControllerTest { + private static final String FONT_SIZE_KEY = "font_size"; + private final Context mContext = ApplicationProvider.getApplicationContext(); + private PreviewSizeSeekBarController mSeekBarController; + private FontSizeData mFontSizeData; + private LabeledSeekBarPreference mSeekBarPreference; + + @Mock + private PreferenceScreen mPreferenceScreen; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mFontSizeData = new FontSizeData(mContext); + + mSeekBarController = + new PreviewSizeSeekBarController(mContext, FONT_SIZE_KEY, mFontSizeData); + + mSeekBarPreference = spy(new LabeledSeekBarPreference(mContext, /* attrs= */ null)); + when(mPreferenceScreen.findPreference(anyString())).thenReturn(mSeekBarPreference); + } + + @Test + public void initMax_matchResult() { + when(mPreferenceScreen.findPreference(anyString())).thenReturn(mSeekBarPreference); + + mSeekBarController.displayPreference(mPreferenceScreen); + + assertThat(mSeekBarPreference.getMax()).isEqualTo( + mFontSizeData.getValues().size() - 1); + } + + @Test + public void initProgress_matchResult() { + when(mPreferenceScreen.findPreference(anyString())).thenReturn(mSeekBarPreference); + + mSeekBarController.displayPreference(mPreferenceScreen); + + verify(mSeekBarPreference).setProgress(mFontSizeData.getInitialIndex()); + } +} From 0eee699438d1e9eae158bd2efb6ee2dc96291dbe Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Fri, 21 Jan 2022 02:26:19 +0800 Subject: [PATCH 2/6] =?UTF-8?q?New=20feature=20=E2=80=9CText=20and=20readi?= =?UTF-8?q?ng=20options=E2=80=9D=20for=20SetupWizard,=20Wallpaper,=20and?= =?UTF-8?q?=20Settings=20(11/n).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Create the display size LabeledSeekBarPreference and add the entry 1) It's integrated with the system display density configurations. 2) Create the new DisplaySizeData component to store the configurations related to the display size features. Bug: 211503117 Test: make RunSettingsRoboTests ROBOTEST_FILTER=DisplaySizeDataTest Change-Id: If9e5cc6e2ff2c4f530634e39eb3cddd9e275bc03 --- .../accessibility_text_reading_options.xml | 10 +++ .../accessibility/DisplaySizeData.java | 65 +++++++++++++++++++ .../TextReadingPreferenceFragment.java | 5 +- .../accessibility/DisplaySizeDataTest.java | 54 +++++++++++++++ 4 files changed, 133 insertions(+), 1 deletion(-) create mode 100644 src/com/android/settings/accessibility/DisplaySizeData.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/DisplaySizeDataTest.java diff --git a/res/xml/accessibility_text_reading_options.xml b/res/xml/accessibility_text_reading_options.xml index 49f697328c4..f46c24e996f 100644 --- a/res/xml/accessibility_text_reading_options.xml +++ b/res/xml/accessibility_text_reading_options.xml @@ -31,6 +31,16 @@ settings:iconStart="@drawable/ic_remove_24dp" settings:iconStartContentDescription="@string/font_size_make_smaller_desc"/> + + { + DisplaySizeData(Context context) { + super(context); + + final DisplayDensityUtils density = new DisplayDensityUtils(getContext()); + final int initialIndex = density.getCurrentIndex(); + if (initialIndex < 0) { + // Failed to obtain default density, which means we failed to + // connect to the window manager service. Just use the current + // density and don't let the user change anything. + final Resources resources = getContext().getResources(); + final int densityDpi = resources.getDisplayMetrics().densityDpi; + setDefaultValue(densityDpi); + setInitialIndex(0); + setValues(Collections.singletonList(densityDpi)); + } else { + setDefaultValue(density.getDefaultDensity()); + setInitialIndex(initialIndex); + setValues(Arrays.stream(density.getValues()).boxed().collect(Collectors.toList())); + } + } + + @Override + void commit(int currentProgress) { + final int densityDpi = getValues().get(currentProgress); + if (densityDpi == getDefaultValue()) { + DisplayDensityConfiguration.clearForcedDisplayDensity(Display.DEFAULT_DISPLAY); + } else { + DisplayDensityConfiguration.setForcedDisplayDensity(Display.DEFAULT_DISPLAY, + densityDpi); + } + } +} diff --git a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java index 9bf5a146754..c7950e47af2 100644 --- a/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java +++ b/src/com/android/settings/accessibility/TextReadingPreferenceFragment.java @@ -36,6 +36,7 @@ import java.util.List; public class TextReadingPreferenceFragment extends DashboardFragment { private static final String TAG = "TextReadingPreferenceFragment"; private static final String FONT_SIZE_KEY = "font_size"; + private static final String DISPLAY_SIZE_KEY = "display_size"; @Override protected int getPreferenceScreenResId() { @@ -56,8 +57,10 @@ public class TextReadingPreferenceFragment extends DashboardFragment { protected List createPreferenceControllers(Context context) { final List controllers = new ArrayList<>(); final FontSizeData fontSizeData = new FontSizeData(context); - + final DisplaySizeData displaySizeData = new DisplaySizeData(context); controllers.add(new PreviewSizeSeekBarController(context, FONT_SIZE_KEY, fontSizeData)); + controllers.add( + new PreviewSizeSeekBarController(context, DISPLAY_SIZE_KEY, displaySizeData)); return controllers; } diff --git a/tests/robotests/src/com/android/settings/accessibility/DisplaySizeDataTest.java b/tests/robotests/src/com/android/settings/accessibility/DisplaySizeDataTest.java new file mode 100644 index 00000000000..fabf1233c49 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/DisplaySizeDataTest.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2022 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.settings.accessibility; + +import static com.google.common.truth.Truth.assertThat; + +import android.content.Context; + +import androidx.test.core.app.ApplicationProvider; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; + +/** + * Tests for {@link DisplaySizeData}. + */ +@RunWith(RobolectricTestRunner.class) +public class DisplaySizeDataTest { + private final Context mContext = ApplicationProvider.getApplicationContext(); + private DisplaySizeData mDisplaySizeData; + + @Before + public void setUp() { + mDisplaySizeData = new DisplaySizeData(mContext); + } + + @Ignore("Ignore it since a NPE is happened in ShadowWindowManagerGlobal. (Ref. b/214161063)") + @Test + public void commit_success() { + final int progress = 4; + + mDisplaySizeData.commit(progress); + final float density = mContext.getResources().getDisplayMetrics().density; + + assertThat(density).isEqualTo(mDisplaySizeData.getValues().get(progress)); + } +} From edb52508d38c3b182ea689ac2d4d8d9fd87753f1 Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Thu, 27 Jan 2022 15:37:36 +0800 Subject: [PATCH 3/6] =?UTF-8?q?New=20feature=20=E2=80=9CText=20and=20readi?= =?UTF-8?q?ng=20options=E2=80=9D=20for=20SetupWizard,=20Wallpaper,=20and?= =?UTF-8?q?=20Settings=20(12/n).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Link-up between the preview, font size, and display size preferences. - Add the preview preference and entry. Bug: 211503117 Test: make RunSettingsRoboTests ROBOTEST_FILTER=TextReadingPreviewControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=TextReadingPreviewPreferenceTest Change-Id: Ic6c48861a0051670fd78b13dca5488711de30cb8 --- .../accessibility_text_reading_options.xml | 4 + .../TextReadingPreferenceFragment.java | 18 +- .../TextReadingPreviewController.java | 194 ++++++++++++++++++ .../TextReadingPreviewPreference.java | 17 +- .../settings/display/PreviewPagerAdapter.java | 10 +- .../TextReadingPreviewControllerTest.java | 119 +++++++++++ .../TextReadingPreviewPreferenceTest.java | 35 +++- 7 files changed, 386 insertions(+), 11 deletions(-) create mode 100644 src/com/android/settings/accessibility/TextReadingPreviewController.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java diff --git a/res/xml/accessibility_text_reading_options.xml b/res/xml/accessibility_text_reading_options.xml index f46c24e996f..167886bdd89 100644 --- a/res/xml/accessibility_text_reading_options.xml +++ b/res/xml/accessibility_text_reading_options.xml @@ -21,6 +21,10 @@ android:persistent="false" android:title="@string/accessibility_text_reading_options_title"> + + controllers = new ArrayList<>(); final FontSizeData fontSizeData = new FontSizeData(context); final DisplaySizeData displaySizeData = new DisplaySizeData(context); - controllers.add(new PreviewSizeSeekBarController(context, FONT_SIZE_KEY, fontSizeData)); - controllers.add( - new PreviewSizeSeekBarController(context, DISPLAY_SIZE_KEY, displaySizeData)); + + final TextReadingPreviewController previewController = new TextReadingPreviewController( + context, PREVIEW_KEY, fontSizeData, displaySizeData); + controllers.add(previewController); + + final PreviewSizeSeekBarController fontSizeController = new PreviewSizeSeekBarController( + context, FONT_SIZE_KEY, fontSizeData); + fontSizeController.setInteractionListener(previewController); + controllers.add(fontSizeController); + + final PreviewSizeSeekBarController displaySizeController = new PreviewSizeSeekBarController( + context, DISPLAY_SIZE_KEY, displaySizeData); + displaySizeController.setInteractionListener(previewController); + controllers.add(displaySizeController); return controllers; } diff --git a/src/com/android/settings/accessibility/TextReadingPreviewController.java b/src/com/android/settings/accessibility/TextReadingPreviewController.java new file mode 100644 index 00000000000..cef20aac546 --- /dev/null +++ b/src/com/android/settings/accessibility/TextReadingPreviewController.java @@ -0,0 +1,194 @@ +/* + * Copyright (C) 2022 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.settings.accessibility; + +import android.content.Context; +import android.content.res.Configuration; +import android.os.SystemClock; +import android.view.Choreographer; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.display.PreviewPagerAdapter; +import com.android.settings.widget.LabeledSeekBarPreference; + +import java.util.Objects; + +/** + * A {@link BasePreferenceController} for controlling the preview pager of the text and reading + * options. + */ +class TextReadingPreviewController extends BasePreferenceController implements + PreviewSizeSeekBarController.ProgressInteractionListener { + static final int[] PREVIEW_SAMPLE_RES_IDS = new int[]{ + R.layout.accessibility_text_reading_preview_app_grid, + R.layout.screen_zoom_preview_1, + R.layout.accessibility_text_reading_preview_mail_content}; + + private static final String PREVIEW_KEY = "preview"; + private static final String FONT_SIZE_KEY = "font_size"; + private static final String DISPLAY_SIZE_KEY = "display_size"; + private static final long MIN_COMMIT_INTERVAL_MS = 800; + private static final long CHANGE_BY_SEEKBAR_DELAY_MS = 100; + private static final long CHANGE_BY_BUTTON_DELAY_MS = 300; + private final FontSizeData mFontSizeData; + private final DisplaySizeData mDisplaySizeData; + private int mLastFontProgress; + private int mLastDisplayProgress; + private long mLastCommitTime; + private TextReadingPreviewPreference mPreviewPreference; + private LabeledSeekBarPreference mFontSizePreference; + private LabeledSeekBarPreference mDisplaySizePreference; + + private final Choreographer.FrameCallback mCommit = f -> { + tryCommitFontSizeConfig(); + tryCommitDisplaySizeConfig(); + + mLastCommitTime = SystemClock.elapsedRealtime(); + }; + + TextReadingPreviewController(Context context, String preferenceKey, + @NonNull FontSizeData fontSizeData, @NonNull DisplaySizeData displaySizeData) { + super(context, preferenceKey); + mFontSizeData = fontSizeData; + mDisplaySizeData = displaySizeData; + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + mPreviewPreference = screen.findPreference(PREVIEW_KEY); + + mFontSizePreference = screen.findPreference(FONT_SIZE_KEY); + mDisplaySizePreference = screen.findPreference(DISPLAY_SIZE_KEY); + Objects.requireNonNull(mFontSizePreference, + /* message= */ "Font size preference is null, the preview controller " + + "couldn't get the info"); + Objects.requireNonNull(mDisplaySizePreference, + /* message= */ "Display size preference is null, the preview controller" + + " couldn't get the info"); + + mLastFontProgress = mFontSizePreference.getProgress(); + mLastDisplayProgress = mDisplaySizePreference.getProgress(); + + final Configuration origConfig = mContext.getResources().getConfiguration(); + final boolean isLayoutRtl = + origConfig.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + final PreviewPagerAdapter pagerAdapter = new PreviewPagerAdapter(mContext, isLayoutRtl, + PREVIEW_SAMPLE_RES_IDS, createConfig(origConfig)); + mPreviewPreference.setPreviewAdapter(pagerAdapter); + pagerAdapter.setPreviewLayer(/* newLayerIndex= */ 0, + /* currentLayerIndex= */ 0, + /* currentFrameIndex= */ 0, /* animate= */ false); + } + + @Override + public void notifyPreferenceChanged() { + final int displayDataSize = mDisplaySizeData.getValues().size(); + final int fontSizeProgress = mFontSizePreference.getProgress(); + final int displaySizeProgress = mDisplaySizePreference.getProgress(); + + // To be consistent with the + // {@link PreviewPagerAdapter#setPreviewLayer(int, int, int, boolean)} behavior, + // here also needs the same design. In addition, please also refer to + // the {@link #createConfig(Configuration)}. + final int pagerIndex = fontSizeProgress * displayDataSize + displaySizeProgress; + + mPreviewPreference.notifyPreviewPagerChanged(pagerIndex); + } + + @Override + public void onProgressChanged() { + postCommitDelayed(CHANGE_BY_BUTTON_DELAY_MS); + } + + @Override + public void onEndTrackingTouch() { + postCommitDelayed(CHANGE_BY_SEEKBAR_DELAY_MS); + } + + /** + * Avoids the flicker when switching to the previous or next level. + * + *


[Flickering problem steps] commit()-> snapshot in framework(old screenshot) -> + * app update the preview -> snapshot(old screen) fade out

+ * + *


To prevent flickering problem, we make sure that we update the local preview + * first and then we do the commit later.

+ * + *


Note: It doesn't matter that we use + * Choreographer or main thread handler since the delay time is longer + * than 1 frame. Use Choreographer to let developer understand it's a + * window update.

+ * + * @param commitDelayMs the interval time after a action. + */ + void postCommitDelayed(long commitDelayMs) { + if (SystemClock.elapsedRealtime() - mLastCommitTime < MIN_COMMIT_INTERVAL_MS) { + commitDelayMs += MIN_COMMIT_INTERVAL_MS; + } + + final Choreographer choreographer = Choreographer.getInstance(); + choreographer.removeFrameCallback(mCommit); + choreographer.postFrameCallbackDelayed(mCommit, commitDelayMs); + } + + private void tryCommitFontSizeConfig() { + final int fontProgress = mFontSizePreference.getProgress(); + if (fontProgress != mLastFontProgress) { + mFontSizeData.commit(fontProgress); + mLastFontProgress = fontProgress; + } + } + + private void tryCommitDisplaySizeConfig() { + final int displayProgress = mDisplaySizePreference.getProgress(); + if (displayProgress != mLastDisplayProgress) { + mDisplaySizeData.commit(displayProgress); + mLastDisplayProgress = displayProgress; + } + } + + private Configuration[] createConfig(Configuration origConfig) { + final int fontDataSize = mFontSizeData.getValues().size(); + final int displayDataSize = mDisplaySizeData.getValues().size(); + final int totalNum = fontDataSize * displayDataSize; + final Configuration[] configurations = new Configuration[totalNum]; + + for (int i = 0; i < fontDataSize; ++i) { + for (int j = 0; j < displayDataSize; ++j) { + final Configuration config = new Configuration(origConfig); + config.fontScale = mFontSizeData.getValues().get(i); + config.densityDpi = mDisplaySizeData.getValues().get(j); + + configurations[i * displayDataSize + j] = config; + } + } + + return configurations; + } +} diff --git a/src/com/android/settings/accessibility/TextReadingPreviewPreference.java b/src/com/android/settings/accessibility/TextReadingPreviewPreference.java index 1b9cc4b5171..4b8ca39ed18 100644 --- a/src/com/android/settings/accessibility/TextReadingPreviewPreference.java +++ b/src/com/android/settings/accessibility/TextReadingPreviewPreference.java @@ -32,8 +32,9 @@ import com.android.settings.widget.DotsPageIndicator; /** * A {@link Preference} that could show the preview related to the text and reading options. */ -final class TextReadingPreviewPreference extends Preference { +public class TextReadingPreviewPreference extends Preference { private int mCurrentItem; + private int mLastLayerIndex; private PreviewPagerAdapter mPreviewAdapter; TextReadingPreviewPreference(Context context) { @@ -41,7 +42,7 @@ final class TextReadingPreviewPreference extends Preference { init(); } - TextReadingPreviewPreference(Context context, AttributeSet attrs) { + public TextReadingPreviewPreference(Context context, AttributeSet attrs) { super(context, attrs); init(); } @@ -120,4 +121,16 @@ final class TextReadingPreviewPreference extends Preference { private void init() { setLayoutResource(R.layout.accessibility_text_reading_preview); } + + void notifyPreviewPagerChanged(int pagerIndex) { + Preconditions.checkNotNull(mPreviewAdapter, + "Preview adapter is null, you should init the preview adapter first"); + + if (pagerIndex != mLastLayerIndex) { + mPreviewAdapter.setPreviewLayer(pagerIndex, mLastLayerIndex, getCurrentItem(), + /* animate= */ false); + } + + mLastLayerIndex = pagerIndex; + } } diff --git a/src/com/android/settings/display/PreviewPagerAdapter.java b/src/com/android/settings/display/PreviewPagerAdapter.java index 018be326d1b..693d574ff83 100644 --- a/src/com/android/settings/display/PreviewPagerAdapter.java +++ b/src/com/android/settings/display/PreviewPagerAdapter.java @@ -117,7 +117,15 @@ public class PreviewPagerAdapter extends PagerAdapter { mAnimationEndAction = action; } - void setPreviewLayer(int newLayerIndex, int currentLayerIndex, int currentFrameIndex, + /** + * Switches the sample layouts for the preview pager. + * + * @param newLayerIndex the new layer index + * @param currentLayerIndex the current layer index + * @param currentFrameIndex the current frame index + * @param animate whether to enable the animation + */ + public void setPreviewLayer(int newLayerIndex, int currentLayerIndex, int currentFrameIndex, final boolean animate) { for (FrameLayout previewFrame : mPreviewFrames) { if (currentLayerIndex >= 0) { diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java new file mode 100644 index 00000000000..b6305099c68 --- /dev/null +++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewControllerTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2022 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.settings.accessibility; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; + +import androidx.preference.PreferenceScreen; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.display.PreviewPagerAdapter; +import com.android.settings.widget.LabeledSeekBarPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowChoreographer; + +import java.util.ArrayList; +import java.util.List; + +/** + * Tests for {@link TextReadingPreviewController}. + */ +@RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowChoreographer.class) +public class TextReadingPreviewControllerTest { + private static final String PREVIEW_KEY = "preview"; + private static final String FONT_SIZE_KEY = "font_size"; + private static final String DISPLAY_SIZE_KEY = "display_size"; + private final Context mContext = ApplicationProvider.getApplicationContext(); + private TextReadingPreviewController mPreviewController; + private TextReadingPreviewPreference mPreviewPreference; + private LabeledSeekBarPreference mFontSizePreference; + private LabeledSeekBarPreference mDisplaySizePreference; + + @Mock + private DisplaySizeData mDisplaySizeData; + + @Mock + private PreferenceScreen mPreferenceScreen; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + final FontSizeData fontSizeData = new FontSizeData(mContext); + final List displayData = createFakeDisplayData(); + when(mDisplaySizeData.getValues()).thenReturn(displayData); + mPreviewPreference = spy(new TextReadingPreviewPreference(mContext, /* attr= */ null)); + mPreviewController = new TextReadingPreviewController(mContext, PREVIEW_KEY, fontSizeData, + mDisplaySizeData); + mFontSizePreference = new LabeledSeekBarPreference(mContext, /* attr= */ null); + mDisplaySizePreference = new LabeledSeekBarPreference(mContext, /* attr= */ null); + } + + @Test + public void initPreviewerAdapter_verifyAction() { + when(mPreferenceScreen.findPreference(PREVIEW_KEY)).thenReturn(mPreviewPreference); + when(mPreferenceScreen.findPreference(FONT_SIZE_KEY)).thenReturn(mFontSizePreference); + when(mPreferenceScreen.findPreference(DISPLAY_SIZE_KEY)).thenReturn(mDisplaySizePreference); + + mPreviewController.displayPreference(mPreferenceScreen); + + verify(mPreviewPreference).setPreviewAdapter(any(PreviewPagerAdapter.class)); + } + + @Test(expected = NullPointerException.class) + public void initPreviewerAdapterWithoutDisplaySizePreference_throwNPE() { + when(mPreferenceScreen.findPreference(PREVIEW_KEY)).thenReturn(mPreviewPreference); + when(mPreferenceScreen.findPreference(DISPLAY_SIZE_KEY)).thenReturn(mDisplaySizePreference); + + mPreviewController.displayPreference(mPreferenceScreen); + + verify(mPreviewPreference).setPreviewAdapter(any(PreviewPagerAdapter.class)); + } + + @Test(expected = NullPointerException.class) + public void initPreviewerAdapterWithoutFontSizePreference_throwNPE() { + when(mPreferenceScreen.findPreference(PREVIEW_KEY)).thenReturn(mPreviewPreference); + when(mPreferenceScreen.findPreference(FONT_SIZE_KEY)).thenReturn(mFontSizePreference); + + mPreviewController.displayPreference(mPreferenceScreen); + + verify(mPreviewPreference).setPreviewAdapter(any(PreviewPagerAdapter.class)); + } + + private List createFakeDisplayData() { + final List list = new ArrayList<>(); + list.add(1); + list.add(2); + list.add(3); + list.add(4); + + return list; + } +} diff --git a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java index 6b9395a335c..3dc82da5add 100644 --- a/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/TextReadingPreviewPreferenceTest.java @@ -16,8 +16,16 @@ package com.android.settings.accessibility; +import static com.android.settings.accessibility.TextReadingPreviewController.PREVIEW_SAMPLE_RES_IDS; + import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + import android.content.Context; import android.content.res.Configuration; import android.view.LayoutInflater; @@ -50,12 +58,11 @@ public class TextReadingPreviewPreferenceTest { @Before public void setUp() { final Context context = ApplicationProvider.getApplicationContext(); - final int[] sampleResIds = new int[]{1, 2, 3, 4, 5, 6}; - final Configuration[] configurations = createConfigurations(6); + final Configuration[] configurations = createConfigurations(PREVIEW_SAMPLE_RES_IDS.length); mTextReadingPreviewPreference = new TextReadingPreviewPreference(context); mPreviewPagerAdapter = - new PreviewPagerAdapter(context, /* isLayoutRtl= */ false, sampleResIds, - configurations); + spy(new PreviewPagerAdapter(context, /* isLayoutRtl= */ false, + PREVIEW_SAMPLE_RES_IDS, configurations)); final LayoutInflater inflater = LayoutInflater.from(context); final View view = inflater.inflate(mTextReadingPreviewPreference.getLayoutResource(), @@ -87,7 +94,7 @@ public class TextReadingPreviewPreferenceTest { @Test public void setCurrentItem_success() { - final int currentItem = 3; + final int currentItem = 1; mTextReadingPreviewPreference.setPreviewAdapter(mPreviewPagerAdapter); mTextReadingPreviewPreference.onBindViewHolder(mHolder); @@ -104,6 +111,24 @@ public class TextReadingPreviewPreferenceTest { mTextReadingPreviewPreference.setCurrentItem(currentItem); } + @Test(expected = NullPointerException.class) + public void updatePagerWithoutPreviewAdapter_throwNPE() { + final int index = 1; + + mTextReadingPreviewPreference.notifyPreviewPagerChanged(index); + } + + @Test + public void notifyPreviewPager_setPreviewLayer() { + final int index = 2; + mTextReadingPreviewPreference.setPreviewAdapter(mPreviewPagerAdapter); + mTextReadingPreviewPreference.onBindViewHolder(mHolder); + + mTextReadingPreviewPreference.notifyPreviewPagerChanged(index); + + verify(mPreviewPagerAdapter).setPreviewLayer(eq(index), anyInt(), anyInt(), anyBoolean()); + } + private static Configuration[] createConfigurations(int count) { final Configuration[] configurations = new Configuration[count]; for (int i = 0; i < count; i++) { From f71050b515c18ca093bbcee969e333130adda466 Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Sun, 6 Feb 2022 21:45:03 +0800 Subject: [PATCH 4/6] =?UTF-8?q?New=20feature=20=E2=80=9CText=20and=20readi?= =?UTF-8?q?ng=20options=E2=80=9D=20for=20SetupWizard,=20Wallpaper,=20and?= =?UTF-8?q?=20Settings=20(13/n).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add the ResetPreference for controlling all preferences state in the text and reading options page. 1) Create a new interface ResetStateListener for the other preferences. - Link-up between the reset, font size, and display size preferences. Bug: 211503117 Test: make RunSettingsRoboTests ROBOTEST_FILTER=PreviewSizeSeekBarControllerTest Test: make RunSettingsRoboTests ROBOTEST_FILTER=TextReadingResetControllerTest Change-Id: Ida773967834e32737b1daac885a2dd71189d32c8 --- ...y_text_reading_reset_button_background.xml | 25 ++++ ...ccessibility_text_reading_reset_button.xml | 36 ++++++ res/values/strings.xml | 2 + .../accessibility_text_reading_options.xml | 6 + .../PreviewSizeSeekBarController.java | 20 +-- .../TextReadingPreferenceFragment.java | 11 ++ .../TextReadingResetController.java | 66 ++++++++++ .../PreviewSizeSeekBarControllerTest.java | 13 ++ .../TextReadingResetControllerTest.java | 119 ++++++++++++++++++ 9 files changed, 291 insertions(+), 7 deletions(-) create mode 100644 res/drawable/accessibility_text_reading_reset_button_background.xml create mode 100644 res/layout/accessibility_text_reading_reset_button.xml create mode 100644 src/com/android/settings/accessibility/TextReadingResetController.java create mode 100644 tests/robotests/src/com/android/settings/accessibility/TextReadingResetControllerTest.java diff --git a/res/drawable/accessibility_text_reading_reset_button_background.xml b/res/drawable/accessibility_text_reading_reset_button_background.xml new file mode 100644 index 00000000000..b86facf4006 --- /dev/null +++ b/res/drawable/accessibility_text_reading_reset_button_background.xml @@ -0,0 +1,25 @@ + + + + + + + + diff --git a/res/layout/accessibility_text_reading_reset_button.xml b/res/layout/accessibility_text_reading_reset_button.xml new file mode 100644 index 00000000000..43800df0b5f --- /dev/null +++ b/res/layout/accessibility_text_reading_reset_button.xml @@ -0,0 +1,36 @@ + + + + + +