content: /*
   * Copyright (C) 2010 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.widget;
  
  import android.animation.ObjectAnimator;
  import android.annotation.DrawableRes;
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.annotation.StyleRes;
  import android.annotation.UnsupportedAppUsage;
  import android.content.Context;
  import android.content.res.ColorStateList;
  import android.content.res.Resources;
  import android.content.res.TypedArray;
  import android.graphics.BlendMode;
  import android.graphics.Canvas;
  import android.graphics.Insets;
  import android.graphics.Paint;
  import android.graphics.PorterDuff;
  import android.graphics.Rect;
  import android.graphics.Region.Op;
  import android.graphics.Typeface;
  import android.graphics.drawable.Drawable;
  import android.os.Build.VERSION_CODES;
  import android.text.Layout;
  import android.text.StaticLayout;
  import android.text.TextPaint;
  import android.text.TextUtils;
  import android.text.method.AllCapsTransformationMethod;
  import android.text.method.TransformationMethod2;
  import android.util.AttributeSet;
  import android.util.FloatProperty;
  import android.util.MathUtils;
  import android.view.Gravity;
  import android.view.MotionEvent;
  import android.view.SoundEffectConstants;
  import android.view.VelocityTracker;
  import android.view.ViewConfiguration;
  import android.view.ViewStructure;
  import android.view.accessibility.AccessibilityEvent;
  import android.view.accessibility.AccessibilityNodeInfo;
  import android.view.inspector.InspectableProperty;
  
  import com.android.internal.R;
  
  /**
   * A Switch is a two-state toggle switch widget that can select between two
   * options. The user may drag the "thumb" back and forth to choose the selected option,
   * or simply tap to toggle as if it were a checkbox. The {@link #setText(CharSequence) text}
   * property controls the text displayed in the label for the switch, whereas the
   * {@link #setTextOff(CharSequence) off} and {@link #setTextOn(CharSequence) on} text
   * controls the text on the thumb. Similarly, the
   * {@link #setTextAppearance(android.content.Context, int) textAppearance} and the related
   * setTypeface() methods control the typeface and style of label text, whereas the
   * {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and
   * the related setSwitchTypeface() methods control that of the thumb.
   *
   * <p>{@link android.support.v7.widget.SwitchCompat} is a version of
   * the Switch widget which runs on devices back to API 7.</p>
   *
   * <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a>
   * guide.</p>
   *
   * @attr ref android.R.styleable#Switch_textOn
   * @attr ref android.R.styleable#Switch_textOff
   * @attr ref android.R.styleable#Switch_switchMinWidth
   * @attr ref android.R.styleable#Switch_switchPadding
   * @attr ref android.R.styleable#Switch_switchTextAppearance
   * @attr ref android.R.styleable#Switch_thumb
   * @attr ref android.R.styleable#Switch_thumbTextPadding
   * @attr ref android.R.styleable#Switch_track
   */
  public class Switch extends CompoundButton {
      private static final int THUMB_ANIMATION_DURATION = 250;
  
      private static final int TOUCH_MODE_IDLE = 0;
      private static final int TOUCH_MODE_DOWN = 1;
      private static final int TOUCH_MODE_DRAGGING = 2;
  
      // Enum for the "typeface" XML parameter.
      private static final int SANS = 1;
      private static final int SERIF = 2;
      private static final int MONOSPACE = 3;
  
      @UnsupportedAppUsage
      private Drawable mThumbDrawable;
      private ColorStateList mThumbTintList = null;
      private BlendMode mThumbBlendMode = null;
      private boolean mHasThumbTint = false;
      private boolean mHasThumbTintMode = false;
  
      @UnsupportedAppUsage
      private Drawable mTrackDrawable;
      private ColorStateList mTrackTintList = null;
      private BlendMode mTrackBlendMode = null;
      private boolean mHasTrackTint = false;
      private boolean mHasTrackTintMode = false;
  
      private int mThumbTextPadding;
      @UnsupportedAppUsage
      private int mSwitchMinWidth;
      private int mSwitchPadding;
      private boolean mSplitTrack;
      private CharSequence mTextOn;
      private CharSequence mTextOff;
      private boolean mShowText;
      private boolean mUseFallbackLineSpacing;
  
      private int mTouchMode;
      private int mTouchSlop;
      private float mTouchX;
      private float mTouchY;
      private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
      private int mMinFlingVelocity;
  
      private float mThumbPosition;
  
      /**
       * Width required to draw the switch track and thumb. Includes padding and
       * optical bounds for both the track and thumb.
       */
      @UnsupportedAppUsage
      private int mSwitchWidth;
  
      /**
       * Height required to draw the switch track and thumb. Includes padding and
       * optical bounds for both the track and thumb.
       */
      @UnsupportedAppUsage
      private int mSwitchHeight;
  
      /**
       * Width of the thumb's content region. Does not include padding or
       * optical bounds.
       */
      @UnsupportedAppUsage
      private int mThumbWidth;
  
      /** Left bound for drawing the switch track and thumb. */
      private int mSwitchLeft;
  
      /** Top bound for drawing the switch track and thumb. */
      private int mSwitchTop;
  
      /** Right bound for drawing the switch track and thumb. */
      private int mSwitchRight;
  
      /** Bottom bound for drawing the switch track and thumb. */
      private int mSwitchBottom;
  
      private TextPaint mTextPaint;
      private ColorStateList mTextColors;
      @UnsupportedAppUsage
      private Layout mOnLayout;
      @UnsupportedAppUsage
      private Layout mOffLayout;
      private TransformationMethod2 mSwitchTransformationMethod;
      private ObjectAnimator mPositionAnimator;
  
      @SuppressWarnings("hiding")
      private final Rect mTempRect = new Rect();
  
      private static final int[] CHECKED_STATE_SET = {
          R.attr.state_checked
      };
  
      /**
       * Construct a new Switch with default styling.
       *
       * @param context The Context that will determine this widget's theming.
       */
      public Switch(Context context) {
          this(context, null);
      }
  
      /**
       * Construct a new Switch with default styling, overriding specific style
       * attributes as requested.
       *
       * @param context The Context that will determine this widget's theming.
       * @param attrs Specification of attributes that should deviate from default styling.
       */
      public Switch(Context context, AttributeSet attrs) {
          this(context, attrs, com.android.internal.R.attr.switchStyle);
      }
  
      /**
       * Construct a new Switch with a default style determined by the given theme attribute,
       * overriding specific style attributes as requested.
       *
       * @param context The Context that will determine this widget's theming.
       * @param attrs Specification of attributes that should deviate from the default styling.
       * @param defStyleAttr An attribute in the current theme that contains a
       *        reference to a style resource that supplies default values for
       *        the view. Can be 0 to not look for defaults.
       */
      public Switch(Context context, AttributeSet attrs, int defStyleAttr) {
          this(context, attrs, defStyleAttr, 0);
      }
  
  
      /**
       * Construct a new Switch with a default style determined by the given theme
       * attribute or style resource, overriding specific style attributes as
       * requested.
       *
       * @param context The Context that will determine this widget's theming.
       * @param attrs Specification of attributes that should deviate from the
       *        default styling.
       * @param defStyleAttr An attribute in the current theme that contains a
       *        reference to a style resource that supplies default values for
       *        the view. Can be 0 to not look for defaults.
       * @param defStyleRes A resource identifier of a style resource that
       *        supplies default values for the view, used only if
       *        defStyleAttr is 0 or can not be found in the theme. Can be 0
       *        to not look for defaults.
       */
      public Switch(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
          super(context, attrs, defStyleAttr, defStyleRes);
  
          mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
  
          final Resources res = getResources();
          mTextPaint.density = res.getDisplayMetrics().density;
          mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
  
          final TypedArray a = context.obtainStyledAttributes(
                  attrs, com.android.internal.R.styleable.Switch, defStyleAttr, defStyleRes);
          saveAttributeDataForStyleable(context, com.android.internal.R.styleable.Switch,
                  attrs, a, defStyleAttr, defStyleRes);
          mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_thumb);
          if (mThumbDrawable != null) {
              mThumbDrawable.setCallback(this);
          }
          mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_track);
          if (mTrackDrawable != null) {
              mTrackDrawable.setCallback(this);
          }
          mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
          mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
          mShowText = a.getBoolean(com.android.internal.R.styleable.Switch_showText, true);
          mThumbTextPadding = a.getDimensionPixelSize(
                  com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
          mSwitchMinWidth = a.getDimensionPixelSize(
                  com.android.internal.R.styleable.Switch_switchMinWidth, 0);
          mSwitchPadding = a.getDimensionPixelSize(
                  com.android.internal.R.styleable.Switch_switchPadding, 0);
          mSplitTrack = a.getBoolean(com.android.internal.R.styleable.Switch_splitTrack, false);
  
          mUseFallbackLineSpacing = context.getApplicationInfo().targetSdkVersion >= VERSION_CODES.P;
  
          ColorStateList thumbTintList = a.getColorStateList(
                  com.android.internal.R.styleable.Switch_thumbTint);
          if (thumbTintList != null) {
              mThumbTintList = thumbTintList;
              mHasThumbTint = true;
          }
          BlendMode thumbTintMode = Drawable.parseBlendMode(
                  a.getInt(com.android.internal.R.styleable.Switch_thumbTintMode, -1),
                  null);
          if (mThumbBlendMode != thumbTintMode) {
              mThumbBlendMode = thumbTintMode;
              mHasThumbTintMode = true;
          }
          if (mHasThumbTint || mHasThumbTintMode) {
              applyThumbTint();
          }
  
          ColorStateList trackTintList = a.getColorStateList(
                  com.android.internal.R.styleable.Switch_trackTint);
          if (trackTintList != null) {
              mTrackTintList = trackTintList;
              mHasTrackTint = true;
          }
          BlendMode trackTintMode = Drawable.parseBlendMode(
                  a.getInt(com.android.internal.R.styleable.Switch_trackTintMode, -1),
                  null);
          if (mTrackBlendMode != trackTintMode) {
              mTrackBlendMode = trackTintMode;
              mHasTrackTintMode = true;
          }
          if (mHasTrackTint || mHasTrackTintMode) {
              applyTrackTint();
          }
  
          final int appearance = a.getResourceId(
                  com.android.internal.R.styleable.Switch_switchTextAppearance, 0);
          if (appearance != 0) {
              setSwitchTextAppearance(context, appearance);
          }
          a.recycle();
  
          final ViewConfiguration config = ViewConfiguration.get(context);
          mTouchSlop = config.getScaledTouchSlop();
          mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
  
          // Refresh display with current params
          refreshDrawableState();
          setChecked(isChecked());
      }
  
      /**
       * Sets the switch text color, size, style, hint color, and highlight color
       * from the specified TextAppearance resource.
       *
       * @attr ref android.R.styleable#Switch_switchTextAppearance
       */
      public void setSwitchTextAppearance(Context context, @StyleRes int resid) {
          TypedArray appearance =
                  context.obtainStyledAttributes(resid,
                          com.android.internal.R.styleable.TextAppearance);
  
          ColorStateList colors;
          int ts;
  
          colors = appearance.getColorStateList(com.android.internal.R.styleable.
                  TextAppearance_textColor);
          if (colors != null) {
              mTextColors = colors;
          } else {
              // If no color set in TextAppearance, default to the view's textColor
              mTextColors = getTextColors();
          }
  
          ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
                  TextAppearance_textSize, 0);
          if (ts != 0) {
              if (ts != mTextPaint.getTextSize()) {
                  mTextPaint.setTextSize(ts);
                  requestLayout();
              }
          }
  
          int typefaceIndex, styleIndex;
  
          typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
                  TextAppearance_typeface, -1);
          styleIndex = appearance.getInt(com.android.internal.R.styleable.
                  TextAppearance_textStyle, -1);
  
          setSwitchTypefaceByIndex(typefaceIndex, styleIndex);
  
          boolean allCaps = appearance.getBoolean(com.android.internal.R.styleable.
                  TextAppearance_textAllCaps, false);
          if (allCaps) {
              mSwitchTransformationMethod = new AllCapsTransformationMethod(getContext());
              mSwitchTransformationMethod.setLengthChangesAllowed(true);
          } else {
              mSwitchTransformationMethod = null;
          }
  
          appearance.recycle();
      }
  
      private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) {
          Typeface tf = null;
          switch (typefaceIndex) {
              case SANS:
                  tf = Typeface.SANS_SERIF;
                  break;
  
              case SERIF:
                  tf = Typeface.SERIF;
                  break;
  
              case MONOSPACE:
                  tf = Typeface.MONOSPACE;
                  break;
          }
  
          setSwitchTypeface(tf, styleIndex);
      }
  
      /**
       * Sets the typeface and style in which the text should be displayed on the
       * switch, and turns on the fake bold and italic bits in the Paint if the
       * Typeface that you provided does not have all the bits in the
       * style that you specified.
       */
      public void setSwitchTypeface(Typeface tf, int style) {
          if (style > 0) {
              if (tf == null) {
                  tf = Typeface.defaultFromStyle(style);
              } else {
                  tf = Typeface.create(tf, style);
              }
  
              setSwitchTypeface(tf);
              // now compute what (if any) algorithmic styling is needed
              int typefaceStyle = tf != null ? tf.getStyle() : 0;
              int need = style & ~typefaceStyle;
              mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
              mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
          } else {
              mTextPaint.setFakeBoldText(false);
              mTextPaint.setTextSkewX(0);
              setSwitchTypeface(tf);
          }
      }
  
      /**
       * Sets the typeface in which the text should be displayed on the switch.
       * Note that not all Typeface families actually have bold and italic
       * variants, so you may need to use
       * {@link #setSwitchTypeface(Typeface, int)} to get the appearance
       * that you actually want.
       *
       * @attr ref android.R.styleable#TextView_typeface
       * @attr ref android.R.styleable#TextView_textStyle
       */
      public void setSwitchTypeface(Typeface tf) {
          if (mTextPaint.getTypeface() != tf) {
              mTextPaint.setTypeface(tf);
  
              requestLayout();
              invalidate();
          }
      }
  
      /**
       * Set the amount of horizontal padding between the switch and the associated text.
       *
       * @param pixels Amount of padding in pixels
       *
       * @attr ref android.R.styleable#Switch_switchPadding
       */
      public void setSwitchPadding(int pixels) {
          mSwitchPadding = pixels;
          requestLayout();
      }
  
      /**
       * Get the amount of horizontal padding between the switch and the associated text.
       *
       * @return Amount of padding in pixels
       *
       * @attr ref android.R.styleable#Switch_switchPadding
       */
      @InspectableProperty
      public int getSwitchPadding() {
          return mSwitchPadding;
      }
  
      /**
       * Set the minimum width of the switch in pixels. The switch's width will be the maximum
       * of this value and its measured width as determined by the switch drawables and text used.
       *
       * @param pixels Minimum width of the switch in pixels
       *
       * @attr ref android.R.styleable#Switch_switchMinWidth
       */
      public void setSwitchMinWidth(int pixels) {
          mSwitchMinWidth = pixels;
          requestLayout();
      }
  
      /**
       * Get the minimum width of the switch in pixels. The switch's width will be the maximum
       * of this value and its measured width as determined by the switch drawables and text used.
       *
       * @return Minimum width of the switch in pixels
       *
       * @attr ref android.R.styleable#Switch_switchMinWidth
       */
      @InspectableProperty
      public int getSwitchMinWidth() {
          return mSwitchMinWidth;
      }
  
      /**
       * Set the horizontal padding around the text drawn on the switch itself.
       *
       * @param pixels Horizontal padding for switch thumb text in pixels
       *
       * @attr ref android.R.styleable#Switch_thumbTextPadding
       */
      public void setThumbTextPadding(int pixels) {
          mThumbTextPadding = pixels;
          requestLayout();
      }
  
      /**
       * Get the horizontal padding around the text drawn on the switch itself.
       *
       * @return Horizontal padding for switch thumb text in pixels
       *
       * @attr ref android.R.styleable#Switch_thumbTextPadding
       */
      @InspectableProperty
      public int getThumbTextPadding() {
          return mThumbTextPadding;
      }
  
      /**
       * Set the drawable used for the track that the switch slides within.
       *
       * @param track Track drawable
       *
       * @attr ref android.R.styleable#Switch_track
       */
      public void setTrackDrawable(Drawable track) {
          if (mTrackDrawable != null) {
              mTrackDrawable.setCallback(null);
          }
          mTrackDrawable = track;
          if (track != null) {
              track.setCallback(this);
          }
          requestLayout();
      }
  
      /**
       * Set the drawable used for the track that the switch slides within.
       *
       * @param resId Resource ID of a track drawable
       *
       * @attr ref android.R.styleable#Switch_track
       */
      public void setTrackResource(@DrawableRes int resId) {
          setTrackDrawable(getContext().getDrawable(resId));
      }
  
      /**
       * Get the drawable used for the track that the switch slides within.
       *
       * @return Track drawable
       *
       * @attr ref android.R.styleable#Switch_track
       */
      @InspectableProperty(name = "track")
      public Drawable getTrackDrawable() {
          return mTrackDrawable;
      }
  
      /**
       * Applies a tint to the track drawable. Does not modify the current
       * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
       * <p>
       * Subsequent calls to {@link #setTrackDrawable(Drawable)} will
       * automatically mutate the drawable and apply the specified tint and tint
       * mode using {@link Drawable#setTintList(ColorStateList)}.
       *
       * @param tint the tint to apply, may be {@code null} to clear tint
       *
       * @attr ref android.R.styleable#Switch_trackTint
       * @see #getTrackTintList()
       * @see Drawable#setTintList(ColorStateList)
       */
      public void setTrackTintList(@Nullable ColorStateList tint) {
          mTrackTintList = tint;
          mHasTrackTint = true;
  
          applyTrackTint();
      }
  
      /**
       * @return the tint applied to the track drawable
       * @attr ref android.R.styleable#Switch_trackTint
       * @see #setTrackTintList(ColorStateList)
       */
      @InspectableProperty(name = "trackTint")
      @Nullable
      public ColorStateList getTrackTintList() {
          return mTrackTintList;
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setTrackTintList(ColorStateList)}} to the track drawable.
       * The default mode is {@link PorterDuff.Mode#SRC_IN}.
       *
       * @param tintMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#Switch_trackTintMode
       * @see #getTrackTintMode()
       * @see Drawable#setTintMode(PorterDuff.Mode)
       */
      public void setTrackTintMode(@Nullable PorterDuff.Mode tintMode) {
          setTrackTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null);
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setTrackTintList(ColorStateList)}} to the track drawable.
       * The default mode is {@link BlendMode#SRC_IN}.
       *
       * @param blendMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#Switch_trackTintMode
       * @see #getTrackTintMode()
       * @see Drawable#setTintBlendMode(BlendMode)
       */
      public void setTrackTintBlendMode(@Nullable BlendMode blendMode) {
          mTrackBlendMode = blendMode;
          mHasTrackTintMode = true;
  
          applyTrackTint();
      }
  
      /**
       * @return the blending mode used to apply the tint to the track
       *         drawable
       * @attr ref android.R.styleable#Switch_trackTintMode
       * @see #setTrackTintMode(PorterDuff.Mode)
       */
      @InspectableProperty
      @Nullable
      public PorterDuff.Mode getTrackTintMode() {
          BlendMode mode = getTrackTintBlendMode();
          return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
      }
  
      /**
       * @return the blending mode used to apply the tint to the track
       *         drawable
       * @attr ref android.R.styleable#Switch_trackTintMode
       * @see #setTrackTintBlendMode(BlendMode)
       */
      @InspectableProperty(attributeId = com.android.internal.R.styleable.Switch_trackTintMode)
      @Nullable
      public BlendMode getTrackTintBlendMode() {
          return mTrackBlendMode;
      }
  
      private void applyTrackTint() {
          if (mTrackDrawable != null && (mHasTrackTint || mHasTrackTintMode)) {
              mTrackDrawable = mTrackDrawable.mutate();
  
              if (mHasTrackTint) {
                  mTrackDrawable.setTintList(mTrackTintList);
              }
  
              if (mHasTrackTintMode) {
                  mTrackDrawable.setTintBlendMode(mTrackBlendMode);
              }
  
              // The drawable (or one of its children) may not have been
              // stateful before applying the tint, so let's try again.
              if (mTrackDrawable.isStateful()) {
                  mTrackDrawable.setState(getDrawableState());
              }
          }
      }
  
      /**
       * Set the drawable used for the switch "thumb" - the piece that the user
       * can physically touch and drag along the track.
       *
       * @param thumb Thumb drawable
       *
       * @attr ref android.R.styleable#Switch_thumb
       */
      public void setThumbDrawable(Drawable thumb) {
          if (mThumbDrawable != null) {
              mThumbDrawable.setCallback(null);
          }
          mThumbDrawable = thumb;
          if (thumb != null) {
              thumb.setCallback(this);
          }
          requestLayout();
      }
  
      /**
       * Set the drawable used for the switch "thumb" - the piece that the user
       * can physically touch and drag along the track.
       *
       * @param resId Resource ID of a thumb drawable
       *
       * @attr ref android.R.styleable#Switch_thumb
       */
      public void setThumbResource(@DrawableRes int resId) {
          setThumbDrawable(getContext().getDrawable(resId));
      }
  
      /**
       * Get the drawable used for the switch "thumb" - the piece that the user
       * can physically touch and drag along the track.
       *
       * @return Thumb drawable
       *
       * @attr ref android.R.styleable#Switch_thumb
       */
      @InspectableProperty(name = "thumb")
      public Drawable getThumbDrawable() {
          return mThumbDrawable;
      }
  
      /**
       * Applies a tint to the thumb drawable. Does not modify the current
       * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
       * <p>
       * Subsequent calls to {@link #setThumbDrawable(Drawable)} will
       * automatically mutate the drawable and apply the specified tint and tint
       * mode using {@link Drawable#setTintList(ColorStateList)}.
       *
       * @param tint the tint to apply, may be {@code null} to clear tint
       *
       * @attr ref android.R.styleable#Switch_thumbTint
       * @see #getThumbTintList()
       * @see Drawable#setTintList(ColorStateList)
       */
      public void setThumbTintList(@Nullable ColorStateList tint) {
          mThumbTintList = tint;
          mHasThumbTint = true;
  
          applyThumbTint();
      }
  
      /**
       * @return the tint applied to the thumb drawable
       * @attr ref android.R.styleable#Switch_thumbTint
       * @see #setThumbTintList(ColorStateList)
       */
      @InspectableProperty(name = "thumbTint")
      @Nullable
      public ColorStateList getThumbTintList() {
          return mThumbTintList;
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable.
       * The default mode is {@link PorterDuff.Mode#SRC_IN}.
       *
       * @param tintMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#Switch_thumbTintMode
       * @see #getThumbTintMode()
       * @see Drawable#setTintMode(PorterDuff.Mode)
       */
      public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) {
          setThumbTintBlendMode(tintMode != null ? BlendMode.fromValue(tintMode.nativeInt) : null);
      }
  
      /**
       * Specifies the blending mode used to apply the tint specified by
       * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable.
       * The default mode is {@link PorterDuff.Mode#SRC_IN}.
       *
       * @param blendMode the blending mode used to apply the tint, may be
       *                 {@code null} to clear tint
       * @attr ref android.R.styleable#Switch_thumbTintMode
       * @see #getThumbTintMode()
       * @see Drawable#setTintBlendMode(BlendMode)
       */
      public void setThumbTintBlendMode(@Nullable BlendMode blendMode) {
          mThumbBlendMode = blendMode;
          mHasThumbTintMode = true;
  
          applyThumbTint();
      }
  
      /**
       * @return the blending mode used to apply the tint to the thumb
       *         drawable
       * @attr ref android.R.styleable#Switch_thumbTintMode
       * @see #setThumbTintMode(PorterDuff.Mode)
       */
      @InspectableProperty
      @Nullable
      public PorterDuff.Mode getThumbTintMode() {
          BlendMode mode = getThumbTintBlendMode();
          return mode != null ? BlendMode.blendModeToPorterDuffMode(mode) : null;
      }
  
      /**
       * @return the blending mode used to apply the tint to the thumb
       *         drawable
       * @attr ref android.R.styleable#Switch_thumbTintMode
       * @see #setThumbTintBlendMode(BlendMode)
       */
      @InspectableProperty(attributeId = com.android.internal.R.styleable.Switch_thumbTintMode)
      @Nullable
      public BlendMode getThumbTintBlendMode() {
          return mThumbBlendMode;
      }
  
      private void applyThumbTint() {
          if (mThumbDrawable != null && (mHasThumbTint || mHasThumbTintMode)) {
              mThumbDrawable = mThumbDrawable.mutate();
  
              if (mHasThumbTint) {
                  mThumbDrawable.setTintList(mThumbTintList);
              }
  
              if (mHasThumbTintMode) {
                  mThumbDrawable.setTintBlendMode(mThumbBlendMode);
              }
  
              // The drawable (or one of its children) may not have been
              // stateful before applying the tint, so let's try again.
              if (mThumbDrawable.isStateful()) {
                  mThumbDrawable.setState(getDrawableState());
              }
          }
      }
  
      /**
       * Specifies whether the track should be split by the thumb. When true,
       * the thumb's optical bounds will be clipped out of the track drawable,
       * then the thumb will be drawn into the resulting gap.
       *
       * @param splitTrack Whether the track should be split by the thumb
       *
       * @attr ref android.R.styleable#Switch_splitTrack
       */
      public void setSplitTrack(boolean splitTrack) {
          mSplitTrack = splitTrack;
          invalidate();
      }
  
      /**
       * Returns whether the track should be split by the thumb.
       *
       * @attr ref android.R.styleable#Switch_splitTrack
       */
      @InspectableProperty
      public boolean getSplitTrack() {
          return mSplitTrack;
      }
  
      /**
       * Returns the text displayed when the button is in the checked state.
       *
       * @attr ref android.R.styleable#Switch_textOn
       */
      @InspectableProperty
      public CharSequence getTextOn() {
          return mTextOn;
      }
  
      /**
       * Sets the text displayed when the button is in the checked state.
       *
       * @attr ref android.R.styleable#Switch_textOn
       */
      public void setTextOn(CharSequence textOn) {
          mTextOn = textOn;
          requestLayout();
      }
  
      /**
       * Returns the text displayed when the button is not in the checked state.
       *
       * @attr ref android.R.styleable#Switch_textOff
       */
      @InspectableProperty
      public CharSequence getTextOff() {
          return mTextOff;
      }
  
      /**
       * Sets the text displayed when the button is not in the checked state.
       *
       * @attr ref android.R.styleable#Switch_textOff
       */
      public void setTextOff(CharSequence textOff) {
          mTextOff = textOff;
          requestLayout();
      }
  
      /**
       * Sets whether the on/off text should be displayed.
       *
       * @param showText {@code true} to display on/off text
       * @attr ref android.R.styleable#Switch_showText
       */
      public void setShowText(boolean showText) {
          if (mShowText != showText) {
              mShowText = showText;
              requestLayout();
          }
      }
  
      /**
       * @return whether the on/off text should be displayed
       * @attr ref android.R.styleable#Switch_showText
       */
      @InspectableProperty
      public boolean getShowText() {
          return mShowText;
      }
  
      @Override
      public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
          if (mShowText) {
              if (mOnLayout == null) {
                  mOnLayout = makeLayout(mTextOn);
              }
  
              if (mOffLayout == null) {
                  mOffLayout = makeLayout(mTextOff);
              }
          }
  
          final Rect padding = mTempRect;
          final int thumbWidth;
          final int thumbHeight;
          if (mThumbDrawable != null) {
              // Cached thumb width does not include padding.
              mThumbDrawable.getPadding(padding);
              thumbWidth = mThumbDrawable.getIntrinsicWidth() - padding.left - padding.right;
              thumbHeight = mThumbDrawable.getIntrinsicHeight();
          } else {
              thumbWidth = 0;
              thumbHeight = 0;
          }
  
          final int maxTextWidth;
          if (mShowText) {
              maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth())
                      + mThumbTextPadding * 2;
          } else {
              maxTextWidth = 0;
          }
  
          mThumbWidth = Math.max(maxTextWidth, thumbWidth);
  
          final int trackHeight;
          if (mTrackDrawable != null) {
              mTrackDrawable.getPadding(padding);
              trackHeight = mTrackDrawable.getIntrinsicHeight();
          } else {
              padding.setEmpty();
              trackHeight = 0;
          }
  
          // Adjust left and right padding to ensure there's enough room for the
          // thumb's padding (when present).
          int paddingLeft = padding.left;
          int paddingRight = padding.right;
          if (mThumbDrawable != null) {
              final Insets inset = mThumbDrawable.getOpticalInsets();
              paddingLeft = Math.max(paddingLeft, inset.left);
              paddingRight = Math.max(paddingRight, inset.right);
          }
  
          final int switchWidth = Math.max(mSwitchMinWidth,
                  2 * mThumbWidth + paddingLeft + paddingRight);
          final int switchHeight = Math.max(trackHeight, thumbHeight);
          mSwitchWidth = switchWidth;
          mSwitchHeight = switchHeight;
  
          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
  
          final int measuredHeight = getMeasuredHeight();
          if (measuredHeight < switchHeight) {
              setMeasuredDimension(getMeasuredWidthAndState(), switchHeight);
          }
      }
  
      /** @hide */
      @Override
      public void onPopulateAccessibilityEventInternal(AccessibilityEvent event) {
          super.onPopulateAccessibilityEventInternal(event);
  
          final CharSequence text = isChecked() ? mTextOn : mTextOff;
          if (text != null) {
              event.getText().add(text);
          }
      }
  
      private Layout makeLayout(CharSequence text) {
          final CharSequence transformed = (mSwitchTransformationMethod != null)
                      ? mSwitchTransformationMethod.getTransformation(text, this)
                      : text;
  
          int width = (int) Math.ceil(Layout.getDesiredWidth(transformed, 0,
                  transformed.length(), mTextPaint, getTextDirectionHeuristic()));
          return StaticLayout.Builder.obtain(transformed, 0, transformed.length(), mTextPaint, width)
                  .setUseLineSpacingFromFallbacks(mUseFallbackLineSpacing)
                  .build();
      }
  
      /**
       * @return true if (x, y) is within the target area of the switch thumb
       */
      private boolean hitThumb(float x, float y) {
          if (mThumbDrawable == null) {
              return false;
          }
  
          // Relies on mTempRect, MUST be called first!
          final int thumbOffset = getThumbOffset();
  
          mThumbDrawable.getPadding(mTempRect);
          final int thumbTop = mSwitchTop - mTouchSlop;
          final int thumbLeft = mSwitchLeft + thumbOffset - mTouchSlop;
          final int thumbRight = thumbLeft + mThumbWidth +
                  mTempRect.left + mTempRect.right + mTouchSlop;
          final int thumbBottom = mSwitchBottom + mTouchSlop;
          return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom;
      }
  
      @Override
      public boolean onTouchEvent(MotionEvent ev) {
          mVelocityTracker.addMovement(ev);
          final int action = ev.getActionMasked();
          switch (action) {
              case MotionEvent.ACTION_DOWN: {
                  final float x = ev.getX();
                  final float y = ev.getY();
                  if (isEnabled() && hitThumb(x, y)) {
                      mTouchMode = TOUCH_MODE_DOWN;
                      mTouchX = x;
                      mTouchY = y;
                  }
                  break;
              }
  
              case MotionEvent.ACTION_MOVE: {
                  switch (mTouchMode) {
                      case TOUCH_MODE_IDLE:
                          // Didn't target the thumb, treat normally.
                          break;
  
                      case TOUCH_MODE_DOWN: {
                          final float x = ev.getX();
                          final float y = ev.getY();
                          if (Math.abs(x - mTouchX) > mTouchSlop ||
                                  Math.abs(y - mTouchY) > mTouchSlop) {
                              mTouchMode = TOUCH_MODE_DRAGGING;
                              getParent().requestDisallowInterceptTouchEvent(true);
                              mTouchX = x;
                              mTouchY = y;
                              return true;
                          }
                          break;
                      }
  
                      case TOUCH_MODE_DRAGGING: {
                          final float x = ev.getX();
                          final int thumbScrollRange = getThumbScrollRange();
                          final float thumbScrollOffset = x - mTouchX;
                          float dPos;
                          if (thumbScrollRange != 0) {
                              dPos = thumbScrollOffset / thumbScrollRange;
                          } else {
                              // If the thumb scroll range is empty, just use the
                              // movement direction to snap on or off.
                              dPos = thumbScrollOffset > 0 ? 1 : -1;
                          }
                          if (isLayoutRtl()) {
                              dPos = -dPos;
                          }
                          final float newPos = MathUtils.constrain(mThumbPosition + dPos, 0, 1);
                          if (newPos != mThumbPosition) {
                              mTouchX = x;
                              setThumbPosition(newPos);
                          }
                          return true;
                      }
                  }
                  break;
              }
  
              case MotionEvent.ACTION_UP:
              case MotionEvent.ACTION_CANCEL: {
                  if (mTouchMode == TOUCH_MODE_DRAGGING) {
                      stopDrag(ev);
                      // Allow super class to handle pressed state, etc.
                      super.onTouchEvent(ev);
                      return true;
                  }
                  mTouchMode = TOUCH_MODE_IDLE;
                  mVelocityTracker.clear();
                  break;
              }
          }
  
          return super.onTouchEvent(ev);
      }
  
      private void cancelSuperTouch(MotionEvent ev) {
          MotionEvent cancel = MotionEvent.obtain(ev);
          cancel.setAction(MotionEvent.ACTION_CANCEL);
          super.onTouchEvent(cancel);
          cancel.recycle();
      }
  
      /**
       * Called from onTouchEvent to end a drag operation.
       *
       * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
       */
      private void stopDrag(MotionEvent ev) {
          mTouchMode = TOUCH_MODE_IDLE;
  
          // Commit the change if the event is up and not canceled and the switch
          // has not been disabled during the drag.
          final boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
          final boolean oldState = isChecked();
          final boolean newState;
          if (commitChange) {
              mVelocityTracker.computeCurrentVelocity(1000);
              final float xvel = mVelocityTracker.getXVelocity();
              if (Math.abs(xvel) > mMinFlingVelocity) {
                  newState = isLayoutRtl() ? (xvel < 0) : (xvel > 0);
              } else {
                  newState = getTargetCheckedState();
              }
          } else {
              newState = oldState;
          }
  
          if (newState != oldState) {
              playSoundEffect(SoundEffectConstants.CLICK);
          }
          // Always call setChecked so that the thumb is moved back to the correct edge
          setChecked(newState);
          cancelSuperTouch(ev);
      }
  
      private void animateThumbToCheckedState(boolean newCheckedState) {
          final float targetPosition = newCheckedState ? 1 : 0;
          mPositionAnimator = ObjectAnimator.ofFloat(this, THUMB_POS, targetPosition);
          mPositionAnimator.setDuration(THUMB_ANIMATION_DURATION);
          mPositionAnimator.setAutoCancel(true);
          mPositionAnimator.start();
      }
  
      @UnsupportedAppUsage
      private void cancelPositionAnimator() {
          if (mPositionAnimator != null) {
              mPositionAnimator.cancel();
          }
      }
  
      private boolean getTargetCheckedState() {
          return mThumbPosition > 0.5f;
      }
  
      /**
       * Sets the thumb position as a decimal value between 0 (off) and 1 (on).
       *
       * @param position new position between [0,1]
       */
      @UnsupportedAppUsage
      private void setThumbPosition(float position) {
          mThumbPosition = position;
          invalidate();
      }
  
      @Override
      public void toggle() {
          setChecked(!isChecked());
      }
  
      @Override
      public void setChecked(boolean checked) {
          super.setChecked(checked);
  
          // Calling the super method may result in setChecked() getting called
          // recursively with a different value, so load the REAL value...
          checked = isChecked();
  
          if (isAttachedToWindow() && isLaidOut()) {
              animateThumbToCheckedState(checked);
          } else {
              // Immediately move the thumb to the new position.
              cancelPositionAnimator();
              setThumbPosition(checked ? 1 : 0);
          }
      }
  
      @Override
      protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
          super.onLayout(changed, left, top, right, bottom);
  
          int opticalInsetLeft = 0;
          int opticalInsetRight = 0;
          if (mThumbDrawable != null) {
              final Rect trackPadding = mTempRect;
              if (mTrackDrawable != null) {
                  mTrackDrawable.getPadding(trackPadding);
              } else {
                  trackPadding.setEmpty();
              }
  
              final Insets insets = mThumbDrawable.getOpticalInsets();
              opticalInsetLeft = Math.max(0, insets.left - trackPadding.left);
              opticalInsetRight = Math.max(0, insets.right - trackPadding.right);
          }
  
          final int switchRight;
          final int switchLeft;
          if (isLayoutRtl()) {
              switchLeft = getPaddingLeft() + opticalInsetLeft;
              switchRight = switchLeft + mSwitchWidth - opticalInsetLeft - opticalInsetRight;
          } else {
              switchRight = getWidth() - getPaddingRight() - opticalInsetRight;
              switchLeft = switchRight - mSwitchWidth + opticalInsetLeft + opticalInsetRight;
          }
  
          final int switchTop;
          final int switchBottom;
          switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
              default:
              case Gravity.TOP:
                  switchTop = getPaddingTop();
                  switchBottom = switchTop + mSwitchHeight;
                  break;
  
              case Gravity.CENTER_VERTICAL:
                  switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 -
                          mSwitchHeight / 2;
                  switchBottom = switchTop + mSwitchHeight;
                  break;
  
              case Gravity.BOTTOM:
                  switchBottom = getHeight() - getPaddingBottom();
                  switchTop = switchBottom - mSwitchHeight;
                  break;
          }
  
          mSwitchLeft = switchLeft;
          mSwitchTop = switchTop;
          mSwitchBottom = switchBottom;
          mSwitchRight = switchRight;
      }
  
      @Override
      public void draw(Canvas c) {
          final Rect padding = mTempRect;
          final int switchLeft = mSwitchLeft;
          final int switchTop = mSwitchTop;
          final int switchRight = mSwitchRight;
          final int switchBottom = mSwitchBottom;
  
          int thumbInitialLeft = switchLeft + getThumbOffset();
  
          final Insets thumbInsets;
          if (mThumbDrawable != null) {
              thumbInsets = mThumbDrawable.getOpticalInsets();
          } else {
              thumbInsets = Insets.NONE;
          }
  
          // Layout the track.
          if (mTrackDrawable != null) {
              mTrackDrawable.getPadding(padding);
  
              // Adjust thumb position for track padding.
              thumbInitialLeft += padding.left;
  
              // If necessary, offset by the optical insets of the thumb asset.
              int trackLeft = switchLeft;
              int trackTop = switchTop;
              int trackRight = switchRight;
              int trackBottom = switchBottom;
              if (thumbInsets != Insets.NONE) {
                  if (thumbInsets.left > padding.left) {
                      trackLeft += thumbInsets.left - padding.left;
                  }
                  if (thumbInsets.top > padding.top) {
                      trackTop += thumbInsets.top - padding.top;
                  }
                  if (thumbInsets.right > padding.right) {
                      trackRight -= thumbInsets.right - padding.right;
                  }
                  if (thumbInsets.bottom > padding.bottom) {
                      trackBottom -= thumbInsets.bottom - padding.bottom;
                  }
              }
              mTrackDrawable.setBounds(trackLeft, trackTop, trackRight, trackBottom);
          }
  
          // Layout the thumb.
          if (mThumbDrawable != null) {
              mThumbDrawable.getPadding(padding);
  
              final int thumbLeft = thumbInitialLeft - padding.left;
              final int thumbRight = thumbInitialLeft + mThumbWidth + padding.right;
              mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
  
              final Drawable background = getBackground();
              if (background != null) {
                  background.setHotspotBounds(thumbLeft, switchTop, thumbRight, switchBottom);
              }
          }
  
          // Draw the background.
          super.draw(c);
      }
  
      @Override
      protected void onDraw(Canvas canvas) {
          super.onDraw(canvas);
  
          final Rect padding = mTempRect;
          final Drawable trackDrawable = mTrackDrawable;
          if (trackDrawable != null) {
              trackDrawable.getPadding(padding);
          } else {
              padding.setEmpty();
          }
  
          final int switchTop = mSwitchTop;
          final int switchBottom = mSwitchBottom;
          final int switchInnerTop = switchTop + padding.top;
          final int switchInnerBottom = switchBottom - padding.bottom;
  
          final Drawable thumbDrawable = mThumbDrawable;
          if (trackDrawable != null) {
              if (mSplitTrack && thumbDrawable != null) {
                  final Insets insets = thumbDrawable.getOpticalInsets();
                  thumbDrawable.copyBounds(padding);
                  padding.left += insets.left;
                  padding.right -= insets.right;
  
                  final int saveCount = canvas.save();
                  canvas.clipRect(padding, Op.DIFFERENCE);
                  trackDrawable.draw(canvas);
                  canvas.restoreToCount(saveCount);
              } else {
                  trackDrawable.draw(canvas);
              }
          }
  
          final int saveCount = canvas.save();
  
          if (thumbDrawable != null) {
              thumbDrawable.draw(canvas);
          }
  
          final Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
          if (switchText != null) {
              final int drawableState[] = getDrawableState();
              if (mTextColors != null) {
                  mTextPaint.setColor(mTextColors.getColorForState(drawableState, 0));
              }
              mTextPaint.drawableState = drawableState;
  
              final int cX;
              if (thumbDrawable != null) {
                  final Rect bounds = thumbDrawable.getBounds();
                  cX = bounds.left + bounds.right;
              } else {
                  cX = getWidth();
              }
  
              final int left = cX / 2 - switchText.getWidth() / 2;
              final int top = (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2;
              canvas.translate(left, top);
              switchText.draw(canvas);
          }
  
          canvas.restoreToCount(saveCount);
      }
  
      @Override
      public int getCompoundPaddingLeft() {
          if (!isLayoutRtl()) {
              return super.getCompoundPaddingLeft();
          }
          int padding = super.getCompoundPaddingLeft() + mSwitchWidth;
          if (!TextUtils.isEmpty(getText())) {
              padding += mSwitchPadding;
          }
          return padding;
      }
  
      @Override
      public int getCompoundPaddingRight() {
          if (isLayoutRtl()) {
              return super.getCompoundPaddingRight();
          }
          int padding = super.getCompoundPaddingRight() + mSwitchWidth;
          if (!TextUtils.isEmpty(getText())) {
              padding += mSwitchPadding;
          }
          return padding;
      }
  
      /**
       * Translates thumb position to offset according to current RTL setting and
       * thumb scroll range. Accounts for both track and thumb padding.
       *
       * @return thumb offset
       */
      private int getThumbOffset() {
          final float thumbPosition;
          if (isLayoutRtl()) {
              thumbPosition = 1 - mThumbPosition;
          } else {
              thumbPosition = mThumbPosition;
          }
          return (int) (thumbPosition * getThumbScrollRange() + 0.5f);
      }
  
      private int getThumbScrollRange() {
          if (mTrackDrawable != null) {
              final Rect padding = mTempRect;
              mTrackDrawable.getPadding(padding);
  
              final Insets insets;
              if (mThumbDrawable != null) {
                  insets = mThumbDrawable.getOpticalInsets();
              } else {
                  insets = Insets.NONE;
              }
  
              return mSwitchWidth - mThumbWidth - padding.left - padding.right
                      - insets.left - insets.right;
          } else {
              return 0;
          }
      }
  
      @Override
      protected int[] onCreateDrawableState(int extraSpace) {
          final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
          if (isChecked()) {
              mergeDrawableStates(drawableState, CHECKED_STATE_SET);
          }
          return drawableState;
      }
  
      @Override
      protected void drawableStateChanged() {
          super.drawableStateChanged();
  
          final int[] state = getDrawableState();
          boolean changed = false;
  
          final Drawable thumbDrawable = mThumbDrawable;
          if (thumbDrawable != null && thumbDrawable.isStateful()) {
              changed |= thumbDrawable.setState(state);
          }
  
          final Drawable trackDrawable = mTrackDrawable;
          if (trackDrawable != null && trackDrawable.isStateful()) {
              changed |= trackDrawable.setState(state);
          }
  
          if (changed) {
              invalidate();
          }
      }
  
      @Override
      public void drawableHotspotChanged(float x, float y) {
          super.drawableHotspotChanged(x, y);
  
          if (mThumbDrawable != null) {
              mThumbDrawable.setHotspot(x, y);
          }
  
          if (mTrackDrawable != null) {
              mTrackDrawable.setHotspot(x, y);
          }
      }
  
      @Override
      protected boolean verifyDrawable(@NonNull Drawable who) {
          return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
      }
  
      @Override
      public void jumpDrawablesToCurrentState() {
          super.jumpDrawablesToCurrentState();
  
          if (mThumbDrawable != null) {
              mThumbDrawable.jumpToCurrentState();
          }
  
          if (mTrackDrawable != null) {
              mTrackDrawable.jumpToCurrentState();
          }
  
          if (mPositionAnimator != null && mPositionAnimator.isStarted()) {
              mPositionAnimator.end();
              mPositionAnimator = null;
          }
      }
  
      @Override
      public CharSequence getAccessibilityClassName() {
          return Switch.class.getName();
      }
  
      /** @hide */
      @Override
      protected void onProvideStructure(@NonNull ViewStructure structure,
              @ViewStructureType int viewFor, int flags) {
          CharSequence switchText = isChecked() ? mTextOn : mTextOff;
          if (!TextUtils.isEmpty(switchText)) {
              CharSequence oldText = structure.getText();
              if (TextUtils.isEmpty(oldText)) {
                  structure.setText(switchText);
              } else {
                  StringBuilder newText = new StringBuilder();
                  newText.append(oldText).append(' ').append(switchText);
                  structure.setText(newText);
              }
              // The style of the label text is provided via the base TextView class. This is more
              // relevant than the style of the (optional) on/off text on the switch button itself,
              // so ignore the size/color/style stored this.mTextPaint.
          }
      }
  
      /** @hide */
      @Override
      public void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) {
          super.onInitializeAccessibilityNodeInfoInternal(info);
          CharSequence switchText = isChecked() ? mTextOn : mTextOff;
          if (!TextUtils.isEmpty(switchText)) {
              CharSequence oldText = info.getText();
              if (TextUtils.isEmpty(oldText)) {
                  info.setText(switchText);
              } else {
                  StringBuilder newText = new StringBuilder();
                  newText.append(oldText).append(' ').append(switchText);
                  info.setText(newText);
              }
          }
      }
  
      private static final FloatProperty<Switch> THUMB_POS = new FloatProperty<Switch>("thumbPos") {
          @Override
          public Float get(Switch object) {
              return object.mThumbPosition;
          }
  
          @Override
          public void setValue(Switch object, float value) {
              object.setThumbPosition(value);
          }
      };
  }
  