1 /* 2 * Copyright (C) 2007 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 android.widget; 18 19 import static android.view.accessibility.Flags.triStateChecked; 20 21 import android.annotation.DrawableRes; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.compat.annotation.UnsupportedAppUsage; 25 import android.content.Context; 26 import android.content.res.ColorStateList; 27 import android.content.res.TypedArray; 28 import android.graphics.BlendMode; 29 import android.graphics.Canvas; 30 import android.graphics.PorterDuff; 31 import android.graphics.drawable.Drawable; 32 import android.graphics.drawable.Icon; 33 import android.os.Parcel; 34 import android.os.Parcelable; 35 import android.util.AttributeSet; 36 import android.util.Log; 37 import android.view.Gravity; 38 import android.view.RemotableViewMethod; 39 import android.view.SoundEffectConstants; 40 import android.view.ViewDebug; 41 import android.view.ViewHierarchyEncoder; 42 import android.view.ViewStructure; 43 import android.view.accessibility.AccessibilityEvent; 44 import android.view.accessibility.AccessibilityNodeInfo; 45 import android.view.autofill.AutofillManager; 46 import android.view.autofill.AutofillValue; 47 import android.view.inspector.InspectableProperty; 48 49 import com.android.internal.R; 50 51 /** 52 * <p> 53 * A button with two states, checked and unchecked. When the button is pressed 54 * or clicked, the state changes automatically. 55 * </p> 56 * 57 * <p><strong>XML attributes</strong></p> 58 * <p> 59 * See {@link android.R.styleable#CompoundButton 60 * CompoundButton Attributes}, {@link android.R.styleable#Button Button 61 * Attributes}, {@link android.R.styleable#TextView TextView Attributes}, {@link 62 * android.R.styleable#View View Attributes} 63 * </p> 64 */ 65 public abstract class CompoundButton extends Button implements Checkable { 66 private static final String LOG_TAG = CompoundButton.class.getSimpleName(); 67 68 private boolean mChecked; 69 @UnsupportedAppUsage 70 private boolean mBroadcasting; 71 72 @UnsupportedAppUsage 73 private Drawable mButtonDrawable; 74 private ColorStateList mButtonTintList = null; 75 private BlendMode mButtonBlendMode = null; 76 private boolean mHasButtonTint = false; 77 private boolean mHasButtonBlendMode = false; 78 79 @UnsupportedAppUsage 80 private OnCheckedChangeListener mOnCheckedChangeListener; 81 private OnCheckedChangeListener mOnCheckedChangeWidgetListener; 82 83 // Indicates whether the toggle state was set from resources or dynamically, so it can be used 84 // to sanitize autofill requests. 85 private boolean mCheckedFromResource = false; 86 87 private CharSequence mCustomStateDescription = null; 88 89 private static final int[] CHECKED_STATE_SET = { 90 R.attr.state_checked 91 }; 92 CompoundButton(Context context)93 public CompoundButton(Context context) { 94 this(context, null); 95 } 96 CompoundButton(Context context, AttributeSet attrs)97 public CompoundButton(Context context, AttributeSet attrs) { 98 this(context, attrs, 0); 99 } 100 CompoundButton(Context context, AttributeSet attrs, int defStyleAttr)101 public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr) { 102 this(context, attrs, defStyleAttr, 0); 103 } 104 CompoundButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)105 public CompoundButton(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 106 super(context, attrs, defStyleAttr, defStyleRes); 107 108 final TypedArray a = context.obtainStyledAttributes( 109 attrs, com.android.internal.R.styleable.CompoundButton, defStyleAttr, defStyleRes); 110 saveAttributeDataForStyleable(context, com.android.internal.R.styleable.CompoundButton, 111 attrs, a, defStyleAttr, defStyleRes); 112 113 final Drawable d = a.getDrawable(com.android.internal.R.styleable.CompoundButton_button); 114 if (d != null) { 115 setButtonDrawable(d); 116 } 117 118 if (a.hasValue(R.styleable.CompoundButton_buttonTintMode)) { 119 mButtonBlendMode = Drawable.parseBlendMode(a.getInt( 120 R.styleable.CompoundButton_buttonTintMode, -1), mButtonBlendMode); 121 mHasButtonBlendMode = true; 122 } 123 124 if (a.hasValue(R.styleable.CompoundButton_buttonTint)) { 125 mButtonTintList = a.getColorStateList(R.styleable.CompoundButton_buttonTint); 126 mHasButtonTint = true; 127 } 128 129 final boolean checked = a.getBoolean( 130 com.android.internal.R.styleable.CompoundButton_checked, false); 131 setChecked(checked); 132 mCheckedFromResource = true; 133 134 a.recycle(); 135 136 applyButtonTint(); 137 } 138 139 @Override toggle()140 public void toggle() { 141 setChecked(!mChecked); 142 } 143 144 @Override performClick()145 public boolean performClick() { 146 toggle(); 147 148 final boolean handled = super.performClick(); 149 if (!handled) { 150 // View only makes a sound effect if the onClickListener was 151 // called, so we'll need to make one here instead. 152 playSoundEffect(SoundEffectConstants.CLICK); 153 } 154 155 return handled; 156 } 157 158 @InspectableProperty 159 @ViewDebug.ExportedProperty 160 @Override isChecked()161 public boolean isChecked() { 162 return mChecked; 163 } 164 165 /** @hide */ 166 @NonNull getButtonStateDescription()167 protected CharSequence getButtonStateDescription() { 168 if (isChecked()) { 169 return getResources().getString(R.string.checked); 170 } else { 171 return getResources().getString(R.string.not_checked); 172 } 173 } 174 175 /** 176 * This function is called when an instance or subclass sets the state description. Once this 177 * is called and the argument is not null, the app developer will be responsible for updating 178 * state description when checked state changes and we will not set state description 179 * in {@link #setChecked}. App developers can restore the default behavior by setting the 180 * argument to null. If {@link #setChecked} is called first and then setStateDescription is 181 * called, two state change events will be merged by event throttling and we can still get 182 * the correct state description. 183 * 184 * @param stateDescription The state description. 185 */ 186 @Override setStateDescription(@ullable CharSequence stateDescription)187 public void setStateDescription(@Nullable CharSequence stateDescription) { 188 mCustomStateDescription = stateDescription; 189 if (stateDescription == null) { 190 setDefaultStateDescription(); 191 } else { 192 super.setStateDescription(stateDescription); 193 } 194 } 195 196 /** @hide **/ setDefaultStateDescription()197 protected void setDefaultStateDescription() { 198 if (mCustomStateDescription == null) { 199 super.setStateDescription(getButtonStateDescription()); 200 } 201 } 202 203 /** 204 * <p>Changes the checked state of this button.</p> 205 * 206 * @param checked true to check the button, false to uncheck it 207 */ 208 @Override setChecked(boolean checked)209 public void setChecked(boolean checked) { 210 if (mChecked != checked) { 211 mCheckedFromResource = false; 212 mChecked = checked; 213 refreshDrawableState(); 214 if (triStateChecked()) { 215 notifyViewAccessibilityStateChangedIfNeeded( 216 AccessibilityEvent.CONTENT_CHANGE_TYPE_CHECKED); 217 } 218 219 // Avoid infinite recursions if setChecked() is called from a listener 220 if (mBroadcasting) { 221 // setStateDescription will not send out event if the description is unchanged. 222 setDefaultStateDescription(); 223 return; 224 } 225 226 mBroadcasting = true; 227 if (mOnCheckedChangeListener != null) { 228 mOnCheckedChangeListener.onCheckedChanged(this, mChecked); 229 } 230 if (mOnCheckedChangeWidgetListener != null) { 231 mOnCheckedChangeWidgetListener.onCheckedChanged(this, mChecked); 232 } 233 final AutofillManager afm = mContext.getSystemService(AutofillManager.class); 234 if (afm != null) { 235 afm.notifyValueChanged(this); 236 } 237 238 mBroadcasting = false; 239 } 240 // setStateDescription will not send out event if the description is unchanged. 241 setDefaultStateDescription(); 242 } 243 244 /** 245 * Register a callback to be invoked when the checked state of this button 246 * changes. 247 * 248 * @param listener the callback to call on checked state change 249 */ setOnCheckedChangeListener(@ullable OnCheckedChangeListener listener)250 public void setOnCheckedChangeListener(@Nullable OnCheckedChangeListener listener) { 251 mOnCheckedChangeListener = listener; 252 } 253 254 /** 255 * Register a callback to be invoked when the checked state of this button 256 * changes. This callback is used for internal purpose only. 257 * 258 * @param listener the callback to call on checked state change 259 * @hide 260 */ setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener)261 void setOnCheckedChangeWidgetListener(OnCheckedChangeListener listener) { 262 mOnCheckedChangeWidgetListener = listener; 263 } 264 265 /** 266 * Interface definition for a callback to be invoked when the checked state 267 * of a compound button changed. 268 */ 269 public static interface OnCheckedChangeListener { 270 /** 271 * Called when the checked state of a compound button has changed. 272 * 273 * @param buttonView The compound button view whose state has changed. 274 * @param isChecked The new checked state of buttonView. 275 */ onCheckedChanged(@onNull CompoundButton buttonView, boolean isChecked)276 void onCheckedChanged(@NonNull CompoundButton buttonView, boolean isChecked); 277 } 278 279 /** 280 * Sets a drawable as the compound button image given its resource 281 * identifier. 282 * 283 * @param resId the resource identifier of the drawable 284 * @attr ref android.R.styleable#CompoundButton_button 285 */ 286 @RemotableViewMethod(asyncImpl = "setButtonDrawableAsync") setButtonDrawable(@rawableRes int resId)287 public void setButtonDrawable(@DrawableRes int resId) { 288 final Drawable d; 289 if (resId != 0) { 290 d = getContext().getDrawable(resId); 291 } else { 292 d = null; 293 } 294 setButtonDrawable(d); 295 } 296 297 /** @hide **/ setButtonDrawableAsync(@rawableRes int resId)298 public Runnable setButtonDrawableAsync(@DrawableRes int resId) { 299 Drawable drawable = resId == 0 ? null : getContext().getDrawable(resId); 300 return () -> setButtonDrawable(drawable); 301 } 302 303 /** 304 * Sets a drawable as the compound button image. 305 * 306 * @param drawable the drawable to set 307 * @attr ref android.R.styleable#CompoundButton_button 308 */ setButtonDrawable(@ullable Drawable drawable)309 public void setButtonDrawable(@Nullable Drawable drawable) { 310 if (mButtonDrawable != drawable) { 311 if (mButtonDrawable != null) { 312 mButtonDrawable.setCallback(null); 313 unscheduleDrawable(mButtonDrawable); 314 } 315 316 mButtonDrawable = drawable; 317 318 if (drawable != null) { 319 drawable.setCallback(this); 320 drawable.setLayoutDirection(getLayoutDirection()); 321 if (drawable.isStateful()) { 322 drawable.setState(getDrawableState()); 323 } 324 drawable.setVisible(getVisibility() == VISIBLE, false); 325 setMinHeight(drawable.getIntrinsicHeight()); 326 applyButtonTint(); 327 } 328 } 329 } 330 331 /** 332 * @hide 333 */ 334 @Override onResolveDrawables(@esolvedLayoutDir int layoutDirection)335 public void onResolveDrawables(@ResolvedLayoutDir int layoutDirection) { 336 super.onResolveDrawables(layoutDirection); 337 if (mButtonDrawable != null) { 338 mButtonDrawable.setLayoutDirection(layoutDirection); 339 } 340 } 341 342 /** 343 * @return the drawable used as the compound button image 344 * @see #setButtonDrawable(Drawable) 345 * @see #setButtonDrawable(int) 346 */ 347 @InspectableProperty(name = "button") 348 @Nullable getButtonDrawable()349 public Drawable getButtonDrawable() { 350 return mButtonDrawable; 351 } 352 353 /** 354 * Sets the button of this CompoundButton to the specified Icon. 355 * 356 * @param icon an Icon holding the desired button, or {@code null} to clear 357 * the button 358 */ 359 @RemotableViewMethod(asyncImpl = "setButtonIconAsync") setButtonIcon(@ullable Icon icon)360 public void setButtonIcon(@Nullable Icon icon) { 361 setButtonDrawable(icon == null ? null : icon.loadDrawable(getContext())); 362 } 363 364 /** @hide **/ setButtonIconAsync(@ullable Icon icon)365 public Runnable setButtonIconAsync(@Nullable Icon icon) { 366 Drawable button = icon == null ? null : icon.loadDrawable(getContext()); 367 return () -> setButtonDrawable(button); 368 } 369 370 /** 371 * Applies a tint to the button drawable. Does not modify the current tint 372 * mode, which is {@link PorterDuff.Mode#SRC_IN} by default. 373 * <p> 374 * Subsequent calls to {@link #setButtonDrawable(Drawable)} will 375 * automatically mutate the drawable and apply the specified tint and tint 376 * mode using 377 * {@link Drawable#setTintList(ColorStateList)}. 378 * 379 * @param tint the tint to apply, may be {@code null} to clear tint 380 * 381 * @attr ref android.R.styleable#CompoundButton_buttonTint 382 * @see #setButtonTintList(ColorStateList) 383 * @see Drawable#setTintList(ColorStateList) 384 */ 385 @RemotableViewMethod setButtonTintList(@ullable ColorStateList tint)386 public void setButtonTintList(@Nullable ColorStateList tint) { 387 mButtonTintList = tint; 388 mHasButtonTint = true; 389 390 applyButtonTint(); 391 } 392 393 /** 394 * @return the tint applied to the button drawable 395 * @attr ref android.R.styleable#CompoundButton_buttonTint 396 * @see #setButtonTintList(ColorStateList) 397 */ 398 @InspectableProperty(name = "buttonTint") 399 @Nullable getButtonTintList()400 public ColorStateList getButtonTintList() { 401 return mButtonTintList; 402 } 403 404 /** 405 * Specifies the blending mode used to apply the tint specified by 406 * {@link #setButtonTintList(ColorStateList)}} to the button drawable. The 407 * default mode is {@link PorterDuff.Mode#SRC_IN}. 408 * 409 * @param tintMode the blending mode used to apply the tint, may be 410 * {@code null} to clear tint 411 * @attr ref android.R.styleable#CompoundButton_buttonTintMode 412 * @see #getButtonTintMode() 413 * @see Drawable#setTintMode(PorterDuff.Mode) 414 */ setButtonTintMode(@ullable PorterDuff.Mode tintMode)415 public void setButtonTintMode(@Nullable PorterDuff.Mode tintMode) { 416 setButtonTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null); 417 } 418 419 /** 420 * Specifies the blending mode used to apply the tint specified by 421 * {@link #setButtonTintList(ColorStateList)}} to the button drawable. The 422 * default mode is {@link PorterDuff.Mode#SRC_IN}. 423 * 424 * @param tintMode the blending mode used to apply the tint, may be 425 * {@code null} to clear tint 426 * @attr ref android.R.styleable#CompoundButton_buttonTintMode 427 * @see #getButtonTintMode() 428 * @see Drawable#setTintBlendMode(BlendMode) 429 */ 430 @RemotableViewMethod setButtonTintBlendMode(@ullable BlendMode tintMode)431 public void setButtonTintBlendMode(@Nullable BlendMode tintMode) { 432 mButtonBlendMode = tintMode; 433 mHasButtonBlendMode = true; 434 435 applyButtonTint(); 436 } 437 438 /** 439 * @return the blending mode used to apply the tint to the button drawable 440 * @attr ref android.R.styleable#CompoundButton_buttonTintMode 441 * @see #setButtonTintMode(PorterDuff.Mode) 442 */ 443 @InspectableProperty(name = "buttonTintMode") 444 @Nullable getButtonTintMode()445 public PorterDuff.Mode getButtonTintMode() { 446 return mButtonBlendMode != null ? BlendMode.blendModeToPorterDuffMode(mButtonBlendMode) : 447 null; 448 } 449 450 /** 451 * @return the blending mode used to apply the tint to the button drawable 452 * @attr ref android.R.styleable#CompoundButton_buttonTintMode 453 * @see #setButtonTintBlendMode(BlendMode) 454 */ 455 @InspectableProperty(name = "buttonBlendMode", 456 attributeId = R.styleable.CompoundButton_buttonTintMode) 457 @Nullable getButtonTintBlendMode()458 public BlendMode getButtonTintBlendMode() { 459 return mButtonBlendMode; 460 } 461 applyButtonTint()462 private void applyButtonTint() { 463 if (mButtonDrawable != null && (mHasButtonTint || mHasButtonBlendMode)) { 464 mButtonDrawable = mButtonDrawable.mutate(); 465 466 if (mHasButtonTint) { 467 mButtonDrawable.setTintList(mButtonTintList); 468 } 469 470 if (mHasButtonBlendMode) { 471 mButtonDrawable.setTintBlendMode(mButtonBlendMode); 472 } 473 474 // The drawable (or one of its children) may not have been 475 // stateful before applying the tint, so let's try again. 476 if (mButtonDrawable.isStateful()) { 477 mButtonDrawable.setState(getDrawableState()); 478 } 479 } 480 } 481 482 @Override getAccessibilityClassName()483 public CharSequence getAccessibilityClassName() { 484 return CompoundButton.class.getName(); 485 } 486 487 /** @hide */ 488 @Override onInitializeAccessibilityEventInternal(AccessibilityEvent event)489 public void onInitializeAccessibilityEventInternal(AccessibilityEvent event) { 490 super.onInitializeAccessibilityEventInternal(event); 491 event.setChecked(mChecked); 492 } 493 494 /** @hide */ 495 @Override onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info)496 public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { 497 super.onInitializeAccessibilityNodeInfoInternal(info); 498 info.setCheckable(true); 499 if (triStateChecked()) { 500 info.setChecked(mChecked ? AccessibilityNodeInfo.CHECKED_STATE_TRUE : 501 AccessibilityNodeInfo.CHECKED_STATE_FALSE); 502 } else { 503 info.setChecked(mChecked); 504 } 505 } 506 507 @Override getCompoundPaddingLeft()508 public int getCompoundPaddingLeft() { 509 int padding = super.getCompoundPaddingLeft(); 510 if (!isLayoutRtl()) { 511 final Drawable buttonDrawable = mButtonDrawable; 512 if (buttonDrawable != null) { 513 padding += buttonDrawable.getIntrinsicWidth(); 514 } 515 } 516 return padding; 517 } 518 519 @Override getCompoundPaddingRight()520 public int getCompoundPaddingRight() { 521 int padding = super.getCompoundPaddingRight(); 522 if (isLayoutRtl()) { 523 final Drawable buttonDrawable = mButtonDrawable; 524 if (buttonDrawable != null) { 525 padding += buttonDrawable.getIntrinsicWidth(); 526 } 527 } 528 return padding; 529 } 530 531 /** 532 * @hide 533 */ 534 @Override getHorizontalOffsetForDrawables()535 public int getHorizontalOffsetForDrawables() { 536 final Drawable buttonDrawable = mButtonDrawable; 537 return (buttonDrawable != null) ? buttonDrawable.getIntrinsicWidth() : 0; 538 } 539 540 @Override onDraw(Canvas canvas)541 protected void onDraw(Canvas canvas) { 542 final Drawable buttonDrawable = mButtonDrawable; 543 if (buttonDrawable != null) { 544 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK; 545 final int drawableHeight = buttonDrawable.getIntrinsicHeight(); 546 final int drawableWidth = buttonDrawable.getIntrinsicWidth(); 547 548 final int top; 549 switch (verticalGravity) { 550 case Gravity.BOTTOM: 551 top = getHeight() - drawableHeight; 552 break; 553 case Gravity.CENTER_VERTICAL: 554 top = (getHeight() - drawableHeight) / 2; 555 break; 556 default: 557 top = 0; 558 } 559 final int bottom = top + drawableHeight; 560 final int left = isLayoutRtl() ? getWidth() - drawableWidth : 0; 561 final int right = isLayoutRtl() ? getWidth() : drawableWidth; 562 563 buttonDrawable.setBounds(left, top, right, bottom); 564 565 final Drawable background = getBackground(); 566 if (background != null) { 567 background.setHotspotBounds(left, top, right, bottom); 568 } 569 } 570 571 super.onDraw(canvas); 572 573 if (buttonDrawable != null) { 574 final int scrollX = mScrollX; 575 final int scrollY = mScrollY; 576 if (scrollX == 0 && scrollY == 0) { 577 buttonDrawable.draw(canvas); 578 } else { 579 canvas.translate(scrollX, scrollY); 580 buttonDrawable.draw(canvas); 581 canvas.translate(-scrollX, -scrollY); 582 } 583 } 584 } 585 586 @Override onCreateDrawableState(int extraSpace)587 protected int[] onCreateDrawableState(int extraSpace) { 588 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 589 if (isChecked()) { 590 mergeDrawableStates(drawableState, CHECKED_STATE_SET); 591 } 592 return drawableState; 593 } 594 595 @Override drawableStateChanged()596 protected void drawableStateChanged() { 597 super.drawableStateChanged(); 598 599 final Drawable buttonDrawable = mButtonDrawable; 600 if (buttonDrawable != null && buttonDrawable.isStateful() 601 && buttonDrawable.setState(getDrawableState())) { 602 invalidateDrawable(buttonDrawable); 603 } 604 } 605 606 @Override drawableHotspotChanged(float x, float y)607 public void drawableHotspotChanged(float x, float y) { 608 super.drawableHotspotChanged(x, y); 609 610 if (mButtonDrawable != null) { 611 mButtonDrawable.setHotspot(x, y); 612 } 613 } 614 615 @Override verifyDrawable(@onNull Drawable who)616 protected boolean verifyDrawable(@NonNull Drawable who) { 617 return super.verifyDrawable(who) || who == mButtonDrawable; 618 } 619 620 @Override jumpDrawablesToCurrentState()621 public void jumpDrawablesToCurrentState() { 622 super.jumpDrawablesToCurrentState(); 623 if (mButtonDrawable != null) mButtonDrawable.jumpToCurrentState(); 624 } 625 626 static class SavedState extends BaseSavedState { 627 boolean checked; 628 629 /** 630 * Constructor called from {@link CompoundButton#onSaveInstanceState()} 631 */ SavedState(Parcelable superState)632 SavedState(Parcelable superState) { 633 super(superState); 634 } 635 636 /** 637 * Constructor called from {@link #CREATOR} 638 */ SavedState(Parcel in)639 private SavedState(Parcel in) { 640 super(in); 641 checked = (Boolean)in.readValue(null); 642 } 643 644 @Override writeToParcel(Parcel out, int flags)645 public void writeToParcel(Parcel out, int flags) { 646 super.writeToParcel(out, flags); 647 out.writeValue(checked); 648 } 649 650 @Override toString()651 public String toString() { 652 return "CompoundButton.SavedState{" 653 + Integer.toHexString(System.identityHashCode(this)) 654 + " checked=" + checked + "}"; 655 } 656 657 @SuppressWarnings("hiding") 658 public static final @android.annotation.NonNull Parcelable.Creator<SavedState> CREATOR = 659 new Parcelable.Creator<SavedState>() { 660 @Override 661 public SavedState createFromParcel(Parcel in) { 662 return new SavedState(in); 663 } 664 665 @Override 666 public SavedState[] newArray(int size) { 667 return new SavedState[size]; 668 } 669 }; 670 } 671 672 @Override onSaveInstanceState()673 public Parcelable onSaveInstanceState() { 674 Parcelable superState = super.onSaveInstanceState(); 675 676 SavedState ss = new SavedState(superState); 677 678 ss.checked = isChecked(); 679 return ss; 680 } 681 682 @Override onRestoreInstanceState(Parcelable state)683 public void onRestoreInstanceState(Parcelable state) { 684 SavedState ss = (SavedState) state; 685 686 super.onRestoreInstanceState(ss.getSuperState()); 687 setChecked(ss.checked); 688 requestLayout(); 689 } 690 691 /** @hide */ 692 @Override encodeProperties(@onNull ViewHierarchyEncoder stream)693 protected void encodeProperties(@NonNull ViewHierarchyEncoder stream) { 694 super.encodeProperties(stream); 695 stream.addProperty("checked", isChecked()); 696 } 697 698 699 /** @hide */ 700 @Override onProvideStructure(@onNull ViewStructure structure, @ViewStructureType int viewFor, int flags)701 protected void onProvideStructure(@NonNull ViewStructure structure, 702 @ViewStructureType int viewFor, int flags) { 703 super.onProvideStructure(structure, viewFor, flags); 704 705 if (viewFor == VIEW_STRUCTURE_FOR_AUTOFILL) { 706 structure.setDataIsSensitive(!mCheckedFromResource); 707 } 708 } 709 710 @Override autofill(AutofillValue value)711 public void autofill(AutofillValue value) { 712 if (!isEnabled()) return; 713 714 if (!value.isToggle()) { 715 Log.w(LOG_TAG, value + " could not be autofilled into " + this); 716 return; 717 } 718 719 setChecked(value.getToggleValue()); 720 } 721 722 @Override getAutofillType()723 public @AutofillType int getAutofillType() { 724 return isEnabled() ? AUTOFILL_TYPE_TOGGLE : AUTOFILL_TYPE_NONE; 725 } 726 727 @Override getAutofillValue()728 public AutofillValue getAutofillValue() { 729 return isEnabled() ? AutofillValue.forToggle(isChecked()) : null; 730 } 731 } 732