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