1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/preferences/MultiChoicePreference.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,251 @@ 1.4 +/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- 1.5 + * This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +package org.mozilla.gecko.preferences; 1.10 + 1.11 +import org.mozilla.gecko.R; 1.12 +import org.mozilla.gecko.GeckoSharedPrefs; 1.13 +import org.mozilla.gecko.util.ThreadUtils; 1.14 + 1.15 +import android.app.AlertDialog; 1.16 +import android.app.AlertDialog.Builder; 1.17 +import android.content.Context; 1.18 +import android.content.DialogInterface; 1.19 +import android.content.res.TypedArray; 1.20 +import android.preference.DialogPreference; 1.21 +import android.util.AttributeSet; 1.22 +import android.widget.Button; 1.23 + 1.24 +class MultiChoicePreference extends DialogPreference { 1.25 + private static final String LOGTAG = "GeckoMultiChoicePreference"; 1.26 + 1.27 + private boolean mValues[]; 1.28 + private boolean mPrevValues[]; 1.29 + private CharSequence mEntryKeys[]; 1.30 + private CharSequence mEntries[]; 1.31 + private CharSequence mInitialValues[]; 1.32 + 1.33 + public MultiChoicePreference(Context context, AttributeSet attrs) { 1.34 + super(context, attrs); 1.35 + 1.36 + TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiChoicePreference); 1.37 + mEntries = a.getTextArray(R.styleable.MultiChoicePreference_entries); 1.38 + mEntryKeys = a.getTextArray(R.styleable.MultiChoicePreference_entryKeys); 1.39 + mInitialValues = a.getTextArray(R.styleable.MultiChoicePreference_initialValues); 1.40 + a.recycle(); 1.41 + 1.42 + loadPersistedValues(); 1.43 + } 1.44 + 1.45 + public MultiChoicePreference(Context context) { 1.46 + this(context, null); 1.47 + } 1.48 + 1.49 + /** 1.50 + * Sets the human-readable entries to be shown in the list. This will be 1.51 + * shown in subsequent dialogs. 1.52 + * <p> 1.53 + * Each entry must have a corresponding index in 1.54 + * {@link #setEntryKeys(CharSequence[])} and 1.55 + * {@link #setInitialValues(CharSequence[])}. 1.56 + * 1.57 + * @param entries The entries. 1.58 + */ 1.59 + public void setEntries(CharSequence[] entries) { 1.60 + mEntries = entries.clone(); 1.61 + } 1.62 + 1.63 + /** 1.64 + * @param entriesResId The entries array as a resource. 1.65 + */ 1.66 + public void setEntries(int entriesResId) { 1.67 + setEntries(getContext().getResources().getTextArray(entriesResId)); 1.68 + } 1.69 + 1.70 + /** 1.71 + * Sets the preference keys for preferences shown in the list. 1.72 + * 1.73 + * @param entryKeys The entry keys. 1.74 + */ 1.75 + public void setEntryKeys(CharSequence[] entryKeys) { 1.76 + mEntryKeys = entryKeys.clone(); 1.77 + loadPersistedValues(); 1.78 + } 1.79 + 1.80 + /** 1.81 + * @param entryKeysResId The entryKeys array as a resource. 1.82 + */ 1.83 + public void setEntryKeys(int entryKeysResId) { 1.84 + setEntryKeys(getContext().getResources().getTextArray(entryKeysResId)); 1.85 + } 1.86 + 1.87 + /** 1.88 + * The array of initial entry values in this list. Each entryValue 1.89 + * corresponds to an entryKey. These values are used if a) the preference 1.90 + * isn't persisted, or b) the preference is persisted but hasn't yet been 1.91 + * set. 1.92 + * 1.93 + * @param initialValues The entry values 1.94 + */ 1.95 + public void setInitialValues(CharSequence[] initialValues) { 1.96 + mInitialValues = initialValues.clone(); 1.97 + loadPersistedValues(); 1.98 + } 1.99 + 1.100 + /** 1.101 + * @param initialValuesResId The initialValues array as a resource. 1.102 + */ 1.103 + public void setInitialValues(int initialValuesResId) { 1.104 + setInitialValues(getContext().getResources().getTextArray(initialValuesResId)); 1.105 + } 1.106 + 1.107 + /** 1.108 + * The list of translated strings corresponding to each preference. 1.109 + * 1.110 + * @return The array of entries. 1.111 + */ 1.112 + public CharSequence[] getEntries() { 1.113 + return mEntries.clone(); 1.114 + } 1.115 + 1.116 + /** 1.117 + * The list of keys corresponding to each preference. 1.118 + * 1.119 + * @return The array of keys. 1.120 + */ 1.121 + public CharSequence[] getEntryKeys() { 1.122 + return mEntryKeys.clone(); 1.123 + } 1.124 + 1.125 + /** 1.126 + * The list of initial values for each preference. Each string in this list 1.127 + * should be either "true" or "false". 1.128 + * 1.129 + * @return The array of initial values. 1.130 + */ 1.131 + public CharSequence[] getInitialValues() { 1.132 + return mInitialValues.clone(); 1.133 + } 1.134 + 1.135 + /** 1.136 + * The list of values for each preference. These values are updated after 1.137 + * the dialog has been displayed. 1.138 + * 1.139 + * @return The array of values. 1.140 + */ 1.141 + public boolean[] getValues() { 1.142 + return mValues.clone(); 1.143 + } 1.144 + 1.145 + @Override 1.146 + protected void onPrepareDialogBuilder(Builder builder) { 1.147 + if (mEntries == null || mEntryKeys == null || mInitialValues == null) { 1.148 + throw new IllegalStateException( 1.149 + "MultiChoicePreference requires entries, entryKeys, and initialValues arrays."); 1.150 + } 1.151 + 1.152 + if (mEntries.length != mEntryKeys.length || mEntryKeys.length != mInitialValues.length) { 1.153 + throw new IllegalStateException( 1.154 + "MultiChoicePreference entries, entryKeys, and initialValues arrays must be the same length"); 1.155 + } 1.156 + 1.157 + builder.setMultiChoiceItems(mEntries, mValues, new DialogInterface.OnMultiChoiceClickListener() { 1.158 + @Override 1.159 + public void onClick(DialogInterface dialog, int which, boolean val) { 1.160 + // mValues is automatically updated when checkboxes are clicked 1.161 + 1.162 + // enable positive button only if at least one item is checked 1.163 + boolean enabled = false; 1.164 + for (int i = 0; i < mValues.length; i++) { 1.165 + if (mValues[i]) { 1.166 + enabled = true; 1.167 + break; 1.168 + } 1.169 + } 1.170 + Button button = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE); 1.171 + if (button.isEnabled() != enabled) 1.172 + button.setEnabled(enabled); 1.173 + } 1.174 + }); 1.175 + } 1.176 + 1.177 + @Override 1.178 + protected void onDialogClosed(boolean positiveResult) { 1.179 + if (mPrevValues == null || mInitialValues == null) { 1.180 + // Initialization is done asynchronously, so these values may not 1.181 + // have been set before the dialog was closed. 1.182 + return; 1.183 + } 1.184 + 1.185 + if (!positiveResult) { 1.186 + // user cancelled; reset checkbox values to their previous state 1.187 + mValues = mPrevValues.clone(); 1.188 + return; 1.189 + } else { 1.190 + mPrevValues = mValues.clone(); 1.191 + } 1.192 + 1.193 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.194 + @Override 1.195 + public void run() { 1.196 + for (int i = 0; i < mEntryKeys.length; i++) { 1.197 + String key = mEntryKeys[i].toString(); 1.198 + persistBoolean(key, mValues[i]); 1.199 + } 1.200 + } 1.201 + }); 1.202 + } 1.203 + 1.204 + protected boolean persistBoolean(String key, boolean value) { 1.205 + if (isPersistent()) { 1.206 + if (value == getPersistedBoolean(!value)) { 1.207 + // It's already there, so the same as persisting 1.208 + return true; 1.209 + } 1.210 + 1.211 + GeckoSharedPrefs.forApp(getContext()) 1.212 + .edit().putBoolean(key, value).commit(); 1.213 + return true; 1.214 + } 1.215 + return false; 1.216 + } 1.217 + 1.218 + protected boolean getPersistedBoolean(String key, boolean defaultReturnValue) { 1.219 + if (!isPersistent()) 1.220 + return defaultReturnValue; 1.221 + 1.222 + return GeckoSharedPrefs.forApp(getContext()) 1.223 + .getBoolean(key, defaultReturnValue); 1.224 + } 1.225 + 1.226 + /** 1.227 + * Loads persistent prefs from shared preferences. If the preferences 1.228 + * aren't persistent or haven't yet been stored, they will be set to their 1.229 + * initial values. 1.230 + */ 1.231 + private void loadPersistedValues() { 1.232 + if (mEntryKeys == null || mInitialValues == null) 1.233 + return; 1.234 + 1.235 + final int entryCount = mEntryKeys.length; 1.236 + if (entryCount != mEntries.length || entryCount != mInitialValues.length) { 1.237 + throw new IllegalStateException( 1.238 + "MultiChoicePreference entryKeys and initialValues arrays must be the same length"); 1.239 + } 1.240 + 1.241 + mValues = new boolean[entryCount]; 1.242 + ThreadUtils.postToBackgroundThread(new Runnable() { 1.243 + @Override 1.244 + public void run() { 1.245 + for (int i = 0; i < entryCount; i++) { 1.246 + String key = mEntryKeys[i].toString(); 1.247 + boolean initialValue = mInitialValues[i].equals("true"); 1.248 + mValues[i] = getPersistedBoolean(key, initialValue); 1.249 + } 1.250 + mPrevValues = mValues.clone(); 1.251 + } 1.252 + }); 1.253 + } 1.254 +}