1 /* 2 * Copyright 2018 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 androidx.preference; 18 19 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 20 21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 import android.text.TextUtils; 26 import android.util.AttributeSet; 27 import android.view.View; 28 import android.widget.TextView; 29 30 import androidx.annotation.RestrictTo; 31 32 /** 33 * Common base class for preferences that have two selectable states, persist a 34 * boolean value in SharedPreferences, and may have dependent preferences that are 35 * enabled/disabled based on the current state. 36 */ 37 public abstract class TwoStatePreference extends Preference { 38 39 private CharSequence mSummaryOn; 40 private CharSequence mSummaryOff; 41 protected boolean mChecked; 42 private boolean mCheckedSet; 43 private boolean mDisableDependentsState; 44 TwoStatePreference( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)45 public TwoStatePreference( 46 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 47 super(context, attrs, defStyleAttr, defStyleRes); 48 } 49 TwoStatePreference(Context context, AttributeSet attrs, int defStyleAttr)50 public TwoStatePreference(Context context, AttributeSet attrs, int defStyleAttr) { 51 this(context, attrs, defStyleAttr, 0); 52 } 53 TwoStatePreference(Context context, AttributeSet attrs)54 public TwoStatePreference(Context context, AttributeSet attrs) { 55 this(context, attrs, 0); 56 } 57 TwoStatePreference(Context context)58 public TwoStatePreference(Context context) { 59 this(context, null); 60 } 61 62 @Override onClick()63 protected void onClick() { 64 super.onClick(); 65 66 final boolean newValue = !isChecked(); 67 if (callChangeListener(newValue)) { 68 setChecked(newValue); 69 } 70 } 71 72 /** 73 * Sets the checked state and saves it to the {@link android.content.SharedPreferences}. 74 * 75 * @param checked The checked state. 76 */ setChecked(boolean checked)77 public void setChecked(boolean checked) { 78 // Always persist/notify the first time; don't assume the field's default of false. 79 final boolean changed = mChecked != checked; 80 if (changed || !mCheckedSet) { 81 mChecked = checked; 82 mCheckedSet = true; 83 persistBoolean(checked); 84 if (changed) { 85 notifyDependencyChange(shouldDisableDependents()); 86 notifyChanged(); 87 } 88 } 89 } 90 91 /** 92 * Returns the checked state. 93 * 94 * @return The checked state. 95 */ isChecked()96 public boolean isChecked() { 97 return mChecked; 98 } 99 100 @Override shouldDisableDependents()101 public boolean shouldDisableDependents() { 102 boolean shouldDisable = mDisableDependentsState ? mChecked : !mChecked; 103 return shouldDisable || super.shouldDisableDependents(); 104 } 105 106 /** 107 * Sets the summary to be shown when checked. 108 * 109 * @param summary The summary to be shown when checked. 110 */ setSummaryOn(CharSequence summary)111 public void setSummaryOn(CharSequence summary) { 112 mSummaryOn = summary; 113 if (isChecked()) { 114 notifyChanged(); 115 } 116 } 117 118 /** 119 * @see #setSummaryOn(CharSequence) 120 * @param summaryResId The summary as a resource. 121 */ setSummaryOn(int summaryResId)122 public void setSummaryOn(int summaryResId) { 123 setSummaryOn(getContext().getString(summaryResId)); 124 } 125 126 /** 127 * Returns the summary to be shown when checked. 128 * @return The summary. 129 */ getSummaryOn()130 public CharSequence getSummaryOn() { 131 return mSummaryOn; 132 } 133 134 /** 135 * Sets the summary to be shown when unchecked. 136 * 137 * @param summary The summary to be shown when unchecked. 138 */ setSummaryOff(CharSequence summary)139 public void setSummaryOff(CharSequence summary) { 140 mSummaryOff = summary; 141 if (!isChecked()) { 142 notifyChanged(); 143 } 144 } 145 146 /** 147 * @see #setSummaryOff(CharSequence) 148 * @param summaryResId The summary as a resource. 149 */ setSummaryOff(int summaryResId)150 public void setSummaryOff(int summaryResId) { 151 setSummaryOff(getContext().getString(summaryResId)); 152 } 153 154 /** 155 * Returns the summary to be shown when unchecked. 156 * @return The summary. 157 */ getSummaryOff()158 public CharSequence getSummaryOff() { 159 return mSummaryOff; 160 } 161 162 /** 163 * Returns whether dependents are disabled when this preference is on ({@code true}) 164 * or when this preference is off ({@code false}). 165 * 166 * @return Whether dependents are disabled when this preference is on ({@code true}) 167 * or when this preference is off ({@code false}). 168 */ getDisableDependentsState()169 public boolean getDisableDependentsState() { 170 return mDisableDependentsState; 171 } 172 173 /** 174 * Sets whether dependents are disabled when this preference is on ({@code true}) 175 * or when this preference is off ({@code false}). 176 * 177 * @param disableDependentsState The preference state that should disable dependents. 178 */ setDisableDependentsState(boolean disableDependentsState)179 public void setDisableDependentsState(boolean disableDependentsState) { 180 mDisableDependentsState = disableDependentsState; 181 } 182 183 @Override onGetDefaultValue(TypedArray a, int index)184 protected Object onGetDefaultValue(TypedArray a, int index) { 185 return a.getBoolean(index, false); 186 } 187 188 @Override onSetInitialValue(boolean restoreValue, Object defaultValue)189 protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { 190 setChecked(restoreValue ? getPersistedBoolean(mChecked) 191 : (Boolean) defaultValue); 192 } 193 194 /** 195 * Sync a summary holder contained within holder's subhierarchy with the correct summary text. 196 * @param holder PreferenceViewHolder which holds a reference to the summary view 197 */ syncSummaryView(PreferenceViewHolder holder)198 protected void syncSummaryView(PreferenceViewHolder holder) { 199 // Sync the summary holder 200 View view = holder.findViewById(android.R.id.summary); 201 syncSummaryView(view); 202 } 203 204 /** 205 * @hide 206 */ 207 @RestrictTo(LIBRARY_GROUP) syncSummaryView(View view)208 protected void syncSummaryView(View view) { 209 if (!(view instanceof TextView)) { 210 return; 211 } 212 TextView summaryView = (TextView) view; 213 boolean useDefaultSummary = true; 214 if (mChecked && !TextUtils.isEmpty(mSummaryOn)) { 215 summaryView.setText(mSummaryOn); 216 useDefaultSummary = false; 217 } else if (!mChecked && !TextUtils.isEmpty(mSummaryOff)) { 218 summaryView.setText(mSummaryOff); 219 useDefaultSummary = false; 220 } 221 if (useDefaultSummary) { 222 final CharSequence summary = getSummary(); 223 if (!TextUtils.isEmpty(summary)) { 224 summaryView.setText(summary); 225 useDefaultSummary = false; 226 } 227 } 228 int newVisibility = View.GONE; 229 if (!useDefaultSummary) { 230 // Someone has written to it 231 newVisibility = View.VISIBLE; 232 } 233 if (newVisibility != summaryView.getVisibility()) { 234 summaryView.setVisibility(newVisibility); 235 } 236 } 237 238 @Override onSaveInstanceState()239 protected Parcelable onSaveInstanceState() { 240 final Parcelable superState = super.onSaveInstanceState(); 241 if (isPersistent()) { 242 // No need to save instance state since it's persistent 243 return superState; 244 } 245 246 final SavedState myState = new SavedState(superState); 247 myState.checked = isChecked(); 248 return myState; 249 } 250 251 @Override onRestoreInstanceState(Parcelable state)252 protected void onRestoreInstanceState(Parcelable state) { 253 if (state == null || !state.getClass().equals(SavedState.class)) { 254 // Didn't save state for us in onSaveInstanceState 255 super.onRestoreInstanceState(state); 256 return; 257 } 258 259 SavedState myState = (SavedState) state; 260 super.onRestoreInstanceState(myState.getSuperState()); 261 setChecked(myState.checked); 262 } 263 264 static class SavedState extends BaseSavedState { 265 boolean checked; 266 SavedState(Parcel source)267 public SavedState(Parcel source) { 268 super(source); 269 checked = source.readInt() == 1; 270 } 271 272 @Override writeToParcel(Parcel dest, int flags)273 public void writeToParcel(Parcel dest, int flags) { 274 super.writeToParcel(dest, flags); 275 dest.writeInt(checked ? 1 : 0); 276 } 277 SavedState(Parcelable superState)278 public SavedState(Parcelable superState) { 279 super(superState); 280 } 281 282 public static final Parcelable.Creator<SavedState> CREATOR = 283 new Parcelable.Creator<SavedState>() { 284 @Override 285 public SavedState createFromParcel(Parcel in) { 286 return new SavedState(in); 287 } 288 289 @Override 290 public SavedState[] newArray(int size) { 291 return new SavedState[size]; 292 } 293 }; 294 } 295 } 296