| /* |
| * Copyright (C) 2014 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.settings.widget; |
| |
| import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; |
| |
| import android.app.settings.SettingsEnums; |
| import android.content.Context; |
| import android.content.res.TypedArray; |
| import android.graphics.drawable.Drawable; |
| import android.os.Parcel; |
| import android.os.Parcelable; |
| import android.text.SpannableStringBuilder; |
| import android.text.TextUtils; |
| import android.text.style.TextAppearanceSpan; |
| import android.util.AttributeSet; |
| import android.view.LayoutInflater; |
| import android.view.View; |
| import android.view.ViewGroup; |
| import android.widget.CompoundButton; |
| import android.widget.ImageView; |
| import android.widget.LinearLayout; |
| import android.widget.Switch; |
| import android.widget.TextView; |
| |
| import androidx.annotation.ColorInt; |
| import androidx.annotation.StringRes; |
| import androidx.annotation.VisibleForTesting; |
| |
| import com.android.settings.R; |
| import com.android.settings.overlay.FeatureFactory; |
| import com.android.settingslib.RestrictedLockUtils; |
| import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| public class SwitchBar extends LinearLayout implements CompoundButton.OnCheckedChangeListener { |
| |
| public interface OnSwitchChangeListener { |
| /** |
| * Called when the checked state of the Switch has changed. |
| * |
| * @param switchView The Switch view whose state has changed. |
| * @param isChecked The new checked state of switchView. |
| */ |
| void onSwitchChanged(Switch switchView, boolean isChecked); |
| } |
| |
| private static final int[] XML_ATTRIBUTES = { |
| R.attr.switchBarMarginStart, |
| R.attr.switchBarMarginEnd, |
| R.attr.switchBarBackgroundColor, |
| R.attr.switchBarBackgroundActivatedColor, |
| R.attr.switchBarRestrictionIcon}; |
| |
| private final List<OnSwitchChangeListener> mSwitchChangeListeners = new ArrayList<>(); |
| private final MetricsFeatureProvider mMetricsFeatureProvider; |
| private final TextAppearanceSpan mSummarySpan; |
| |
| private ToggleSwitch mSwitch; |
| private ImageView mRestrictedIcon; |
| private TextView mTextView; |
| private String mLabel; |
| private String mSummary; |
| private String mOnText; |
| private String mOffText; |
| @ColorInt |
| private int mBackgroundColor; |
| @ColorInt |
| private int mBackgroundActivatedColor; |
| |
| private boolean mLoggingIntialized; |
| private boolean mDisabledByAdmin; |
| private EnforcedAdmin mEnforcedAdmin = null; |
| private String mMetricsTag; |
| |
| |
| public SwitchBar(Context context) { |
| this(context, null); |
| } |
| |
| public SwitchBar(Context context, AttributeSet attrs) { |
| this(context, attrs, 0); |
| } |
| |
| public SwitchBar(Context context, AttributeSet attrs, int defStyleAttr) { |
| this(context, attrs, defStyleAttr, 0); |
| } |
| |
| public SwitchBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { |
| super(context, attrs, defStyleAttr, defStyleRes); |
| |
| LayoutInflater.from(context).inflate(R.layout.switch_bar, this); |
| // Set the whole SwitchBar focusable and clickable. |
| setFocusable(true); |
| setClickable(true); |
| |
| final TypedArray a = context.obtainStyledAttributes(attrs, XML_ATTRIBUTES); |
| final int switchBarMarginStart = (int) a.getDimension(0, 0); |
| final int switchBarMarginEnd = (int) a.getDimension(1, 0); |
| mBackgroundColor = a.getColor(2, 0); |
| mBackgroundActivatedColor = a.getColor(3, 0); |
| final Drawable restrictedIconDrawable = a.getDrawable(4); |
| a.recycle(); |
| |
| mTextView = findViewById(R.id.switch_text); |
| mSummarySpan = new TextAppearanceSpan(mContext, R.style.TextAppearance_Small_SwitchBar); |
| ViewGroup.MarginLayoutParams lp = (MarginLayoutParams) mTextView.getLayoutParams(); |
| lp.setMarginStart(switchBarMarginStart); |
| |
| mSwitch = findViewById(R.id.switch_widget); |
| // Prevent onSaveInstanceState() to be called as we are managing the state of the Switch |
| // on our own |
| mSwitch.setSaveEnabled(false); |
| // Set the ToggleSwitch non-focusable and non-clickable to avoid multiple focus. |
| mSwitch.setFocusable(false); |
| mSwitch.setClickable(false); |
| |
| lp = (MarginLayoutParams) mSwitch.getLayoutParams(); |
| lp.setMarginEnd(switchBarMarginEnd); |
| setBackgroundColor(mBackgroundColor); |
| |
| setSwitchBarText(R.string.switch_on_text, R.string.switch_off_text); |
| |
| addOnSwitchChangeListener( |
| (switchView, isChecked) -> setTextViewLabelAndBackground(isChecked)); |
| |
| mRestrictedIcon = findViewById(R.id.restricted_icon); |
| mRestrictedIcon.setImageDrawable(restrictedIconDrawable); |
| mRestrictedIcon.setOnClickListener(new View.OnClickListener() { |
| @Override |
| public void onClick(View v) { |
| if (mDisabledByAdmin) { |
| mMetricsFeatureProvider.action( |
| SettingsEnums.PAGE_UNKNOWN, |
| SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE, |
| SettingsEnums.PAGE_UNKNOWN, |
| mMetricsTag + "/switch_bar|restricted", |
| 1); |
| |
| RestrictedLockUtils.sendShowAdminSupportDetailsIntent(context, |
| mEnforcedAdmin); |
| } |
| } |
| }); |
| |
| // Default is hide |
| setVisibility(View.GONE); |
| |
| mMetricsFeatureProvider = FeatureFactory.getFactory(context).getMetricsFeatureProvider(); |
| } |
| |
| // Override the performClick method to eliminate redundant click. |
| @Override |
| public boolean performClick() { |
| return getDelegatingView().performClick(); |
| } |
| |
| public void setMetricsTag(String tag) { |
| mMetricsTag = tag; |
| } |
| |
| public void setTextViewLabelAndBackground(boolean isChecked) { |
| mLabel = isChecked ? mOnText : mOffText; |
| setBackgroundColor(isChecked ? mBackgroundActivatedColor : mBackgroundColor); |
| updateText(); |
| } |
| |
| public void setSwitchBarText(int onTextId, int offTextId) { |
| mOnText = getResources().getString(onTextId); |
| mOffText = getResources().getString(offTextId); |
| setTextViewLabelAndBackground(isChecked()); |
| } |
| |
| public void setSwitchBarText(String onText, String offText) { |
| mOnText = onText; |
| mOffText = offText; |
| setTextViewLabelAndBackground(isChecked()); |
| } |
| |
| public void setSummary(String summary) { |
| mSummary = summary; |
| updateText(); |
| } |
| |
| private void updateText() { |
| if (TextUtils.isEmpty(mSummary)) { |
| mTextView.setText(mLabel); |
| return; |
| } |
| final SpannableStringBuilder ssb = new SpannableStringBuilder(mLabel).append('\n'); |
| final int start = ssb.length(); |
| ssb.append(mSummary); |
| ssb.setSpan(mSummarySpan, start, ssb.length(), 0); |
| mTextView.setText(ssb); |
| } |
| |
| public void setChecked(boolean checked) { |
| setTextViewLabelAndBackground(checked); |
| mSwitch.setChecked(checked); |
| } |
| |
| public void setCheckedInternal(boolean checked) { |
| setTextViewLabelAndBackground(checked); |
| mSwitch.setCheckedInternal(checked); |
| } |
| |
| public boolean isChecked() { |
| return mSwitch.isChecked(); |
| } |
| |
| public void setEnabled(boolean enabled) { |
| if (enabled && mDisabledByAdmin) { |
| setDisabledByAdmin(null); |
| return; |
| } |
| super.setEnabled(enabled); |
| mTextView.setEnabled(enabled); |
| mSwitch.setEnabled(enabled); |
| } |
| |
| @VisibleForTesting |
| View getDelegatingView() { |
| return mDisabledByAdmin ? mRestrictedIcon : mSwitch; |
| } |
| |
| /** |
| * If admin is not null, disables the text and switch but keeps the view clickable. |
| * Otherwise, calls setEnabled which will enables the entire view including |
| * the text and switch. |
| */ |
| public void setDisabledByAdmin(EnforcedAdmin admin) { |
| mEnforcedAdmin = admin; |
| if (admin != null) { |
| super.setEnabled(true); |
| mDisabledByAdmin = true; |
| mTextView.setEnabled(false); |
| mSwitch.setEnabled(false); |
| mSwitch.setVisibility(View.GONE); |
| mRestrictedIcon.setVisibility(View.VISIBLE); |
| } else { |
| mDisabledByAdmin = false; |
| mSwitch.setVisibility(View.VISIBLE); |
| mRestrictedIcon.setVisibility(View.GONE); |
| setEnabled(true); |
| } |
| } |
| |
| public final ToggleSwitch getSwitch() { |
| return mSwitch; |
| } |
| |
| public void show() { |
| if (!isShowing()) { |
| setVisibility(View.VISIBLE); |
| mSwitch.setOnCheckedChangeListener(this); |
| } |
| } |
| |
| public void hide() { |
| if (isShowing()) { |
| setVisibility(View.GONE); |
| mSwitch.setOnCheckedChangeListener(null); |
| } |
| } |
| |
| public boolean isShowing() { |
| return (getVisibility() == View.VISIBLE); |
| } |
| |
| public void propagateChecked(boolean isChecked) { |
| final int count = mSwitchChangeListeners.size(); |
| for (int n = 0; n < count; n++) { |
| mSwitchChangeListeners.get(n).onSwitchChanged(mSwitch, isChecked); |
| } |
| } |
| |
| @Override |
| public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { |
| if (mLoggingIntialized) { |
| mMetricsFeatureProvider.action( |
| SettingsEnums.PAGE_UNKNOWN, |
| SettingsEnums.ACTION_SETTINGS_PREFERENCE_CHANGE, |
| SettingsEnums.PAGE_UNKNOWN, |
| mMetricsTag + "/switch_bar", |
| isChecked ? 1 : 0); |
| } |
| mLoggingIntialized = true; |
| propagateChecked(isChecked); |
| } |
| |
| public void addOnSwitchChangeListener(OnSwitchChangeListener listener) { |
| if (mSwitchChangeListeners.contains(listener)) { |
| throw new IllegalStateException("Cannot add twice the same OnSwitchChangeListener"); |
| } |
| mSwitchChangeListeners.add(listener); |
| } |
| |
| public void removeOnSwitchChangeListener(OnSwitchChangeListener listener) { |
| if (!mSwitchChangeListeners.contains(listener)) { |
| throw new IllegalStateException("Cannot remove OnSwitchChangeListener"); |
| } |
| mSwitchChangeListeners.remove(listener); |
| } |
| |
| static class SavedState extends BaseSavedState { |
| boolean checked; |
| boolean visible; |
| |
| SavedState(Parcelable superState) { |
| super(superState); |
| } |
| |
| /** |
| * Constructor called from {@link #CREATOR} |
| */ |
| private SavedState(Parcel in) { |
| super(in); |
| checked = (Boolean) in.readValue(null); |
| visible = (Boolean) in.readValue(null); |
| } |
| |
| @Override |
| public void writeToParcel(Parcel out, int flags) { |
| super.writeToParcel(out, flags); |
| out.writeValue(checked); |
| out.writeValue(visible); |
| } |
| |
| @Override |
| public String toString() { |
| return "SwitchBar.SavedState{" |
| + Integer.toHexString(System.identityHashCode(this)) |
| + " checked=" + checked |
| + " visible=" + visible + "}"; |
| } |
| |
| public static final Parcelable.Creator<SavedState> CREATOR |
| = new Parcelable.Creator<SavedState>() { |
| public SavedState createFromParcel(Parcel in) { |
| return new SavedState(in); |
| } |
| |
| public SavedState[] newArray(int size) { |
| return new SavedState[size]; |
| } |
| }; |
| } |
| |
| @Override |
| public Parcelable onSaveInstanceState() { |
| Parcelable superState = super.onSaveInstanceState(); |
| |
| SavedState ss = new SavedState(superState); |
| ss.checked = mSwitch.isChecked(); |
| ss.visible = isShowing(); |
| return ss; |
| } |
| |
| @Override |
| public void onRestoreInstanceState(Parcelable state) { |
| SavedState ss = (SavedState) state; |
| |
| super.onRestoreInstanceState(ss.getSuperState()); |
| |
| mSwitch.setCheckedInternal(ss.checked); |
| setTextViewLabelAndBackground(ss.checked); |
| setVisibility(ss.visible ? View.VISIBLE : View.GONE); |
| mSwitch.setOnCheckedChangeListener(ss.visible ? this : null); |
| |
| requestLayout(); |
| } |
| } |