1 /* 2 * Copyright (C) 2015 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 android.support.v14.preference; 18 19 import android.content.Context; 20 import android.content.SharedPreferences; 21 import android.content.res.TypedArray; 22 import android.os.Parcel; 23 import android.os.Parcelable; 24 import android.support.annotation.ArrayRes; 25 import android.support.annotation.NonNull; 26 import android.support.v4.content.SharedPreferencesCompat; 27 import android.support.v4.content.res.TypedArrayUtils; 28 import android.support.v7.preference.DialogPreference; 29 import android.util.AttributeSet; 30 31 import java.util.Collections; 32 import java.util.HashSet; 33 import java.util.Set; 34 35 /** 36 * A {@link android.support.v7.preference.Preference} that displays a list of entries as 37 * a dialog. 38 * <p> 39 * This preference will store a set of strings into the SharedPreferences. 40 * This set will contain one or more values from the 41 * {@link #setEntryValues(CharSequence[])} array. 42 * 43 * @attr ref android.R.styleable#MultiSelectListPreference_entries 44 * @attr ref android.R.styleable#MultiSelectListPreference_entryValues 45 */ 46 public class MultiSelectListPreference extends DialogPreference { 47 private CharSequence[] mEntries; 48 private CharSequence[] mEntryValues; 49 private Set<String> mValues = new HashSet<>(); 50 MultiSelectListPreference( Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)51 public MultiSelectListPreference( 52 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 53 super(context, attrs, defStyleAttr, defStyleRes); 54 55 final TypedArray a = context.obtainStyledAttributes(attrs, 56 R.styleable.MultiSelectListPreference, defStyleAttr, 57 defStyleRes); 58 59 mEntries = TypedArrayUtils.getTextArray(a, 60 R.styleable.MultiSelectListPreference_entries, 61 R.styleable.MultiSelectListPreference_android_entries); 62 63 mEntryValues = TypedArrayUtils.getTextArray(a, 64 R.styleable.MultiSelectListPreference_entryValues, 65 R.styleable.MultiSelectListPreference_android_entryValues); 66 67 a.recycle(); 68 } 69 MultiSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr)70 public MultiSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr) { 71 this(context, attrs, defStyleAttr, 0); 72 } 73 MultiSelectListPreference(Context context, AttributeSet attrs)74 public MultiSelectListPreference(Context context, AttributeSet attrs) { 75 this(context, attrs, R.attr.dialogPreferenceStyle); 76 } 77 MultiSelectListPreference(Context context)78 public MultiSelectListPreference(Context context) { 79 this(context, null); 80 } 81 82 /** 83 * Attempts to persist a set of Strings to the {@link android.content.SharedPreferences}. 84 * <p> 85 * This will check if this Preference is persistent, get an editor from 86 * the {@link android.preference.PreferenceManager}, put in the strings, and check if we should 87 * commit (and commit if so). 88 * 89 * @param values The values to persist. 90 * @return True if the Preference is persistent. (This is not whether the 91 * value was persisted, since we may not necessarily commit if there 92 * will be a batch commit later.) 93 * @see #getPersistedString 94 * 95 * @hide 96 */ persistStringSet(Set<String> values)97 protected boolean persistStringSet(Set<String> values) { 98 if (shouldPersist()) { 99 // Shouldn't store null 100 if (values.equals(getPersistedStringSet(null))) { 101 // It's already there, so the same as persisting 102 return true; 103 } 104 105 SharedPreferences.Editor editor = getPreferenceManager().getSharedPreferences().edit(); 106 editor.putStringSet(getKey(), values); 107 SharedPreferencesCompat.EditorCompat.getInstance().apply(editor); 108 return true; 109 } 110 return false; 111 } 112 113 /** 114 * Attempts to get a persisted set of Strings from the 115 * {@link android.content.SharedPreferences}. 116 * <p> 117 * This will check if this Preference is persistent, get the SharedPreferences 118 * from the {@link android.preference.PreferenceManager}, and get the value. 119 * 120 * @param defaultReturnValue The default value to return if either the 121 * Preference is not persistent or the Preference is not in the 122 * shared preferences. 123 * @return The value from the SharedPreferences or the default return 124 * value. 125 * @see #persistStringSet(Set) 126 * 127 * @hide 128 */ getPersistedStringSet(Set<String> defaultReturnValue)129 protected Set<String> getPersistedStringSet(Set<String> defaultReturnValue) { 130 if (!shouldPersist()) { 131 return defaultReturnValue; 132 } 133 134 return getPreferenceManager().getSharedPreferences() 135 .getStringSet(getKey(), defaultReturnValue); 136 } 137 138 /** 139 * Sets the human-readable entries to be shown in the list. This will be 140 * shown in subsequent dialogs. 141 * <p> 142 * Each entry must have a corresponding index in 143 * {@link #setEntryValues(CharSequence[])}. 144 * 145 * @param entries The entries. 146 * @see #setEntryValues(CharSequence[]) 147 */ setEntries(CharSequence[] entries)148 public void setEntries(CharSequence[] entries) { 149 mEntries = entries; 150 } 151 152 /** 153 * @see #setEntries(CharSequence[]) 154 * @param entriesResId The entries array as a resource. 155 */ setEntries(@rrayRes int entriesResId)156 public void setEntries(@ArrayRes int entriesResId) { 157 setEntries(getContext().getResources().getTextArray(entriesResId)); 158 } 159 160 /** 161 * The list of entries to be shown in the list in subsequent dialogs. 162 * 163 * @return The list as an array. 164 */ getEntries()165 public CharSequence[] getEntries() { 166 return mEntries; 167 } 168 169 /** 170 * The array to find the value to save for a preference when an entry from 171 * entries is selected. If a user clicks on the second item in entries, the 172 * second item in this array will be saved to the preference. 173 * 174 * @param entryValues The array to be used as values to save for the preference. 175 */ setEntryValues(CharSequence[] entryValues)176 public void setEntryValues(CharSequence[] entryValues) { 177 mEntryValues = entryValues; 178 } 179 180 /** 181 * @see #setEntryValues(CharSequence[]) 182 * @param entryValuesResId The entry values array as a resource. 183 */ setEntryValues(@rrayRes int entryValuesResId)184 public void setEntryValues(@ArrayRes int entryValuesResId) { 185 setEntryValues(getContext().getResources().getTextArray(entryValuesResId)); 186 } 187 188 /** 189 * Returns the array of values to be saved for the preference. 190 * 191 * @return The array of values. 192 */ getEntryValues()193 public CharSequence[] getEntryValues() { 194 return mEntryValues; 195 } 196 197 /** 198 * Sets the value of the key. This should contain entries in 199 * {@link #getEntryValues()}. 200 * 201 * @param values The values to set for the key. 202 */ setValues(Set<String> values)203 public void setValues(Set<String> values) { 204 mValues.clear(); 205 mValues.addAll(values); 206 207 persistStringSet(values); 208 } 209 210 /** 211 * Retrieves the current value of the key. 212 */ getValues()213 public Set<String> getValues() { 214 return mValues; 215 } 216 217 /** 218 * Returns the index of the given value (in the entry values array). 219 * 220 * @param value The value whose index should be returned. 221 * @return The index of the value, or -1 if not found. 222 */ findIndexOfValue(String value)223 public int findIndexOfValue(String value) { 224 if (value != null && mEntryValues != null) { 225 for (int i = mEntryValues.length - 1; i >= 0; i--) { 226 if (mEntryValues[i].equals(value)) { 227 return i; 228 } 229 } 230 } 231 return -1; 232 } 233 getSelectedItems()234 protected boolean[] getSelectedItems() { 235 final CharSequence[] entries = mEntryValues; 236 final int entryCount = entries.length; 237 final Set<String> values = mValues; 238 boolean[] result = new boolean[entryCount]; 239 240 for (int i = 0; i < entryCount; i++) { 241 result[i] = values.contains(entries[i].toString()); 242 } 243 244 return result; 245 } 246 247 @Override onGetDefaultValue(TypedArray a, int index)248 protected Object onGetDefaultValue(TypedArray a, int index) { 249 final CharSequence[] defaultValues = a.getTextArray(index); 250 final Set<String> result = new HashSet<>(); 251 252 for (final CharSequence defaultValue : defaultValues) { 253 result.add(defaultValue.toString()); 254 } 255 256 return result; 257 } 258 259 @Override onSetInitialValue(boolean restoreValue, Object defaultValue)260 protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { 261 setValues(restoreValue ? getPersistedStringSet(mValues) : (Set<String>) defaultValue); 262 } 263 264 @Override onSaveInstanceState()265 protected Parcelable onSaveInstanceState() { 266 final Parcelable superState = super.onSaveInstanceState(); 267 if (isPersistent()) { 268 // No need to save instance state 269 return superState; 270 } 271 272 final SavedState myState = new SavedState(superState); 273 myState.values = getValues(); 274 return myState; 275 } 276 277 @Override onRestoreInstanceState(Parcelable state)278 protected void onRestoreInstanceState(Parcelable state) { 279 if (state == null || !state.getClass().equals(SavedState.class)) { 280 // Didn't save state for us in onSaveInstanceState 281 super.onRestoreInstanceState(state); 282 return; 283 } 284 285 SavedState myState = (SavedState) state; 286 super.onRestoreInstanceState(myState.getSuperState()); 287 setValues(myState.values); 288 } 289 290 private static class SavedState extends BaseSavedState { 291 Set<String> values; 292 SavedState(Parcel source)293 public SavedState(Parcel source) { 294 super(source); 295 final int size = source.readInt(); 296 values = new HashSet<>(); 297 String[] strings = new String[size]; 298 source.readStringArray(strings); 299 300 Collections.addAll(values, strings); 301 } 302 SavedState(Parcelable superState)303 public SavedState(Parcelable superState) { 304 super(superState); 305 } 306 307 @Override writeToParcel(@onNull Parcel dest, int flags)308 public void writeToParcel(@NonNull Parcel dest, int flags) { 309 super.writeToParcel(dest, flags); 310 dest.writeInt(values.size()); 311 dest.writeStringArray(values.toArray(new String[values.size()])); 312 } 313 314 public static final Parcelable.Creator<SavedState> CREATOR = 315 new Parcelable.Creator<SavedState>() { 316 public SavedState createFromParcel(Parcel in) { 317 return new SavedState(in); 318 } 319 320 public SavedState[] newArray(int size) { 321 return new SavedState[size]; 322 } 323 }; 324 } 325 } 326