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