• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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