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