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