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