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