New feature “Text and reading options” for SetupWizard, Wallpaper, and Settings (12/n).

- 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
This commit is contained in:
Peter_Liang
2022-01-27 15:37:36 +08:00
parent 0eee699438
commit edb52508d3
7 changed files with 386 additions and 11 deletions

View File

@@ -21,6 +21,10 @@
android:persistent="false" android:persistent="false"
android:title="@string/accessibility_text_reading_options_title"> android:title="@string/accessibility_text_reading_options_title">
<com.android.settings.accessibility.TextReadingPreviewPreference
android:key="preview"
android:selectable="false"/>
<com.android.settings.widget.LabeledSeekBarPreference <com.android.settings.widget.LabeledSeekBarPreference
android:key="font_size" android:key="font_size"
android:selectable="false" android:selectable="false"

View File

@@ -37,6 +37,7 @@ public class TextReadingPreferenceFragment extends DashboardFragment {
private static final String TAG = "TextReadingPreferenceFragment"; private static final String TAG = "TextReadingPreferenceFragment";
private static final String FONT_SIZE_KEY = "font_size"; private static final String FONT_SIZE_KEY = "font_size";
private static final String DISPLAY_SIZE_KEY = "display_size"; private static final String DISPLAY_SIZE_KEY = "display_size";
private static final String PREVIEW_KEY = "preview";
@Override @Override
protected int getPreferenceScreenResId() { protected int getPreferenceScreenResId() {
@@ -58,9 +59,20 @@ public class TextReadingPreferenceFragment extends DashboardFragment {
final List<AbstractPreferenceController> controllers = new ArrayList<>(); final List<AbstractPreferenceController> controllers = new ArrayList<>();
final FontSizeData fontSizeData = new FontSizeData(context); final FontSizeData fontSizeData = new FontSizeData(context);
final DisplaySizeData displaySizeData = new DisplaySizeData(context); final DisplaySizeData displaySizeData = new DisplaySizeData(context);
controllers.add(new PreviewSizeSeekBarController(context, FONT_SIZE_KEY, fontSizeData));
controllers.add( final TextReadingPreviewController previewController = new TextReadingPreviewController(
new PreviewSizeSeekBarController(context, DISPLAY_SIZE_KEY, displaySizeData)); 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; return controllers;
} }

View File

@@ -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.
*
* <p><br>[Flickering problem steps] commit()-> snapshot in framework(old screenshot) ->
* app update the preview -> snapshot(old screen) fade out</p>
*
* <p><br>To prevent flickering problem, we make sure that we update the local preview
* first and then we do the commit later. </p>
*
* <p><br><b>Note:</b> 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.</p>
*
* @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;
}
}

View File

@@ -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. * 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 mCurrentItem;
private int mLastLayerIndex;
private PreviewPagerAdapter mPreviewAdapter; private PreviewPagerAdapter mPreviewAdapter;
TextReadingPreviewPreference(Context context) { TextReadingPreviewPreference(Context context) {
@@ -41,7 +42,7 @@ final class TextReadingPreviewPreference extends Preference {
init(); init();
} }
TextReadingPreviewPreference(Context context, AttributeSet attrs) { public TextReadingPreviewPreference(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
init(); init();
} }
@@ -120,4 +121,16 @@ final class TextReadingPreviewPreference extends Preference {
private void init() { private void init() {
setLayoutResource(R.layout.accessibility_text_reading_preview); 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;
}
} }

View File

@@ -117,7 +117,15 @@ public class PreviewPagerAdapter extends PagerAdapter {
mAnimationEndAction = action; 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) { final boolean animate) {
for (FrameLayout previewFrame : mPreviewFrames) { for (FrameLayout previewFrame : mPreviewFrames) {
if (currentLayerIndex >= 0) { if (currentLayerIndex >= 0) {

View File

@@ -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<Integer> 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<Integer> createFakeDisplayData() {
final List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
list.add(3);
list.add(4);
return list;
}
}

View File

@@ -16,8 +16,16 @@
package com.android.settings.accessibility; 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 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.Context;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -50,12 +58,11 @@ public class TextReadingPreviewPreferenceTest {
@Before @Before
public void setUp() { public void setUp() {
final Context context = ApplicationProvider.getApplicationContext(); final Context context = ApplicationProvider.getApplicationContext();
final int[] sampleResIds = new int[]{1, 2, 3, 4, 5, 6}; final Configuration[] configurations = createConfigurations(PREVIEW_SAMPLE_RES_IDS.length);
final Configuration[] configurations = createConfigurations(6);
mTextReadingPreviewPreference = new TextReadingPreviewPreference(context); mTextReadingPreviewPreference = new TextReadingPreviewPreference(context);
mPreviewPagerAdapter = mPreviewPagerAdapter =
new PreviewPagerAdapter(context, /* isLayoutRtl= */ false, sampleResIds, spy(new PreviewPagerAdapter(context, /* isLayoutRtl= */ false,
configurations); PREVIEW_SAMPLE_RES_IDS, configurations));
final LayoutInflater inflater = LayoutInflater.from(context); final LayoutInflater inflater = LayoutInflater.from(context);
final View view = final View view =
inflater.inflate(mTextReadingPreviewPreference.getLayoutResource(), inflater.inflate(mTextReadingPreviewPreference.getLayoutResource(),
@@ -87,7 +94,7 @@ public class TextReadingPreviewPreferenceTest {
@Test @Test
public void setCurrentItem_success() { public void setCurrentItem_success() {
final int currentItem = 3; final int currentItem = 1;
mTextReadingPreviewPreference.setPreviewAdapter(mPreviewPagerAdapter); mTextReadingPreviewPreference.setPreviewAdapter(mPreviewPagerAdapter);
mTextReadingPreviewPreference.onBindViewHolder(mHolder); mTextReadingPreviewPreference.onBindViewHolder(mHolder);
@@ -104,6 +111,24 @@ public class TextReadingPreviewPreferenceTest {
mTextReadingPreviewPreference.setCurrentItem(currentItem); 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) { private static Configuration[] createConfigurations(int count) {
final Configuration[] configurations = new Configuration[count]; final Configuration[] configurations = new Configuration[count];
for (int i = 0; i < count; i++) { for (int i = 0; i < count; i++) {