1/*
2 * Copyright (C) 2007 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.app.Service;
20import android.content.Context;
21import android.content.SharedPreferences;
22import android.content.res.TypedArray;
23import android.os.Parcel;
24import android.os.Parcelable;
25import android.util.AttributeSet;
26import android.view.View;
27import android.view.accessibility.AccessibilityEvent;
28import android.view.accessibility.AccessibilityManager;
29import android.widget.Checkable;
30import android.widget.TextView;
31
32/**
33 * A {@link Preference} that provides checkbox widget
34 * functionality.
35 * <p>
36 * This preference will store a boolean into the SharedPreferences.
37 *
38 * @attr ref android.R.styleable#CheckBoxPreference_summaryOff
39 * @attr ref android.R.styleable#CheckBoxPreference_summaryOn
40 * @attr ref android.R.styleable#CheckBoxPreference_disableDependentsState
41 */
42public class CheckBoxPreference extends Preference {
43
44    private CharSequence mSummaryOn;
45    private CharSequence mSummaryOff;
46
47    private boolean mChecked;
48    private boolean mSendAccessibilityEventViewClickedType;
49
50    private AccessibilityManager mAccessibilityManager;
51
52    private boolean mDisableDependentsState;
53
54    public CheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
55        super(context, attrs, defStyle);
56
57        TypedArray a = context.obtainStyledAttributes(attrs,
58                com.android.internal.R.styleable.CheckBoxPreference, defStyle, 0);
59        mSummaryOn = a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOn);
60        mSummaryOff = a.getString(com.android.internal.R.styleable.CheckBoxPreference_summaryOff);
61        mDisableDependentsState = a.getBoolean(
62                com.android.internal.R.styleable.CheckBoxPreference_disableDependentsState, false);
63        a.recycle();
64
65        mAccessibilityManager =
66            (AccessibilityManager) getContext().getSystemService(Service.ACCESSIBILITY_SERVICE);
67    }
68
69    public CheckBoxPreference(Context context, AttributeSet attrs) {
70        this(context, attrs, com.android.internal.R.attr.checkBoxPreferenceStyle);
71    }
72
73    public CheckBoxPreference(Context context) {
74        this(context, null);
75    }
76
77    @Override
78    protected void onBindView(View view) {
79        super.onBindView(view);
80
81        View checkboxView = view.findViewById(com.android.internal.R.id.checkbox);
82        if (checkboxView != null && checkboxView instanceof Checkable) {
83            ((Checkable) checkboxView).setChecked(mChecked);
84
85            // send an event to announce the value change of the CheckBox and is done here
86            // because clicking a preference does not immediately change the checked state
87            // for example when enabling the WiFi
88            if (mSendAccessibilityEventViewClickedType &&
89                    mAccessibilityManager.isEnabled() &&
90                    checkboxView.isEnabled()) {
91                mSendAccessibilityEventViewClickedType = false;
92
93                int eventType = AccessibilityEvent.TYPE_VIEW_CLICKED;
94                checkboxView.sendAccessibilityEventUnchecked(AccessibilityEvent.obtain(eventType));
95            }
96        }
97
98        // Sync the summary view
99        TextView summaryView = (TextView) view.findViewById(com.android.internal.R.id.summary);
100        if (summaryView != null) {
101            boolean useDefaultSummary = true;
102            if (mChecked && mSummaryOn != null) {
103                summaryView.setText(mSummaryOn);
104                useDefaultSummary = false;
105            } else if (!mChecked && mSummaryOff != null) {
106                summaryView.setText(mSummaryOff);
107                useDefaultSummary = false;
108            }
109
110            if (useDefaultSummary) {
111                final CharSequence summary = getSummary();
112                if (summary != null) {
113                    summaryView.setText(summary);
114                    useDefaultSummary = false;
115                }
116            }
117
118            int newVisibility = View.GONE;
119            if (!useDefaultSummary) {
120                // Someone has written to it
121                newVisibility = View.VISIBLE;
122            }
123            if (newVisibility != summaryView.getVisibility()) {
124                summaryView.setVisibility(newVisibility);
125            }
126        }
127    }
128
129    @Override
130    protected void onClick() {
131        super.onClick();
132
133        boolean newValue = !isChecked();
134
135        // in onBindView() an AccessibilityEventViewClickedType is sent to announce the change
136        // not sending
137        mSendAccessibilityEventViewClickedType = true;
138
139        if (!callChangeListener(newValue)) {
140            return;
141        }
142
143        setChecked(newValue);
144    }
145
146    /**
147     * Sets the checked state and saves it to the {@link SharedPreferences}.
148     *
149     * @param checked The checked state.
150     */
151    public void setChecked(boolean checked) {
152        if (mChecked != checked) {
153            mChecked = checked;
154            persistBoolean(checked);
155            notifyDependencyChange(shouldDisableDependents());
156            notifyChanged();
157        }
158    }
159
160    /**
161     * Returns the checked state.
162     *
163     * @return The checked state.
164     */
165    public boolean isChecked() {
166        return mChecked;
167    }
168
169    @Override
170    public boolean shouldDisableDependents() {
171        boolean shouldDisable = mDisableDependentsState ? mChecked : !mChecked;
172        return shouldDisable || super.shouldDisableDependents();
173    }
174
175    /**
176     * Sets the summary to be shown when checked.
177     *
178     * @param summary The summary to be shown when checked.
179     */
180    public void setSummaryOn(CharSequence summary) {
181        mSummaryOn = summary;
182        if (isChecked()) {
183            notifyChanged();
184        }
185    }
186
187    /**
188     * @see #setSummaryOn(CharSequence)
189     * @param summaryResId The summary as a resource.
190     */
191    public void setSummaryOn(int summaryResId) {
192        setSummaryOn(getContext().getString(summaryResId));
193    }
194
195    /**
196     * Returns the summary to be shown when checked.
197     * @return The summary.
198     */
199    public CharSequence getSummaryOn() {
200        return mSummaryOn;
201    }
202
203    /**
204     * Sets the summary to be shown when unchecked.
205     *
206     * @param summary The summary to be shown when unchecked.
207     */
208    public void setSummaryOff(CharSequence summary) {
209        mSummaryOff = summary;
210        if (!isChecked()) {
211            notifyChanged();
212        }
213    }
214
215    /**
216     * @see #setSummaryOff(CharSequence)
217     * @param summaryResId The summary as a resource.
218     */
219    public void setSummaryOff(int summaryResId) {
220        setSummaryOff(getContext().getString(summaryResId));
221    }
222
223    /**
224     * Returns the summary to be shown when unchecked.
225     * @return The summary.
226     */
227    public CharSequence getSummaryOff() {
228        return mSummaryOff;
229    }
230
231    /**
232     * Returns whether dependents are disabled when this preference is on ({@code true})
233     * or when this preference is off ({@code false}).
234     *
235     * @return Whether dependents are disabled when this preference is on ({@code true})
236     *         or when this preference is off ({@code false}).
237     */
238    public boolean getDisableDependentsState() {
239        return mDisableDependentsState;
240    }
241
242    /**
243     * Sets whether dependents are disabled when this preference is on ({@code true})
244     * or when this preference is off ({@code false}).
245     *
246     * @param disableDependentsState The preference state that should disable dependents.
247     */
248    public void setDisableDependentsState(boolean disableDependentsState) {
249        mDisableDependentsState = disableDependentsState;
250    }
251
252    @Override
253    protected Object onGetDefaultValue(TypedArray a, int index) {
254        return a.getBoolean(index, false);
255    }
256
257    @Override
258    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
259        setChecked(restoreValue ? getPersistedBoolean(mChecked)
260                : (Boolean) defaultValue);
261    }
262
263    @Override
264    protected Parcelable onSaveInstanceState() {
265        final Parcelable superState = super.onSaveInstanceState();
266        if (isPersistent()) {
267            // No need to save instance state since it's persistent
268            return superState;
269        }
270
271        final SavedState myState = new SavedState(superState);
272        myState.checked = isChecked();
273        return myState;
274    }
275
276    @Override
277    protected void onRestoreInstanceState(Parcelable state) {
278        if (state == null || !state.getClass().equals(SavedState.class)) {
279            // Didn't save state for us in onSaveInstanceState
280            super.onRestoreInstanceState(state);
281            return;
282        }
283
284        SavedState myState = (SavedState) state;
285        super.onRestoreInstanceState(myState.getSuperState());
286        setChecked(myState.checked);
287    }
288
289    private static class SavedState extends BaseSavedState {
290        boolean checked;
291
292        public SavedState(Parcel source) {
293            super(source);
294            checked = source.readInt() == 1;
295        }
296
297        @Override
298        public void writeToParcel(Parcel dest, int flags) {
299            super.writeToParcel(dest, flags);
300            dest.writeInt(checked ? 1 : 0);
301        }
302
303        public SavedState(Parcelable superState) {
304            super(superState);
305        }
306
307        public static final Parcelable.Creator<SavedState> CREATOR =
308                new Parcelable.Creator<SavedState>() {
309            public SavedState createFromParcel(Parcel in) {
310                return new SavedState(in);
311            }
312
313            public SavedState[] newArray(int size) {
314                return new SavedState[size];
315            }
316        };
317    }
318
319}
320