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