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