michael@0: /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: package org.mozilla.gecko.preferences; michael@0: michael@0: import org.mozilla.gecko.R; michael@0: import org.mozilla.gecko.GeckoSharedPrefs; michael@0: import org.mozilla.gecko.util.ThreadUtils; michael@0: michael@0: import android.app.AlertDialog; michael@0: import android.app.AlertDialog.Builder; michael@0: import android.content.Context; michael@0: import android.content.DialogInterface; michael@0: import android.content.res.TypedArray; michael@0: import android.preference.DialogPreference; michael@0: import android.util.AttributeSet; michael@0: import android.widget.Button; michael@0: michael@0: class MultiChoicePreference extends DialogPreference { michael@0: private static final String LOGTAG = "GeckoMultiChoicePreference"; michael@0: michael@0: private boolean mValues[]; michael@0: private boolean mPrevValues[]; michael@0: private CharSequence mEntryKeys[]; michael@0: private CharSequence mEntries[]; michael@0: private CharSequence mInitialValues[]; michael@0: michael@0: public MultiChoicePreference(Context context, AttributeSet attrs) { michael@0: super(context, attrs); michael@0: michael@0: TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MultiChoicePreference); michael@0: mEntries = a.getTextArray(R.styleable.MultiChoicePreference_entries); michael@0: mEntryKeys = a.getTextArray(R.styleable.MultiChoicePreference_entryKeys); michael@0: mInitialValues = a.getTextArray(R.styleable.MultiChoicePreference_initialValues); michael@0: a.recycle(); michael@0: michael@0: loadPersistedValues(); michael@0: } michael@0: michael@0: public MultiChoicePreference(Context context) { michael@0: this(context, null); michael@0: } michael@0: michael@0: /** michael@0: * Sets the human-readable entries to be shown in the list. This will be michael@0: * shown in subsequent dialogs. michael@0: *
michael@0: * Each entry must have a corresponding index in michael@0: * {@link #setEntryKeys(CharSequence[])} and michael@0: * {@link #setInitialValues(CharSequence[])}. michael@0: * michael@0: * @param entries The entries. michael@0: */ michael@0: public void setEntries(CharSequence[] entries) { michael@0: mEntries = entries.clone(); michael@0: } michael@0: michael@0: /** michael@0: * @param entriesResId The entries array as a resource. michael@0: */ michael@0: public void setEntries(int entriesResId) { michael@0: setEntries(getContext().getResources().getTextArray(entriesResId)); michael@0: } michael@0: michael@0: /** michael@0: * Sets the preference keys for preferences shown in the list. michael@0: * michael@0: * @param entryKeys The entry keys. michael@0: */ michael@0: public void setEntryKeys(CharSequence[] entryKeys) { michael@0: mEntryKeys = entryKeys.clone(); michael@0: loadPersistedValues(); michael@0: } michael@0: michael@0: /** michael@0: * @param entryKeysResId The entryKeys array as a resource. michael@0: */ michael@0: public void setEntryKeys(int entryKeysResId) { michael@0: setEntryKeys(getContext().getResources().getTextArray(entryKeysResId)); michael@0: } michael@0: michael@0: /** michael@0: * The array of initial entry values in this list. Each entryValue michael@0: * corresponds to an entryKey. These values are used if a) the preference michael@0: * isn't persisted, or b) the preference is persisted but hasn't yet been michael@0: * set. michael@0: * michael@0: * @param initialValues The entry values michael@0: */ michael@0: public void setInitialValues(CharSequence[] initialValues) { michael@0: mInitialValues = initialValues.clone(); michael@0: loadPersistedValues(); michael@0: } michael@0: michael@0: /** michael@0: * @param initialValuesResId The initialValues array as a resource. michael@0: */ michael@0: public void setInitialValues(int initialValuesResId) { michael@0: setInitialValues(getContext().getResources().getTextArray(initialValuesResId)); michael@0: } michael@0: michael@0: /** michael@0: * The list of translated strings corresponding to each preference. michael@0: * michael@0: * @return The array of entries. michael@0: */ michael@0: public CharSequence[] getEntries() { michael@0: return mEntries.clone(); michael@0: } michael@0: michael@0: /** michael@0: * The list of keys corresponding to each preference. michael@0: * michael@0: * @return The array of keys. michael@0: */ michael@0: public CharSequence[] getEntryKeys() { michael@0: return mEntryKeys.clone(); michael@0: } michael@0: michael@0: /** michael@0: * The list of initial values for each preference. Each string in this list michael@0: * should be either "true" or "false". michael@0: * michael@0: * @return The array of initial values. michael@0: */ michael@0: public CharSequence[] getInitialValues() { michael@0: return mInitialValues.clone(); michael@0: } michael@0: michael@0: /** michael@0: * The list of values for each preference. These values are updated after michael@0: * the dialog has been displayed. michael@0: * michael@0: * @return The array of values. michael@0: */ michael@0: public boolean[] getValues() { michael@0: return mValues.clone(); michael@0: } michael@0: michael@0: @Override michael@0: protected void onPrepareDialogBuilder(Builder builder) { michael@0: if (mEntries == null || mEntryKeys == null || mInitialValues == null) { michael@0: throw new IllegalStateException( michael@0: "MultiChoicePreference requires entries, entryKeys, and initialValues arrays."); michael@0: } michael@0: michael@0: if (mEntries.length != mEntryKeys.length || mEntryKeys.length != mInitialValues.length) { michael@0: throw new IllegalStateException( michael@0: "MultiChoicePreference entries, entryKeys, and initialValues arrays must be the same length"); michael@0: } michael@0: michael@0: builder.setMultiChoiceItems(mEntries, mValues, new DialogInterface.OnMultiChoiceClickListener() { michael@0: @Override michael@0: public void onClick(DialogInterface dialog, int which, boolean val) { michael@0: // mValues is automatically updated when checkboxes are clicked michael@0: michael@0: // enable positive button only if at least one item is checked michael@0: boolean enabled = false; michael@0: for (int i = 0; i < mValues.length; i++) { michael@0: if (mValues[i]) { michael@0: enabled = true; michael@0: break; michael@0: } michael@0: } michael@0: Button button = ((AlertDialog) dialog).getButton(DialogInterface.BUTTON_POSITIVE); michael@0: if (button.isEnabled() != enabled) michael@0: button.setEnabled(enabled); michael@0: } michael@0: }); michael@0: } michael@0: michael@0: @Override michael@0: protected void onDialogClosed(boolean positiveResult) { michael@0: if (mPrevValues == null || mInitialValues == null) { michael@0: // Initialization is done asynchronously, so these values may not michael@0: // have been set before the dialog was closed. michael@0: return; michael@0: } michael@0: michael@0: if (!positiveResult) { michael@0: // user cancelled; reset checkbox values to their previous state michael@0: mValues = mPrevValues.clone(); michael@0: return; michael@0: } else { michael@0: mPrevValues = mValues.clone(); michael@0: } michael@0: michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: for (int i = 0; i < mEntryKeys.length; i++) { michael@0: String key = mEntryKeys[i].toString(); michael@0: persistBoolean(key, mValues[i]); michael@0: } michael@0: } michael@0: }); michael@0: } michael@0: michael@0: protected boolean persistBoolean(String key, boolean value) { michael@0: if (isPersistent()) { michael@0: if (value == getPersistedBoolean(!value)) { michael@0: // It's already there, so the same as persisting michael@0: return true; michael@0: } michael@0: michael@0: GeckoSharedPrefs.forApp(getContext()) michael@0: .edit().putBoolean(key, value).commit(); michael@0: return true; michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: protected boolean getPersistedBoolean(String key, boolean defaultReturnValue) { michael@0: if (!isPersistent()) michael@0: return defaultReturnValue; michael@0: michael@0: return GeckoSharedPrefs.forApp(getContext()) michael@0: .getBoolean(key, defaultReturnValue); michael@0: } michael@0: michael@0: /** michael@0: * Loads persistent prefs from shared preferences. If the preferences michael@0: * aren't persistent or haven't yet been stored, they will be set to their michael@0: * initial values. michael@0: */ michael@0: private void loadPersistedValues() { michael@0: if (mEntryKeys == null || mInitialValues == null) michael@0: return; michael@0: michael@0: final int entryCount = mEntryKeys.length; michael@0: if (entryCount != mEntries.length || entryCount != mInitialValues.length) { michael@0: throw new IllegalStateException( michael@0: "MultiChoicePreference entryKeys and initialValues arrays must be the same length"); michael@0: } michael@0: michael@0: mValues = new boolean[entryCount]; michael@0: ThreadUtils.postToBackgroundThread(new Runnable() { michael@0: @Override michael@0: public void run() { michael@0: for (int i = 0; i < entryCount; i++) { michael@0: String key = mEntryKeys[i].toString(); michael@0: boolean initialValue = mInitialValues[i].equals("true"); michael@0: mValues[i] = getPersistedBoolean(key, initialValue); michael@0: } michael@0: mPrevValues = mValues.clone(); michael@0: } michael@0: }); michael@0: } michael@0: }