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