1 /* 2 * Copyright (C) 2011 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.widget; 18 19 import android.content.Context; 20 import android.content.res.TypedArray; 21 import android.os.Parcel; 22 import android.os.Parcelable; 23 import android.text.TextUtils; 24 import android.util.AttributeSet; 25 import android.view.KeyEvent; 26 import android.view.View; 27 import android.view.accessibility.AccessibilityNodeInfo; 28 import android.widget.SeekBar; 29 import android.widget.SeekBar.OnSeekBarChangeListener; 30 31 import androidx.core.content.res.TypedArrayUtils; 32 import androidx.preference.PreferenceViewHolder; 33 34 import com.android.settingslib.RestrictedPreference; 35 36 /** 37 * Based on android.preference.SeekBarPreference, but uses support preference as base. 38 */ 39 public class SeekBarPreference extends RestrictedPreference 40 implements OnSeekBarChangeListener, View.OnKeyListener { 41 42 private int mProgress; 43 private int mMax; 44 private int mMin; 45 private boolean mTrackingTouch; 46 47 private boolean mContinuousUpdates; 48 private int mDefaultProgress = -1; 49 50 private SeekBar mSeekBar; 51 private boolean mShouldBlink; 52 private int mAccessibilityRangeInfoType = AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT; 53 private CharSequence mSeekBarContentDescription; 54 SeekBarPreference( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)55 public SeekBarPreference( 56 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 57 super(context, attrs, defStyleAttr, defStyleRes); 58 59 TypedArray a = context.obtainStyledAttributes( 60 attrs, com.android.internal.R.styleable.ProgressBar, defStyleAttr, defStyleRes); 61 setMax(a.getInt(com.android.internal.R.styleable.ProgressBar_max, mMax)); 62 setMin(a.getInt(com.android.internal.R.styleable.ProgressBar_min, mMin)); 63 a.recycle(); 64 65 a = context.obtainStyledAttributes(attrs, 66 com.android.internal.R.styleable.SeekBarPreference, defStyleAttr, defStyleRes); 67 final int layoutResId = a.getResourceId( 68 com.android.internal.R.styleable.SeekBarPreference_layout, 69 com.android.internal.R.layout.preference_widget_seekbar); 70 a.recycle(); 71 72 a = context.obtainStyledAttributes( 73 attrs, com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes); 74 final boolean isSelectable = a.getBoolean( 75 com.android.settings.R.styleable.Preference_android_selectable, false); 76 setSelectable(isSelectable); 77 a.recycle(); 78 79 setLayoutResource(layoutResId); 80 } 81 SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr)82 public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) { 83 this(context, attrs, defStyleAttr, 0); 84 } 85 SeekBarPreference(Context context, AttributeSet attrs)86 public SeekBarPreference(Context context, AttributeSet attrs) { 87 this(context, attrs, TypedArrayUtils.getAttr(context, 88 androidx.preference.R.attr.seekBarPreferenceStyle, 89 com.android.internal.R.attr.seekBarPreferenceStyle)); 90 } 91 SeekBarPreference(Context context)92 public SeekBarPreference(Context context) { 93 this(context, null); 94 } 95 setShouldBlink(boolean shouldBlink)96 public void setShouldBlink(boolean shouldBlink) { 97 mShouldBlink = shouldBlink; 98 notifyChanged(); 99 } 100 101 @Override isSelectable()102 public boolean isSelectable() { 103 if(isDisabledByAdmin()) { 104 return true; 105 } else { 106 return super.isSelectable(); 107 } 108 } 109 110 @Override onBindViewHolder(PreferenceViewHolder view)111 public void onBindViewHolder(PreferenceViewHolder view) { 112 super.onBindViewHolder(view); 113 view.itemView.setOnKeyListener(this); 114 mSeekBar = (SeekBar) view.findViewById( 115 com.android.internal.R.id.seekbar); 116 mSeekBar.setOnSeekBarChangeListener(this); 117 mSeekBar.setMax(mMax); 118 mSeekBar.setMin(mMin); 119 mSeekBar.setProgress(mProgress); 120 mSeekBar.setEnabled(isEnabled()); 121 final CharSequence title = getTitle(); 122 if (!TextUtils.isEmpty(mSeekBarContentDescription)) { 123 mSeekBar.setContentDescription(mSeekBarContentDescription); 124 } else if (!TextUtils.isEmpty(title)) { 125 mSeekBar.setContentDescription(title); 126 } 127 if (mSeekBar instanceof DefaultIndicatorSeekBar) { 128 ((DefaultIndicatorSeekBar) mSeekBar).setDefaultProgress(mDefaultProgress); 129 } 130 if (mShouldBlink) { 131 View v = view.itemView; 132 v.post(() -> { 133 if (v.getBackground() != null) { 134 final int centerX = v.getWidth() / 2; 135 final int centerY = v.getHeight() / 2; 136 v.getBackground().setHotspot(centerX, centerY); 137 } 138 v.setPressed(true); 139 v.setPressed(false); 140 mShouldBlink = false; 141 }); 142 } 143 mSeekBar.setAccessibilityDelegate(new View.AccessibilityDelegate() { 144 @Override 145 public void onInitializeAccessibilityNodeInfo(View view, AccessibilityNodeInfo info) { 146 super.onInitializeAccessibilityNodeInfo(view, info); 147 // Update the range info with the correct type 148 AccessibilityNodeInfo.RangeInfo rangeInfo = info.getRangeInfo(); 149 if (rangeInfo != null) { 150 info.setRangeInfo(AccessibilityNodeInfo.RangeInfo.obtain( 151 mAccessibilityRangeInfoType, rangeInfo.getMin(), 152 rangeInfo.getMax(), rangeInfo.getCurrent())); 153 } 154 } 155 }); 156 } 157 158 @Override getSummary()159 public CharSequence getSummary() { 160 return null; 161 } 162 163 @Override onSetInitialValue(boolean restoreValue, Object defaultValue)164 protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { 165 setProgress(restoreValue ? getPersistedInt(mProgress) 166 : (Integer) defaultValue); 167 } 168 169 @Override onGetDefaultValue(TypedArray a, int index)170 protected Object onGetDefaultValue(TypedArray a, int index) { 171 return a.getInt(index, 0); 172 } 173 174 @Override onKey(View v, int keyCode, KeyEvent event)175 public boolean onKey(View v, int keyCode, KeyEvent event) { 176 if (event.getAction() != KeyEvent.ACTION_DOWN) { 177 return false; 178 } 179 180 SeekBar seekBar = (SeekBar) v.findViewById(com.android.internal.R.id.seekbar); 181 if (seekBar == null) { 182 return false; 183 } 184 return seekBar.onKeyDown(keyCode, event); 185 } 186 setMax(int max)187 public void setMax(int max) { 188 if (max != mMax) { 189 mMax = max; 190 notifyChanged(); 191 } 192 } 193 setMin(int min)194 public void setMin(int min) { 195 if (min != mMin) { 196 mMin = min; 197 notifyChanged(); 198 } 199 } 200 getMax()201 public int getMax() { 202 return mMax; 203 } 204 getMin()205 public int getMin() { 206 return mMin; 207 } 208 setProgress(int progress)209 public void setProgress(int progress) { 210 setProgress(progress, true); 211 } 212 213 /** 214 * Sets the progress point to draw a single tick mark representing a default value. 215 */ setDefaultProgress(int defaultProgress)216 public void setDefaultProgress(int defaultProgress) { 217 if (mDefaultProgress != defaultProgress) { 218 mDefaultProgress = defaultProgress; 219 if (mSeekBar instanceof DefaultIndicatorSeekBar) { 220 ((DefaultIndicatorSeekBar) mSeekBar).setDefaultProgress(mDefaultProgress); 221 } 222 } 223 } 224 225 /** 226 * When {@code continuousUpdates} is true, update the persisted setting immediately as the thumb 227 * is dragged along the SeekBar. Otherwise, only update the value of the setting when the thumb 228 * is dropped. 229 */ setContinuousUpdates(boolean continuousUpdates)230 public void setContinuousUpdates(boolean continuousUpdates) { 231 mContinuousUpdates = continuousUpdates; 232 } 233 setProgress(int progress, boolean notifyChanged)234 private void setProgress(int progress, boolean notifyChanged) { 235 if (progress > mMax) { 236 progress = mMax; 237 } 238 if (progress < mMin) { 239 progress = mMin; 240 } 241 if (progress != mProgress) { 242 mProgress = progress; 243 persistInt(progress); 244 if (notifyChanged) { 245 notifyChanged(); 246 } 247 } 248 } 249 getProgress()250 public int getProgress() { 251 return mProgress; 252 } 253 254 /** 255 * Persist the seekBar's progress value if callChangeListener 256 * returns true, otherwise set the seekBar's progress to the stored value 257 */ syncProgress(SeekBar seekBar)258 void syncProgress(SeekBar seekBar) { 259 int progress = seekBar.getProgress(); 260 if (progress != mProgress) { 261 if (callChangeListener(progress)) { 262 setProgress(progress, false); 263 } else { 264 seekBar.setProgress(mProgress); 265 } 266 } 267 } 268 269 @Override onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)270 public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { 271 if (fromUser && (mContinuousUpdates || !mTrackingTouch)) { 272 syncProgress(seekBar); 273 } 274 } 275 276 @Override onStartTrackingTouch(SeekBar seekBar)277 public void onStartTrackingTouch(SeekBar seekBar) { 278 mTrackingTouch = true; 279 } 280 281 @Override onStopTrackingTouch(SeekBar seekBar)282 public void onStopTrackingTouch(SeekBar seekBar) { 283 mTrackingTouch = false; 284 if (seekBar.getProgress() != mProgress) { 285 syncProgress(seekBar); 286 } 287 } 288 289 /** 290 * Specify the type of range this seek bar represents. 291 * 292 * @param rangeInfoType The type of range to be shared with accessibility 293 * 294 * @see android.view.accessibility.AccessibilityNodeInfo.RangeInfo 295 */ setAccessibilityRangeInfoType(int rangeInfoType)296 public void setAccessibilityRangeInfoType(int rangeInfoType) { 297 mAccessibilityRangeInfoType = rangeInfoType; 298 } 299 setSeekBarContentDescription(CharSequence contentDescription)300 public void setSeekBarContentDescription(CharSequence contentDescription) { 301 mSeekBarContentDescription = contentDescription; 302 if (mSeekBar != null) { 303 mSeekBar.setContentDescription(contentDescription); 304 } 305 } 306 307 @Override onSaveInstanceState()308 protected Parcelable onSaveInstanceState() { 309 /* 310 * Suppose a client uses this preference type without persisting. We 311 * must save the instance state so it is able to, for example, survive 312 * orientation changes. 313 */ 314 315 final Parcelable superState = super.onSaveInstanceState(); 316 if (isPersistent()) { 317 // No need to save instance state since it's persistent 318 return superState; 319 } 320 321 // Save the instance state 322 final SavedState myState = new SavedState(superState); 323 myState.progress = mProgress; 324 myState.max = mMax; 325 myState.min = mMin; 326 return myState; 327 } 328 329 @Override onRestoreInstanceState(Parcelable state)330 protected void onRestoreInstanceState(Parcelable state) { 331 if (!state.getClass().equals(SavedState.class)) { 332 // Didn't save state for us in onSaveInstanceState 333 super.onRestoreInstanceState(state); 334 return; 335 } 336 337 // Restore the instance state 338 SavedState myState = (SavedState) state; 339 super.onRestoreInstanceState(myState.getSuperState()); 340 mProgress = myState.progress; 341 mMax = myState.max; 342 mMin = myState.min; 343 notifyChanged(); 344 } 345 346 /** 347 * SavedState, a subclass of {@link BaseSavedState}, will store the state 348 * of MyPreference, a subclass of Preference. 349 * <p> 350 * It is important to always call through to super methods. 351 */ 352 private static class SavedState extends BaseSavedState { 353 int progress; 354 int max; 355 int min; 356 SavedState(Parcel source)357 public SavedState(Parcel source) { 358 super(source); 359 360 // Restore the click counter 361 progress = source.readInt(); 362 max = source.readInt(); 363 min = source.readInt(); 364 } 365 366 @Override writeToParcel(Parcel dest, int flags)367 public void writeToParcel(Parcel dest, int flags) { 368 super.writeToParcel(dest, flags); 369 370 // Save the click counter 371 dest.writeInt(progress); 372 dest.writeInt(max); 373 dest.writeInt(min); 374 } 375 SavedState(Parcelable superState)376 public SavedState(Parcelable superState) { 377 super(superState); 378 } 379 380 @SuppressWarnings("unused") 381 public static final Parcelable.Creator<SavedState> CREATOR = 382 new Parcelable.Creator<SavedState>() { 383 public SavedState createFromParcel(Parcel in) { 384 return new SavedState(in); 385 } 386 387 public SavedState[] newArray(int size) { 388 return new SavedState[size]; 389 } 390 }; 391 } 392 } 393