1/* 2 * Copyright (C) 2007 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.text.TextUtils; 27import android.util.AttributeSet; 28 29/** 30 * A {@link Preference} that displays a list of entries as 31 * a dialog. 32 * <p> 33 * This preference will store a string into the SharedPreferences. This string will be the value 34 * from the {@link #setEntryValues(CharSequence[])} array. 35 * 36 * @attr ref android.R.styleable#ListPreference_entries 37 * @attr ref android.R.styleable#ListPreference_entryValues 38 */ 39public class ListPreference extends DialogPreference { 40 private CharSequence[] mEntries; 41 private CharSequence[] mEntryValues; 42 private String mValue; 43 private String mSummary; 44 private int mClickedDialogEntryIndex; 45 private boolean mValueSet; 46 47 public ListPreference(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 48 super(context, attrs, defStyleAttr, defStyleRes); 49 50 TypedArray a = context.obtainStyledAttributes( 51 attrs, com.android.internal.R.styleable.ListPreference, defStyleAttr, defStyleRes); 52 mEntries = a.getTextArray(com.android.internal.R.styleable.ListPreference_entries); 53 mEntryValues = a.getTextArray(com.android.internal.R.styleable.ListPreference_entryValues); 54 a.recycle(); 55 56 /* Retrieve the Preference summary attribute since it's private 57 * in the Preference class. 58 */ 59 a = context.obtainStyledAttributes(attrs, 60 com.android.internal.R.styleable.Preference, defStyleAttr, defStyleRes); 61 mSummary = a.getString(com.android.internal.R.styleable.Preference_summary); 62 a.recycle(); 63 } 64 65 public ListPreference(Context context, AttributeSet attrs, int defStyleAttr) { 66 this(context, attrs, defStyleAttr, 0); 67 } 68 69 public ListPreference(Context context, AttributeSet attrs) { 70 this(context, attrs, com.android.internal.R.attr.dialogPreferenceStyle); 71 } 72 73 public ListPreference(Context context) { 74 this(context, null); 75 } 76 77 /** 78 * Sets the human-readable entries to be shown in the list. This will be 79 * shown in subsequent dialogs. 80 * <p> 81 * Each entry must have a corresponding index in 82 * {@link #setEntryValues(CharSequence[])}. 83 * 84 * @param entries The entries. 85 * @see #setEntryValues(CharSequence[]) 86 */ 87 public void setEntries(CharSequence[] entries) { 88 mEntries = entries; 89 } 90 91 /** 92 * @see #setEntries(CharSequence[]) 93 * @param entriesResId The entries array as a resource. 94 */ 95 public void setEntries(@ArrayRes int entriesResId) { 96 setEntries(getContext().getResources().getTextArray(entriesResId)); 97 } 98 99 /** 100 * The list of entries to be shown in the list in subsequent dialogs. 101 * 102 * @return The list as an array. 103 */ 104 public CharSequence[] getEntries() { 105 return mEntries; 106 } 107 108 /** 109 * The array to find the value to save for a preference when an entry from 110 * entries is selected. If a user clicks on the second item in entries, the 111 * second item in this array will be saved to the preference. 112 * 113 * @param entryValues The array to be used as values to save for the preference. 114 */ 115 public void setEntryValues(CharSequence[] entryValues) { 116 mEntryValues = entryValues; 117 } 118 119 /** 120 * @see #setEntryValues(CharSequence[]) 121 * @param entryValuesResId The entry values array as a resource. 122 */ 123 public void setEntryValues(@ArrayRes int entryValuesResId) { 124 setEntryValues(getContext().getResources().getTextArray(entryValuesResId)); 125 } 126 127 /** 128 * Returns the array of values to be saved for the preference. 129 * 130 * @return The array of values. 131 */ 132 public CharSequence[] getEntryValues() { 133 return mEntryValues; 134 } 135 136 /** 137 * Sets the value of the key. This should be one of the entries in 138 * {@link #getEntryValues()}. 139 * 140 * @param value The value to set for the key. 141 */ 142 public void setValue(String value) { 143 // Always persist/notify the first time. 144 final boolean changed = !TextUtils.equals(mValue, value); 145 if (changed || !mValueSet) { 146 mValue = value; 147 mValueSet = true; 148 persistString(value); 149 if (changed) { 150 notifyChanged(); 151 } 152 } 153 } 154 155 /** 156 * Returns the summary of this ListPreference. If the summary 157 * has a {@linkplain java.lang.String#format String formatting} 158 * marker in it (i.e. "%s" or "%1$s"), then the current entry 159 * value will be substituted in its place. 160 * 161 * @return the summary with appropriate string substitution 162 */ 163 @Override 164 public CharSequence getSummary() { 165 final CharSequence entry = getEntry(); 166 if (mSummary == null) { 167 return super.getSummary(); 168 } else { 169 return String.format(mSummary, entry == null ? "" : entry); 170 } 171 } 172 173 /** 174 * Sets the summary for this Preference with a CharSequence. 175 * If the summary has a 176 * {@linkplain java.lang.String#format String formatting} 177 * marker in it (i.e. "%s" or "%1$s"), then the current entry 178 * value will be substituted in its place when it's retrieved. 179 * 180 * @param summary The summary for the preference. 181 */ 182 @Override 183 public void setSummary(CharSequence summary) { 184 super.setSummary(summary); 185 if (summary == null && mSummary != null) { 186 mSummary = null; 187 } else if (summary != null && !summary.equals(mSummary)) { 188 mSummary = summary.toString(); 189 } 190 } 191 192 /** 193 * Sets the value to the given index from the entry values. 194 * 195 * @param index The index of the value to set. 196 */ 197 public void setValueIndex(int index) { 198 if (mEntryValues != null) { 199 setValue(mEntryValues[index].toString()); 200 } 201 } 202 203 /** 204 * Returns the value of the key. This should be one of the entries in 205 * {@link #getEntryValues()}. 206 * 207 * @return The value of the key. 208 */ 209 public String getValue() { 210 return mValue; 211 } 212 213 /** 214 * Returns the entry corresponding to the current value. 215 * 216 * @return The entry corresponding to the current value, or null. 217 */ 218 public CharSequence getEntry() { 219 int index = getValueIndex(); 220 return index >= 0 && mEntries != null ? mEntries[index] : null; 221 } 222 223 /** 224 * Returns the index of the given value (in the entry values array). 225 * 226 * @param value The value whose index should be returned. 227 * @return The index of the value, or -1 if not found. 228 */ 229 public int findIndexOfValue(String value) { 230 if (value != null && mEntryValues != null) { 231 for (int i = mEntryValues.length - 1; i >= 0; i--) { 232 if (mEntryValues[i].equals(value)) { 233 return i; 234 } 235 } 236 } 237 return -1; 238 } 239 240 private int getValueIndex() { 241 return findIndexOfValue(mValue); 242 } 243 244 @Override 245 protected void onPrepareDialogBuilder(Builder builder) { 246 super.onPrepareDialogBuilder(builder); 247 248 if (mEntries == null || mEntryValues == null) { 249 throw new IllegalStateException( 250 "ListPreference requires an entries array and an entryValues array."); 251 } 252 253 mClickedDialogEntryIndex = getValueIndex(); 254 builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex, 255 new DialogInterface.OnClickListener() { 256 public void onClick(DialogInterface dialog, int which) { 257 mClickedDialogEntryIndex = which; 258 259 /* 260 * Clicking on an item simulates the positive button 261 * click, and dismisses the dialog. 262 */ 263 ListPreference.this.onClick(dialog, DialogInterface.BUTTON_POSITIVE); 264 dialog.dismiss(); 265 } 266 }); 267 268 /* 269 * The typical interaction for list-based dialogs is to have 270 * click-on-an-item dismiss the dialog instead of the user having to 271 * press 'Ok'. 272 */ 273 builder.setPositiveButton(null, null); 274 } 275 276 @Override 277 protected void onDialogClosed(boolean positiveResult) { 278 super.onDialogClosed(positiveResult); 279 280 if (positiveResult && mClickedDialogEntryIndex >= 0 && mEntryValues != null) { 281 String value = mEntryValues[mClickedDialogEntryIndex].toString(); 282 if (callChangeListener(value)) { 283 setValue(value); 284 } 285 } 286 } 287 288 @Override 289 protected Object onGetDefaultValue(TypedArray a, int index) { 290 return a.getString(index); 291 } 292 293 @Override 294 protected void onSetInitialValue(boolean restoreValue, Object defaultValue) { 295 setValue(restoreValue ? getPersistedString(mValue) : (String) defaultValue); 296 } 297 298 @Override 299 protected Parcelable onSaveInstanceState() { 300 final Parcelable superState = super.onSaveInstanceState(); 301 if (isPersistent()) { 302 // No need to save instance state since it's persistent 303 return superState; 304 } 305 306 final SavedState myState = new SavedState(superState); 307 myState.value = getValue(); 308 return myState; 309 } 310 311 @Override 312 protected void onRestoreInstanceState(Parcelable state) { 313 if (state == null || !state.getClass().equals(SavedState.class)) { 314 // Didn't save state for us in onSaveInstanceState 315 super.onRestoreInstanceState(state); 316 return; 317 } 318 319 SavedState myState = (SavedState) state; 320 super.onRestoreInstanceState(myState.getSuperState()); 321 setValue(myState.value); 322 } 323 324 private static class SavedState extends BaseSavedState { 325 String value; 326 327 public SavedState(Parcel source) { 328 super(source); 329 value = source.readString(); 330 } 331 332 @Override 333 public void writeToParcel(Parcel dest, int flags) { 334 super.writeToParcel(dest, flags); 335 dest.writeString(value); 336 } 337 338 public SavedState(Parcelable superState) { 339 super(superState); 340 } 341 342 public static final Parcelable.Creator<SavedState> CREATOR = 343 new Parcelable.Creator<SavedState>() { 344 public SavedState createFromParcel(Parcel in) { 345 return new SavedState(in); 346 } 347 348 public SavedState[] newArray(int size) { 349 return new SavedState[size]; 350 } 351 }; 352 } 353 354} 355