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