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