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.v7.preference;
18
19import android.content.Context;
20import android.content.res.TypedArray;
21import android.os.Parcel;
22import android.os.Parcelable;
23import android.support.annotation.ArrayRes;
24import android.support.annotation.NonNull;
25import android.support.v4.content.res.TypedArrayUtils;
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 name android:entries
37 * @attr name android: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 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, 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
72    public ListPreference(Context context, AttributeSet attrs, int defStyleAttr) {
73        this(context, attrs, defStyleAttr, 0);
74    }
75
76    public ListPreference(Context context, AttributeSet attrs) {
77        this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
78                android.R.attr.dialogPreferenceStyle));
79    }
80
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     */
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     */
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     */
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     */
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     */
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     */
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     */
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
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
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     */
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     */
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     */
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     */
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
248    private int getValueIndex() {
249        return findIndexOfValue(mValue);
250    }
251
252    @Override
253    protected Object onGetDefaultValue(TypedArray a, int index) {
254        return a.getString(index);
255    }
256
257    @Override
258    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
259        setValue(restoreValue ? getPersistedString(mValue) : (String) defaultValue);
260    }
261
262    @Override
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
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
291        public SavedState(Parcel source) {
292            super(source);
293            value = source.readString();
294        }
295
296        @Override
297        public void writeToParcel(@NonNull Parcel dest, int flags) {
298            super.writeToParcel(dest, flags);
299            dest.writeString(value);
300        }
301
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