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