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