• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2014 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 
17 package com.android.settings.widget;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.os.Parcel;
22 import android.os.Parcelable;
23 import android.text.SpannableStringBuilder;
24 import android.text.TextUtils;
25 import android.text.style.TextAppearanceSpan;
26 import android.util.AttributeSet;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.view.accessibility.AccessibilityEvent;
31 import android.view.accessibility.AccessibilityNodeInfo;
32 import android.widget.CompoundButton;
33 import android.widget.LinearLayout;
34 import android.widget.Switch;
35 import android.widget.TextView;
36 
37 import com.android.internal.logging.MetricsLogger;
38 import com.android.settings.R;
39 import com.android.settingslib.RestrictedLockUtils;
40 
41 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
42 
43 import java.util.ArrayList;
44 
45 public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedChangeListener,
46         View.OnClickListener {
47 
48     public interface OnSwitchChangeListener {
49         /**
50          * Called when the checked state of the Switch has changed.
51          *
52          * @param switchView The Switch view whose state has changed.
53          * @param isChecked  The new checked state of switchView.
54          */
onSwitchChanged(Switch switchView, boolean isChecked)55         void onSwitchChanged(Switch switchView, boolean isChecked);
56     }
57 
58     private final TextAppearanceSpan mSummarySpan;
59 
60     private ToggleSwitch mSwitch;
61     private View mRestrictedIcon;
62     private TextView mTextView;
63     private String mLabel;
64     private String mSummary;
65 
66     private boolean mLoggingIntialized;
67     private boolean mDisabledByAdmin;
68     private EnforcedAdmin mEnforcedAdmin = null;
69 
70     private String mMetricsTag;
71 
72     private ArrayList<OnSwitchChangeListener> mSwitchChangeListeners =
73             new ArrayList<OnSwitchChangeListener>();
74 
75     private static int[] XML_ATTRIBUTES = {
76             R.attr.switchBarMarginStart, R.attr.switchBarMarginEnd,
77             R.attr.switchBarBackgroundColor};
78 
SwitchBar(Context context)79     public SwitchBar(Context context) {
80         this(context, null);
81     }
82 
SwitchBar(Context context, AttributeSet attrs)83     public SwitchBar(Context context, AttributeSet attrs) {
84         this(context, attrs, 0);
85     }
86 
SwitchBar(Context context, AttributeSet attrs, int defStyleAttr)87     public SwitchBar(Context context, AttributeSet attrs, int defStyleAttr) {
88         this(context, attrs, defStyleAttr, 0);
89     }
90 
SwitchBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)91     public SwitchBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
92         super(context, attrs, defStyleAttr, defStyleRes);
93 
94         LayoutInflater.from(context).inflate(R.layout.switch_bar, this);
95 
96         final TypedArray a = context.obtainStyledAttributes(attrs, XML_ATTRIBUTES);
97         int switchBarMarginStart = (int) a.getDimension(0, 0);
98         int switchBarMarginEnd = (int) a.getDimension(1, 0);
99         int switchBarBackgroundColor = (int) a.getColor(2, 0);
100         a.recycle();
101 
102         mTextView = (TextView) findViewById(R.id.switch_text);
103         mTextView.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
104         mLabel = getResources().getString(R.string.switch_off_text);
105         mSummarySpan = new TextAppearanceSpan(mContext, R.style.TextAppearance_Small_SwitchBar);
106         updateText();
107         ViewGroup.MarginLayoutParams lp = (MarginLayoutParams) mTextView.getLayoutParams();
108         lp.setMarginStart(switchBarMarginStart);
109 
110         mSwitch = (ToggleSwitch) findViewById(R.id.switch_widget);
111         // Prevent onSaveInstanceState() to be called as we are managing the state of the Switch
112         // on our own
113         mSwitch.setSaveEnabled(false);
114         mSwitch.setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_NO);
115         lp = (MarginLayoutParams) mSwitch.getLayoutParams();
116         lp.setMarginEnd(switchBarMarginEnd);
117         setBackgroundColor(switchBarBackgroundColor);
118         mSwitch.setBackgroundColor(switchBarBackgroundColor);
119 
120         addOnSwitchChangeListener(new OnSwitchChangeListener() {
121             @Override
122             public void onSwitchChanged(Switch switchView, boolean isChecked) {
123                 setTextViewLabel(isChecked);
124             }
125         });
126 
127         mRestrictedIcon = findViewById(R.id.restricted_icon);
128 
129         setOnClickListener(this);
130 
131         // Default is hide
132         setVisibility(View.GONE);
133     }
134 
setMetricsTag(String tag)135     public void setMetricsTag(String tag) {
136         mMetricsTag = tag;
137     }
138 
setTextViewLabel(boolean isChecked)139     public void setTextViewLabel(boolean isChecked) {
140         mLabel = getResources()
141                 .getString(isChecked ? R.string.switch_on_text : R.string.switch_off_text);
142         updateText();
143     }
144 
setSummary(String summary)145     public void setSummary(String summary) {
146         mSummary = summary;
147         updateText();
148     }
149 
updateText()150     private void updateText() {
151         if (TextUtils.isEmpty(mSummary)) {
152             mTextView.setText(mLabel);
153             return;
154         }
155         final SpannableStringBuilder ssb = new SpannableStringBuilder(mLabel).append('\n');
156         final int start = ssb.length();
157         ssb.append(mSummary);
158         ssb.setSpan(mSummarySpan, start, ssb.length(), 0);
159         mTextView.setText(ssb);
160     }
161 
setChecked(boolean checked)162     public void setChecked(boolean checked) {
163         setTextViewLabel(checked);
164         mSwitch.setChecked(checked);
165     }
166 
setCheckedInternal(boolean checked)167     public void setCheckedInternal(boolean checked) {
168         setTextViewLabel(checked);
169         mSwitch.setCheckedInternal(checked);
170     }
171 
isChecked()172     public boolean isChecked() {
173         return mSwitch.isChecked();
174     }
175 
setEnabled(boolean enabled)176     public void setEnabled(boolean enabled) {
177         if (enabled && mDisabledByAdmin) {
178             setDisabledByAdmin(null);
179             return;
180         }
181         super.setEnabled(enabled);
182         mTextView.setEnabled(enabled);
183         mSwitch.setEnabled(enabled);
184     }
185 
186     /**
187      * If admin is not null, disables the text and switch but keeps the view clickable.
188      * Otherwise, calls setEnabled which will enables the entire view including
189      * the text and switch.
190      */
setDisabledByAdmin(EnforcedAdmin admin)191     public void setDisabledByAdmin(EnforcedAdmin admin) {
192         mEnforcedAdmin = admin;
193         if (admin != null) {
194             super.setEnabled(true);
195             mDisabledByAdmin = true;
196             mTextView.setEnabled(false);
197             mSwitch.setEnabled(false);
198             mSwitch.setVisibility(View.GONE);
199             mRestrictedIcon.setVisibility(View.VISIBLE);
200         } else {
201             mDisabledByAdmin = false;
202             mSwitch.setVisibility(View.VISIBLE);
203             mRestrictedIcon.setVisibility(View.GONE);
204             setEnabled(true);
205         }
206     }
207 
getSwitch()208     public final ToggleSwitch getSwitch() {
209         return mSwitch;
210     }
211 
show()212     public void show() {
213         if (!isShowing()) {
214             setVisibility(View.VISIBLE);
215             mSwitch.setOnCheckedChangeListener(this);
216         }
217     }
218 
hide()219     public void hide() {
220         if (isShowing()) {
221             setVisibility(View.GONE);
222             mSwitch.setOnCheckedChangeListener(null);
223         }
224     }
225 
isShowing()226     public boolean isShowing() {
227         return (getVisibility() == View.VISIBLE);
228     }
229 
230     @Override
onClick(View v)231     public void onClick(View v) {
232         if (mDisabledByAdmin) {
233             MetricsLogger.count(mContext, mMetricsTag + "/switch_bar|restricted", 1);
234             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin);
235         } else {
236             final boolean isChecked = !mSwitch.isChecked();
237             setChecked(isChecked);
238         }
239     }
240 
propagateChecked(boolean isChecked)241     public void propagateChecked(boolean isChecked) {
242         final int count = mSwitchChangeListeners.size();
243         for (int n = 0; n < count; n++) {
244             mSwitchChangeListeners.get(n).onSwitchChanged(mSwitch, isChecked);
245         }
246     }
247 
248     @Override
onCheckedChanged(CompoundButton buttonView, boolean isChecked)249     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
250         if (mLoggingIntialized) {
251             MetricsLogger.count(mContext, mMetricsTag + "/switch_bar|" + isChecked, 1);
252         }
253         mLoggingIntialized = true;
254         propagateChecked(isChecked);
255     }
256 
addOnSwitchChangeListener(OnSwitchChangeListener listener)257     public void addOnSwitchChangeListener(OnSwitchChangeListener listener) {
258         if (mSwitchChangeListeners.contains(listener)) {
259             throw new IllegalStateException("Cannot add twice the same OnSwitchChangeListener");
260         }
261         mSwitchChangeListeners.add(listener);
262     }
263 
removeOnSwitchChangeListener(OnSwitchChangeListener listener)264     public void removeOnSwitchChangeListener(OnSwitchChangeListener listener) {
265         if (!mSwitchChangeListeners.contains(listener)) {
266             throw new IllegalStateException("Cannot remove OnSwitchChangeListener");
267         }
268         mSwitchChangeListeners.remove(listener);
269     }
270 
271     static class SavedState extends BaseSavedState {
272         boolean checked;
273         boolean visible;
274 
SavedState(Parcelable superState)275         SavedState(Parcelable superState) {
276             super(superState);
277         }
278 
279         /**
280          * Constructor called from {@link #CREATOR}
281          */
SavedState(Parcel in)282         private SavedState(Parcel in) {
283             super(in);
284             checked = (Boolean)in.readValue(null);
285             visible = (Boolean)in.readValue(null);
286         }
287 
288         @Override
writeToParcel(Parcel out, int flags)289         public void writeToParcel(Parcel out, int flags) {
290             super.writeToParcel(out, flags);
291             out.writeValue(checked);
292             out.writeValue(visible);
293         }
294 
295         @Override
toString()296         public String toString() {
297             return "SwitchBar.SavedState{"
298                     + Integer.toHexString(System.identityHashCode(this))
299                     + " checked=" + checked
300                     + " visible=" + visible + "}";
301         }
302 
303         public static final Parcelable.Creator<SavedState> CREATOR
304                 = new Parcelable.Creator<SavedState>() {
305             public SavedState createFromParcel(Parcel in) {
306                 return new SavedState(in);
307             }
308 
309             public SavedState[] newArray(int size) {
310                 return new SavedState[size];
311             }
312         };
313     }
314 
315     @Override
onSaveInstanceState()316     public Parcelable onSaveInstanceState() {
317         Parcelable superState = super.onSaveInstanceState();
318 
319         SavedState ss = new SavedState(superState);
320         ss.checked = mSwitch.isChecked();
321         ss.visible = isShowing();
322         return ss;
323     }
324 
325     @Override
onRestoreInstanceState(Parcelable state)326     public void onRestoreInstanceState(Parcelable state) {
327         SavedState ss = (SavedState) state;
328 
329         super.onRestoreInstanceState(ss.getSuperState());
330 
331         mSwitch.setCheckedInternal(ss.checked);
332         setTextViewLabel(ss.checked);
333         setVisibility(ss.visible ? View.VISIBLE : View.GONE);
334         mSwitch.setOnCheckedChangeListener(ss.visible ? this : null);
335 
336         requestLayout();
337     }
338 
339     @Override
getAccessibilityClassName()340     public CharSequence getAccessibilityClassName() {
341         return Switch.class.getName();
342     }
343 
344     /** @hide */
345     @Override
onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)346     public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
347         super.onInitializeAccessibilityNodeInfoInternal(info);
348         info.setText(mTextView.getText());
349         info.setCheckable(true);
350         info.setChecked(mSwitch.isChecked());
351     }
352 
353     /** @hide */
354     @Override
onInitializeAccessibilityEventInternal(AccessibilityEvent event)355     public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) {
356         super.onInitializeAccessibilityEventInternal(event);
357         // Don't say "on on" or "off off" - rather, speak the state only once. We need to specify
358         // this explicitly as each of our children (the textview and the checkbox) contribute to
359         // the state once, giving us duplicate text by default.
360         event.setContentDescription(mTextView.getText());
361         event.setChecked(mSwitch.isChecked());
362     }
363 }
364