1 /*
2  * Copyright (C) 2020 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.settingslib.widget;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.drawable.Drawable;
22 import android.os.Parcel;
23 import android.os.Parcelable;
24 import android.util.AttributeSet;
25 import android.view.LayoutInflater;
26 import android.view.View;
27 import android.widget.CompoundButton;
28 import android.widget.LinearLayout;
29 import android.widget.Switch;
30 import android.widget.TextView;
31 
32 import androidx.annotation.ColorInt;
33 
34 import com.android.settingslib.utils.BuildCompatUtils;
35 
36 import java.util.ArrayList;
37 import java.util.List;
38 
39 /**
40  * MainSwitchBar is a View with a customized Switch.
41  * This component is used as the main switch of the page
42  * to enable or disable the prefereces on the page.
43  */
44 public class MainSwitchBar extends LinearLayout implements CompoundButton.OnCheckedChangeListener {
45 
46     private final List<OnMainSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>();
47 
48     @ColorInt
49     private int mBackgroundColor;
50     @ColorInt
51     private int mBackgroundActivatedColor;
52 
53     protected TextView mTextView;
54     protected Switch mSwitch;
55     private Drawable mBackgroundOn;
56     private Drawable mBackgroundOff;
57     private Drawable mBackgroundDisabled;
58     private View mFrameView;
59 
MainSwitchBar(Context context)60     public MainSwitchBar(Context context) {
61         this(context, null);
62     }
63 
MainSwitchBar(Context context, AttributeSet attrs)64     public MainSwitchBar(Context context, AttributeSet attrs) {
65         this(context, attrs, 0);
66     }
67 
MainSwitchBar(Context context, AttributeSet attrs, int defStyleAttr)68     public MainSwitchBar(Context context, AttributeSet attrs, int defStyleAttr) {
69         this(context, attrs, defStyleAttr, 0);
70     }
71 
MainSwitchBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)72     public MainSwitchBar(Context context, AttributeSet attrs, int defStyleAttr,
73             int defStyleRes) {
74         super(context, attrs, defStyleAttr, defStyleRes);
75 
76         LayoutInflater.from(context).inflate(R.layout.settingslib_main_switch_bar, this);
77 
78         if (!BuildCompatUtils.isAtLeastS()) {
79             final TypedArray a = context.obtainStyledAttributes(
80                     new int[]{android.R.attr.colorAccent});
81             mBackgroundActivatedColor = a.getColor(0, 0);
82             mBackgroundColor = context.getColor(R.color.material_grey_600);
83             a.recycle();
84         }
85 
86         setFocusable(true);
87         setClickable(true);
88 
89         mFrameView = findViewById(R.id.frame);
90         mTextView = (TextView) findViewById(R.id.switch_text);
91         mSwitch = (Switch) findViewById(android.R.id.switch_widget);
92         if (BuildCompatUtils.isAtLeastS()) {
93             mBackgroundOn = getContext().getDrawable(R.drawable.settingslib_switch_bar_bg_on);
94             mBackgroundOff = getContext().getDrawable(R.drawable.settingslib_switch_bar_bg_off);
95             mBackgroundDisabled = getContext().getDrawable(
96                     R.drawable.settingslib_switch_bar_bg_disabled);
97         }
98         addOnSwitchChangeListener((switchView, isChecked) -> setChecked(isChecked));
99 
100         if (mSwitch.getVisibility() == VISIBLE) {
101             mSwitch.setOnCheckedChangeListener(this);
102         }
103 
104         setChecked(mSwitch.isChecked());
105 
106         if (attrs != null) {
107             final TypedArray a = context.obtainStyledAttributes(attrs,
108                     androidx.preference.R.styleable.Preference, 0 /*defStyleAttr*/,
109                     0 /*defStyleRes*/);
110             final CharSequence title = a.getText(
111                     androidx.preference.R.styleable.Preference_android_title);
112             setTitle(title);
113             a.recycle();
114         }
115 
116         setBackground(mSwitch.isChecked());
117     }
118 
119     @Override
onCheckedChanged(CompoundButton buttonView, boolean isChecked)120     public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
121         propagateChecked(isChecked);
122     }
123 
124     @Override
performClick()125     public boolean performClick() {
126         mSwitch.performClick();
127         return super.performClick();
128     }
129 
130     /**
131      * Update the switch status
132      */
setChecked(boolean checked)133     public void setChecked(boolean checked) {
134         if (mSwitch != null) {
135             mSwitch.setChecked(checked);
136         }
137         setBackground(checked);
138     }
139 
140     /**
141      * Return the status of the Switch
142      */
isChecked()143     public boolean isChecked() {
144         return mSwitch.isChecked();
145     }
146 
147     /**
148      * Return the Switch
149      */
getSwitch()150     public final Switch getSwitch() {
151         return mSwitch;
152     }
153 
154     /**
155      * Set the title text
156      */
setTitle(CharSequence text)157     public void setTitle(CharSequence text) {
158         if (mTextView != null) {
159             mTextView.setText(text);
160         }
161     }
162 
163     /**
164      * Set icon space reserved for title
165      */
setIconSpaceReserved(boolean iconSpaceReserved)166     public void setIconSpaceReserved(boolean iconSpaceReserved) {
167         if (mTextView != null && !BuildCompatUtils.isAtLeastS()) {
168             LayoutParams params = (LayoutParams) mTextView.getLayoutParams();
169             int iconSpace = getContext().getResources().getDimensionPixelSize(
170                     R.dimen.settingslib_switchbar_subsettings_margin_start);
171             params.setMarginStart(iconSpaceReserved ? iconSpace : 0);
172             mTextView.setLayoutParams(params);
173         }
174     }
175 
176     /**
177      * Show the MainSwitchBar
178      */
show()179     public void show() {
180         setVisibility(View.VISIBLE);
181         mSwitch.setOnCheckedChangeListener(this);
182     }
183 
184     /**
185      * Hide the MainSwitchBar
186      */
hide()187     public void hide() {
188         if (isShowing()) {
189             setVisibility(View.GONE);
190             mSwitch.setOnCheckedChangeListener(null);
191         }
192     }
193 
194     /**
195      * Return the displaying status of MainSwitchBar
196      */
isShowing()197     public boolean isShowing() {
198         return (getVisibility() == View.VISIBLE);
199     }
200 
201     /**
202      * Adds a listener for switch changes
203      */
addOnSwitchChangeListener(OnMainSwitchChangeListener listener)204     public void addOnSwitchChangeListener(OnMainSwitchChangeListener listener) {
205         if (!mSwitchChangeListeners.contains(listener)) {
206             mSwitchChangeListeners.add(listener);
207         }
208     }
209 
210     /**
211      * Remove a listener for switch changes
212      */
removeOnSwitchChangeListener(OnMainSwitchChangeListener listener)213     public void removeOnSwitchChangeListener(OnMainSwitchChangeListener listener) {
214         mSwitchChangeListeners.remove(listener);
215     }
216 
217     /**
218      * Enable or disable the text and switch.
219      */
setEnabled(boolean enabled)220     public void setEnabled(boolean enabled) {
221         super.setEnabled(enabled);
222         mTextView.setEnabled(enabled);
223         mSwitch.setEnabled(enabled);
224 
225         if (BuildCompatUtils.isAtLeastS()) {
226             if (enabled) {
227                 mFrameView.setBackground(isChecked() ? mBackgroundOn : mBackgroundOff);
228             } else {
229                 mFrameView.setBackground(mBackgroundDisabled);
230             }
231         }
232     }
233 
propagateChecked(boolean isChecked)234     private void propagateChecked(boolean isChecked) {
235         setBackground(isChecked);
236 
237         final int count = mSwitchChangeListeners.size();
238         for (int n = 0; n < count; n++) {
239             mSwitchChangeListeners.get(n).onSwitchChanged(mSwitch, isChecked);
240         }
241     }
242 
setBackground(boolean isChecked)243     private void setBackground(boolean isChecked) {
244         if (!BuildCompatUtils.isAtLeastS()) {
245             setBackgroundColor(isChecked ? mBackgroundActivatedColor : mBackgroundColor);
246         } else {
247             mFrameView.setBackground(isChecked ? mBackgroundOn : mBackgroundOff);
248         }
249     }
250 
251     static class SavedState extends BaseSavedState {
252         boolean mChecked;
253         boolean mVisible;
254 
SavedState(Parcelable superState)255         SavedState(Parcelable superState) {
256             super(superState);
257         }
258 
259         /**
260          * Constructor called from {@link #CREATOR}
261          */
SavedState(Parcel in)262         private SavedState(Parcel in) {
263             super(in);
264             mChecked = (Boolean) in.readValue(null);
265             mVisible = (Boolean) in.readValue(null);
266         }
267 
268         @Override
writeToParcel(Parcel out, int flags)269         public void writeToParcel(Parcel out, int flags) {
270             super.writeToParcel(out, flags);
271             out.writeValue(mChecked);
272             out.writeValue(mVisible);
273         }
274 
275         @Override
toString()276         public String toString() {
277             return "MainSwitchBar.SavedState{"
278                     + Integer.toHexString(System.identityHashCode(this))
279                     + " checked=" + mChecked
280                     + " visible=" + mVisible + "}";
281         }
282 
283         public static final Parcelable.Creator<SavedState> CREATOR =
284                 new Parcelable.Creator<SavedState>() {
285                     @Override
286                     public SavedState createFromParcel(Parcel in) {
287                         return new SavedState(in);
288                     }
289 
290                     @Override
291                     public SavedState[] newArray(int size) {
292                         return new SavedState[size];
293                     }
294                 };
295     }
296 
297     @Override
onSaveInstanceState()298     public Parcelable onSaveInstanceState() {
299         Parcelable superState = super.onSaveInstanceState();
300 
301         SavedState ss = new SavedState(superState);
302         ss.mChecked = mSwitch.isChecked();
303         ss.mVisible = isShowing();
304         return ss;
305     }
306 
307     @Override
onRestoreInstanceState(Parcelable state)308     public void onRestoreInstanceState(Parcelable state) {
309         SavedState ss = (SavedState) state;
310 
311         super.onRestoreInstanceState(ss.getSuperState());
312 
313         mSwitch.setChecked(ss.mChecked);
314         setChecked(ss.mChecked);
315         setBackground(ss.mChecked);
316         setVisibility(ss.mVisible ? View.VISIBLE : View.GONE);
317         mSwitch.setOnCheckedChangeListener(ss.mVisible ? this : null);
318 
319         requestLayout();
320     }
321 }
322