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