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