| /* |
| * 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.os.Bundle; |
| import android.os.Parcelable; |
| import android.util.AttributeSet; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.FrameLayout; |
| import android.widget.ImageButton; |
| import android.widget.LinearLayout; |
| |
| import androidx.preference.Preference; |
| import androidx.preference.PreferenceViewHolder; |
| import androidx.viewpager.widget.ViewPager; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.Preconditions; |
| import com.android.settings.R; |
| import com.android.settings.accessibility.shared.utils.SetupWizardUtilKt; |
| import com.android.settings.display.PreviewPagerAdapter; |
| import com.android.settings.widget.DotsPageIndicator; |
| import com.android.settings.widget.FocusIndicatorDrawable; |
| import com.android.settingslib.widget.GroupSectionDividerMixin; |
| |
| /** |
| * A {@link Preference} that could show the preview related to the text and reading options. |
| */ |
| public class TextReadingPreviewPreference extends Preference implements GroupSectionDividerMixin { |
| |
| private static final int FOCUS_INDICATOR_VERTICAL_PADDING_ADJUSTMENT_DP = 2; |
| private static final int FOCUS_INDICATOR_CORNER_RADIUS_DP = 2; |
| |
| private static final String KEY_LAST_INDEX = "last_preview_index"; |
| private int mCurrentItem; |
| private int mLastLayerIndex; |
| private PreviewPagerAdapter mPreviewAdapter; |
| |
| private int mLayoutMinHorizontalPadding = 0; |
| private int mBackgroundMinHorizontalPadding = 0; |
| private final ViewPager.OnPageChangeListener mPageChangeListener = |
| new ViewPager.OnPageChangeListener() { |
| @Override |
| public void onPageScrolled(int i, float v, int i1) { |
| // Do nothing |
| } |
| |
| @Override |
| public void onPageSelected(int i) { |
| mCurrentItem = i; |
| } |
| |
| @Override |
| public void onPageScrollStateChanged(int i) { |
| // Do nothing |
| } |
| }; |
| |
| TextReadingPreviewPreference(Context context) { |
| super(context); |
| init(); |
| } |
| |
| public TextReadingPreviewPreference(Context context, AttributeSet attrs) { |
| super(context, attrs); |
| init(); |
| } |
| |
| TextReadingPreviewPreference(Context context, AttributeSet attrs, int defStyleAttr) { |
| super(context, attrs, defStyleAttr); |
| init(); |
| } |
| |
| TextReadingPreviewPreference(Context context, AttributeSet attrs, int defStyleAttr, |
| int defStyleRes) { |
| super(context, attrs, defStyleAttr, defStyleRes); |
| init(); |
| } |
| |
| @Override |
| public void onBindViewHolder(PreferenceViewHolder holder) { |
| super.onBindViewHolder(holder); |
| |
| FrameLayout previewLayout = (FrameLayout) holder.itemView; |
| LinearLayout backgroundView = previewLayout.findViewById(R.id.preview_background); |
| adjustPaddings(previewLayout, backgroundView); |
| |
| ViewPager viewPager = (ViewPager) holder.findViewById(R.id.preview_pager); |
| if (SetupWizardUtilKt.shouldShowFocusRingsInSuw(getContext())) { |
| // These changes prevent elements within the text reading preview, which cannot be |
| // interacted with, from being focusable. |
| viewPager.setFocusable(false); |
| viewPager.setFocusableInTouchMode(false); |
| if (viewPager instanceof ViewGroup viewGroup) { |
| viewGroup.setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); |
| } |
| } |
| viewPager.addOnPageChangeListener(mPageChangeListener); |
| DotsPageIndicator pageIndicator = |
| (DotsPageIndicator) holder.findViewById(R.id.page_indicator); |
| updateAdapterIfNeeded(viewPager, pageIndicator, mPreviewAdapter); |
| updatePagerAndIndicator(viewPager, pageIndicator); |
| viewPager.setClipToOutline(true); |
| |
| int layoutDirection = |
| getContext().getResources().getConfiguration().getLayoutDirection(); |
| int previousId = (layoutDirection == View.LAYOUT_DIRECTION_RTL) |
| ? R.id.preview_right_button : R.id.preview_left_button; |
| int nextId = (layoutDirection == View.LAYOUT_DIRECTION_RTL) |
| ? R.id.preview_left_button : R.id.preview_right_button; |
| ImageButton previousButton = previewLayout.findViewById(previousId); |
| ImageButton nextButton = previewLayout.findViewById(nextId); |
| |
| if (SetupWizardUtilKt.shouldShowFocusRingsInSuw(getContext())) { |
| // These changes add the focus ring indicator to the "previous" and "next" buttons of |
| // the text reading preview container. |
| if (previousButton != null) { |
| previousButton.setForeground( |
| new FocusIndicatorDrawable.Builder(getContext()) |
| .withVerticalPaddingAdjustment( |
| FOCUS_INDICATOR_VERTICAL_PADDING_ADJUSTMENT_DP) |
| .withCornerRadius(FOCUS_INDICATOR_CORNER_RADIUS_DP) |
| .build()); |
| } |
| if (nextButton != null) { |
| nextButton.setForeground( |
| new FocusIndicatorDrawable.Builder(getContext()) |
| .withVerticalPaddingAdjustment( |
| FOCUS_INDICATOR_VERTICAL_PADDING_ADJUSTMENT_DP) |
| .withCornerRadius(FOCUS_INDICATOR_CORNER_RADIUS_DP) |
| .build()); |
| } |
| } |
| |
| // These call ViewPager#setCurrentItem directly |
| // because that doesn't force a refresh through notifyChanged(). |
| // We found this avoids a crash in SUW (See b/386906497). |
| previousButton.setOnClickListener((view) -> |
| viewPager.setCurrentItem(getCurrentItem() - 1)); |
| previousButton.setContentDescription(getContext().getString( |
| R.string.preview_pager_previous_button)); |
| nextButton.setOnClickListener((view) -> |
| viewPager.setCurrentItem(getCurrentItem() + 1)); |
| nextButton.setContentDescription(getContext().getString( |
| R.string.preview_pager_next_button)); |
| } |
| |
| @Override |
| protected Parcelable onSaveInstanceState() { |
| Bundle state = new Bundle(); |
| state.putParcelable(null, super.onSaveInstanceState()); |
| state.putInt(KEY_LAST_INDEX, getCurrentItem()); |
| return state; |
| } |
| |
| @Override |
| protected void onRestoreInstanceState(Parcelable state) { |
| Bundle bundle = (Bundle) state; |
| super.onRestoreInstanceState(bundle.getParcelable(null)); |
| setCurrentItem(bundle.getInt(KEY_LAST_INDEX)); |
| } |
| |
| |
| /** |
| * Set the minimum preview layout horizontal inner padding. |
| */ |
| void setLayoutMinHorizontalPadding(int layoutMinHorizontalPadding) { |
| mLayoutMinHorizontalPadding = layoutMinHorizontalPadding; |
| } |
| |
| /** |
| * Set the minimum preview background view horizontal inner padding. |
| */ |
| void setBackgroundMinHorizontalPadding(int backgroundMinHorizontalPadding) { |
| mBackgroundMinHorizontalPadding = backgroundMinHorizontalPadding; |
| } |
| |
| @VisibleForTesting |
| void adjustPaddings(FrameLayout previewLayout, LinearLayout backgroundView) { |
| previewLayout.setPadding( |
| Math.max(previewLayout.getPaddingStart(), mLayoutMinHorizontalPadding), |
| previewLayout.getPaddingTop(), |
| Math.max(previewLayout.getPaddingEnd(), mLayoutMinHorizontalPadding), |
| previewLayout.getPaddingBottom() |
| ); |
| backgroundView.setPadding( |
| Math.max(backgroundView.getPaddingStart(), mBackgroundMinHorizontalPadding), |
| backgroundView.getPaddingTop(), |
| Math.max(backgroundView.getPaddingEnd(), mBackgroundMinHorizontalPadding), |
| backgroundView.getPaddingBottom() |
| ); |
| } |
| |
| /** |
| * Sets the {@link PreviewPagerAdapter} for the preview. |
| * |
| * @param previewAdapter The adapter to use for displaying preview pages. |
| */ |
| public void setPreviewAdapter(PreviewPagerAdapter previewAdapter) { |
| if (previewAdapter != mPreviewAdapter) { |
| mPreviewAdapter = previewAdapter; |
| notifyChanged(); |
| } |
| } |
| |
| /** |
| * Set the current item in the preview pager. |
| * |
| * @param currentItem The index of the preview items to set as current. |
| * @throws NullPointerException if the preview adapter is null. |
| */ |
| public void setCurrentItem(int currentItem) { |
| Preconditions.checkNotNull(mPreviewAdapter, |
| "Preview adapter is null, you should init the preview adapter first"); |
| |
| if (currentItem != mCurrentItem) { |
| mCurrentItem = currentItem; |
| notifyChanged(); |
| } |
| } |
| |
| public void setLastLayerIndex(int lastLayerIndex) { |
| mLastLayerIndex = lastLayerIndex; |
| } |
| |
| int getCurrentItem() { |
| return mCurrentItem; |
| } |
| |
| private void updateAdapterIfNeeded(ViewPager viewPager, DotsPageIndicator pageIndicator, |
| PreviewPagerAdapter previewAdapter) { |
| if (viewPager.getAdapter() == previewAdapter) { |
| return; |
| } |
| |
| viewPager.setAdapter(previewAdapter); |
| |
| if (previewAdapter != null) { |
| pageIndicator.setViewPager(viewPager); |
| } else { |
| mCurrentItem = 0; |
| } |
| } |
| |
| private void updatePagerAndIndicator(ViewPager viewPager, DotsPageIndicator pageIndicator) { |
| if (viewPager.getAdapter() == null) { |
| return; |
| } |
| |
| if (viewPager.getCurrentItem() != mCurrentItem) { |
| viewPager.setCurrentItem(mCurrentItem); |
| } |
| |
| pageIndicator.setVisibility( |
| viewPager.getAdapter().getCount() > 1 ? View.VISIBLE : View.GONE); |
| } |
| |
| private void init() { |
| // set up the minimum horizontal paddings |
| setLayoutMinHorizontalPadding(getContext().getResources().getDimensionPixelSize( |
| R.dimen.text_reading_preview_layout_padding_horizontal_min)); |
| setBackgroundMinHorizontalPadding(getContext().getResources().getDimensionPixelSize( |
| R.dimen.text_reading_preview_background_padding_horizontal_min)); |
| |
| setLayoutResource(R.layout.accessibility_text_reading_preview); |
| } |
| |
| /** |
| * Notifies the preview pager that the configuration has changed. |
| * |
| * @param pagerIndex The index of the current configuration in preview pager. |
| */ |
| public void notifyPreviewPagerChanged(int pagerIndex) { |
| Preconditions.checkNotNull(mPreviewAdapter, |
| "Preview adapter is null, you should init the preview adapter first"); |
| |
| if (pagerIndex != mLastLayerIndex) { |
| mPreviewAdapter.setPreviewLayer(pagerIndex, mLastLayerIndex, getCurrentItem(), |
| /* animate= */ false); |
| } |
| |
| mLastLayerIndex = pagerIndex; |
| } |
| } |