• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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