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 final int initialPagerIndex = 116 mLastFontProgress * mDisplaySizeData.getValues().size() + mLastDisplayProgress; 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 136 /** 137 * The entry point is used for logging. 138 * 139 * @param entryPoint from which settings page 140 */ setEntryPoint(@ntryPoint int entryPoint)141 void setEntryPoint(@EntryPoint int entryPoint) { 142 mEntryPoint = entryPoint; 143 } 144 145 /** 146 * Avoids the flicker when switching to the previous or next level. 147 * 148 * <p><br>[Flickering problem steps] commit()-> snapshot in framework(old screenshot) -> 149 * app update the preview -> snapshot(old screen) fade out</p> 150 * 151 * <p><br>To prevent flickering problem, we make sure that we update the local preview 152 * first and then we do the commit later. </p> 153 * 154 * <p><br><b>Note:</b> It doesn't matter that we use 155 * Choreographer or main thread handler since the delay time is longer 156 * than 1 frame. Use Choreographer to let developer understand it's a 157 * window update.</p> 158 * 159 * @param commitDelayMs the interval time after a action. 160 */ postCommitDelayed(long commitDelayMs)161 void postCommitDelayed(long commitDelayMs) { 162 if (SystemClock.elapsedRealtime() - mLastCommitTime < MIN_COMMIT_INTERVAL_MS) { 163 commitDelayMs += MIN_COMMIT_INTERVAL_MS; 164 } 165 166 final Choreographer choreographer = Choreographer.getInstance(); 167 choreographer.removeFrameCallback(mCommit); 168 choreographer.postFrameCallbackDelayed(mCommit, commitDelayMs); 169 } 170 getPagerIndex()171 private int getPagerIndex() { 172 final int displayDataSize = mDisplaySizeData.getValues().size(); 173 final int fontSizeProgress = mFontSizePreference.getProgress(); 174 final int displaySizeProgress = mDisplaySizePreference.getProgress(); 175 176 // To be consistent with the {@link PreviewPagerAdapter#setPreviewLayer(int, int, int, 177 // boolean)} behavior, here also needs the same design. In addition, please also refer to 178 // the {@link #createConfig(Configuration)}. 179 return fontSizeProgress * displayDataSize + displaySizeProgress; 180 } 181 tryCommitFontSizeConfig()182 private void tryCommitFontSizeConfig() { 183 final int fontProgress = mFontSizePreference.getProgress(); 184 if (fontProgress != mLastFontProgress) { 185 mFontSizeData.commit(fontProgress); 186 mLastFontProgress = fontProgress; 187 188 if (Log.isLoggable(TAG, Log.DEBUG)) { 189 Log.d(TAG, "Font size: " + fontProgress); 190 } 191 192 SettingsStatsLog.write( 193 SettingsStatsLog.ACCESSIBILITY_TEXT_READING_OPTIONS_CHANGED, 194 AccessibilityStatsLogUtils.convertToItemKeyName(mFontSizePreference.getKey()), 195 fontProgress, 196 AccessibilityStatsLogUtils.convertToEntryPoint(mEntryPoint)); 197 } 198 } 199 tryCommitDisplaySizeConfig()200 private void tryCommitDisplaySizeConfig() { 201 final int displayProgress = mDisplaySizePreference.getProgress(); 202 if (displayProgress != mLastDisplayProgress) { 203 mDisplaySizeData.commit(displayProgress); 204 mLastDisplayProgress = displayProgress; 205 206 if (Log.isLoggable(TAG, Log.DEBUG)) { 207 Log.d(TAG, "Display size: " + displayProgress); 208 } 209 210 SettingsStatsLog.write( 211 SettingsStatsLog.ACCESSIBILITY_TEXT_READING_OPTIONS_CHANGED, 212 AccessibilityStatsLogUtils.convertToItemKeyName( 213 mDisplaySizePreference.getKey()), 214 displayProgress, 215 AccessibilityStatsLogUtils.convertToEntryPoint(mEntryPoint)); 216 } 217 } 218 createConfig(Configuration origConfig)219 private Configuration[] createConfig(Configuration origConfig) { 220 final int fontDataSize = mFontSizeData.getValues().size(); 221 final int displayDataSize = mDisplaySizeData.getValues().size(); 222 final int totalNum = fontDataSize * displayDataSize; 223 final Configuration[] configurations = new Configuration[totalNum]; 224 225 for (int i = 0; i < fontDataSize; ++i) { 226 for (int j = 0; j < displayDataSize; ++j) { 227 final Configuration config = new Configuration(origConfig); 228 config.fontScale = mFontSizeData.getValues().get(i); 229 config.densityDpi = mDisplaySizeData.getValues().get(j); 230 231 configurations[i * displayDataSize + j] = config; 232 } 233 } 234 235 return configurations; 236 } 237 } 238