blob: 4ec0b3ddd973a1c29bc977328145a028de3647e6 [file] [log] [blame]
/*
* 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 com.android.settings.flags.Flags;
import com.android.settings.widget.LabeledSeekBarPreference;
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 int[] PREVIEW_SAMPLE_RES_IDS = new int[]{
R.layout.accessibility_text_reading_preview_app_grid,
R.layout.screen_zoom_preview_1,
R.layout.accessibility_text_reading_preview_mail_content};
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 LabeledSeekBarPreference mFontSizePreference;
private LabeledSeekBarPreference 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) {
if (!Flags.accessibilityCustomizeTextReadingPreview()) {
return PREVIEW_SAMPLE_RES_IDS;
}
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;
}
}