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