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