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