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