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