1 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
2  * This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 package org.mozilla.gecko.preferences;
7 
8 import org.mozilla.gecko.R;
9 import org.mozilla.gecko.GeckoSharedPrefs;
10 import org.mozilla.gecko.util.PrefUtils;
11 import org.mozilla.gecko.util.ThreadUtils;
12 
13 import android.app.AlertDialog.Builder;
14 import android.content.Context;
15 import android.content.DialogInterface;
16 import android.content.res.TypedArray;
17 import android.content.SharedPreferences;
18 import android.preference.DialogPreference;
19 import android.util.AttributeSet;
20 
21 import java.util.HashSet;
22 import java.util.Set;
23 
24 class MultiChoicePreference extends DialogPreference implements DialogInterface.OnMultiChoiceClickListener {
25     private static final String LOGTAG = "GeckoMultiChoicePreference";
26 
27     private boolean mValues[];
28     private boolean mPrevValues[];
29     private CharSequence mEntryValues[];
30     private CharSequence mEntries[];
31     private CharSequence mInitialValues[];
32 
MultiChoicePreference(Context context, AttributeSet attrs)33     public MultiChoicePreference(Context context, AttributeSet attrs) {
34         super(context, attrs);
35 
36         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiChoicePreference);
37         mEntries = a.getTextArray(R.styleable.MultiChoicePreference_entries);
38         mEntryValues = a.getTextArray(R.styleable.MultiChoicePreference_entryValues);
39         mInitialValues = a.getTextArray(R.styleable.MultiChoicePreference_initialValues);
40         a.recycle();
41 
42         loadPersistedValues();
43     }
44 
MultiChoicePreference(Context context)45     public MultiChoicePreference(Context context) {
46         this(context, null);
47     }
48 
49     /**
50      * Sets the human-readable entries to be shown in the list. This will be
51      * shown in subsequent dialogs.
52      * <p>
53      * Each entry must have a corresponding index in
54      * {@link #setEntryValues(CharSequence[])} and
55      * {@link #setInitialValues(CharSequence[])}.
56      *
57      * @param entries The entries.
58      */
setEntries(CharSequence[] entries)59     public void setEntries(CharSequence[] entries) {
60         mEntries = entries.clone();
61     }
62 
63     /**
64      * @param entriesResId The entries array as a resource.
65      */
setEntries(int entriesResId)66     public void setEntries(int entriesResId) {
67         setEntries(getContext().getResources().getTextArray(entriesResId));
68     }
69 
70     /**
71      * Sets the preference values for preferences shown in the list.
72      *
73      * @param entryValues The entry values.
74      */
setEntryValues(CharSequence[] entryValues)75     public void setEntryValues(CharSequence[] entryValues) {
76         mEntryValues = entryValues.clone();
77         loadPersistedValues();
78     }
79 
80     /**
81      * Entry values define a separate pref for each row in the dialog.
82      *
83      * @param entryValuesResId The entryValues array as a resource.
84      */
setEntryValues(int entryValuesResId)85     public void setEntryValues(int entryValuesResId) {
86         setEntryValues(getContext().getResources().getTextArray(entryValuesResId));
87     }
88 
89     /**
90      * The array of initial entry values in this list. Each entryValue
91      * corresponds to an entryKey. These values are used if a) the preference
92      * isn't persisted, or b) the preference is persisted but hasn't yet been
93      * set.
94      *
95      * @param initialValues The entry values
96      */
setInitialValues(CharSequence[] initialValues)97     public void setInitialValues(CharSequence[] initialValues) {
98         mInitialValues = initialValues.clone();
99         loadPersistedValues();
100     }
101 
102     /**
103      * @param initialValuesResId The initialValues array as a resource.
104      */
setInitialValues(int initialValuesResId)105     public void setInitialValues(int initialValuesResId) {
106         setInitialValues(getContext().getResources().getTextArray(initialValuesResId));
107     }
108 
109     /**
110      * The list of translated strings corresponding to each preference.
111      *
112      * @return The array of entries.
113      */
getEntries()114     public CharSequence[] getEntries() {
115         return mEntries.clone();
116     }
117 
118     /**
119      * The list of values corresponding to each preference.
120      *
121      * @return The array of values.
122      */
getEntryValues()123     public CharSequence[] getEntryValues() {
124         return mEntryValues.clone();
125     }
126 
127     /**
128      * The list of initial values for each preference. Each string in this list
129      * should be either "true" or "false".
130      *
131      * @return The array of initial values.
132      */
getInitialValues()133     public CharSequence[] getInitialValues() {
134         return mInitialValues.clone();
135     }
136 
setValue(final int i, final boolean value)137     public void setValue(final int i, final boolean value) {
138         mValues[i] = value;
139         mPrevValues = mValues.clone();
140     }
141 
142     /**
143      * The list of values for each preference. These values are updated after
144      * the dialog has been displayed.
145      *
146      * @return The array of values.
147      */
getValues()148     public Set<String> getValues() {
149         final Set<String> values = new HashSet<String>();
150 
151         if (mValues == null) {
152             return values;
153         }
154 
155         for (int i = 0; i < mValues.length; i++) {
156             if (mValues[i]) {
157                 values.add(mEntryValues[i].toString());
158             }
159         }
160 
161         return values;
162     }
163 
164     @Override
onClick(DialogInterface dialog, int which, boolean val)165     public void onClick(DialogInterface dialog, int which, boolean val) {
166     }
167 
168     @Override
onPrepareDialogBuilder(Builder builder)169     protected void onPrepareDialogBuilder(Builder builder) {
170         if (mEntries == null || mInitialValues == null || mEntryValues == null) {
171             throw new IllegalStateException(
172                     "MultiChoicePreference requires entries, entryValues, and initialValues arrays.");
173         }
174 
175         if (mEntries.length != mEntryValues.length || mEntries.length != mInitialValues.length) {
176             throw new IllegalStateException(
177                     "MultiChoicePreference entries, entryValues, and initialValues arrays must be the same length");
178         }
179 
180         builder.setMultiChoiceItems(mEntries, mValues, this);
181     }
182 
183     @Override
onDialogClosed(boolean positiveResult)184     protected void onDialogClosed(boolean positiveResult) {
185         if (mPrevValues == null || mInitialValues == null) {
186             // Initialization is done asynchronously, so these values may not
187             // have been set before the dialog was closed.
188             return;
189         }
190 
191         if (!positiveResult) {
192             // user cancelled; reset checkbox values to their previous state
193             mValues = mPrevValues.clone();
194             return;
195         }
196 
197         mPrevValues = mValues.clone();
198 
199         if (!callChangeListener(getValues())) {
200             return;
201         }
202 
203         persist();
204     }
205 
206     /* Persists the current data stored by this pref to SharedPreferences. */
persist()207     public boolean persist() {
208         if (isPersistent()) {
209             final SharedPreferences.Editor edit = GeckoSharedPrefs.forProfile(getContext()).edit();
210             final boolean res = persist(edit);
211             edit.apply();
212             return res;
213         }
214 
215         return false;
216     }
217 
218     /* Internal persist method. Take an edit so that multiple prefs can be persisted in a single commit. */
persist(SharedPreferences.Editor edit)219     protected boolean persist(SharedPreferences.Editor edit) {
220         if (isPersistent()) {
221             Set<String> vals = getValues();
222             PrefUtils.putStringSet(edit, getKey(), vals).apply();;
223             return true;
224         }
225 
226         return false;
227     }
228 
229     /* Returns a list of EntryValues that are currently enabled. */
getPersistedStrings(Set<String> defaultVal)230     public Set<String> getPersistedStrings(Set<String> defaultVal) {
231         if (!isPersistent()) {
232             return defaultVal;
233         }
234 
235         final SharedPreferences prefs = GeckoSharedPrefs.forProfile(getContext());
236         return PrefUtils.getStringSet(prefs, getKey(), defaultVal);
237     }
238 
239     /**
240      * Loads persistent prefs from shared preferences. If the preferences
241      * aren't persistent or haven't yet been stored, they will be set to their
242      * initial values.
243      */
loadPersistedValues()244     protected void loadPersistedValues() {
245         final int entryCount = mInitialValues.length;
246         mValues = new boolean[entryCount];
247 
248         if (entryCount != mEntries.length || entryCount != mEntryValues.length) {
249             throw new IllegalStateException(
250                     "MultiChoicePreference entryValues and initialValues arrays must be the same length");
251         }
252 
253         ThreadUtils.postToBackgroundThread(new Runnable() {
254             @Override
255             public void run() {
256                 final Set<String> stringVals = getPersistedStrings(null);
257 
258                 for (int i = 0; i < entryCount; i++) {
259                     if (stringVals != null) {
260                         mValues[i] = stringVals.contains(mEntryValues[i]);
261                     } else {
262                         final boolean defaultVal = mInitialValues[i].equals("true");
263                         mValues[i] = defaultVal;
264                     }
265                 }
266 
267                 mPrevValues = mValues.clone();
268             }
269         });
270     }
271 }
272