1/*
2 * Copyright (C) 2010 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
17package android.preference;
18
19import android.content.Context;
20import android.content.SharedPreferences;
21import android.content.res.TypedArray;
22import android.os.Parcel;
23import android.os.Parcelable;
24import android.text.TextUtils;
25import android.util.AttributeSet;
26import android.view.View;
27import android.widget.TextView;
28
29/**
30 * Common base class for preferences that have two selectable states, persist a
31 * boolean value in SharedPreferences, and may have dependent preferences that are
32 * enabled/disabled based on the current state.
33 */
34public abstract class TwoStatePreference extends Preference {
35
36    private CharSequence mSummaryOn;
37    private CharSequence mSummaryOff;
38    boolean mChecked;
39    private boolean mCheckedSet;
40    private boolean mDisableDependentsState;
41
42    public TwoStatePreference(
43            Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
44        super(context, attrs, defStyleAttr, defStyleRes);
45    }
46
47    public TwoStatePreference(Context context, AttributeSet attrs, int defStyleAttr) {
48        this(context, attrs, defStyleAttr, 0);
49    }
50
51    public TwoStatePreference(Context context, AttributeSet attrs) {
52        this(context, attrs, 0);
53    }
54
55    public TwoStatePreference(Context context) {
56        this(context, null);
57    }
58
59    @Override
60    protected void onClick() {
61        super.onClick();
62
63        final boolean newValue = !isChecked();
64        if (callChangeListener(newValue)) {
65            setChecked(newValue);
66        }
67    }
68
69    /**
70     * Sets the checked state and saves it to the {@link SharedPreferences}.
71     *
72     * @param checked The checked state.
73     */
74    public void setChecked(boolean checked) {
75        // Always persist/notify the first time; don't assume the field's default of false.
76        final boolean changed = mChecked != checked;
77        if (changed || !mCheckedSet) {
78            mChecked = checked;
79            mCheckedSet = true;
80            persistBoolean(checked);
81            if (changed) {
82                notifyDependencyChange(shouldDisableDependents());
83                notifyChanged();
84            }
85        }
86    }
87
88    /**
89     * Returns the checked state.
90     *
91     * @return The checked state.
92     */
93    public boolean isChecked() {
94        return mChecked;
95    }
96
97    @Override
98    public boolean shouldDisableDependents() {
99        boolean shouldDisable = mDisableDependentsState ? mChecked : !mChecked;
100        return shouldDisable || super.shouldDisableDependents();
101    }
102
103    /**
104     * Sets the summary to be shown when checked.
105     *
106     * @param summary The summary to be shown when checked.
107     */
108    public void setSummaryOn(CharSequence summary) {
109        mSummaryOn = summary;
110        if (isChecked()) {
111            notifyChanged();
112        }
113    }
114
115    /**
116     * @see #setSummaryOn(CharSequence)
117     * @param summaryResId The summary as a resource.
118     */
119    public void setSummaryOn(int summaryResId) {
120        setSummaryOn(getContext().getString(summaryResId));
121    }
122
123    /**
124     * Returns the summary to be shown when checked.
125     * @return The summary.
126     */
127    public CharSequence getSummaryOn() {
128        return mSummaryOn;
129    }
130
131    /**
132     * Sets the summary to be shown when unchecked.
133     *
134     * @param summary The summary to be shown when unchecked.
135     */
136    public void setSummaryOff(CharSequence summary) {
137        mSummaryOff = summary;
138        if (!isChecked()) {
139            notifyChanged();
140        }
141    }
142
143    /**
144     * @see #setSummaryOff(CharSequence)
145     * @param summaryResId The summary as a resource.
146     */
147    public void setSummaryOff(int summaryResId) {
148        setSummaryOff(getContext().getString(summaryResId));
149    }
150
151    /**
152     * Returns the summary to be shown when unchecked.
153     * @return The summary.
154     */
155    public CharSequence getSummaryOff() {
156        return mSummaryOff;
157    }
158
159    /**
160     * Returns whether dependents are disabled when this preference is on ({@code true})
161     * or when this preference is off ({@code false}).
162     *
163     * @return Whether dependents are disabled when this preference is on ({@code true})
164     *         or when this preference is off ({@code false}).
165     */
166    public boolean getDisableDependentsState() {
167        return mDisableDependentsState;
168    }
169
170    /**
171     * Sets whether dependents are disabled when this preference is on ({@code true})
172     * or when this preference is off ({@code false}).
173     *
174     * @param disableDependentsState The preference state that should disable dependents.
175     */
176    public void setDisableDependentsState(boolean disableDependentsState) {
177        mDisableDependentsState = disableDependentsState;
178    }
179
180    @Override
181    protected Object onGetDefaultValue(TypedArray a, int index) {
182        return a.getBoolean(index, false);
183    }
184
185    @Override
186    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
187        setChecked(restoreValue ? getPersistedBoolean(mChecked)
188                : (Boolean) defaultValue);
189    }
190
191    /**
192     * Sync a summary view contained within view's subhierarchy with the correct summary text.
193     * @param view View where a summary should be located
194     */
195    void syncSummaryView(View view) {
196        // Sync the summary view
197        TextView summaryView = (TextView) view.findViewById(com.android.internal.R.id.summary);
198        if (summaryView != null) {
199            boolean useDefaultSummary = true;
200            if (mChecked && !TextUtils.isEmpty(mSummaryOn)) {
201                summaryView.setText(mSummaryOn);
202                useDefaultSummary = false;
203            } else if (!mChecked && !TextUtils.isEmpty(mSummaryOff)) {
204                summaryView.setText(mSummaryOff);
205                useDefaultSummary = false;
206            }
207
208            if (useDefaultSummary) {
209                final CharSequence summary = getSummary();
210                if (!TextUtils.isEmpty(summary)) {
211                    summaryView.setText(summary);
212                    useDefaultSummary = false;
213                }
214            }
215
216            int newVisibility = View.GONE;
217            if (!useDefaultSummary) {
218                // Someone has written to it
219                newVisibility = View.VISIBLE;
220            }
221            if (newVisibility != summaryView.getVisibility()) {
222                summaryView.setVisibility(newVisibility);
223            }
224        }
225    }
226
227    @Override
228    protected Parcelable onSaveInstanceState() {
229        final Parcelable superState = super.onSaveInstanceState();
230        if (isPersistent()) {
231            // No need to save instance state since it's persistent
232            return superState;
233        }
234
235        final SavedState myState = new SavedState(superState);
236        myState.checked = isChecked();
237        return myState;
238    }
239
240    @Override
241    protected void onRestoreInstanceState(Parcelable state) {
242        if (state == null || !state.getClass().equals(SavedState.class)) {
243            // Didn't save state for us in onSaveInstanceState
244            super.onRestoreInstanceState(state);
245            return;
246        }
247
248        SavedState myState = (SavedState) state;
249        super.onRestoreInstanceState(myState.getSuperState());
250        setChecked(myState.checked);
251    }
252
253    static class SavedState extends BaseSavedState {
254        boolean checked;
255
256        public SavedState(Parcel source) {
257            super(source);
258            checked = source.readInt() == 1;
259        }
260
261        @Override
262        public void writeToParcel(Parcel dest, int flags) {
263            super.writeToParcel(dest, flags);
264            dest.writeInt(checked ? 1 : 0);
265        }
266
267        public SavedState(Parcelable superState) {
268            super(superState);
269        }
270
271        public static final Parcelable.Creator<SavedState> CREATOR =
272                new Parcelable.Creator<SavedState>() {
273            public SavedState createFromParcel(Parcel in) {
274                return new SavedState(in);
275            }
276
277            public SavedState[] newArray(int size) {
278                return new SavedState[size];
279            }
280        };
281    }
282}
283