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.os.Bundle; 21 import android.os.Parcelable; 22 import android.util.AttributeSet; 23 import android.view.View; 24 import android.widget.FrameLayout; 25 import android.widget.ImageButton; 26 import android.widget.LinearLayout; 27 28 import androidx.preference.Preference; 29 import androidx.preference.PreferenceViewHolder; 30 import androidx.viewpager.widget.ViewPager; 31 32 import com.android.internal.annotations.VisibleForTesting; 33 import com.android.internal.util.Preconditions; 34 import com.android.settings.R; 35 import com.android.settings.display.PreviewPagerAdapter; 36 import com.android.settings.widget.DotsPageIndicator; 37 38 /** 39 * A {@link Preference} that could show the preview related to the text and reading options. 40 */ 41 public class TextReadingPreviewPreference extends Preference { 42 private static final String KEY_LAST_INDEX = "last_preview_index"; 43 private int mCurrentItem; 44 private int mLastLayerIndex; 45 private PreviewPagerAdapter mPreviewAdapter; 46 47 private int mLayoutMinHorizontalPadding = 0; 48 private int mBackgroundMinHorizontalPadding = 0; 49 private final ViewPager.OnPageChangeListener mPageChangeListener = 50 new ViewPager.OnPageChangeListener() { 51 @Override 52 public void onPageScrolled(int i, float v, int i1) { 53 // Do nothing 54 } 55 56 @Override 57 public void onPageSelected(int i) { 58 mCurrentItem = i; 59 } 60 61 @Override 62 public void onPageScrollStateChanged(int i) { 63 // Do nothing 64 } 65 }; 66 TextReadingPreviewPreference(Context context)67 TextReadingPreviewPreference(Context context) { 68 super(context); 69 init(); 70 } 71 TextReadingPreviewPreference(Context context, AttributeSet attrs)72 public TextReadingPreviewPreference(Context context, AttributeSet attrs) { 73 super(context, attrs); 74 init(); 75 } 76 TextReadingPreviewPreference(Context context, AttributeSet attrs, int defStyleAttr)77 TextReadingPreviewPreference(Context context, AttributeSet attrs, int defStyleAttr) { 78 super(context, attrs, defStyleAttr); 79 init(); 80 } 81 TextReadingPreviewPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)82 TextReadingPreviewPreference(Context context, AttributeSet attrs, int defStyleAttr, 83 int defStyleRes) { 84 super(context, attrs, defStyleAttr, defStyleRes); 85 init(); 86 } 87 88 @Override onBindViewHolder(PreferenceViewHolder holder)89 public void onBindViewHolder(PreferenceViewHolder holder) { 90 super.onBindViewHolder(holder); 91 92 FrameLayout previewLayout = (FrameLayout) holder.itemView; 93 LinearLayout backgroundView = previewLayout.findViewById(R.id.preview_background); 94 adjustPaddings(previewLayout, backgroundView); 95 96 final ViewPager viewPager = (ViewPager) holder.findViewById(R.id.preview_pager); 97 viewPager.addOnPageChangeListener(mPageChangeListener); 98 final DotsPageIndicator pageIndicator = 99 (DotsPageIndicator) holder.findViewById(R.id.page_indicator); 100 updateAdapterIfNeeded(viewPager, pageIndicator, mPreviewAdapter); 101 updatePagerAndIndicator(viewPager, pageIndicator); 102 viewPager.setClipToOutline(true); 103 104 int layoutDirection = 105 getContext().getResources().getConfiguration().getLayoutDirection(); 106 int previousId = (layoutDirection == View.LAYOUT_DIRECTION_RTL) 107 ? R.id.preview_right_button : R.id.preview_left_button; 108 int nextId = (layoutDirection == View.LAYOUT_DIRECTION_RTL) 109 ? R.id.preview_left_button : R.id.preview_right_button; 110 final ImageButton previousButton = previewLayout.findViewById(previousId); 111 final ImageButton nextButton = previewLayout.findViewById(nextId); 112 113 // These call ViewPager#setCurrentItem directly 114 // because that doesn't force a refresh through notifyChanged(). 115 // We found this avoids a crash in SUW (See b/386906497). 116 previousButton.setOnClickListener((view) -> 117 viewPager.setCurrentItem(getCurrentItem() - 1)); 118 previousButton.setContentDescription(getContext().getString( 119 R.string.preview_pager_previous_button)); 120 nextButton.setOnClickListener((view) -> 121 viewPager.setCurrentItem(getCurrentItem() + 1)); 122 nextButton.setContentDescription(getContext().getString( 123 R.string.preview_pager_next_button)); 124 } 125 126 @Override onSaveInstanceState()127 protected Parcelable onSaveInstanceState() { 128 Bundle state = new Bundle(); 129 state.putParcelable(null, super.onSaveInstanceState()); 130 state.putInt(KEY_LAST_INDEX, getCurrentItem()); 131 return state; 132 } 133 134 @Override onRestoreInstanceState(Parcelable state)135 protected void onRestoreInstanceState(Parcelable state) { 136 Bundle bundle = (Bundle) state; 137 super.onRestoreInstanceState(bundle.getParcelable(null)); 138 setCurrentItem(bundle.getInt(KEY_LAST_INDEX)); 139 } 140 141 142 /** 143 * Set the minimum preview layout horizontal inner padding. 144 */ setLayoutMinHorizontalPadding(int layoutMinHorizontalPadding)145 void setLayoutMinHorizontalPadding(int layoutMinHorizontalPadding) { 146 mLayoutMinHorizontalPadding = layoutMinHorizontalPadding; 147 } 148 149 /** 150 * Set the minimum preview background view horizontal inner padding. 151 */ setBackgroundMinHorizontalPadding(int backgroundMinHorizontalPadding)152 void setBackgroundMinHorizontalPadding(int backgroundMinHorizontalPadding) { 153 mBackgroundMinHorizontalPadding = backgroundMinHorizontalPadding; 154 } 155 156 @VisibleForTesting adjustPaddings(FrameLayout previewLayout, LinearLayout backgroundView)157 void adjustPaddings(FrameLayout previewLayout, LinearLayout backgroundView) { 158 previewLayout.setPadding( 159 Math.max(previewLayout.getPaddingStart(), mLayoutMinHorizontalPadding), 160 previewLayout.getPaddingTop(), 161 Math.max(previewLayout.getPaddingEnd(), mLayoutMinHorizontalPadding), 162 previewLayout.getPaddingBottom() 163 ); 164 backgroundView.setPadding( 165 Math.max(backgroundView.getPaddingStart(), mBackgroundMinHorizontalPadding), 166 backgroundView.getPaddingTop(), 167 Math.max(backgroundView.getPaddingEnd(), mBackgroundMinHorizontalPadding), 168 backgroundView.getPaddingBottom() 169 ); 170 } 171 setPreviewAdapter(PreviewPagerAdapter previewAdapter)172 void setPreviewAdapter(PreviewPagerAdapter previewAdapter) { 173 if (previewAdapter != mPreviewAdapter) { 174 mPreviewAdapter = previewAdapter; 175 notifyChanged(); 176 } 177 } 178 setCurrentItem(int currentItem)179 void setCurrentItem(int currentItem) { 180 Preconditions.checkNotNull(mPreviewAdapter, 181 "Preview adapter is null, you should init the preview adapter first"); 182 183 if (currentItem != mCurrentItem) { 184 mCurrentItem = currentItem; 185 notifyChanged(); 186 } 187 } 188 setLastLayerIndex(int lastLayerIndex)189 void setLastLayerIndex(int lastLayerIndex) { 190 mLastLayerIndex = lastLayerIndex; 191 } 192 getCurrentItem()193 int getCurrentItem() { 194 return mCurrentItem; 195 } 196 updateAdapterIfNeeded(ViewPager viewPager, DotsPageIndicator pageIndicator, PreviewPagerAdapter previewAdapter)197 private void updateAdapterIfNeeded(ViewPager viewPager, DotsPageIndicator pageIndicator, 198 PreviewPagerAdapter previewAdapter) { 199 if (viewPager.getAdapter() == previewAdapter) { 200 return; 201 } 202 203 viewPager.setAdapter(previewAdapter); 204 205 if (previewAdapter != null) { 206 pageIndicator.setViewPager(viewPager); 207 } else { 208 mCurrentItem = 0; 209 } 210 } 211 updatePagerAndIndicator(ViewPager viewPager, DotsPageIndicator pageIndicator)212 private void updatePagerAndIndicator(ViewPager viewPager, DotsPageIndicator pageIndicator) { 213 if (viewPager.getAdapter() == null) { 214 return; 215 } 216 217 if (viewPager.getCurrentItem() != mCurrentItem) { 218 viewPager.setCurrentItem(mCurrentItem); 219 } 220 221 pageIndicator.setVisibility( 222 viewPager.getAdapter().getCount() > 1 ? View.VISIBLE : View.GONE); 223 } 224 init()225 private void init() { 226 // set up the minimum horizontal paddings 227 setLayoutMinHorizontalPadding(getContext().getResources().getDimensionPixelSize( 228 R.dimen.text_reading_preview_layout_padding_horizontal_min)); 229 setBackgroundMinHorizontalPadding(getContext().getResources().getDimensionPixelSize( 230 R.dimen.text_reading_preview_background_padding_horizontal_min)); 231 232 setLayoutResource(R.layout.accessibility_text_reading_preview); 233 } 234 notifyPreviewPagerChanged(int pagerIndex)235 void notifyPreviewPagerChanged(int pagerIndex) { 236 Preconditions.checkNotNull(mPreviewAdapter, 237 "Preview adapter is null, you should init the preview adapter first"); 238 239 if (pagerIndex != mLastLayerIndex) { 240 mPreviewAdapter.setPreviewLayer(pagerIndex, mLastLayerIndex, getCurrentItem(), 241 /* animate= */ false); 242 } 243 244 mLastLayerIndex = pagerIndex; 245 } 246 } 247