| /* | |
| * Copyright (C) 2022 The Android Open Source Project | |
| * | |
| * Licensed under the Apache License, Version 2.0 (the "License"); | |
| * you may not use this file except in compliance with the License. | |
| * You may obtain a copy of the License at | |
| * | |
| * http://www.apache.org/licenses/LICENSE-2.0 | |
| * | |
| * Unless required by applicable law or agreed to in writing, software | |
| * distributed under the License is distributed on an "AS IS" BASIS, | |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| * See the License for the specific language governing permissions and | |
| * limitations under the License. | |
| */ | |
| package com.android.settings.accessibility; | |
| import android.content.Context; | |
| import android.content.res.Configuration; | |
| import android.content.res.TypedArray; | |
| import android.os.SystemClock; | |
| import android.util.Log; | |
| import android.view.Choreographer; | |
| import android.view.View; | |
| import androidx.annotation.NonNull; | |
| import androidx.annotation.VisibleForTesting; | |
| import androidx.preference.PreferenceScreen; | |
| import com.android.settings.R; | |
| import com.android.settings.accessibility.TextReadingPreferenceFragment.EntryPoint; | |
| import com.android.settings.core.BasePreferenceController; | |
| import com.android.settings.core.instrumentation.SettingsStatsLog; | |
| import com.android.settings.display.PreviewPagerAdapter; | |
| import java.util.Objects; | |
| /** | |
| * A {@link BasePreferenceController} for controlling the preview pager of the text and reading | |
| * options. | |
| */ | |
| class TextReadingPreviewController extends BasePreferenceController implements | |
| PreviewSizeSeekBarController.ProgressInteractionListener { | |
| private static final String TAG = "TextReadingPreviewCtrl"; | |
| private static final int LAYER_INITIAL_INDEX = 0; | |
| private static final int FRAME_INITIAL_INDEX = 0; | |
| private static final String PREVIEW_KEY = "preview"; | |
| private static final String FONT_SIZE_KEY = "font_size"; | |
| private static final String DISPLAY_SIZE_KEY = "display_size"; | |
| private static final long MIN_COMMIT_INTERVAL_MS = 800; | |
| private static final long CHANGE_BY_SEEKBAR_DELAY_MS = 100; | |
| private static final long CHANGE_BY_BUTTON_DELAY_MS = 300; | |
| private final FontSizeData mFontSizeData; | |
| private final DisplaySizeData mDisplaySizeData; | |
| private int mLastFontProgress; | |
| private int mLastDisplayProgress; | |
| private long mLastCommitTime; | |
| private TextReadingPreviewPreference mPreviewPreference; | |
| private AccessibilitySeekBarPreference mFontSizePreference; | |
| private AccessibilitySeekBarPreference mDisplaySizePreference; | |
| @EntryPoint | |
| private int mEntryPoint; | |
| private final Choreographer.FrameCallback mCommit = f -> { | |
| tryCommitFontSizeConfig(); | |
| tryCommitDisplaySizeConfig(); | |
| mLastCommitTime = SystemClock.elapsedRealtime(); | |
| }; | |
| TextReadingPreviewController(Context context, String preferenceKey, | |
| @NonNull FontSizeData fontSizeData, @NonNull DisplaySizeData displaySizeData) { | |
| super(context, preferenceKey); | |
| mFontSizeData = fontSizeData; | |
| mDisplaySizeData = displaySizeData; | |
| } | |
| @Override | |
| public int getAvailabilityStatus() { | |
| return AVAILABLE; | |
| } | |
| @Override | |
| public void displayPreference(PreferenceScreen screen) { | |
| super.displayPreference(screen); | |
| mPreviewPreference = screen.findPreference(PREVIEW_KEY); | |
| mFontSizePreference = screen.findPreference(FONT_SIZE_KEY); | |
| mDisplaySizePreference = screen.findPreference(DISPLAY_SIZE_KEY); | |
| Objects.requireNonNull(mFontSizePreference, | |
| /* message= */ "Font size preference is null, the preview controller " | |
| + "couldn't get the info"); | |
| Objects.requireNonNull(mDisplaySizePreference, | |
| /* message= */ "Display size preference is null, the preview controller" | |
| + " couldn't get the info"); | |
| mLastFontProgress = mFontSizeData.getInitialIndex(); | |
| mLastDisplayProgress = mDisplaySizeData.getInitialIndex(); | |
| final Configuration origConfig = mContext.getResources().getConfiguration(); | |
| final boolean isLayoutRtl = | |
| origConfig.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; | |
| final int[] previewSamples = getPreviewSampleLayouts(mContext); | |
| final PreviewPagerAdapter pagerAdapter = new PreviewPagerAdapter(mContext, isLayoutRtl, | |
| previewSamples, createConfig(origConfig)); | |
| mPreviewPreference.setPreviewAdapter(pagerAdapter); | |
| mPreviewPreference.setCurrentItem( | |
| isLayoutRtl ? previewSamples.length - 1 : FRAME_INITIAL_INDEX); | |
| final int initialPagerIndex = | |
| mLastFontProgress * mDisplaySizeData.getValues().size() + mLastDisplayProgress; | |
| mPreviewPreference.setLastLayerIndex(initialPagerIndex); | |
| pagerAdapter.setPreviewLayer(initialPagerIndex, LAYER_INITIAL_INDEX, | |
| FRAME_INITIAL_INDEX, /* animate= */ false); | |
| } | |
| @Override | |
| public void notifyPreferenceChanged() { | |
| mPreviewPreference.notifyPreviewPagerChanged(getPagerIndex()); | |
| } | |
| @Override | |
| public void onProgressChanged() { | |
| postCommitDelayed(CHANGE_BY_BUTTON_DELAY_MS); | |
| } | |
| @Override | |
| public void onEndTrackingTouch() { | |
| postCommitDelayed(CHANGE_BY_SEEKBAR_DELAY_MS); | |
| } | |
| void setCurrentItem(int index) { | |
| mPreviewPreference.setCurrentItem(index); | |
| } | |
| int getCurrentItem() { | |
| return mPreviewPreference.getCurrentItem(); | |
| } | |
| /** | |
| * The entry point is used for logging. | |
| * | |
| * @param entryPoint from which settings page | |
| */ | |
| void setEntryPoint(@EntryPoint int entryPoint) { | |
| mEntryPoint = entryPoint; | |
| } | |
| /** | |
| * Avoids the flicker when switching to the previous or next level. | |
| * | |
| * <p><br>[Flickering problem steps] commit()-> snapshot in framework(old screenshot) -> | |
| * app update the preview -> snapshot(old screen) fade out</p> | |
| * | |
| * <p><br>To prevent flickering problem, we make sure that we update the local preview | |
| * first and then we do the commit later. </p> | |
| * | |
| * <p><br><b>Note:</b> It doesn't matter that we use | |
| * Choreographer or main thread handler since the delay time is longer | |
| * than 1 frame. Use Choreographer to let developer understand it's a | |
| * window update.</p> | |
| * | |
| * @param commitDelayMs the interval time after a action. | |
| */ | |
| void postCommitDelayed(long commitDelayMs) { | |
| if (SystemClock.elapsedRealtime() - mLastCommitTime < MIN_COMMIT_INTERVAL_MS) { | |
| commitDelayMs += MIN_COMMIT_INTERVAL_MS; | |
| } | |
| final Choreographer choreographer = Choreographer.getInstance(); | |
| choreographer.removeFrameCallback(mCommit); | |
| choreographer.postFrameCallbackDelayed(mCommit, commitDelayMs); | |
| } | |
| @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE) | |
| static int[] getPreviewSampleLayouts(Context context) { | |
| TypedArray previews = context.getResources().obtainTypedArray( | |
| R.array.config_text_reading_preview_samples); | |
| int previewCount = previews.length(); | |
| int[] previewSamples = new int[previewCount]; | |
| for (int i = 0; i < previewCount; i++) { | |
| previewSamples[i] = previews.getResourceId(i, R.layout.screen_zoom_preview_1); | |
| } | |
| previews.recycle(); | |
| return previewSamples; | |
| } | |
| private int getPagerIndex() { | |
| final int displayDataSize = mDisplaySizeData.getValues().size(); | |
| final int fontSizeProgress = mFontSizePreference.getProgress(); | |
| final int displaySizeProgress = mDisplaySizePreference.getProgress(); | |
| // To be consistent with the {@link PreviewPagerAdapter#setPreviewLayer(int, int, int, | |
| // boolean)} behavior, here also needs the same design. In addition, please also refer to | |
| // the {@link #createConfig(Configuration)}. | |
| return fontSizeProgress * displayDataSize + displaySizeProgress; | |
| } | |
| private void tryCommitFontSizeConfig() { | |
| final int fontProgress = mFontSizePreference.getProgress(); | |
| if (fontProgress != mLastFontProgress) { | |
| mFontSizeData.commit(fontProgress); | |
| mLastFontProgress = fontProgress; | |
| if (Log.isLoggable(TAG, Log.DEBUG)) { | |
| Log.d(TAG, "Font size: " + fontProgress); | |
| } | |
| SettingsStatsLog.write( | |
| SettingsStatsLog.ACCESSIBILITY_TEXT_READING_OPTIONS_CHANGED, | |
| AccessibilityStatsLogUtils.convertToItemKeyName(mFontSizePreference.getKey()), | |
| fontProgress, | |
| AccessibilityStatsLogUtils.convertToEntryPoint(mEntryPoint)); | |
| } | |
| } | |
| private void tryCommitDisplaySizeConfig() { | |
| final int displayProgress = mDisplaySizePreference.getProgress(); | |
| if (displayProgress != mLastDisplayProgress) { | |
| mDisplaySizeData.commit(displayProgress); | |
| mLastDisplayProgress = displayProgress; | |
| if (Log.isLoggable(TAG, Log.DEBUG)) { | |
| Log.d(TAG, "Display size: " + displayProgress); | |
| } | |
| SettingsStatsLog.write( | |
| SettingsStatsLog.ACCESSIBILITY_TEXT_READING_OPTIONS_CHANGED, | |
| AccessibilityStatsLogUtils.convertToItemKeyName( | |
| mDisplaySizePreference.getKey()), | |
| displayProgress, | |
| AccessibilityStatsLogUtils.convertToEntryPoint(mEntryPoint)); | |
| } | |
| } | |
| private Configuration[] createConfig(Configuration origConfig) { | |
| final int fontDataSize = mFontSizeData.getValues().size(); | |
| final int displayDataSize = mDisplaySizeData.getValues().size(); | |
| final int totalNum = fontDataSize * displayDataSize; | |
| final Configuration[] configurations = new Configuration[totalNum]; | |
| for (int i = 0; i < fontDataSize; ++i) { | |
| for (int j = 0; j < displayDataSize; ++j) { | |
| final Configuration config = new Configuration(origConfig); | |
| config.fontScale = mFontSizeData.getValues().get(i); | |
| config.densityDpi = mDisplaySizeData.getValues().get(j); | |
| configurations[i * displayDataSize + j] = config; | |
| } | |
| } | |
| return configurations; | |
| } | |
| } |