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 */ onSwitchChanged(Switch switchView, boolean isChecked)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 SwitchBar(Context context)78 public SwitchBar(Context context) { 79 this(context, null); 80 } 81 SwitchBar(Context context, AttributeSet attrs)82 public SwitchBar(Context context, AttributeSet attrs) { 83 this(context, attrs, 0); 84 } 85 SwitchBar(Context context, AttributeSet attrs, int defStyleAttr)86 public SwitchBar(Context context, AttributeSet attrs, int defStyleAttr) { 87 this(context, attrs, defStyleAttr, 0); 88 } 89 SwitchBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)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 setMetricsTag(String tag)134 public void setMetricsTag(String tag) { 135 mMetricsTag = tag; 136 } 137 setTextViewLabel(boolean isChecked)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 setSummary(String summary)144 public void setSummary(String summary) { 145 mSummary = summary; 146 updateText(); 147 } 148 updateText()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 setChecked(boolean checked)161 public void setChecked(boolean checked) { 162 setTextViewLabel(checked); 163 mSwitch.setChecked(checked); 164 } 165 setCheckedInternal(boolean checked)166 public void setCheckedInternal(boolean checked) { 167 setTextViewLabel(checked); 168 mSwitch.setCheckedInternal(checked); 169 } 170 isChecked()171 public boolean isChecked() { 172 return mSwitch.isChecked(); 173 } 174 setEnabled(boolean enabled)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 */ setDisabledByAdmin(EnforcedAdmin admin)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 getSwitch()207 public final ToggleSwitch getSwitch() { 208 return mSwitch; 209 } 210 show()211 public void show() { 212 if (!isShowing()) { 213 setVisibility(View.VISIBLE); 214 mSwitch.setOnCheckedChangeListener(this); 215 } 216 } 217 hide()218 public void hide() { 219 if (isShowing()) { 220 setVisibility(View.GONE); 221 mSwitch.setOnCheckedChangeListener(null); 222 } 223 } 224 isShowing()225 public boolean isShowing() { 226 return (getVisibility() == View.VISIBLE); 227 } 228 229 @Override onClick(View v)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 propagateChecked(boolean isChecked)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 onCheckedChanged(CompoundButton buttonView, boolean isChecked)249 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 250 propagateChecked(isChecked); 251 } 252 addOnSwitchChangeListener(OnSwitchChangeListener listener)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 removeOnSwitchChangeListener(OnSwitchChangeListener listener)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 SavedState(Parcelable superState)271 SavedState(Parcelable superState) { 272 super(superState); 273 } 274 275 /** 276 * Constructor called from {@link #CREATOR} 277 */ SavedState(Parcel in)278 private SavedState(Parcel in) { 279 super(in); 280 checked = (Boolean)in.readValue(null); 281 visible = (Boolean)in.readValue(null); 282 } 283 284 @Override writeToParcel(Parcel out, int flags)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 toString()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 onSaveInstanceState()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 onRestoreInstanceState(Parcelable state)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 getAccessibilityClassName()336 public CharSequence getAccessibilityClassName() { 337 return Switch.class.getName(); 338 } 339 340 /** @hide */ 341 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)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 onInitializeAccessibilityEventInternal(AccessibilityEvent event)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