1 /* 2 * Copyright (C) 2022 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 17 package com.android.settings.accessibility; 18 19 import android.content.Context; 20 import android.content.res.Configuration; 21 import android.content.res.TypedArray; 22 import android.os.SystemClock; 23 import android.util.Log; 24 import android.view.Choreographer; 25 import android.view.View; 26 27 import androidx.annotation.NonNull; 28 import androidx.annotation.VisibleForTesting; 29 import androidx.preference.PreferenceScreen; 30 31 import com.android.settings.R; 32 import com.android.settings.accessibility.TextReadingPreferenceFragment.EntryPoint; 33 import com.android.settings.core.BasePreferenceController; 34 import com.android.settings.core.instrumentation.SettingsStatsLog; 35 import com.android.settings.display.PreviewPagerAdapter; 36 import com.android.settings.widget.LabeledSeekBarPreference; 37 38 import java.util.Objects; 39 40 /** 41 * A {@link BasePreferenceController} for controlling the preview pager of the text and reading 42 * options. 43 */ 44 class TextReadingPreviewController extends BasePreferenceController implements 45 PreviewSizeSeekBarController.ProgressInteractionListener { 46 private static final String TAG = "TextReadingPreviewCtrl"; 47 private static final int LAYER_INITIAL_INDEX = 0; 48 private static final int FRAME_INITIAL_INDEX = 0; 49 private static final String PREVIEW_KEY = "preview"; 50 private static final String FONT_SIZE_KEY = "font_size"; 51 private static final String DISPLAY_SIZE_KEY = "display_size"; 52 private static final long MIN_COMMIT_INTERVAL_MS = 800; 53 private static final long CHANGE_BY_SEEKBAR_DELAY_MS = 100; 54 private static final long CHANGE_BY_BUTTON_DELAY_MS = 300; 55 private final FontSizeData mFontSizeData; 56 private final DisplaySizeData mDisplaySizeData; 57 private int mLastFontProgress; 58 private int mLastDisplayProgress; 59 private long mLastCommitTime; 60 private TextReadingPreviewPreference mPreviewPreference; 61 private LabeledSeekBarPreference mFontSizePreference; 62 private LabeledSeekBarPreference mDisplaySizePreference; 63 64 @EntryPoint 65 private int mEntryPoint; 66 67 private final Choreographer.FrameCallback mCommit = f -> { 68 tryCommitFontSizeConfig(); 69 tryCommitDisplaySizeConfig(); 70 71 mLastCommitTime = SystemClock.elapsedRealtime(); 72 }; 73 TextReadingPreviewController(Context context, String preferenceKey, @NonNull FontSizeData fontSizeData, @NonNull DisplaySizeData displaySizeData)74 TextReadingPreviewController(Context context, String preferenceKey, 75 @NonNull FontSizeData fontSizeData, @NonNull DisplaySizeData displaySizeData) { 76 super(context, preferenceKey); 77 mFontSizeData = fontSizeData; 78 mDisplaySizeData = displaySizeData; 79 } 80 81 @Override getAvailabilityStatus()82 public int getAvailabilityStatus() { 83 return AVAILABLE; 84 } 85 86 @Override displayPreference(PreferenceScreen screen)87 public void displayPreference(PreferenceScreen screen) { 88 super.displayPreference(screen); 89 90 mPreviewPreference = screen.findPreference(PREVIEW_KEY); 91 92 mFontSizePreference = screen.findPreference(FONT_SIZE_KEY); 93 mDisplaySizePreference = screen.findPreference(DISPLAY_SIZE_KEY); 94 Objects.requireNonNull(mFontSizePreference, 95 /* message= */ "Font size preference is null, the preview controller " 96 + "couldn't get the info"); 97 Objects.requireNonNull(mDisplaySizePreference, 98 /* message= */ "Display size preference is null, the preview controller" 99 + " couldn't get the info"); 100 101 mLastFontProgress = mFontSizeData.getInitialIndex(); 102 mLastDisplayProgress = mDisplaySizeData.getInitialIndex(); 103 104 final Configuration origConfig = mContext.getResources().getConfiguration(); 105 final boolean isLayoutRtl = 106 origConfig.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; 107 final int[] previewSamples = getPreviewSampleLayouts(mContext); 108 final PreviewPagerAdapter pagerAdapter = new PreviewPagerAdapter(mContext, isLayoutRtl, 109 previewSamples, createConfig(origConfig)); 110 mPreviewPreference.setPreviewAdapter(pagerAdapter); 111 mPreviewPreference.setCurrentItem( 112 isLayoutRtl ? previewSamples.length - 1 : FRAME_INITIAL_INDEX); 113 114 final int initialPagerIndex = 115 mLastFontProgress * mDisplaySizeData.getValues().size() + mLastDisplayProgress; 116 mPreviewPreference.setLastLayerIndex(initialPagerIndex); 117 pagerAdapter.setPreviewLayer(initialPagerIndex, LAYER_INITIAL_INDEX, 118 FRAME_INITIAL_INDEX, /* animate= */ false); 119 } 120 121 @Override notifyPreferenceChanged()122 public void notifyPreferenceChanged() { 123 mPreviewPreference.notifyPreviewPagerChanged(getPagerIndex()); 124 } 125 126 @Override onProgressChanged()127 public void onProgressChanged() { 128 postCommitDelayed(CHANGE_BY_BUTTON_DELAY_MS); 129 } 130 131 @Override onEndTrackingTouch()132 public void onEndTrackingTouch() { 133 postCommitDelayed(CHANGE_BY_SEEKBAR_DELAY_MS); 134 } 135 setCurrentItem(int index)136 void setCurrentItem(int index) { 137 mPreviewPreference.setCurrentItem(index); 138 } 139 getCurrentItem()140 int getCurrentItem() { 141 return mPreviewPreference.getCurrentItem(); 142 } 143 144 /** 145 * The entry point is used for logging. 146 * 147 * @param entryPoint from which settings page 148 */ setEntryPoint(@ntryPoint int entryPoint)149 void setEntryPoint(@EntryPoint int entryPoint) { 150 mEntryPoint = entryPoint; 151 } 152 153 /** 154 * Avoids the flicker when switching to the previous or next level. 155 * 156 * <p><br>[Flickering problem steps] commit()-> snapshot in framework(old screenshot) -> 157 * app update the preview -> snapshot(old screen) fade out</p> 158 * 159 * <p><br>To prevent flickering problem, we make sure that we update the local preview 160 * first and then we do the commit later. </p> 161 * 162 * <p><br><b>Note:</b> It doesn't matter that we use 163 * Choreographer or main thread handler since the delay time is longer 164 * than 1 frame. Use Choreographer to let developer understand it's a 165 * window update.</p> 166 * 167 * @param commitDelayMs the interval time after a action. 168 */ postCommitDelayed(long commitDelayMs)169 void postCommitDelayed(long commitDelayMs) { 170 if (SystemClock.elapsedRealtime() - mLastCommitTime < MIN_COMMIT_INTERVAL_MS) { 171 commitDelayMs += MIN_COMMIT_INTERVAL_MS; 172 } 173 174 final Choreographer choreographer = Choreographer.getInstance(); 175 choreographer.removeFrameCallback(mCommit); 176 choreographer.postFrameCallbackDelayed(mCommit, commitDelayMs); 177 } 178 179 @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) getPreviewSampleLayouts(Context context)180 static int[] getPreviewSampleLayouts(Context context) { 181 TypedArray previews = context.getResources().obtainTypedArray( 182 R.array.config_text_reading_preview_samples); 183 int previewCount = previews.length(); 184 int[] previewSamples = new int[previewCount]; 185 for (int i = 0; i < previewCount; i++) { 186 previewSamples[i] = previews.getResourceId(i, R.layout.screen_zoom_preview_1); 187 } 188 previews.recycle(); 189 return previewSamples; 190 } 191 getPagerIndex()192 private int getPagerIndex() { 193 final int displayDataSize = mDisplaySizeData.getValues().size(); 194 final int fontSizeProgress = mFontSizePreference.getProgress(); 195 final int displaySizeProgress = mDisplaySizePreference.getProgress(); 196 197 // To be consistent with the {@link PreviewPagerAdapter#setPreviewLayer(int, int, int, 198 // boolean)} behavior, here also needs the same design. In addition, please also refer to 199 // the {@link #createConfig(Configuration)}. 200 return fontSizeProgress * displayDataSize + displaySizeProgress; 201 } 202 tryCommitFontSizeConfig()203 private void tryCommitFontSizeConfig() { 204 final int fontProgress = mFontSizePreference.getProgress(); 205 if (fontProgress != mLastFontProgress) { 206 mFontSizeData.commit(fontProgress); 207 mLastFontProgress = fontProgress; 208 209 if (Log.isLoggable(TAG, Log.DEBUG)) { 210 Log.d(TAG, "Font size: " + fontProgress); 211 } 212 213 SettingsStatsLog.write( 214 SettingsStatsLog.ACCESSIBILITY_TEXT_READING_OPTIONS_CHANGED, 215 AccessibilityStatsLogUtils.convertToItemKeyName(mFontSizePreference.getKey()), 216 fontProgress, 217 AccessibilityStatsLogUtils.convertToEntryPoint(mEntryPoint)); 218 } 219 } 220 tryCommitDisplaySizeConfig()221 private void tryCommitDisplaySizeConfig() { 222 final int displayProgress = mDisplaySizePreference.getProgress(); 223 if (displayProgress != mLastDisplayProgress) { 224 mDisplaySizeData.commit(displayProgress); 225 mLastDisplayProgress = displayProgress; 226 227 if (Log.isLoggable(TAG, Log.DEBUG)) { 228 Log.d(TAG, "Display size: " + displayProgress); 229 } 230 231 SettingsStatsLog.write( 232 SettingsStatsLog.ACCESSIBILITY_TEXT_READING_OPTIONS_CHANGED, 233 AccessibilityStatsLogUtils.convertToItemKeyName( 234 mDisplaySizePreference.getKey()), 235 displayProgress, 236 AccessibilityStatsLogUtils.convertToEntryPoint(mEntryPoint)); 237 } 238 } 239 createConfig(Configuration origConfig)240 private Configuration[] createConfig(Configuration origConfig) { 241 final int fontDataSize = mFontSizeData.getValues().size(); 242 final int displayDataSize = mDisplaySizeData.getValues().size(); 243 final int totalNum = fontDataSize * displayDataSize; 244 final Configuration[] configurations = new Configuration[totalNum]; 245 246 for (int i = 0; i < fontDataSize; ++i) { 247 for (int j = 0; j < displayDataSize; ++j) { 248 final Configuration config = new Configuration(origConfig); 249 config.fontScale = mFontSizeData.getValues().get(i); 250 config.densityDpi = mDisplaySizeData.getValues().get(j); 251 252 configurations[i * displayDataSize + j] = config; 253 } 254 } 255 256 return configurations; 257 } 258 } 259