• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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