diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index a65bf41210a1b..6c691f81d9a67 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -73,6 +73,7 @@ java_defaults {
"SettingsLibCollapsingToolbarBaseActivity",
"SettingsLibTwoTargetPreference",
"SettingsLibSettingsTransition",
+ "SettingsLibSeekBarPreference",
],
}
diff --git a/packages/SettingsLib/SeekBarPreference/Android.bp b/packages/SettingsLib/SeekBarPreference/Android.bp
new file mode 100644
index 0000000000000..98f5d2aaf6320
--- /dev/null
+++ b/packages/SettingsLib/SeekBarPreference/Android.bp
@@ -0,0 +1,14 @@
+android_library {
+ name: "SettingsLibSeekBarPreference",
+
+ srcs: ["src/**/*.java"],
+ resource_dirs: ["res"],
+
+ static_libs: [
+ "androidx.annotation_annotation",
+ "androidx.preference_preference",
+ "SettingsLibSettingsTheme",
+ ],
+
+ min_sdk_version: "28",
+}
diff --git a/packages/SettingsLib/SeekBarPreference/AndroidManifest.xml b/packages/SettingsLib/SeekBarPreference/AndroidManifest.xml
new file mode 100644
index 0000000000000..73163fca53628
--- /dev/null
+++ b/packages/SettingsLib/SeekBarPreference/AndroidManifest.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
diff --git a/packages/SettingsLib/SeekBarPreference/lint-baseline.xml b/packages/SettingsLib/SeekBarPreference/lint-baseline.xml
new file mode 100644
index 0000000000000..934b27b72a42d
--- /dev/null
+++ b/packages/SettingsLib/SeekBarPreference/lint-baseline.xml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
diff --git a/packages/SettingsLib/SeekBarPreference/res/layout/preference_labeled_slider.xml b/packages/SettingsLib/SeekBarPreference/res/layout/preference_labeled_slider.xml
new file mode 100644
index 0000000000000..d9d42bf9ce4e7
--- /dev/null
+++ b/packages/SettingsLib/SeekBarPreference/res/layout/preference_labeled_slider.xml
@@ -0,0 +1,79 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SettingsLib/SeekBarPreference/res/layout/preference_widget_slider.xml b/packages/SettingsLib/SeekBarPreference/res/layout/preference_widget_slider.xml
new file mode 100644
index 0000000000000..f0510a2be29de
--- /dev/null
+++ b/packages/SettingsLib/SeekBarPreference/res/layout/preference_widget_slider.xml
@@ -0,0 +1,68 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SettingsLib/SeekBarPreference/res/values/attrs.xml b/packages/SettingsLib/SeekBarPreference/res/values/attrs.xml
new file mode 100644
index 0000000000000..1a24a4e84aa2f
--- /dev/null
+++ b/packages/SettingsLib/SeekBarPreference/res/values/attrs.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/packages/SettingsLib/SeekBarPreference/res/values/strings.xml b/packages/SettingsLib/SeekBarPreference/res/values/strings.xml
new file mode 100644
index 0000000000000..8c398446c37b7
--- /dev/null
+++ b/packages/SettingsLib/SeekBarPreference/res/values/strings.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/packages/SettingsLib/SeekBarPreference/src/com/android/settingslib/widget/LabeledSeekBarPreference.java b/packages/SettingsLib/SeekBarPreference/src/com/android/settingslib/widget/LabeledSeekBarPreference.java
new file mode 100644
index 0000000000000..5d5ebe8ea167f
--- /dev/null
+++ b/packages/SettingsLib/SeekBarPreference/src/com/android/settingslib/widget/LabeledSeekBarPreference.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 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.settingslib.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+import android.widget.TextView;
+
+import androidx.preference.PreferenceViewHolder;
+
+/** A slider preference with left and right labels **/
+public class LabeledSeekBarPreference extends SeekBarPreference {
+
+ private final int mTextStartId;
+ private final int mTextEndId;
+ private final int mTickMarkId;
+ private OnPreferenceChangeListener mStopListener;
+
+ public LabeledSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+
+ super(context, attrs, defStyleAttr, defStyleRes);
+ setLayoutResource(R.layout.preference_labeled_slider);
+
+ final TypedArray styledAttrs = context.obtainStyledAttributes(attrs,
+ R.styleable.LabeledSeekBarPreference);
+ mTextStartId = styledAttrs.getResourceId(
+ R.styleable.LabeledSeekBarPreference_textStart,
+ R.string.summary_placeholder);
+ mTextEndId = styledAttrs.getResourceId(
+ R.styleable.LabeledSeekBarPreference_textEnd,
+ R.string.summary_placeholder);
+ mTickMarkId = styledAttrs.getResourceId(
+ R.styleable.LabeledSeekBarPreference_tickMark, /* defValue= */ 0);
+ styledAttrs.recycle();
+ }
+
+ public LabeledSeekBarPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, com.android.internal.R.attr.seekBarPreferenceStyle, 0);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+
+ final TextView startText = (TextView) holder.findViewById(android.R.id.text1);
+ final TextView endText = (TextView) holder.findViewById(android.R.id.text2);
+ startText.setText(mTextStartId);
+ endText.setText(mTextEndId);
+
+ if (mTickMarkId != 0) {
+ final Drawable tickMark = getContext().getDrawable(mTickMarkId);
+ final SeekBar seekBar = (SeekBar) holder.findViewById(
+ com.android.internal.R.id.seekbar);
+ seekBar.setTickMark(tickMark);
+ }
+ }
+
+ public void setOnPreferenceChangeStopListener(OnPreferenceChangeListener listener) {
+ mStopListener = listener;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ super.onStopTrackingTouch(seekBar);
+
+ if (mStopListener != null) {
+ mStopListener.onPreferenceChange(this, seekBar.getProgress());
+ }
+ }
+}
+
diff --git a/packages/SettingsLib/SeekBarPreference/src/com/android/settingslib/widget/SeekBarPreference.java b/packages/SettingsLib/SeekBarPreference/src/com/android/settingslib/widget/SeekBarPreference.java
new file mode 100644
index 0000000000000..6f32618d5266a
--- /dev/null
+++ b/packages/SettingsLib/SeekBarPreference/src/com/android/settingslib/widget/SeekBarPreference.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2021 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.settingslib.widget;
+
+import static android.view.HapticFeedbackConstants.CLOCK_TICK;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.SeekBar;
+import android.widget.SeekBar.OnSeekBarChangeListener;
+
+import androidx.annotation.RequiresApi;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+/**
+ * Based on android.preference.SeekBarPreference, but uses support preference as base.
+ */
+public class SeekBarPreference extends Preference
+ implements OnSeekBarChangeListener, View.OnKeyListener {
+
+ public static final int HAPTIC_FEEDBACK_MODE_NONE = 0;
+ public static final int HAPTIC_FEEDBACK_MODE_ON_TICKS = 1;
+ public static final int HAPTIC_FEEDBACK_MODE_ON_ENDS = 2;
+ protected int mDefaultProgress = -1;
+ protected SeekBar mSeekBar;
+ private int mProgress;
+ private int mMax;
+ private int mMin;
+ private boolean mTrackingTouch;
+ private boolean mContinuousUpdates;
+ private int mHapticFeedbackMode = HAPTIC_FEEDBACK_MODE_NONE;
+ private boolean mShouldBlink;
+ private int mAccessibilityRangeInfoType = AccessibilityNodeInfo.RangeInfo.RANGE_TYPE_INT;
+ private CharSequence mSeekBarContentDescription;
+ private CharSequence mSeekBarStateDescription;
+
+ public SeekBarPreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ TypedArray a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.ProgressBar, defStyleAttr, defStyleRes);
+ setMax(a.getInt(com.android.internal.R.styleable.ProgressBar_max, mMax));
+ setMin(a.getInt(com.android.internal.R.styleable.ProgressBar_min, mMin));
+ a.recycle();
+
+ a = context.obtainStyledAttributes(
+ attrs, com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes);
+ final boolean isSelectable = a.getBoolean(
+ R.styleable.Preference_android_selectable, false);
+ setSelectable(isSelectable);
+ a.recycle();
+
+ setLayoutResource(R.layout.preference_widget_slider);
+ }
+
+ public SeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public SeekBarPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, androidx.preference.R.attr.seekBarPreferenceStyle);
+ }
+
+ public SeekBarPreference(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder view) {
+ super.onBindViewHolder(view);
+ view.itemView.setOnKeyListener(this);
+ mSeekBar = (SeekBar) view.findViewById(com.android.internal.R.id.seekbar);
+
+ if (mSeekBar == null) {
+ return;
+ }
+
+ mSeekBar.setOnSeekBarChangeListener(this);
+ mSeekBar.setMax(mMax);
+ mSeekBar.setMin(mMin);
+ mSeekBar.setProgress(mProgress);
+ mSeekBar.setEnabled(isEnabled());
+ final CharSequence title = getTitle();
+ if (!TextUtils.isEmpty(mSeekBarContentDescription)) {
+ mSeekBar.setContentDescription(mSeekBarContentDescription);
+ } else if (!TextUtils.isEmpty(title)) {
+ mSeekBar.setContentDescription(title);
+ }
+ if (!TextUtils.isEmpty(mSeekBarStateDescription) && (Build.VERSION.SDK_INT
+ >= Build.VERSION_CODES.R)) {
+ mSeekBar.setStateDescription(mSeekBarStateDescription);
+ }
+ if (mShouldBlink) {
+ View v = view.itemView;
+ v.post(() -> {
+ if (v.getBackground() != null) {
+ final int centerX = v.getWidth() / 2;
+ final int centerY = v.getHeight() / 2;
+ v.getBackground().setHotspot(centerX, centerY);
+ }
+ v.setPressed(true);
+ v.setPressed(false);
+ mShouldBlink = false;
+ });
+ }
+ mSeekBar.setAccessibilityDelegate(new View.AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View view, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(view, info);
+ // Update the range info with the correct type
+ final AccessibilityNodeInfo.RangeInfo rangeInfo = info.getRangeInfo();
+ if (rangeInfo != null) {
+ info.setRangeInfo(AccessibilityNodeInfo.RangeInfo.obtain(
+ mAccessibilityRangeInfoType, rangeInfo.getMin(),
+ rangeInfo.getMax(), rangeInfo.getCurrent()));
+ }
+ }
+ });
+ }
+
+ @Override
+ public CharSequence getSummary() {
+ return null;
+ }
+
+ @Override
+ protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+ setProgress(restoreValue ? getPersistedInt(mProgress)
+ : (Integer) defaultValue);
+ }
+
+ @Override
+ protected Object onGetDefaultValue(TypedArray a, int index) {
+ return a.getInt(index, 0);
+ }
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() != KeyEvent.ACTION_DOWN) {
+ return false;
+ }
+
+ final SeekBar seekBar = v.findViewById(com.android.internal.R.id.seekbar);
+ if (seekBar == null) {
+ return false;
+ }
+ return seekBar.onKeyDown(keyCode, event);
+ }
+
+ public int getMax() {
+ return mMax;
+ }
+
+ /**
+ * Sets the upper bound on the {@link SeekBar}.
+ *
+ * @param max The upper bound to set
+ */
+ public void setMax(int max) {
+ if (max != mMax) {
+ mMax = max;
+ notifyChanged();
+ }
+ }
+
+ public int getMin() {
+ return mMin;
+ }
+
+ /**
+ * Sets the lower bound on the {@link SeekBar}.
+ *
+ * @param min The lower bound to set
+ */
+ public void setMin(int min) {
+ if (min != mMin) {
+ mMin = min;
+ notifyChanged();
+ }
+ }
+
+ /**
+ * Sets the progress point to draw a single tick mark representing a default value.
+ */
+ public void setDefaultProgress(int defaultProgress) {
+ if (mDefaultProgress != defaultProgress) {
+ mDefaultProgress = defaultProgress;
+ }
+ }
+
+ /**
+ * When {@code continuousUpdates} is true, update the persisted setting immediately as the thumb
+ * is dragged along the SeekBar. Otherwise, only update the value of the setting when the thumb
+ * is dropped.
+ */
+ public void setContinuousUpdates(boolean continuousUpdates) {
+ mContinuousUpdates = continuousUpdates;
+ }
+
+ /**
+ * Sets the haptic feedback mode. HAPTIC_FEEDBACK_MODE_ON_TICKS means to perform haptic feedback
+ * as the SeekBar's progress is updated; HAPTIC_FEEDBACK_MODE_ON_ENDS means to perform haptic
+ * feedback as the SeekBar's progress value is equal to the min/max value.
+ *
+ * @param hapticFeedbackMode the haptic feedback mode.
+ */
+ public void setHapticFeedbackMode(int hapticFeedbackMode) {
+ mHapticFeedbackMode = hapticFeedbackMode;
+ }
+
+ private void setProgress(int progress, boolean notifyChanged) {
+ if (progress > mMax) {
+ progress = mMax;
+ }
+ if (progress < mMin) {
+ progress = mMin;
+ }
+ if (progress != mProgress) {
+ mProgress = progress;
+ persistInt(progress);
+ if (notifyChanged) {
+ notifyChanged();
+ }
+ }
+ }
+
+ public int getProgress() {
+ return mProgress;
+ }
+
+ /**
+ * Sets the current progress of the {@link SeekBar}.
+ *
+ * @param progress The current progress of the {@link SeekBar}
+ */
+ public void setProgress(int progress) {
+ setProgress(progress, true);
+ }
+
+ /**
+ * Persist the seekBar's progress value if callChangeListener
+ * returns true, otherwise set the seekBar's progress to the stored value
+ */
+ void syncProgress(SeekBar seekBar) {
+ int progress = seekBar.getProgress();
+ if (progress != mProgress) {
+ if (callChangeListener(progress)) {
+ setProgress(progress, false);
+ switch (mHapticFeedbackMode) {
+ case HAPTIC_FEEDBACK_MODE_ON_TICKS:
+ seekBar.performHapticFeedback(CLOCK_TICK);
+ break;
+ case HAPTIC_FEEDBACK_MODE_ON_ENDS:
+ if (progress == mMax || progress == mMin) {
+ seekBar.performHapticFeedback(CLOCK_TICK);
+ }
+ break;
+ }
+ } else {
+ seekBar.setProgress(mProgress);
+ }
+ }
+ }
+
+ @Override
+ public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
+ if (fromUser && (mContinuousUpdates || !mTrackingTouch)) {
+ syncProgress(seekBar);
+ }
+ }
+
+ @Override
+ public void onStartTrackingTouch(SeekBar seekBar) {
+ mTrackingTouch = true;
+ }
+
+ @Override
+ public void onStopTrackingTouch(SeekBar seekBar) {
+ mTrackingTouch = false;
+ if (seekBar.getProgress() != mProgress) {
+ syncProgress(seekBar);
+ }
+ }
+
+ /**
+ * Specify the type of range this seek bar represents.
+ *
+ * @param rangeInfoType The type of range to be shared with accessibility
+ * @see android.view.accessibility.AccessibilityNodeInfo.RangeInfo
+ */
+ public void setAccessibilityRangeInfoType(int rangeInfoType) {
+ mAccessibilityRangeInfoType = rangeInfoType;
+ }
+
+ /**
+ * Specify the content description for this seek bar represents.
+ *
+ * @param contentDescription the content description of seek bar
+ */
+ public void setSeekBarContentDescription(CharSequence contentDescription) {
+ mSeekBarContentDescription = contentDescription;
+ if (mSeekBar != null) {
+ mSeekBar.setContentDescription(contentDescription);
+ }
+ }
+
+ /**
+ * Specify the state description for this seek bar represents.
+ *
+ * @param stateDescription the state description of seek bar
+ */
+ @RequiresApi(Build.VERSION_CODES.R)
+ public void setSeekBarStateDescription(CharSequence stateDescription) {
+ mSeekBarStateDescription = stateDescription;
+ if (mSeekBar != null) {
+ mSeekBar.setStateDescription(stateDescription);
+ }
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ /*
+ * Suppose a client uses this preference type without persisting. We
+ * must save the instance state so it is able to, for example, survive
+ * orientation changes.
+ */
+
+ final Parcelable superState = super.onSaveInstanceState();
+ if (isPersistent()) {
+ // No need to save instance state since it's persistent
+ return superState;
+ }
+
+ // Save the instance state
+ final SavedState myState = new SavedState(superState);
+ myState.mProgress = mProgress;
+ myState.mMax = mMax;
+ myState.mMin = mMin;
+ return myState;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (!state.getClass().equals(SavedState.class)) {
+ // Didn't save state for us in onSaveInstanceState
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
+ // Restore the instance state
+ SavedState myState = (SavedState) state;
+ super.onRestoreInstanceState(myState.getSuperState());
+ mProgress = myState.mProgress;
+ mMax = myState.mMax;
+ mMin = myState.mMin;
+ notifyChanged();
+ }
+
+ /**
+ * SavedState, a subclass of {@link BaseSavedState}, will store the state
+ * of MyPreference, a subclass of Preference.
+ *
+ * It is important to always call through to super methods.
+ */
+ private static class SavedState extends BaseSavedState {
+ @SuppressWarnings("unused")
+ public static final Parcelable.Creator CREATOR =
+ new Parcelable.Creator() {
+ public SavedState createFromParcel(Parcel in) {
+ return new SavedState(in);
+ }
+
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ };
+ int mProgress;
+ int mMax;
+ int mMin;
+
+ SavedState(Parcel source) {
+ super(source);
+
+ // Restore the click counter
+ mProgress = source.readInt();
+ mMax = source.readInt();
+ mMin = source.readInt();
+ }
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+
+ // Save the click counter
+ dest.writeInt(mProgress);
+ dest.writeInt(mMax);
+ dest.writeInt(mMin);
+ }
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/Restrictable.java b/packages/SettingsLib/src/com/android/settingslib/Restrictable.java
new file mode 100644
index 0000000000000..dfe84233fe839
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/Restrictable.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2021 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.settingslib;
+import android.os.UserHandle;
+/**
+ * A collection of API making a Preference "restrictable"
+ */
+public interface Restrictable {
+ /**
+ * Returns RestrictedPreferenceHelper
+ * @return
+ */
+ RestrictedPreferenceHelper getHelper();
+ /**
+ * call preference notifyChanged()
+ */
+ void notifyPreferenceChanged();
+ /**
+ * Set if show restriction message in Preference summary or not.
+ * @param useSummary.
+ */
+ default void useAdminDisabledSummary(boolean useSummary) {
+ getHelper().useAdminDisabledSummary(useSummary);
+ }
+ /**
+ * Set the user restriction and disable this preference.
+ *
+ * @param userRestriction constant from {@link android.os.UserManager}
+ */
+ default void checkRestrictionAndSetDisabled(String userRestriction) {
+ getHelper().checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId());
+ }
+ /**
+ * Set the user restriction and disable this preference for the given user.
+ *
+ * @param userRestriction constant from {@link android.os.UserManager}
+ * @param userId user to check the restriction for.
+ */
+ default void checkRestrictionAndSetDisabled(String userRestriction, int userId) {
+ getHelper().checkRestrictionAndSetDisabled(userRestriction, userId);
+ }
+ /**
+ * Disable preference based on the enforce admin.
+ *
+ * @param admin details of the admin who enforced the restriction. If it is {@code null}, then
+ * this preference will be enabled. Otherwise, it will be disabled.
+ */
+ default void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) {
+ if (getHelper().setDisabledByAdmin(admin)) {
+ notifyPreferenceChanged();
+ }
+ }
+ /**
+ * Check whether this preference is disabled by admin.
+ *
+ * @return true if this preference is disabled by admin.
+ */
+ default boolean isDisabledByAdmin() {
+ return getHelper().isDisabledByAdmin();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index fc8b5879c5fa1..bd0f33bcb56af 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -16,10 +16,7 @@
package com.android.settingslib;
-import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
import android.content.Context;
-import android.os.UserHandle;
import android.util.AttributeSet;
import android.view.View;
@@ -33,8 +30,8 @@ import com.android.settingslib.widget.TwoTargetPreference;
* Preference class that supports being disabled by a user restriction
* set by a device admin.
*/
-public class RestrictedPreference extends TwoTargetPreference {
- RestrictedPreferenceHelper mHelper;
+public class RestrictedPreference extends TwoTargetPreference implements Restrictable {
+ private RestrictedPreferenceHelper mHelper;
public RestrictedPreference(Context context, AttributeSet attrs,
int defStyleAttr, int defStyleRes) {
@@ -82,8 +79,14 @@ public class RestrictedPreference extends TwoTargetPreference {
}
}
- public void useAdminDisabledSummary(boolean useSummary) {
- mHelper.useAdminDisabledSummary(useSummary);
+ @Override
+ public RestrictedPreferenceHelper getHelper() {
+ return mHelper;
+ }
+
+ @Override
+ public void notifyPreferenceChanged() {
+ notifyChanged();
}
@Override
@@ -92,14 +95,6 @@ public class RestrictedPreference extends TwoTargetPreference {
super.onAttachedToHierarchy(preferenceManager);
}
- public void checkRestrictionAndSetDisabled(String userRestriction) {
- mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId());
- }
-
- public void checkRestrictionAndSetDisabled(String userRestriction, int userId) {
- mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
- }
-
@Override
public void setEnabled(boolean enabled) {
if (enabled && isDisabledByAdmin()) {
@@ -108,14 +103,4 @@ public class RestrictedPreference extends TwoTargetPreference {
}
super.setEnabled(enabled);
}
-
- public void setDisabledByAdmin(EnforcedAdmin admin) {
- if (mHelper.setDisabledByAdmin(admin)) {
- notifyChanged();
- }
- }
-
- public boolean isDisabledByAdmin() {
- return mHelper.isDisabledByAdmin();
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSeekBarPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSeekBarPreference.java
new file mode 100644
index 0000000000000..486dfef9f7381
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSeekBarPreference.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2021 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.settingslib;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+
+import androidx.preference.PreferenceManager;
+import androidx.preference.PreferenceViewHolder;
+
+import com.android.settingslib.widget.DefaultIndicatorSeekBar;
+import com.android.settingslib.widget.SeekBarPreference;
+
+/**
+ * Based on android.preference.SeekBarPreference, but uses support preference as base.
+ */
+public class RestrictedSeekBarPreference extends SeekBarPreference implements Restrictable {
+
+ private CharSequence mSeekBarStateDescription;
+ private RestrictedPreferenceHelper mHelper;
+
+ public RestrictedSeekBarPreference(
+ Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+
+ mHelper = new RestrictedPreferenceHelper(context, this, attrs);
+
+ final int secondTargetResId = getSecondTargetResId();
+ if (secondTargetResId != 0) {
+ setWidgetLayoutResource(secondTargetResId);
+ }
+ }
+
+ public RestrictedSeekBarPreference(Context context, AttributeSet attrs, int defStyleAttr) {
+ this(context, attrs, defStyleAttr, 0);
+ }
+
+ public RestrictedSeekBarPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, androidx.preference.R.attr.seekBarPreferenceStyle);
+ }
+
+ public RestrictedSeekBarPreference(Context context) {
+ this(context, null);
+ }
+
+ @Override
+ public boolean isSelectable() {
+ if (isDisabledByAdmin()) {
+ return true;
+ } else {
+ return super.isSelectable();
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ if (mSeekBar instanceof DefaultIndicatorSeekBar) {
+ ((DefaultIndicatorSeekBar) mSeekBar).setDefaultProgress(mDefaultProgress);
+ }
+
+ mHelper.onBindViewHolder(holder);
+ final View restrictedIcon = holder.findViewById(R.id.restricted_icon);
+ if (restrictedIcon != null) {
+ restrictedIcon.setVisibility(isDisabledByAdmin() ? View.VISIBLE : View.GONE);
+ }
+
+ final View widgetFrame = holder.findViewById(android.R.id.widget_frame);
+ if (widgetFrame != null) {
+ widgetFrame.setVisibility(shouldHideSecondTarget() ? View.GONE : View.VISIBLE);
+ }
+ }
+
+ @Override
+ public void performClick() {
+ if (!mHelper.performClick()) {
+ super.performClick();
+ }
+ }
+
+ @Override
+ public void setEnabled(boolean enabled) {
+ if (enabled && isDisabledByAdmin()) {
+ mHelper.setDisabledByAdmin(null);
+ return;
+ }
+ super.setEnabled(enabled);
+ }
+
+ /**
+ * Sets the progress point to draw a single tick mark representing a default value.
+ */
+ public void setDefaultProgress(int defaultProgress) {
+ if (mDefaultProgress != defaultProgress) {
+ mDefaultProgress = defaultProgress;
+ if (mSeekBar instanceof DefaultIndicatorSeekBar) {
+ ((DefaultIndicatorSeekBar) mSeekBar).setDefaultProgress(mDefaultProgress);
+ }
+ }
+ }
+
+ @Override
+ public RestrictedPreferenceHelper getHelper() {
+ return mHelper;
+ }
+
+ @Override
+ public void notifyPreferenceChanged() {
+ notifyChanged();
+ }
+
+ @Override
+ protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
+ mHelper.onAttachedToHierarchy();
+ super.onAttachedToHierarchy(preferenceManager);
+ }
+
+ protected int getSecondTargetResId() {
+ return R.layout.restricted_icon;
+ }
+
+ protected boolean shouldHideSecondTarget() {
+ return !isDisabledByAdmin();
+ }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 5c05a1bd67226..cdbeda8125914 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -16,11 +16,8 @@
package com.android.settingslib;
-import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
import android.content.Context;
import android.content.res.TypedArray;
-import android.os.UserHandle;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.View;
@@ -37,10 +34,10 @@ import androidx.preference.SwitchPreference;
* Version of SwitchPreference that can be disabled by a device admin
* using a user restriction.
*/
-public class RestrictedSwitchPreference extends SwitchPreference {
- RestrictedPreferenceHelper mHelper;
- boolean mUseAdditionalSummary = false;
- CharSequence mRestrictedSwitchSummary;
+public class RestrictedSwitchPreference extends SwitchPreference implements Restrictable {
+ private RestrictedPreferenceHelper mHelper;
+ private boolean mUseAdditionalSummary = false;
+ private CharSequence mRestrictedSwitchSummary;
private int mIconSize;
public RestrictedSwitchPreference(Context context, AttributeSet attrs,
@@ -153,8 +150,14 @@ public class RestrictedSwitchPreference extends SwitchPreference {
}
}
- public void useAdminDisabledSummary(boolean useSummary) {
- mHelper.useAdminDisabledSummary(useSummary);
+ @Override
+ public RestrictedPreferenceHelper getHelper() {
+ return mHelper;
+ }
+
+ @Override
+ public void notifyPreferenceChanged() {
+ notifyChanged();
}
@Override
@@ -163,14 +166,6 @@ public class RestrictedSwitchPreference extends SwitchPreference {
super.onAttachedToHierarchy(preferenceManager);
}
- public void checkRestrictionAndSetDisabled(String userRestriction) {
- mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId());
- }
-
- public void checkRestrictionAndSetDisabled(String userRestriction, int userId) {
- mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
- }
-
@Override
public void setEnabled(boolean enabled) {
if (enabled && isDisabledByAdmin()) {
@@ -179,14 +174,4 @@ public class RestrictedSwitchPreference extends SwitchPreference {
}
super.setEnabled(enabled);
}
-
- public void setDisabledByAdmin(EnforcedAdmin admin) {
- if (mHelper.setDisabledByAdmin(admin)) {
- notifyChanged();
- }
- }
-
- public boolean isDisabledByAdmin() {
- return mHelper.isDisabledByAdmin();
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java
index 0096015aa875f..4da0e5e020d13 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedTopLevelPreference.java
@@ -16,10 +16,7 @@
package com.android.settingslib;
-import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
import android.content.Context;
-import android.os.UserHandle;
import android.util.AttributeSet;
import androidx.core.content.res.TypedArrayUtils;
@@ -28,7 +25,7 @@ import androidx.preference.PreferenceManager;
import androidx.preference.PreferenceViewHolder;
/** Top level preference that can be disabled by a device admin using a user restriction. */
-public class RestrictedTopLevelPreference extends Preference {
+public class RestrictedTopLevelPreference extends Preference implements Restrictable {
private RestrictedPreferenceHelper mHelper;
public RestrictedTopLevelPreference(Context context, AttributeSet attrs,
@@ -69,23 +66,14 @@ public class RestrictedTopLevelPreference extends Preference {
super.onAttachedToHierarchy(preferenceManager);
}
- /**
- * Set the user restriction and disable this preference.
- *
- * @param userRestriction constant from {@link android.os.UserManager}
- */
- public void checkRestrictionAndSetDisabled(String userRestriction) {
- mHelper.checkRestrictionAndSetDisabled(userRestriction, UserHandle.myUserId());
+ @Override
+ public RestrictedPreferenceHelper getHelper() {
+ return mHelper;
}
- /**
- * Set the user restriction and disable this preference for the given user.
- *
- * @param userRestriction constant from {@link android.os.UserManager}
- * @param userId user to check the restriction for.
- */
- public void checkRestrictionAndSetDisabled(String userRestriction, int userId) {
- mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
+ @Override
+ public void notifyPreferenceChanged() {
+ notifyChanged();
}
@Override
@@ -96,25 +84,4 @@ public class RestrictedTopLevelPreference extends Preference {
}
super.setEnabled(enabled);
}
-
- /**
- * Check whether this preference is disabled by admin.
- *
- * @return true if this preference is disabled by admin.
- */
- public boolean isDisabledByAdmin() {
- return mHelper.isDisabledByAdmin();
- }
-
- /**
- * Disable preference based on the enforce admin.
- *
- * @param admin details of the admin who enforced the restriction. If it is {@code null}, then
- * this preference will be enabled. Otherwise, it will be disabled.
- */
- public void setDisabledByAdmin(EnforcedAdmin admin) {
- if (mHelper.setDisabledByAdmin(admin)) {
- notifyChanged();
- }
- }
}
diff --git a/packages/SettingsLib/src/com/android/settingslib/widget/DefaultIndicatorSeekBar.java b/packages/SettingsLib/src/com/android/settingslib/widget/DefaultIndicatorSeekBar.java
new file mode 100644
index 0000000000000..7a639784de2d2
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/widget/DefaultIndicatorSeekBar.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 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.settingslib.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.widget.SeekBar;
+
+/**
+ * Customized default indicator seekbar
+ */
+public class DefaultIndicatorSeekBar extends SeekBar {
+
+ private int mDefaultProgress = -1;
+
+ public DefaultIndicatorSeekBar(Context context) {
+ super(context);
+ }
+
+ public DefaultIndicatorSeekBar(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public DefaultIndicatorSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public DefaultIndicatorSeekBar(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ /**
+ * N.B. Only draws the default indicator tick mark, NOT equally spaced tick marks.
+ */
+ @Override
+ protected void drawTickMarks(Canvas canvas) {
+ if (isEnabled() && mDefaultProgress <= getMax() && mDefaultProgress >= getMin()) {
+ final Drawable defaultIndicator = getTickMark();
+
+ // Adjust the drawable's bounds to center it at the point where it's drawn.
+ final int w = defaultIndicator.getIntrinsicWidth();
+ final int h = defaultIndicator.getIntrinsicHeight();
+ final int halfW = w >= 0 ? w / 2 : 1;
+ final int halfH = h >= 0 ? h / 2 : 1;
+ defaultIndicator.setBounds(-halfW, -halfH, halfW, halfH);
+
+ // This mimics the computation of the thumb position, to get the true "default."
+ final int availableWidth = getWidth() - mPaddingLeft - mPaddingRight;
+ final int range = getMax() - getMin();
+ final float scale = range > 0f ? mDefaultProgress / (float) range : 0f;
+ final int offset = (int) ((scale * availableWidth) + 0.5f);
+ final int indicatorPosition = isLayoutRtl() && getMirrorForRtl()
+ ? availableWidth - offset + mPaddingRight : offset + mPaddingLeft;
+
+ final int saveCount = canvas.save();
+ canvas.translate(indicatorPosition, getHeight() / 2);
+ defaultIndicator.draw(canvas);
+ canvas.restoreToCount(saveCount);
+ }
+ }
+
+ /**
+ * N.B. This sets the default *unadjusted* progress, i.e. in the SeekBar's [0 - max] terms.
+ */
+ public void setDefaultProgress(int defaultProgress) {
+ if (mDefaultProgress != defaultProgress) {
+ mDefaultProgress = defaultProgress;
+ invalidate();
+ }
+ }
+
+ public int getDefaultProgress() {
+ return mDefaultProgress;
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/res/xml-mcc998/seekbar_preference.xml b/packages/SettingsLib/tests/robotests/res/xml-mcc998/seekbar_preference.xml
new file mode 100644
index 0000000000000..ad5775536bf4e
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/res/xml-mcc998/seekbar_preference.xml
@@ -0,0 +1,23 @@
+
+
+
+
+
+
diff --git a/packages/SettingsLib/tests/robotests/res/xml-mcc999/seekbar_preference.xml b/packages/SettingsLib/tests/robotests/res/xml-mcc999/seekbar_preference.xml
new file mode 100644
index 0000000000000..28527fc90c856
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/res/xml-mcc999/seekbar_preference.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/DefaultIndicatorSeekBarTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/DefaultIndicatorSeekBarTest.java
new file mode 100644
index 0000000000000..86625481b1889
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/DefaultIndicatorSeekBarTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2021 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.settingslib.widget;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class DefaultIndicatorSeekBarTest {
+
+ private DefaultIndicatorSeekBar mDefaultIndicatorSeekBar;
+
+ @Before
+ public void setUp() {
+ mDefaultIndicatorSeekBar = new DefaultIndicatorSeekBar(RuntimeEnvironment.application);
+ mDefaultIndicatorSeekBar.setMax(100);
+ }
+
+ @After
+ public void tearDown() {
+ mDefaultIndicatorSeekBar = null;
+ }
+
+ @Test
+ public void defaultProgress_setSucceeds() {
+ mDefaultIndicatorSeekBar.setDefaultProgress(40);
+ assertEquals(40, mDefaultIndicatorSeekBar.getDefaultProgress());
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/LabeledSeekBarPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/LabeledSeekBarPreferenceTest.java
new file mode 100644
index 0000000000000..6dd72b460f6dd
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/LabeledSeekBarPreferenceTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2021 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.settingslib.widget;
+
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.LinearLayout;
+import android.widget.SeekBar;
+
+import androidx.preference.Preference;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+
+@RunWith(RobolectricTestRunner.class)
+public class LabeledSeekBarPreferenceTest {
+
+ private Context mContext;
+ private SeekBar mSeekBar;
+ private LabeledSeekBarPreference mSeekBarPreference;
+
+ @Mock
+ private Preference.OnPreferenceChangeListener mListener;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+
+ mContext = RuntimeEnvironment.application;
+ mSeekBarPreference = new LabeledSeekBarPreference(mContext, null);
+ LayoutInflater inflater = LayoutInflater.from(mContext);
+ final View view =
+ inflater.inflate(mSeekBarPreference.getLayoutResource(),
+ new LinearLayout(mContext), false);
+ mSeekBar = view.findViewById(com.android.internal.R.id.seekbar);
+ }
+
+ @Test
+ public void seekBarPreferenceOnStopTrackingTouch_callsListener() {
+ mSeekBar.setProgress(2);
+
+ mSeekBarPreference.setOnPreferenceChangeStopListener(mListener);
+ mSeekBarPreference.onStopTrackingTouch(mSeekBar);
+
+ verify(mListener, times(1)).onPreferenceChange(mSeekBarPreference, 2);
+ }
+}
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SeekBarPreferenceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SeekBarPreferenceTest.java
new file mode 100644
index 0000000000000..19ee3f940aa2b
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/widget/SeekBarPreferenceTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2021 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.settingslib.widget;
+
+import static android.view.HapticFeedbackConstants.CLOCK_TICK;
+import static android.view.HapticFeedbackConstants.CONTEXT_CLICK;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.widget.SeekBar;
+
+import androidx.preference.PreferenceFragmentCompat;
+
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadows.androidx.fragment.FragmentController;
+
+@RunWith(RobolectricTestRunner.class)
+public class SeekBarPreferenceTest {
+
+ private static final int MAX = 75;
+ private static final int MIN = 5;
+ private static final int PROGRESS = 16;
+ private static final int NEW_PROGRESS = 17;
+
+ private Context mContext;
+ private SeekBarPreference mSeekBarPreference;
+ private SeekBar mSeekBar;
+
+ @Before
+ public void setUp() {
+ MockitoAnnotations.initMocks(this);
+ mContext = RuntimeEnvironment.application;
+
+ mSeekBarPreference = spy(new SeekBarPreference(mContext));
+ mSeekBarPreference.setMax(MAX);
+ mSeekBarPreference.setMin(MIN);
+ mSeekBarPreference.setProgress(PROGRESS);
+ mSeekBarPreference.setPersistent(false);
+ mSeekBarPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_NONE);
+
+ mSeekBar = new SeekBar(mContext);
+ mSeekBar.setMax(MAX);
+ mSeekBar.setMin(MIN);
+ }
+
+ @Test
+ public void testSaveAndRestoreInstanceState() {
+ final Parcelable parcelable = mSeekBarPreference.onSaveInstanceState();
+
+ final SeekBarPreference preference = new SeekBarPreference(mContext);
+ preference.onRestoreInstanceState(parcelable);
+
+ assertThat(preference.getMax()).isEqualTo(MAX);
+ assertThat(preference.getMin()).isEqualTo(MIN);
+ assertThat(preference.getProgress()).isEqualTo(PROGRESS);
+ }
+
+ @Test
+ @Config(qualifiers = "mcc998")
+ @Ignore("b/188888268")
+ public void isSelectable_default_returnFalse() {
+ final PreferenceFragmentCompat fragment = FragmentController.of(new TestFragment(),
+ new Bundle())
+ .create()
+ .start()
+ .resume()
+ .get();
+
+ final SeekBarPreference seekBarPreference = fragment.findPreference("seek_bar");
+
+ assertThat(seekBarPreference.isSelectable()).isFalse();
+ }
+
+ @Test
+ @Config(qualifiers = "mcc999")
+ @Ignore("b/188888268")
+ public void isSelectable_selectableInXml_returnTrue() {
+ final PreferenceFragmentCompat fragment = FragmentController.of(new TestFragment(),
+ new Bundle())
+ .create()
+ .start()
+ .resume()
+ .get();
+
+ final SeekBarPreference seekBarPreference = fragment.findPreference("seek_bar");
+
+ assertThat(seekBarPreference.isSelectable()).isTrue();
+ }
+
+ @Test
+ public void onProgressChanged_hapticFeedbackModeNone_clockTickFeedbackNotPerformed() {
+ mSeekBar.setProgress(NEW_PROGRESS);
+ when(mSeekBarPreference.callChangeListener(anyInt())).thenReturn(true);
+ mSeekBar.performHapticFeedback(CONTEXT_CLICK);
+
+ mSeekBarPreference.onProgressChanged(mSeekBar, NEW_PROGRESS, true);
+
+ assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isNotEqualTo(CLOCK_TICK);
+ }
+
+ @Test
+ public void onProgressChanged_hapticFeedbackModeOnTicks_clockTickFeedbackPerformed() {
+ mSeekBarPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_TICKS);
+ mSeekBar.setProgress(NEW_PROGRESS);
+ when(mSeekBarPreference.callChangeListener(anyInt())).thenReturn(true);
+ mSeekBar.performHapticFeedback(CONTEXT_CLICK);
+
+ mSeekBarPreference.onProgressChanged(mSeekBar, NEW_PROGRESS, true);
+
+ assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isEqualTo(CLOCK_TICK);
+ }
+
+ @Test
+ public void onProgressChanged_hapticFeedbackModeOnEnds_clockTickFeedbackNotPerformed() {
+ mSeekBarPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS);
+ mSeekBar.setProgress(NEW_PROGRESS);
+ when(mSeekBarPreference.callChangeListener(anyInt())).thenReturn(true);
+ mSeekBar.performHapticFeedback(CONTEXT_CLICK);
+
+ mSeekBarPreference.onProgressChanged(mSeekBar, NEW_PROGRESS, true);
+
+ assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isNotEqualTo(CLOCK_TICK);
+ }
+
+ @Test
+ public void onProgressChanged_hapticFeedbackModeOnEndsAndMinValue_clockTickFeedbackPerformed() {
+ mSeekBarPreference.setHapticFeedbackMode(SeekBarPreference.HAPTIC_FEEDBACK_MODE_ON_ENDS);
+ mSeekBar.setProgress(MIN);
+ when(mSeekBarPreference.callChangeListener(anyInt())).thenReturn(true);
+ mSeekBar.performHapticFeedback(CONTEXT_CLICK);
+
+ mSeekBarPreference.onProgressChanged(mSeekBar, MIN, true);
+
+ assertThat(shadowOf(mSeekBar).lastHapticFeedbackPerformed()).isEqualTo(CLOCK_TICK);
+ }
+
+ public static class TestFragment extends PreferenceFragmentCompat {
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ addPreferencesFromResource(R.xml.seekbar_preference);
+ }
+ }
+}