1/* 2 * Copyright (C) 2010 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.preference; 18 19import android.annotation.ArrayRes; 20import android.app.AlertDialog.Builder; 21import android.content.Context; 22import android.content.DialogInterface; 23import android.content.res.TypedArray; 24import android.os.Parcel; 25import android.os.Parcelable; 26import android.util.AttributeSet; 27 28import java.util.HashSet; 29import java.util.Set; 30 31/** 32 * A {@link Preference} that displays a list of entries as 33 * a dialog. 34 * <p> 35 * This preference will store a set of strings into the SharedPreferences. 36 * This set will contain one or more values from the 37 * {@link #setEntryValues(CharSequence[])} array. 38 * 39 * @attr ref android.R.styleable#MultiSelectListPreference_entries 40 * @attr ref android.R.styleable#MultiSelectListPreference_entryValues 41 */ 42public class MultiSelectListPreference extends DialogPreference { 43 private CharSequence[] mEntries; 44 private CharSequence[] mEntryValues; 45 private Set<String> mValues = new HashSet<String>(); 46 private Set<String> mNewValues = new HashSet<String>(); 47 private boolean mPreferenceChanged; 48 49 public MultiSelectListPreference( 50 Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 51 super(context, attrs, defStyleAttr, defStyleRes); 52 53 final TypedArray a = context.obtainStyledAttributes(attrs, 54 com.android.internal.R.styleable.MultiSelectListPreference, defStyleAttr, 55 defStyleRes); 56 mEntries = a.getTextArray(com.android.internal.R.styleable.MultiSelectListPreference_entries); 57 mEntryValues = a.getTextArray(com.android.internal.R.styleable.MultiSelectListPreference_entryValues); 58 a.recycle(); 59 } 60 61 public MultiSelectListPreference(Context context, AttributeSet attrs, int defStyleAttr) { 62 this(context, attrs, defStyleAttr, 0); 63 } 64 65 public MultiSelectListPreference(Context context, AttributeSet attrs) { 66 this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle); 67 } 68 69 public MultiSelectListPreference(Context context) { 70 this(context, null); 71 } 72 73 /** 74 * Sets the human-readable entries to be shown in the list. This will be 75 * shown in subsequent dialogs. 76 * <p> 77 * Each entry must have a corresponding index in 78 * {@link #setEntryValues(CharSequence[])}. 79 * 80 * @param entries The entries. 81 * @see #setEntryValues(CharSequence[]) 82 */ 83 public void setEntries(CharSequence[] entries) { 84 mEntries = entries; 85 } 86 87 /** 88 * @see #setEntries(CharSequence[]) 89 * @param entriesResId The entries array as a resource. 90 */ 91 public void setEntries(@ArrayRes int entriesResId) { 92 setEntries(getContext().getResources().getTextArray(entriesResId)); 93 } 94 95 /** 96 * The list of entries to be shown in the list in subsequent dialogs. 97 * 98 * @return The list as an array. 99 */ 100 public CharSequence[] getEntries() { 101 return mEntries; 102 } 103 104 /** 105 * The array to find the value to save for a preference when an entry from 106 * entries is selected. If a user clicks on the second item in entries, the 107 * second item in this array will be saved to the preference. 108 * 109 * @param entryValues The array to be used as values to save for the preference. 110 */ 111 public void setEntryValues(CharSequence[] entryValues) { 112 mEntryValues = entryValues; 113 } 114 115 /** 116 * @see #setEntryValues(CharSequence[]) 117 * @param entryValuesResId The entry values array as a resource. 118 */ 119 public void setEntryValues(@ArrayRes int entryValuesResId) { 120 setEntryValues(getContext().getResources().getTextArray(entryValuesResId)); 121 } 122 123 /** 124 * Returns the array of values to be saved for the preference. 125 * 126 * @return The array of values. 127 */ 128 public CharSequence[] getEntryValues() { 129 return mEntryValues; 130 } 131 132 /** 133 * Sets the value of the key. This should contain entries in 134 * {@link #getEntryValues()}. 135 * 136 * @param values The values to set for the key. 137 */ 138 public void setValues(Set<String> values) { 139 mValues.clear(); 140 mValues.addAll(values); 141 142 persistStringSet(values); 143 } 144 145 /** 146 * Retrieves the current value of the key. 147 */ 148 public Set<String> getValues() { 149 return mValues; 150 } 151 152 /** 153 * Returns the index of the given value (in the entry values array). 154 * 155 * @param value The value whose index should be returned. 156 * @return The index of the value, or -1 if not found. 157 */ 158 public int findIndexOfValue(String value) { 159 if (value != null && mEntryValues != null) { 160 for (int i = mEntryValues.length - 1; i >= 0; i--) { 161 if (mEntryValues[i].equals(value)) { 162 return i; 163 } 164 } 165 } 166 return -1; 167 } 168 169 @Override 170 protected void onPrepareDialogBuilder(Builder builder) { 171 super.onPrepareDialogBuilder(builder); 172 173 if (mEntries == null || mEntryValues == null) { 174 throw new IllegalStateException( 175 "MultiSelectListPreference requires an entries array and " + 176 "an entryValues array."); 177 } 178 179 boolean[] checkedItems = getSelectedItems(); 180 builder.setMultiChoiceItems(mEntries, checkedItems, 181 new DialogInterface.OnMultiChoiceClickListener() { 182 public void onClick(DialogInterface dialog, int which, boolean isChecked) { 183 if (isChecked) { 184 mPreferenceChanged |= mNewValues.add(mEntryValues[which].toString()); 185 } else { 186 mPreferenceChanged |= mNewValues.remove(mEntryValues[which].toString()); 187 } 188 } 189 }); 190 mNewValues.clear(); 191 mNewValues.addAll(mValues); 192 } 193 194 private boolean[] getSelectedItems() { 195 final CharSequence[] entries = mEntryValues; 196 final int entryCount = entries.length; 197 final Set<String> values = mValues; 198 boolean[] result = new boolean[entryCount]; 199 200 for (int i = 0; i < entryCount; i++) { 201 result[i] = values.contains(entries[i].toString()); 202 } 203 204 return result; 205 } 206 207 @Override 208 protected void onDialogClosed(boolean positiveResult) { 209 super.onDialogClosed(positiveResult); 210 211 if (positiveResult && mPreferenceChanged) { 212 final Set<String> values = mNewValues; 213 if (callChangeListener(values)) { 214 setValues(values); 215 } 216 } 217 mPreferenceChanged = false; 218 } 219 220 @Override 221 protected Object onGetDefaultValue(TypedArray a, int index) { 222 final CharSequence[] defaultValues = a.getTextArray(index); 223 final int valueCount = defaultValues.length; 224 final Set<String> result = new HashSet<String>(); 225 226 for (int i = 0; i < valueCount; i++) { 227 result.add(defaultValues[i].toString()); 228 } 229 230 return result; 231 } 232 233 @Override 234 protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { 235 setValues(restoreValue ? getPersistedStringSet(mValues) : (Set<String>) defaultValue); 236 } 237 238 @Override 239 protected Parcelable onSaveInstanceState() { 240 final Parcelable superState = super.onSaveInstanceState(); 241 if (isPersistent()) { 242 // No need to save instance state 243 return superState; 244 } 245 246 final SavedState myState = new SavedState(superState); 247 myState.values = getValues(); 248 return myState; 249 } 250 251 private static class SavedState extends BaseSavedState { 252 Set<String> values; 253 254 public SavedState(Parcel source) { 255 super(source); 256 values = new HashSet<String>(); 257 String[] strings = source.readStringArray(); 258 259 final int stringCount = strings.length; 260 for (int i = 0; i < stringCount; i++) { 261 values.add(strings[i]); 262 } 263 } 264 265 public SavedState(Parcelable superState) { 266 super(superState); 267 } 268 269 @Override 270 public void writeToParcel(Parcel dest, int flags) { 271 super.writeToParcel(dest, flags); 272 dest.writeStringArray(values.toArray(new String[0])); 273 } 274 275 public static final Parcelable.Creator<SavedState> CREATOR = 276 new Parcelable.Creator<SavedState>() { 277 public SavedState createFromParcel(Parcel in) { 278 return new SavedState(in); 279 } 280 281 public SavedState[] newArray(int size) { 282 return new SavedState[size]; 283 } 284 }; 285 } 286} 287