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