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