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