1/*
2 * Copyright (C) 2010 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
17package android.widget;
18
19import android.animation.ObjectAnimator;
20import android.annotation.DrawableRes;
21import android.annotation.NonNull;
22import android.annotation.Nullable;
23import android.annotation.StyleRes;
24import android.content.Context;
25import android.content.res.ColorStateList;
26import android.content.res.Resources;
27import android.content.res.TypedArray;
28import android.graphics.Canvas;
29import android.graphics.Insets;
30import android.graphics.Paint;
31import android.graphics.PorterDuff;
32import android.graphics.Rect;
33import android.graphics.Region.Op;
34import android.graphics.Typeface;
35import android.graphics.drawable.Drawable;
36import android.text.Layout;
37import android.text.StaticLayout;
38import android.text.TextPaint;
39import android.text.TextUtils;
40import android.text.method.AllCapsTransformationMethod;
41import android.text.method.TransformationMethod2;
42import android.util.AttributeSet;
43import android.util.FloatProperty;
44import android.util.MathUtils;
45import android.view.Gravity;
46import android.view.MotionEvent;
47import android.view.SoundEffectConstants;
48import android.view.VelocityTracker;
49import android.view.ViewConfiguration;
50import android.view.ViewStructure;
51import android.view.accessibility.AccessibilityEvent;
52import android.view.accessibility.AccessibilityNodeInfo;
53
54import com.android.internal.R;
55
56/**
57 * A Switch is a two-state toggle switch widget that can select between two
58 * options. The user may drag the "thumb" back and forth to choose the selected option,
59 * or simply tap to toggle as if it were a checkbox. The {@link #setText(CharSequence) text}
60 * property controls the text displayed in the label for the switch, whereas the
61 * {@link #setTextOff(CharSequence) off} and {@link #setTextOn(CharSequence) on} text
62 * controls the text on the thumb. Similarly, the
63 * {@link #setTextAppearance(android.content.Context, int) textAppearance} and the related
64 * setTypeface() methods control the typeface and style of label text, whereas the
65 * {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and
66 * the related setSwitchTypeface() methods control that of the thumb.
67 *
68 * <p>{@link android.support.v7.widget.SwitchCompat} is a version of
69 * the Switch widget which runs on devices back to API 7.</p>
70 *
71 * <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a>
72 * guide.</p>
73 *
74 * @attr ref android.R.styleable#Switch_textOn
75 * @attr ref android.R.styleable#Switch_textOff
76 * @attr ref android.R.styleable#Switch_switchMinWidth
77 * @attr ref android.R.styleable#Switch_switchPadding
78 * @attr ref android.R.styleable#Switch_switchTextAppearance
79 * @attr ref android.R.styleable#Switch_thumb
80 * @attr ref android.R.styleable#Switch_thumbTextPadding
81 * @attr ref android.R.styleable#Switch_track
82 */
83public class Switch extends CompoundButton {
84    private static final int THUMB_ANIMATION_DURATION = 250;
85
86    private static final int TOUCH_MODE_IDLE = 0;
87    private static final int TOUCH_MODE_DOWN = 1;
88    private static final int TOUCH_MODE_DRAGGING = 2;
89
90    // Enum for the "typeface" XML parameter.
91    private static final int SANS = 1;
92    private static final int SERIF = 2;
93    private static final int MONOSPACE = 3;
94
95    private Drawable mThumbDrawable;
96    private ColorStateList mThumbTintList = null;
97    private PorterDuff.Mode mThumbTintMode = null;
98    private boolean mHasThumbTint = false;
99    private boolean mHasThumbTintMode = false;
100
101    private Drawable mTrackDrawable;
102    private ColorStateList mTrackTintList = null;
103    private PorterDuff.Mode mTrackTintMode = null;
104    private boolean mHasTrackTint = false;
105    private boolean mHasTrackTintMode = false;
106
107    private int mThumbTextPadding;
108    private int mSwitchMinWidth;
109    private int mSwitchPadding;
110    private boolean mSplitTrack;
111    private CharSequence mTextOn;
112    private CharSequence mTextOff;
113    private boolean mShowText;
114
115    private int mTouchMode;
116    private int mTouchSlop;
117    private float mTouchX;
118    private float mTouchY;
119    private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
120    private int mMinFlingVelocity;
121
122    private float mThumbPosition;
123
124    /**
125     * Width required to draw the switch track and thumb. Includes padding and
126     * optical bounds for both the track and thumb.
127     */
128    private int mSwitchWidth;
129
130    /**
131     * Height required to draw the switch track and thumb. Includes padding and
132     * optical bounds for both the track and thumb.
133     */
134    private int mSwitchHeight;
135
136    /**
137     * Width of the thumb's content region. Does not include padding or
138     * optical bounds.
139     */
140    private int mThumbWidth;
141
142    /** Left bound for drawing the switch track and thumb. */
143    private int mSwitchLeft;
144
145    /** Top bound for drawing the switch track and thumb. */
146    private int mSwitchTop;
147
148    /** Right bound for drawing the switch track and thumb. */
149    private int mSwitchRight;
150
151    /** Bottom bound for drawing the switch track and thumb. */
152    private int mSwitchBottom;
153
154    private TextPaint mTextPaint;
155    private ColorStateList mTextColors;
156    private Layout mOnLayout;
157    private Layout mOffLayout;
158    private TransformationMethod2 mSwitchTransformationMethod;
159    private ObjectAnimator mPositionAnimator;
160
161    @SuppressWarnings("hiding")
162    private final Rect mTempRect = new Rect();
163
164    private static final int[] CHECKED_STATE_SET = {
165        R.attr.state_checked
166    };
167
168    /**
169     * Construct a new Switch with default styling.
170     *
171     * @param context The Context that will determine this widget's theming.
172     */
173    public Switch(Context context) {
174        this(context, null);
175    }
176
177    /**
178     * Construct a new Switch with default styling, overriding specific style
179     * attributes as requested.
180     *
181     * @param context The Context that will determine this widget's theming.
182     * @param attrs Specification of attributes that should deviate from default styling.
183     */
184    public Switch(Context context, AttributeSet attrs) {
185        this(context, attrs, com.android.internal.R.attr.switchStyle);
186    }
187
188    /**
189     * Construct a new Switch with a default style determined by the given theme attribute,
190     * overriding specific style attributes as requested.
191     *
192     * @param context The Context that will determine this widget's theming.
193     * @param attrs Specification of attributes that should deviate from the default styling.
194     * @param defStyleAttr An attribute in the current theme that contains a
195     *        reference to a style resource that supplies default values for
196     *        the view. Can be 0 to not look for defaults.
197     */
198    public Switch(Context context, AttributeSet attrs, int defStyleAttr) {
199        this(context, attrs, defStyleAttr, 0);
200    }
201
202
203    /**
204     * Construct a new Switch with a default style determined by the given theme
205     * attribute or style resource, overriding specific style attributes as
206     * requested.
207     *
208     * @param context The Context that will determine this widget's theming.
209     * @param attrs Specification of attributes that should deviate from the
210     *        default styling.
211     * @param defStyleAttr An attribute in the current theme that contains a
212     *        reference to a style resource that supplies default values for
213     *        the view. Can be 0 to not look for defaults.
214     * @param defStyleRes A resource identifier of a style resource that
215     *        supplies default values for the view, used only if
216     *        defStyleAttr is 0 or can not be found in the theme. Can be 0
217     *        to not look for defaults.
218     */
219    public Switch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
220        super(context, attrs, defStyleAttr, defStyleRes);
221
222        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
223
224        final Resources res = getResources();
225        mTextPaint.density = res.getDisplayMetrics().density;
226        mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
227
228        final TypedArray a = context.obtainStyledAttributes(
229                attrs, com.android.internal.R.styleable.Switch, defStyleAttr, defStyleRes);
230        mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb);
231        if (mThumbDrawable != null) {
232            mThumbDrawable.setCallback(this);
233        }
234        mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
235        if (mTrackDrawable != null) {
236            mTrackDrawable.setCallback(this);
237        }
238        mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
239        mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
240        mShowText = a.getBoolean(com.android.internal.R.styleable.Switch_showText, true);
241        mThumbTextPadding = a.getDimensionPixelSize(
242                com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
243        mSwitchMinWidth = a.getDimensionPixelSize(
244                com.android.internal.R.styleable.Switch_switchMinWidth, 0);
245        mSwitchPadding = a.getDimensionPixelSize(
246                com.android.internal.R.styleable.Switch_switchPadding, 0);
247        mSplitTrack = a.getBoolean(com.android.internal.R.styleable.Switch_splitTrack, false);
248
249        ColorStateList thumbTintList = a.getColorStateList(
250                com.android.internal.R.styleable.Switch_thumbTint);
251        if (thumbTintList != null) {
252            mThumbTintList = thumbTintList;
253            mHasThumbTint = true;
254        }
255        PorterDuff.Mode thumbTintMode = Drawable.parseTintMode(
256                a.getInt(com.android.internal.R.styleable.Switch_thumbTintMode, -1), null);
257        if (mThumbTintMode != thumbTintMode) {
258            mThumbTintMode = thumbTintMode;
259            mHasThumbTintMode = true;
260        }
261        if (mHasThumbTint || mHasThumbTintMode) {
262            applyThumbTint();
263        }
264
265        ColorStateList trackTintList = a.getColorStateList(
266                com.android.internal.R.styleable.Switch_trackTint);
267        if (trackTintList != null) {
268            mTrackTintList = trackTintList;
269            mHasTrackTint = true;
270        }
271        PorterDuff.Mode trackTintMode = Drawable.parseTintMode(
272                a.getInt(com.android.internal.R.styleable.Switch_trackTintMode, -1), null);
273        if (mTrackTintMode != trackTintMode) {
274            mTrackTintMode = trackTintMode;
275            mHasTrackTintMode = true;
276        }
277        if (mHasTrackTint || mHasTrackTintMode) {
278            applyTrackTint();
279        }
280
281        final int appearance = a.getResourceId(
282                com.android.internal.R.styleable.Switch_switchTextAppearance, 0);
283        if (appearance != 0) {
284            setSwitchTextAppearance(context, appearance);
285        }
286        a.recycle();
287
288        final ViewConfiguration config = ViewConfiguration.get(context);
289        mTouchSlop = config.getScaledTouchSlop();
290        mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
291
292        // Refresh display with current params
293        refreshDrawableState();
294        setChecked(isChecked());
295    }
296
297    /**
298     * Sets the switch text color, size, style, hint color, and highlight color
299     * from the specified TextAppearance resource.
300     *
301     * @attr ref android.R.styleable#Switch_switchTextAppearance
302     */
303    public void setSwitchTextAppearance(Context context, @StyleRes int resid) {
304        TypedArray appearance =
305                context.obtainStyledAttributes(resid,
306                        com.android.internal.R.styleable.TextAppearance);
307
308        ColorStateList colors;
309        int ts;
310
311        colors = appearance.getColorStateList(com.android.internal.R.styleable.
312                TextAppearance_textColor);
313        if (colors != null) {
314            mTextColors = colors;
315        } else {
316            // If no color set in TextAppearance, default to the view's textColor
317            mTextColors = getTextColors();
318        }
319
320        ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
321                TextAppearance_textSize, 0);
322        if (ts != 0) {
323            if (ts != mTextPaint.getTextSize()) {
324                mTextPaint.setTextSize(ts);
325                requestLayout();
326            }
327        }
328
329        int typefaceIndex, styleIndex;
330
331        typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
332                TextAppearance_typeface, -1);
333        styleIndex = appearance.getInt(com.android.internal.R.styleable.
334                TextAppearance_textStyle, -1);
335
336        setSwitchTypefaceByIndex(typefaceIndex, styleIndex);
337
338        boolean allCaps = appearance.getBoolean(com.android.internal.R.styleable.
339                TextAppearance_textAllCaps, false);
340        if (allCaps) {
341            mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext());
342            mSwitchTransformationMethod.setLengthChangesAllowed(true);
343        } else {
344            mSwitchTransformationMethod = null;
345        }
346
347        appearance.recycle();
348    }
349
350    private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) {
351        Typeface tf = null;
352        switch (typefaceIndex) {
353            case SANS:
354                tf = Typeface.SANS_SERIF;
355                break;
356
357            case SERIF:
358                tf = Typeface.SERIF;
359                break;
360
361            case MONOSPACE:
362                tf = Typeface.MONOSPACE;
363                break;
364        }
365
366        setSwitchTypeface(tf, styleIndex);
367    }
368
369    /**
370     * Sets the typeface and style in which the text should be displayed on the
371     * switch, and turns on the fake bold and italic bits in the Paint if the
372     * Typeface that you provided does not have all the bits in the
373     * style that you specified.
374     */
375    public void setSwitchTypeface(Typeface tf, int style) {
376        if (style > 0) {
377            if (tf == null) {
378                tf = Typeface.defaultFromStyle(style);
379            } else {
380                tf = Typeface.create(tf, style);
381            }
382
383            setSwitchTypeface(tf);
384            // now compute what (if any) algorithmic styling is needed
385            int typefaceStyle = tf != null ? tf.getStyle() : 0;
386            int need = style & ~typefaceStyle;
387            mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
388            mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
389        } else {
390            mTextPaint.setFakeBoldText(false);
391            mTextPaint.setTextSkewX(0);
392            setSwitchTypeface(tf);
393        }
394    }
395
396    /**
397     * Sets the typeface in which the text should be displayed on the switch.
398     * Note that not all Typeface families actually have bold and italic
399     * variants, so you may need to use
400     * {@link #setSwitchTypeface(Typeface, int)} to get the appearance
401     * that you actually want.
402     *
403     * @attr ref android.R.styleable#TextView_typeface
404     * @attr ref android.R.styleable#TextView_textStyle
405     */
406    public void setSwitchTypeface(Typeface tf) {
407        if (mTextPaint.getTypeface() != tf) {
408            mTextPaint.setTypeface(tf);
409
410            requestLayout();
411            invalidate();
412        }
413    }
414
415    /**
416     * Set the amount of horizontal padding between the switch and the associated text.
417     *
418     * @param pixels Amount of padding in pixels
419     *
420     * @attr ref android.R.styleable#Switch_switchPadding
421     */
422    public void setSwitchPadding(int pixels) {
423        mSwitchPadding = pixels;
424        requestLayout();
425    }
426
427    /**
428     * Get the amount of horizontal padding between the switch and the associated text.
429     *
430     * @return Amount of padding in pixels
431     *
432     * @attr ref android.R.styleable#Switch_switchPadding
433     */
434    public int getSwitchPadding() {
435        return mSwitchPadding;
436    }
437
438    /**
439     * Set the minimum width of the switch in pixels. The switch's width will be the maximum
440     * of this value and its measured width as determined by the switch drawables and text used.
441     *
442     * @param pixels Minimum width of the switch in pixels
443     *
444     * @attr ref android.R.styleable#Switch_switchMinWidth
445     */
446    public void setSwitchMinWidth(int pixels) {
447        mSwitchMinWidth = pixels;
448        requestLayout();
449    }
450
451    /**
452     * Get the minimum width of the switch in pixels. The switch's width will be the maximum
453     * of this value and its measured width as determined by the switch drawables and text used.
454     *
455     * @return Minimum width of the switch in pixels
456     *
457     * @attr ref android.R.styleable#Switch_switchMinWidth
458     */
459    public int getSwitchMinWidth() {
460        return mSwitchMinWidth;
461    }
462
463    /**
464     * Set the horizontal padding around the text drawn on the switch itself.
465     *
466     * @param pixels Horizontal padding for switch thumb text in pixels
467     *
468     * @attr ref android.R.styleable#Switch_thumbTextPadding
469     */
470    public void setThumbTextPadding(int pixels) {
471        mThumbTextPadding = pixels;
472        requestLayout();
473    }
474
475    /**
476     * Get the horizontal padding around the text drawn on the switch itself.
477     *
478     * @return Horizontal padding for switch thumb text in pixels
479     *
480     * @attr ref android.R.styleable#Switch_thumbTextPadding
481     */
482    public int getThumbTextPadding() {
483        return mThumbTextPadding;
484    }
485
486    /**
487     * Set the drawable used for the track that the switch slides within.
488     *
489     * @param track Track drawable
490     *
491     * @attr ref android.R.styleable#Switch_track
492     */
493    public void setTrackDrawable(Drawable track) {
494        if (mTrackDrawable != null) {
495            mTrackDrawable.setCallback(null);
496        }
497        mTrackDrawable = track;
498        if (track != null) {
499            track.setCallback(this);
500        }
501        requestLayout();
502    }
503
504    /**
505     * Set the drawable used for the track that the switch slides within.
506     *
507     * @param resId Resource ID of a track drawable
508     *
509     * @attr ref android.R.styleable#Switch_track
510     */
511    public void setTrackResource(@DrawableRes int resId) {
512        setTrackDrawable(getContext().getDrawable(resId));
513    }
514
515    /**
516     * Get the drawable used for the track that the switch slides within.
517     *
518     * @return Track drawable
519     *
520     * @attr ref android.R.styleable#Switch_track
521     */
522    public Drawable getTrackDrawable() {
523        return mTrackDrawable;
524    }
525
526    /**
527     * Applies a tint to the track drawable. Does not modify the current
528     * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
529     * <p>
530     * Subsequent calls to {@link #setTrackDrawable(Drawable)} will
531     * automatically mutate the drawable and apply the specified tint and tint
532     * mode using {@link Drawable#setTintList(ColorStateList)}.
533     *
534     * @param tint the tint to apply, may be {@code null} to clear tint
535     *
536     * @attr ref android.R.styleable#Switch_trackTint
537     * @see #getTrackTintList()
538     * @see Drawable#setTintList(ColorStateList)
539     */
540    public void setTrackTintList(@Nullable ColorStateList tint) {
541        mTrackTintList = tint;
542        mHasTrackTint = true;
543
544        applyTrackTint();
545    }
546
547    /**
548     * @return the tint applied to the track drawable
549     * @attr ref android.R.styleable#Switch_trackTint
550     * @see #setTrackTintList(ColorStateList)
551     */
552    @Nullable
553    public ColorStateList getTrackTintList() {
554        return mTrackTintList;
555    }
556
557    /**
558     * Specifies the blending mode used to apply the tint specified by
559     * {@link #setTrackTintList(ColorStateList)}} to the track drawable.
560     * The default mode is {@link PorterDuff.Mode#SRC_IN}.
561     *
562     * @param tintMode the blending mode used to apply the tint, may be
563     *                 {@code null} to clear tint
564     * @attr ref android.R.styleable#Switch_trackTintMode
565     * @see #getTrackTintMode()
566     * @see Drawable#setTintMode(PorterDuff.Mode)
567     */
568    public void setTrackTintMode(@Nullable PorterDuff.Mode tintMode) {
569        mTrackTintMode = tintMode;
570        mHasTrackTintMode = true;
571
572        applyTrackTint();
573    }
574
575    /**
576     * @return the blending mode used to apply the tint to the track
577     *         drawable
578     * @attr ref android.R.styleable#Switch_trackTintMode
579     * @see #setTrackTintMode(PorterDuff.Mode)
580     */
581    @Nullable
582    public PorterDuff.Mode getTrackTintMode() {
583        return mTrackTintMode;
584    }
585
586    private void applyTrackTint() {
587        if (mTrackDrawable != null && (mHasTrackTint || mHasTrackTintMode)) {
588            mTrackDrawable = mTrackDrawable.mutate();
589
590            if (mHasTrackTint) {
591                mTrackDrawable.setTintList(mTrackTintList);
592            }
593
594            if (mHasTrackTintMode) {
595                mTrackDrawable.setTintMode(mTrackTintMode);
596            }
597
598            // The drawable (or one of its children) may not have been
599            // stateful before applying the tint, so let's try again.
600            if (mTrackDrawable.isStateful()) {
601                mTrackDrawable.setState(getDrawableState());
602            }
603        }
604    }
605
606    /**
607     * Set the drawable used for the switch "thumb" - the piece that the user
608     * can physically touch and drag along the track.
609     *
610     * @param thumb Thumb drawable
611     *
612     * @attr ref android.R.styleable#Switch_thumb
613     */
614    public void setThumbDrawable(Drawable thumb) {
615        if (mThumbDrawable != null) {
616            mThumbDrawable.setCallback(null);
617        }
618        mThumbDrawable = thumb;
619        if (thumb != null) {
620            thumb.setCallback(this);
621        }
622        requestLayout();
623    }
624
625    /**
626     * Set the drawable used for the switch "thumb" - the piece that the user
627     * can physically touch and drag along the track.
628     *
629     * @param resId Resource ID of a thumb drawable
630     *
631     * @attr ref android.R.styleable#Switch_thumb
632     */
633    public void setThumbResource(@DrawableRes int resId) {
634        setThumbDrawable(getContext().getDrawable(resId));
635    }
636
637    /**
638     * Get the drawable used for the switch "thumb" - the piece that the user
639     * can physically touch and drag along the track.
640     *
641     * @return Thumb drawable
642     *
643     * @attr ref android.R.styleable#Switch_thumb
644     */
645    public Drawable getThumbDrawable() {
646        return mThumbDrawable;
647    }
648
649    /**
650     * Applies a tint to the thumb drawable. Does not modify the current
651     * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
652     * <p>
653     * Subsequent calls to {@link #setThumbDrawable(Drawable)} will
654     * automatically mutate the drawable and apply the specified tint and tint
655     * mode using {@link Drawable#setTintList(ColorStateList)}.
656     *
657     * @param tint the tint to apply, may be {@code null} to clear tint
658     *
659     * @attr ref android.R.styleable#Switch_thumbTint
660     * @see #getThumbTintList()
661     * @see Drawable#setTintList(ColorStateList)
662     */
663    public void setThumbTintList(@Nullable ColorStateList tint) {
664        mThumbTintList = tint;
665        mHasThumbTint = true;
666
667        applyThumbTint();
668    }
669
670    /**
671     * @return the tint applied to the thumb drawable
672     * @attr ref android.R.styleable#Switch_thumbTint
673     * @see #setThumbTintList(ColorStateList)
674     */
675    @Nullable
676    public ColorStateList getThumbTintList() {
677        return mThumbTintList;
678    }
679
680    /**
681     * Specifies the blending mode used to apply the tint specified by
682     * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable.
683     * The default mode is {@link PorterDuff.Mode#SRC_IN}.
684     *
685     * @param tintMode the blending mode used to apply the tint, may be
686     *                 {@code null} to clear tint
687     * @attr ref android.R.styleable#Switch_thumbTintMode
688     * @see #getThumbTintMode()
689     * @see Drawable#setTintMode(PorterDuff.Mode)
690     */
691    public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) {
692        mThumbTintMode = tintMode;
693        mHasThumbTintMode = true;
694
695        applyThumbTint();
696    }
697
698    /**
699     * @return the blending mode used to apply the tint to the thumb
700     *         drawable
701     * @attr ref android.R.styleable#Switch_thumbTintMode
702     * @see #setThumbTintMode(PorterDuff.Mode)
703     */
704    @Nullable
705    public PorterDuff.Mode getThumbTintMode() {
706        return mThumbTintMode;
707    }
708
709    private void applyThumbTint() {
710        if (mThumbDrawable != null && (mHasThumbTint || mHasThumbTintMode)) {
711            mThumbDrawable = mThumbDrawable.mutate();
712
713            if (mHasThumbTint) {
714                mThumbDrawable.setTintList(mThumbTintList);
715            }
716
717            if (mHasThumbTintMode) {
718                mThumbDrawable.setTintMode(mThumbTintMode);
719            }
720
721            // The drawable (or one of its children) may not have been
722            // stateful before applying the tint, so let's try again.
723            if (mThumbDrawable.isStateful()) {
724                mThumbDrawable.setState(getDrawableState());
725            }
726        }
727    }
728
729    /**
730     * Specifies whether the track should be split by the thumb. When true,
731     * the thumb's optical bounds will be clipped out of the track drawable,
732     * then the thumb will be drawn into the resulting gap.
733     *
734     * @param splitTrack Whether the track should be split by the thumb
735     *
736     * @attr ref android.R.styleable#Switch_splitTrack
737     */
738    public void setSplitTrack(boolean splitTrack) {
739        mSplitTrack = splitTrack;
740        invalidate();
741    }
742
743    /**
744     * Returns whether the track should be split by the thumb.
745     *
746     * @attr ref android.R.styleable#Switch_splitTrack
747     */
748    public boolean getSplitTrack() {
749        return mSplitTrack;
750    }
751
752    /**
753     * Returns the text displayed when the button is in the checked state.
754     *
755     * @attr ref android.R.styleable#Switch_textOn
756     */
757    public CharSequence getTextOn() {
758        return mTextOn;
759    }
760
761    /**
762     * Sets the text displayed when the button is in the checked state.
763     *
764     * @attr ref android.R.styleable#Switch_textOn
765     */
766    public void setTextOn(CharSequence textOn) {
767        mTextOn = textOn;
768        requestLayout();
769    }
770
771    /**
772     * Returns the text displayed when the button is not in the checked state.
773     *
774     * @attr ref android.R.styleable#Switch_textOff
775     */
776    public CharSequence getTextOff() {
777        return mTextOff;
778    }
779
780    /**
781     * Sets the text displayed when the button is not in the checked state.
782     *
783     * @attr ref android.R.styleable#Switch_textOff
784     */
785    public void setTextOff(CharSequence textOff) {
786        mTextOff = textOff;
787        requestLayout();
788    }
789
790    /**
791     * Sets whether the on/off text should be displayed.
792     *
793     * @param showText {@code true} to display on/off text
794     * @attr ref android.R.styleable#Switch_showText
795     */
796    public void setShowText(boolean showText) {
797        if (mShowText != showText) {
798            mShowText = showText;
799            requestLayout();
800        }
801    }
802
803    /**
804     * @return whether the on/off text should be displayed
805     * @attr ref android.R.styleable#Switch_showText
806     */
807    public boolean getShowText() {
808        return mShowText;
809    }
810
811    @Override
812    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
813        if (mShowText) {
814            if (mOnLayout == null) {
815                mOnLayout = makeLayout(mTextOn);
816            }
817
818            if (mOffLayout == null) {
819                mOffLayout = makeLayout(mTextOff);
820            }
821        }
822
823        final Rect padding = mTempRect;
824        final int thumbWidth;
825        final int thumbHeight;
826        if (mThumbDrawable != null) {
827            // Cached thumb width does not include padding.
828            mThumbDrawable.getPadding(padding);
829            thumbWidth = mThumbDrawable.getIntrinsicWidth() - padding.left - padding.right;
830            thumbHeight = mThumbDrawable.getIntrinsicHeight();
831        } else {
832            thumbWidth = 0;
833            thumbHeight = 0;
834        }
835
836        final int maxTextWidth;
837        if (mShowText) {
838            maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
839                    + mThumbTextPadding * 2;
840        } else {
841            maxTextWidth = 0;
842        }
843
844        mThumbWidth = Math.max(maxTextWidth, thumbWidth);
845
846        final int trackHeight;
847        if (mTrackDrawable != null) {
848            mTrackDrawable.getPadding(padding);
849            trackHeight = mTrackDrawable.getIntrinsicHeight();
850        } else {
851            padding.setEmpty();
852            trackHeight = 0;
853        }
854
855        // Adjust left and right padding to ensure there's enough room for the
856        // thumb's padding (when present).
857        int paddingLeft = padding.left;
858        int paddingRight = padding.right;
859        if (mThumbDrawable != null) {
860            final Insets inset = mThumbDrawable.getOpticalInsets();
861            paddingLeft = Math.max(paddingLeft, inset.left);
862            paddingRight = Math.max(paddingRight, inset.right);
863        }
864
865        final int switchWidth = Math.max(mSwitchMinWidth,
866                2 * mThumbWidth + paddingLeft + paddingRight);
867        final int switchHeight = Math.max(trackHeight, thumbHeight);
868        mSwitchWidth = switchWidth;
869        mSwitchHeight = switchHeight;
870
871        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
872
873        final int measuredHeight = getMeasuredHeight();
874        if (measuredHeight < switchHeight) {
875            setMeasuredDimension(getMeasuredWidthAndState(), switchHeight);
876        }
877    }
878
879    /** @hide */
880    @Override
881    public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
882        super.onPopulateAccessibilityEventInternal(event);
883
884        final CharSequence text = isChecked() ? mTextOn : mTextOff;
885        if (text != null) {
886            event.getText().add(text);
887        }
888    }
889
890    private Layout makeLayout(CharSequence text) {
891        final CharSequence transformed = (mSwitchTransformationMethod != null)
892                    ? mSwitchTransformationMethod.getTransformation(text, this)
893                    : text;
894
895        int width = (int) Math.ceil(Layout.getDesiredWidth(transformed, 0,
896                transformed.length(), mTextPaint, getTextDirectionHeuristic()));
897        return new StaticLayout(transformed, mTextPaint, width,
898                Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
899    }
900
901    /**
902     * @return true if (x, y) is within the target area of the switch thumb
903     */
904    private boolean hitThumb(float x, float y) {
905        if (mThumbDrawable == null) {
906            return false;
907        }
908
909        // Relies on mTempRect, MUST be called first!
910        final int thumbOffset = getThumbOffset();
911
912        mThumbDrawable.getPadding(mTempRect);
913        final int thumbTop = mSwitchTop - mTouchSlop;
914        final int thumbLeft = mSwitchLeft + thumbOffset - mTouchSlop;
915        final int thumbRight = thumbLeft + mThumbWidth +
916                mTempRect.left + mTempRect.right + mTouchSlop;
917        final int thumbBottom = mSwitchBottom + mTouchSlop;
918        return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom;
919    }
920
921    @Override
922    public boolean onTouchEvent(MotionEvent ev) {
923        mVelocityTracker.addMovement(ev);
924        final int action = ev.getActionMasked();
925        switch (action) {
926            case MotionEvent.ACTION_DOWN: {
927                final float x = ev.getX();
928                final float y = ev.getY();
929                if (isEnabled() && hitThumb(x, y)) {
930                    mTouchMode = TOUCH_MODE_DOWN;
931                    mTouchX = x;
932                    mTouchY = y;
933                }
934                break;
935            }
936
937            case MotionEvent.ACTION_MOVE: {
938                switch (mTouchMode) {
939                    case TOUCH_MODE_IDLE:
940                        // Didn't target the thumb, treat normally.
941                        break;
942
943                    case TOUCH_MODE_DOWN: {
944                        final float x = ev.getX();
945                        final float y = ev.getY();
946                        if (Math.abs(x - mTouchX) > mTouchSlop ||
947                                Math.abs(y - mTouchY) > mTouchSlop) {
948                            mTouchMode = TOUCH_MODE_DRAGGING;
949                            getParent().requestDisallowInterceptTouchEvent(true);
950                            mTouchX = x;
951                            mTouchY = y;
952                            return true;
953                        }
954                        break;
955                    }
956
957                    case TOUCH_MODE_DRAGGING: {
958                        final float x = ev.getX();
959                        final int thumbScrollRange = getThumbScrollRange();
960                        final float thumbScrollOffset = x - mTouchX;
961                        float dPos;
962                        if (thumbScrollRange != 0) {
963                            dPos = thumbScrollOffset / thumbScrollRange;
964                        } else {
965                            // If the thumb scroll range is empty, just use the
966                            // movement direction to snap on or off.
967                            dPos = thumbScrollOffset > 0 ? 1 : -1;
968                        }
969                        if (isLayoutRtl()) {
970                            dPos = -dPos;
971                        }
972                        final float newPos = MathUtils.constrain(mThumbPosition + dPos, 0, 1);
973                        if (newPos != mThumbPosition) {
974                            mTouchX = x;
975                            setThumbPosition(newPos);
976                        }
977                        return true;
978                    }
979                }
980                break;
981            }
982
983            case MotionEvent.ACTION_UP:
984            case MotionEvent.ACTION_CANCEL: {
985                if (mTouchMode == TOUCH_MODE_DRAGGING) {
986                    stopDrag(ev);
987                    // Allow super class to handle pressed state, etc.
988                    super.onTouchEvent(ev);
989                    return true;
990                }
991                mTouchMode = TOUCH_MODE_IDLE;
992                mVelocityTracker.clear();
993                break;
994            }
995        }
996
997        return super.onTouchEvent(ev);
998    }
999
1000    private void cancelSuperTouch(MotionEvent ev) {
1001        MotionEvent cancel = MotionEvent.obtain(ev);
1002        cancel.setAction(MotionEvent.ACTION_CANCEL);
1003        super.onTouchEvent(cancel);
1004        cancel.recycle();
1005    }
1006
1007    /**
1008     * Called from onTouchEvent to end a drag operation.
1009     *
1010     * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
1011     */
1012    private void stopDrag(MotionEvent ev) {
1013        mTouchMode = TOUCH_MODE_IDLE;
1014
1015        // Commit the change if the event is up and not canceled and the switch
1016        // has not been disabled during the drag.
1017        final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
1018        final boolean oldState = isChecked();
1019        final boolean newState;
1020        if (commitChange) {
1021            mVelocityTracker.computeCurrentVelocity(1000);
1022            final float xvel = mVelocityTracker.getXVelocity();
1023            if (Math.abs(xvel) > mMinFlingVelocity) {
1024                newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0);
1025            } else {
1026                newState = getTargetCheckedState();
1027            }
1028        } else {
1029            newState = oldState;
1030        }
1031
1032        if (newState != oldState) {
1033            playSoundEffect(SoundEffectConstants.CLICK);
1034        }
1035        // Always call setChecked so that the thumb is moved back to the correct edge
1036        setChecked(newState);
1037        cancelSuperTouch(ev);
1038    }
1039
1040    private void animateThumbToCheckedState(boolean newCheckedState) {
1041        final float targetPosition = newCheckedState ? 1 : 0;
1042        mPositionAnimator = ObjectAnimator.ofFloat(this, THUMB_POS, targetPosition);
1043        mPositionAnimator.setDuration(THUMB_ANIMATION_DURATION);
1044        mPositionAnimator.setAutoCancel(true);
1045        mPositionAnimator.start();
1046    }
1047
1048    private void cancelPositionAnimator() {
1049        if (mPositionAnimator != null) {
1050            mPositionAnimator.cancel();
1051        }
1052    }
1053
1054    private boolean getTargetCheckedState() {
1055        return mThumbPosition > 0.5f;
1056    }
1057
1058    /**
1059     * Sets the thumb position as a decimal value between 0 (off) and 1 (on).
1060     *
1061     * @param position new position between [0,1]
1062     */
1063    private void setThumbPosition(float position) {
1064        mThumbPosition = position;
1065        invalidate();
1066    }
1067
1068    @Override
1069    public void toggle() {
1070        setChecked(!isChecked());
1071    }
1072
1073    @Override
1074    public void setChecked(boolean checked) {
1075        super.setChecked(checked);
1076
1077        // Calling the super method may result in setChecked() getting called
1078        // recursively with a different value, so load the REAL value...
1079        checked = isChecked();
1080
1081        if (isAttachedToWindow() && isLaidOut()) {
1082            animateThumbToCheckedState(checked);
1083        } else {
1084            // Immediately move the thumb to the new position.
1085            cancelPositionAnimator();
1086            setThumbPosition(checked ? 1 : 0);
1087        }
1088    }
1089
1090    @Override
1091    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
1092        super.onLayout(changed, left, top, right, bottom);
1093
1094        int opticalInsetLeft = 0;
1095        int opticalInsetRight = 0;
1096        if (mThumbDrawable != null) {
1097            final Rect trackPadding = mTempRect;
1098            if (mTrackDrawable != null) {
1099                mTrackDrawable.getPadding(trackPadding);
1100            } else {
1101                trackPadding.setEmpty();
1102            }
1103
1104            final Insets insets = mThumbDrawable.getOpticalInsets();
1105            opticalInsetLeft = Math.max(0, insets.left - trackPadding.left);
1106            opticalInsetRight = Math.max(0, insets.right - trackPadding.right);
1107        }
1108
1109        final int switchRight;
1110        final int switchLeft;
1111        if (isLayoutRtl()) {
1112            switchLeft = getPaddingLeft() + opticalInsetLeft;
1113            switchRight = switchLeft + mSwitchWidth - opticalInsetLeft - opticalInsetRight;
1114        } else {
1115            switchRight = getWidth() - getPaddingRight() - opticalInsetRight;
1116            switchLeft = switchRight - mSwitchWidth + opticalInsetLeft + opticalInsetRight;
1117        }
1118
1119        final int switchTop;
1120        final int switchBottom;
1121        switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
1122            default:
1123            case Gravity.TOP:
1124                switchTop = getPaddingTop();
1125                switchBottom = switchTop + mSwitchHeight;
1126                break;
1127
1128            case Gravity.CENTER_VERTICAL:
1129                switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 -
1130                        mSwitchHeight / 2;
1131                switchBottom = switchTop + mSwitchHeight;
1132                break;
1133
1134            case Gravity.BOTTOM:
1135                switchBottom = getHeight() - getPaddingBottom();
1136                switchTop = switchBottom - mSwitchHeight;
1137                break;
1138        }
1139
1140        mSwitchLeft = switchLeft;
1141        mSwitchTop = switchTop;
1142        mSwitchBottom = switchBottom;
1143        mSwitchRight = switchRight;
1144    }
1145
1146    @Override
1147    public void draw(Canvas c) {
1148        final Rect padding = mTempRect;
1149        final int switchLeft = mSwitchLeft;
1150        final int switchTop = mSwitchTop;
1151        final int switchRight = mSwitchRight;
1152        final int switchBottom = mSwitchBottom;
1153
1154        int thumbInitialLeft = switchLeft + getThumbOffset();
1155
1156        final Insets thumbInsets;
1157        if (mThumbDrawable != null) {
1158            thumbInsets = mThumbDrawable.getOpticalInsets();
1159        } else {
1160            thumbInsets = Insets.NONE;
1161        }
1162
1163        // Layout the track.
1164        if (mTrackDrawable != null) {
1165            mTrackDrawable.getPadding(padding);
1166
1167            // Adjust thumb position for track padding.
1168            thumbInitialLeft += padding.left;
1169
1170            // If necessary, offset by the optical insets of the thumb asset.
1171            int trackLeft = switchLeft;
1172            int trackTop = switchTop;
1173            int trackRight = switchRight;
1174            int trackBottom = switchBottom;
1175            if (thumbInsets != Insets.NONE) {
1176                if (thumbInsets.left > padding.left) {
1177                    trackLeft += thumbInsets.left - padding.left;
1178                }
1179                if (thumbInsets.top > padding.top) {
1180                    trackTop += thumbInsets.top - padding.top;
1181                }
1182                if (thumbInsets.right > padding.right) {
1183                    trackRight -= thumbInsets.right - padding.right;
1184                }
1185                if (thumbInsets.bottom > padding.bottom) {
1186                    trackBottom -= thumbInsets.bottom - padding.bottom;
1187                }
1188            }
1189            mTrackDrawable.setBounds(trackLeft, trackTop, trackRight, trackBottom);
1190        }
1191
1192        // Layout the thumb.
1193        if (mThumbDrawable != null) {
1194            mThumbDrawable.getPadding(padding);
1195
1196            final int thumbLeft = thumbInitialLeft - padding.left;
1197            final int thumbRight = thumbInitialLeft + mThumbWidth + padding.right;
1198            mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
1199
1200            final Drawable background = getBackground();
1201            if (background != null) {
1202                background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom);
1203            }
1204        }
1205
1206        // Draw the background.
1207        super.draw(c);
1208    }
1209
1210    @Override
1211    protected void onDraw(Canvas canvas) {
1212        super.onDraw(canvas);
1213
1214        final Rect padding = mTempRect;
1215        final Drawable trackDrawable = mTrackDrawable;
1216        if (trackDrawable != null) {
1217            trackDrawable.getPadding(padding);
1218        } else {
1219            padding.setEmpty();
1220        }
1221
1222        final int switchTop = mSwitchTop;
1223        final int switchBottom = mSwitchBottom;
1224        final int switchInnerTop = switchTop + padding.top;
1225        final int switchInnerBottom = switchBottom - padding.bottom;
1226
1227        final Drawable thumbDrawable = mThumbDrawable;
1228        if (trackDrawable != null) {
1229            if (mSplitTrack && thumbDrawable != null) {
1230                final Insets insets = thumbDrawable.getOpticalInsets();
1231                thumbDrawable.copyBounds(padding);
1232                padding.left += insets.left;
1233                padding.right -= insets.right;
1234
1235                final int saveCount = canvas.save();
1236                canvas.clipRect(padding, Op.DIFFERENCE);
1237                trackDrawable.draw(canvas);
1238                canvas.restoreToCount(saveCount);
1239            } else {
1240                trackDrawable.draw(canvas);
1241            }
1242        }
1243
1244        final int saveCount = canvas.save();
1245
1246        if (thumbDrawable != null) {
1247            thumbDrawable.draw(canvas);
1248        }
1249
1250        final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
1251        if (switchText != null) {
1252            final int drawableState[] = getDrawableState();
1253            if (mTextColors != null) {
1254                mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0));
1255            }
1256            mTextPaint.drawableState = drawableState;
1257
1258            final int cX;
1259            if (thumbDrawable != null) {
1260                final Rect bounds = thumbDrawable.getBounds();
1261                cX = bounds.left + bounds.right;
1262            } else {
1263                cX = getWidth();
1264            }
1265
1266            final int left = cX / 2 - switchText.getWidth() / 2;
1267            final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2;
1268            canvas.translate(left, top);
1269            switchText.draw(canvas);
1270        }
1271
1272        canvas.restoreToCount(saveCount);
1273    }
1274
1275    @Override
1276    public int getCompoundPaddingLeft() {
1277        if (!isLayoutRtl()) {
1278            return super.getCompoundPaddingLeft();
1279        }
1280        int padding = super.getCompoundPaddingLeft() + mSwitchWidth;
1281        if (!TextUtils.isEmpty(getText())) {
1282            padding += mSwitchPadding;
1283        }
1284        return padding;
1285    }
1286
1287    @Override
1288    public int getCompoundPaddingRight() {
1289        if (isLayoutRtl()) {
1290            return super.getCompoundPaddingRight();
1291        }
1292        int padding = super.getCompoundPaddingRight() + mSwitchWidth;
1293        if (!TextUtils.isEmpty(getText())) {
1294            padding += mSwitchPadding;
1295        }
1296        return padding;
1297    }
1298
1299    /**
1300     * Translates thumb position to offset according to current RTL setting and
1301     * thumb scroll range. Accounts for both track and thumb padding.
1302     *
1303     * @return thumb offset
1304     */
1305    private int getThumbOffset() {
1306        final float thumbPosition;
1307        if (isLayoutRtl()) {
1308            thumbPosition = 1 - mThumbPosition;
1309        } else {
1310            thumbPosition = mThumbPosition;
1311        }
1312        return (int) (thumbPosition * getThumbScrollRange() + 0.5f);
1313    }
1314
1315    private int getThumbScrollRange() {
1316        if (mTrackDrawable != null) {
1317            final Rect padding = mTempRect;
1318            mTrackDrawable.getPadding(padding);
1319
1320            final Insets insets;
1321            if (mThumbDrawable != null) {
1322                insets = mThumbDrawable.getOpticalInsets();
1323            } else {
1324                insets = Insets.NONE;
1325            }
1326
1327            return mSwitchWidth - mThumbWidth - padding.left - padding.right
1328                    - insets.left - insets.right;
1329        } else {
1330            return 0;
1331        }
1332    }
1333
1334    @Override
1335    protected int[] onCreateDrawableState(int extraSpace) {
1336        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
1337        if (isChecked()) {
1338            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
1339        }
1340        return drawableState;
1341    }
1342
1343    @Override
1344    protected void drawableStateChanged() {
1345        super.drawableStateChanged();
1346
1347        final int[] state = getDrawableState();
1348        boolean changed = false;
1349
1350        final Drawable thumbDrawable = mThumbDrawable;
1351        if (thumbDrawable != null && thumbDrawable.isStateful()) {
1352            changed |= thumbDrawable.setState(state);
1353        }
1354
1355        final Drawable trackDrawable = mTrackDrawable;
1356        if (trackDrawable != null && trackDrawable.isStateful()) {
1357            changed |= trackDrawable.setState(state);
1358        }
1359
1360        if (changed) {
1361            invalidate();
1362        }
1363    }
1364
1365    @Override
1366    public void drawableHotspotChanged(float x, float y) {
1367        super.drawableHotspotChanged(x, y);
1368
1369        if (mThumbDrawable != null) {
1370            mThumbDrawable.setHotspot(x, y);
1371        }
1372
1373        if (mTrackDrawable != null) {
1374            mTrackDrawable.setHotspot(x, y);
1375        }
1376    }
1377
1378    @Override
1379    protected boolean verifyDrawable(@NonNull Drawable who) {
1380        return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
1381    }
1382
1383    @Override
1384    public void jumpDrawablesToCurrentState() {
1385        super.jumpDrawablesToCurrentState();
1386
1387        if (mThumbDrawable != null) {
1388            mThumbDrawable.jumpToCurrentState();
1389        }
1390
1391        if (mTrackDrawable != null) {
1392            mTrackDrawable.jumpToCurrentState();
1393        }
1394
1395        if (mPositionAnimator != null && mPositionAnimator.isStarted()) {
1396            mPositionAnimator.end();
1397            mPositionAnimator = null;
1398        }
1399    }
1400
1401    @Override
1402    public CharSequence getAccessibilityClassName() {
1403        return Switch.class.getName();
1404    }
1405
1406    @Override
1407    public void onProvideStructure(ViewStructure structure) {
1408        super.onProvideStructure(structure);
1409        onProvideAutoFillStructureForAssistOrAutofill(structure);
1410    }
1411
1412    @Override
1413    public void onProvideAutofillStructure(ViewStructure structure, int flags) {
1414        super.onProvideAutofillStructure(structure, flags);
1415        onProvideAutoFillStructureForAssistOrAutofill(structure);
1416    }
1417
1418    // NOTE: currently there is no difference for Assist or AutoFill, so it doesn't take flags
1419    private void onProvideAutoFillStructureForAssistOrAutofill(ViewStructure structure) {
1420        CharSequence switchText = isChecked() ? mTextOn : mTextOff;
1421        if (!TextUtils.isEmpty(switchText)) {
1422            CharSequence oldText = structure.getText();
1423            if (TextUtils.isEmpty(oldText)) {
1424                structure.setText(switchText);
1425            } else {
1426                StringBuilder newText = new StringBuilder();
1427                newText.append(oldText).append(' ').append(switchText);
1428                structure.setText(newText);
1429            }
1430            // The style of the label text is provided via the base TextView class. This is more
1431            // relevant than the style of the (optional) on/off text on the switch button itself,
1432            // so ignore the size/color/style stored this.mTextPaint.
1433        }
1434    }
1435
1436    /** @hide */
1437    @Override
1438    public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
1439        super.onInitializeAccessibilityNodeInfoInternal(info);
1440        CharSequence switchText = isChecked() ? mTextOn : mTextOff;
1441        if (!TextUtils.isEmpty(switchText)) {
1442            CharSequence oldText = info.getText();
1443            if (TextUtils.isEmpty(oldText)) {
1444                info.setText(switchText);
1445            } else {
1446                StringBuilder newText = new StringBuilder();
1447                newText.append(oldText).append(' ').append(switchText);
1448                info.setText(newText);
1449            }
1450        }
1451    }
1452
1453    private static final FloatProperty<Switch> THUMB_POS = new FloatProperty<Switch>("thumbPos") {
1454        @Override
1455        public Float get(Switch object) {
1456            return object.mThumbPosition;
1457        }
1458
1459        @Override
1460        public void setValue(Switch object, float value) {
1461            object.setThumbPosition(value);
1462        }
1463    };
1464}
1465