Switch.java revision 01d11edc1ea2c495ba4bd6bbea8ba7bb7f597678
112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell/*
212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * Copyright (C) 2010 The Android Open Source Project
312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell *
412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * Licensed under the Apache License, Version 2.0 (the "License");
512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * you may not use this file except in compliance with the License.
612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * You may obtain a copy of the License at
712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell *
812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell *      http://www.apache.org/licenses/LICENSE-2.0
912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell *
1012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * Unless required by applicable law or agreed to in writing, software
1112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * distributed under the License is distributed on an "AS IS" BASIS,
1212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * See the License for the specific language governing permissions and
1412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * limitations under the License.
1512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell */
1612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
1712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellpackage android.widget;
1812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
1912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.content.Context;
2012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.content.res.ColorStateList;
2112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.content.res.Resources;
2212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.content.res.TypedArray;
2312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.graphics.Canvas;
2412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.graphics.Paint;
2512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.graphics.Rect;
2612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.graphics.Typeface;
2712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.graphics.drawable.Drawable;
2812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.text.Layout;
2912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.text.StaticLayout;
3012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.text.TextPaint;
3112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.text.TextUtils;
3212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.util.AttributeSet;
3312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.view.Gravity;
3412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.view.MotionEvent;
3512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.view.VelocityTracker;
3612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellimport android.view.ViewConfiguration;
3763bce03cc69be4a45230aa8bbd89dbde60681067Svetoslav Ganovimport android.view.accessibility.AccessibilityEvent;
3812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
39be0a4535053bbfdebd215e244b154ac810fd8edcAdam Powellimport com.android.internal.R;
40be0a4535053bbfdebd215e244b154ac810fd8edcAdam Powell
4112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell/**
4212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * A Switch is a two-state toggle switch widget that can select between two
4312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * options. The user may drag the "thumb" back and forth to choose the selected option,
4412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * or simply tap to toggle as if it were a checkbox.
4512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell *
4612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell * @hide
4712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell */
4812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powellpublic class Switch extends CompoundButton {
4912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private static final int TOUCH_MODE_IDLE = 0;
5012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private static final int TOUCH_MODE_DOWN = 1;
5112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private static final int TOUCH_MODE_DRAGGING = 2;
5212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
5312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    // Enum for the "typeface" XML parameter.
5412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private static final int SANS = 1;
5512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private static final int SERIF = 2;
5612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private static final int MONOSPACE = 3;
5712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
5812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private Drawable mThumbDrawable;
5912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private Drawable mTrackDrawable;
6012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mThumbTextPadding;
6112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mSwitchMinWidth;
6212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mSwitchPadding;
6312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private CharSequence mTextOn;
6412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private CharSequence mTextOff;
6512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
6612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mTouchMode;
6712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mTouchSlop;
6812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private float mTouchX;
6912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private float mTouchY;
7012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private VelocityTracker mVelocityTracker = VelocityTracker.obtain();
7112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mMinFlingVelocity;
7212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
7312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private float mThumbPosition;
7412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mSwitchWidth;
7512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mSwitchHeight;
7612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mThumbWidth; // Does not include padding
7712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
7812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mSwitchLeft;
7912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mSwitchTop;
8012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mSwitchRight;
8112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int mSwitchBottom;
8212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
8312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private TextPaint mTextPaint;
8412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private ColorStateList mTextColors;
8512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private Layout mOnLayout;
8612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private Layout mOffLayout;
8712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
88be0a4535053bbfdebd215e244b154ac810fd8edcAdam Powell    @SuppressWarnings("hiding")
8912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private final Rect mTempRect = new Rect();
9012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
9112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private static final int[] CHECKED_STATE_SET = {
9212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        R.attr.state_checked
9312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    };
9412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
9512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
9612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Construct a new Switch with default styling.
9712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     *
9812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @param context The Context that will determine this widget's theming.
9912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
10012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public Switch(Context context) {
10112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        this(context, null);
10212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
10312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
10412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
10512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Construct a new Switch with default styling, overriding specific style
10612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * attributes as requested.
10712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     *
10812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @param context The Context that will determine this widget's theming.
10912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @param attrs Specification of attributes that should deviate from default styling.
11012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
11112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public Switch(Context context, AttributeSet attrs) {
11212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        this(context, attrs, com.android.internal.R.attr.switchStyle);
11312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
11412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
11512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
11612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Construct a new Switch with a default style determined by the given theme attribute,
11712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * overriding specific style attributes as requested.
11812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     *
11912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @param context The Context that will determine this widget's theming.
12012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @param attrs Specification of attributes that should deviate from the default styling.
12112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @param defStyle An attribute ID within the active theme containing a reference to the
12212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     *                 default style for this widget. e.g. android.R.attr.switchStyle.
12312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
12412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public Switch(Context context, AttributeSet attrs, int defStyle) {
12512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        super(context, attrs, defStyle);
12612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
12712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG);
12812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        Resources res = getResources();
12912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTextPaint.density = res.getDisplayMetrics().density;
13012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTextPaint.setCompatibilityScaling(res.getCompatibilityInfo().applicationScale);
13112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
13212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        TypedArray a = context.obtainStyledAttributes(attrs,
13312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                com.android.internal.R.styleable.Switch, defStyle, 0);
13412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
13512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mThumbDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_switchThumb);
13612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTrackDrawable = a.getDrawable(com.android.internal.R.styleable.Switch_switchTrack);
13712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTextOn = a.getText(com.android.internal.R.styleable.Switch_textOn);
13812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTextOff = a.getText(com.android.internal.R.styleable.Switch_textOff);
13912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mThumbTextPadding = a.getDimensionPixelSize(
14012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                com.android.internal.R.styleable.Switch_thumbTextPadding, 0);
14112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mSwitchMinWidth = a.getDimensionPixelSize(
14212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                com.android.internal.R.styleable.Switch_switchMinWidth, 0);
14312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mSwitchPadding = a.getDimensionPixelSize(
14412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                com.android.internal.R.styleable.Switch_switchPadding, 0);
14512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
14612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int appearance = a.getResourceId(
14712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                com.android.internal.R.styleable.Switch_switchTextAppearance, 0);
14812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (appearance != 0) {
14912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            setSwitchTextAppearance(appearance);
15012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
15112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        a.recycle();
15212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
15312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        ViewConfiguration config = ViewConfiguration.get(context);
15412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTouchSlop = config.getScaledTouchSlop();
15512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mMinFlingVelocity = config.getScaledMinimumFlingVelocity();
15612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
15712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        // Refresh display with current params
15812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        setChecked(isChecked());
15912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
16012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
16112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
16212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Sets the switch text color, size, style, hint color, and highlight color
16312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * from the specified TextAppearance resource.
16412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
16512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public void setSwitchTextAppearance(int resid) {
16612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        TypedArray appearance =
16712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                getContext().obtainStyledAttributes(resid,
16812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        com.android.internal.R.styleable.TextAppearance);
16912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
17012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        ColorStateList colors;
17112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int ts;
17212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
17312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        colors = appearance.getColorStateList(com.android.internal.R.styleable.
17412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                TextAppearance_textColor);
17512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (colors != null) {
17612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            mTextColors = colors;
17712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
17812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
17912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        ts = appearance.getDimensionPixelSize(com.android.internal.R.styleable.
18012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                TextAppearance_textSize, 0);
18112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (ts != 0) {
18212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            if (ts != mTextPaint.getTextSize()) {
18312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                mTextPaint.setTextSize(ts);
18412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                requestLayout();
18512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            }
18612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
18712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
18812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int typefaceIndex, styleIndex;
18912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
19012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        typefaceIndex = appearance.getInt(com.android.internal.R.styleable.
19112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                TextAppearance_typeface, -1);
19212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        styleIndex = appearance.getInt(com.android.internal.R.styleable.
19312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                TextAppearance_textStyle, -1);
19412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
19512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        setSwitchTypefaceByIndex(typefaceIndex, styleIndex);
19612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
19712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        appearance.recycle();
19812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
19912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
20012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private void setSwitchTypefaceByIndex(int typefaceIndex, int styleIndex) {
20112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        Typeface tf = null;
20212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        switch (typefaceIndex) {
20312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case SANS:
20412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                tf = Typeface.SANS_SERIF;
20512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
20612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
20712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case SERIF:
20812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                tf = Typeface.SERIF;
20912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
21012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
21112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case MONOSPACE:
21212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                tf = Typeface.MONOSPACE;
21312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
21412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
21512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
21612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        setSwitchTypeface(tf, styleIndex);
21712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
21812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
21912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
22012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Sets the typeface and style in which the text should be displayed on the
22112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * switch, and turns on the fake bold and italic bits in the Paint if the
22212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Typeface that you provided does not have all the bits in the
22312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * style that you specified.
22412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
22512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public void setSwitchTypeface(Typeface tf, int style) {
22612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (style > 0) {
22712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            if (tf == null) {
22812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                tf = Typeface.defaultFromStyle(style);
22912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            } else {
23012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                tf = Typeface.create(tf, style);
23112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            }
23212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
23312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            setSwitchTypeface(tf);
23412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            // now compute what (if any) algorithmic styling is needed
23512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            int typefaceStyle = tf != null ? tf.getStyle() : 0;
23612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            int need = style & ~typefaceStyle;
23712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            mTextPaint.setFakeBoldText((need & Typeface.BOLD) != 0);
23812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            mTextPaint.setTextSkewX((need & Typeface.ITALIC) != 0 ? -0.25f : 0);
23912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        } else {
24012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            mTextPaint.setFakeBoldText(false);
24112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            mTextPaint.setTextSkewX(0);
24212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            setSwitchTypeface(tf);
24312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
24412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
24512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
24612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
24712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Sets the typeface and style in which the text should be displayed on the switch.
24812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Note that not all Typeface families actually have bold and italic
24912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * variants, so you may need to use
25012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * {@link #setSwitchTypeface(Typeface, int)} to get the appearance
25112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * that you actually want.
25212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     *
25312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @attr ref android.R.styleable#TextView_typeface
25412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @attr ref android.R.styleable#TextView_textStyle
25512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
25612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public void setSwitchTypeface(Typeface tf) {
25712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (mTextPaint.getTypeface() != tf) {
25812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            mTextPaint.setTypeface(tf);
25912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
26012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            requestLayout();
26112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            invalidate();
26212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
26312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
26412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
26512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
26612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Returns the text for when the button is in the checked state.
26712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     *
26812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @return The text.
26912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
27012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public CharSequence getTextOn() {
27112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        return mTextOn;
27212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
27312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
27412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
27512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Sets the text for when the button is in the checked state.
27612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     *
27712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @param textOn The text.
27812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
27912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public void setTextOn(CharSequence textOn) {
28012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTextOn = textOn;
28112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        requestLayout();
28212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
28312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
28412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
28512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Returns the text for when the button is not in the checked state.
28612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     *
28712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @return The text.
28812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
28912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public CharSequence getTextOff() {
29012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        return mTextOff;
29112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
29212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
29312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
29412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Sets the text for when the button is not in the checked state.
29512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     *
29612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @param textOff The text.
29712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
29812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public void setTextOff(CharSequence textOff) {
29912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTextOff = textOff;
30012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        requestLayout();
30112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
30212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
30312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    @Override
30412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
30512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
30612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
30712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
30812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
30912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
31012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
31112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (mOnLayout == null) {
31212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            mOnLayout = makeLayout(mTextOn);
31312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
31412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (mOffLayout == null) {
31512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            mOffLayout = makeLayout(mTextOff);
31612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
31712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
31812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTrackDrawable.getPadding(mTempRect);
31912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int maxTextWidth = Math.max(mOnLayout.getWidth(), mOffLayout.getWidth());
32012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int switchWidth = Math.max(mSwitchMinWidth,
32112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                maxTextWidth * 2 + mThumbTextPadding * 4 + mTempRect.left + mTempRect.right);
32212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int switchHeight = mTrackDrawable.getIntrinsicHeight();
32312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
32412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mThumbWidth = maxTextWidth + mThumbTextPadding * 2;
32512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
32612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        switch (widthMode) {
32712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case MeasureSpec.AT_MOST:
32812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                widthSize = Math.min(widthSize, switchWidth);
32912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
33012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
33112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case MeasureSpec.UNSPECIFIED:
33212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                widthSize = switchWidth;
33312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
33412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
33512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case MeasureSpec.EXACTLY:
33612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                // Just use what we were given
33712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
33812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
33912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
34012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        switch (heightMode) {
34112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case MeasureSpec.AT_MOST:
34212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                heightSize = Math.min(heightSize, switchHeight);
34312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
34412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
34512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case MeasureSpec.UNSPECIFIED:
34612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                heightSize = switchHeight;
34712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
34812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
34912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case MeasureSpec.EXACTLY:
35012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                // Just use what we were given
35112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
35212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
35312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
35412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mSwitchWidth = switchWidth;
35512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mSwitchHeight = switchHeight;
35612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
35712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
35812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int measuredHeight = getMeasuredHeight();
35912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (measuredHeight < switchHeight) {
360189ee18d6c6483ad63cc864267328259e2e00b95Dianne Hackborn            setMeasuredDimension(getMeasuredWidthAndState(), switchHeight);
36112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
36212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
36312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
36463bce03cc69be4a45230aa8bbd89dbde60681067Svetoslav Ganov    @Override
36563bce03cc69be4a45230aa8bbd89dbde60681067Svetoslav Ganov    public void onPopulateAccessibilityEvent(AccessibilityEvent event) {
36663bce03cc69be4a45230aa8bbd89dbde60681067Svetoslav Ganov        super.onPopulateAccessibilityEvent(event);
3677650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov        if (isChecked()) {
3687650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov            CharSequence text = mOnLayout.getText();
3697650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov            if (TextUtils.isEmpty(text)) {
3707650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov                text = mContext.getString(R.string.switch_on);
3717650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov            }
3727650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov            event.getText().add(text);
3737650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov        } else {
3747650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov            CharSequence text = mOffLayout.getText();
3757650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov            if (TextUtils.isEmpty(text)) {
3767650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov                text = mContext.getString(R.string.switch_off);
3777650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov            }
3787650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov            event.getText().add(text);
3797650259a597dd24137420d32acc35efc44db381eSvetoslav Ganov        }
38063bce03cc69be4a45230aa8bbd89dbde60681067Svetoslav Ganov    }
38163bce03cc69be4a45230aa8bbd89dbde60681067Svetoslav Ganov
38212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private Layout makeLayout(CharSequence text) {
38312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        return new StaticLayout(text, mTextPaint,
38412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint)),
38512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                Layout.Alignment.ALIGN_NORMAL, 1.f, 0, true);
38612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
38712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
38812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
38912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @return true if (x, y) is within the target area of the switch thumb
39012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
39112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private boolean hitThumb(float x, float y) {
39212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mThumbDrawable.getPadding(mTempRect);
39312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int thumbTop = mSwitchTop - mTouchSlop;
39412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int thumbLeft = mSwitchLeft + (int) (mThumbPosition + 0.5f) - mTouchSlop;
39512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int thumbRight = thumbLeft + mThumbWidth +
39612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                mTempRect.left + mTempRect.right + mTouchSlop;
39712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int thumbBottom = mSwitchBottom + mTouchSlop;
39812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        return x > thumbLeft && x < thumbRight && y > thumbTop && y < thumbBottom;
39912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
40012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
40112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    @Override
40212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public boolean onTouchEvent(MotionEvent ev) {
40312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mVelocityTracker.addMovement(ev);
40412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int action = ev.getActionMasked();
40512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        switch (action) {
40612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case MotionEvent.ACTION_DOWN: {
40712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                final float x = ev.getX();
40812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                final float y = ev.getY();
409c2ab0d665c9d1c332fbd726abf582a27cf7a6701Gilles Debunne                if (isEnabled() && hitThumb(x, y)) {
41012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                    mTouchMode = TOUCH_MODE_DOWN;
41112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                    mTouchX = x;
41212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                    mTouchY = y;
41312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                }
41412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
41512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            }
41612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
41712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case MotionEvent.ACTION_MOVE: {
41812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                switch (mTouchMode) {
41912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                    case TOUCH_MODE_IDLE:
42012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        // Didn't target the thumb, treat normally.
42112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        break;
42212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
42312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                    case TOUCH_MODE_DOWN: {
42412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        final float x = ev.getX();
42512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        final float y = ev.getY();
42612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        if (Math.abs(x - mTouchX) > mTouchSlop ||
42712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                                Math.abs(y - mTouchY) > mTouchSlop) {
42812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                            mTouchMode = TOUCH_MODE_DRAGGING;
42912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                            getParent().requestDisallowInterceptTouchEvent(true);
43012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                            mTouchX = x;
43112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                            mTouchY = y;
43212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                            return true;
43312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        }
43412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        break;
43512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                    }
43612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
43712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                    case TOUCH_MODE_DRAGGING: {
43812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        final float x = ev.getX();
43912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        final float dx = x - mTouchX;
44012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        float newPos = Math.max(0,
44112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                                Math.min(mThumbPosition + dx, getThumbScrollRange()));
44212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        if (newPos != mThumbPosition) {
44312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                            mThumbPosition = newPos;
44412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                            mTouchX = x;
44512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                            invalidate();
44612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        }
44712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        return true;
44812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                    }
44912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                }
45012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
45112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            }
45212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
45312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case MotionEvent.ACTION_UP:
45412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case MotionEvent.ACTION_CANCEL: {
45512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                if (mTouchMode == TOUCH_MODE_DRAGGING) {
45612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                    stopDrag(ev);
45712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                    return true;
45812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                }
45912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                mTouchMode = TOUCH_MODE_IDLE;
46012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                mVelocityTracker.clear();
46112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
46212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            }
46312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
46412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
46512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        return super.onTouchEvent(ev);
46612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
46712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
46812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private void cancelSuperTouch(MotionEvent ev) {
46912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        MotionEvent cancel = MotionEvent.obtain(ev);
47012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        cancel.setAction(MotionEvent.ACTION_CANCEL);
47112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        super.onTouchEvent(cancel);
47212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        cancel.recycle();
47312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
47412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
47512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    /**
47612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * Called from onTouchEvent to end a drag operation.
47712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     *
47812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     * @param ev Event that triggered the end of drag mode - ACTION_UP or ACTION_CANCEL
47912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell     */
48012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private void stopDrag(MotionEvent ev) {
48112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTouchMode = TOUCH_MODE_IDLE;
482c2ab0d665c9d1c332fbd726abf582a27cf7a6701Gilles Debunne        // Up and not canceled, also checks the switch has not been disabled during the drag
483c2ab0d665c9d1c332fbd726abf582a27cf7a6701Gilles Debunne        boolean commitChange = ev.getAction() == MotionEvent.ACTION_UP && isEnabled();
48412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
48512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        cancelSuperTouch(ev);
48612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
48712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (commitChange) {
48812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            boolean newState;
48912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            mVelocityTracker.computeCurrentVelocity(1000);
49012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            float xvel = mVelocityTracker.getXVelocity();
49112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            if (Math.abs(xvel) > mMinFlingVelocity) {
49201d11edc1ea2c495ba4bd6bbea8ba7bb7f597678Adam Powell                newState = xvel > 0;
49312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            } else {
49412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                newState = getTargetCheckedState();
49512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            }
49612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            animateThumbToCheckedState(newState);
49712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        } else {
49812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            animateThumbToCheckedState(isChecked());
49912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
50012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
50112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
50212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private void animateThumbToCheckedState(boolean newCheckedState) {
50312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        // TODO animate!
504c3eabb9b6ce7f556313c8e3870d76c5b443f1c51Joe Onorato        //float targetPos = newCheckedState ? 0 : getThumbScrollRange();
505c3eabb9b6ce7f556313c8e3870d76c5b443f1c51Joe Onorato        //mThumbPosition = targetPos;
50612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        setChecked(newCheckedState);
50712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
50812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
50912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private boolean getTargetCheckedState() {
510ec1d60369f751c2d9b60237077ceb9c03e4c0aedAdam Powell        return mThumbPosition >= getThumbScrollRange() / 2;
51112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
51212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
51312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    @Override
51412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public void setChecked(boolean checked) {
51512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        super.setChecked(checked);
516ec1d60369f751c2d9b60237077ceb9c03e4c0aedAdam Powell        mThumbPosition = checked ? getThumbScrollRange() : 0;
517c3eabb9b6ce7f556313c8e3870d76c5b443f1c51Joe Onorato        invalidate();
51812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
51912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
52012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    @Override
52112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
52212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        super.onLayout(changed, left, top, right, bottom);
52312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
524ec1d60369f751c2d9b60237077ceb9c03e4c0aedAdam Powell        mThumbPosition = isChecked() ? getThumbScrollRange() : 0;
525c3eabb9b6ce7f556313c8e3870d76c5b443f1c51Joe Onorato
52612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchRight = getWidth() - getPaddingRight();
52712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchLeft = switchRight - mSwitchWidth;
52812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchTop = 0;
52912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchBottom = 0;
53012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        switch (getGravity() & Gravity.VERTICAL_GRAVITY_MASK) {
53112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            default:
53212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case Gravity.TOP:
53312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                switchTop = getPaddingTop();
53412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                switchBottom = switchTop + mSwitchHeight;
53512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
53612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
53712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case Gravity.CENTER_VERTICAL:
53812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                switchTop = (getPaddingTop() + getHeight() - getPaddingBottom()) / 2 -
53912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                        mSwitchHeight / 2;
54012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                switchBottom = switchTop + mSwitchHeight;
54112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
54212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
54312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            case Gravity.BOTTOM:
54412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                switchBottom = getHeight() - getPaddingBottom();
54512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                switchTop = switchBottom - mSwitchHeight;
54612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                break;
54712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
54812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
54912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mSwitchLeft = switchLeft;
55012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mSwitchTop = switchTop;
55112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mSwitchBottom = switchBottom;
55212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mSwitchRight = switchRight;
55312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
55412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
55512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    @Override
55612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    protected void onDraw(Canvas canvas) {
55712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        super.onDraw(canvas);
55812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
55912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        // Draw the switch
56012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchLeft = mSwitchLeft;
56112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchTop = mSwitchTop;
56212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchRight = mSwitchRight;
56312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchBottom = mSwitchBottom;
56412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
56512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTrackDrawable.setBounds(switchLeft, switchTop, switchRight, switchBottom);
56612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTrackDrawable.draw(canvas);
56712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
56812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        canvas.save();
56912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
57012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTrackDrawable.getPadding(mTempRect);
57112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchInnerLeft = switchLeft + mTempRect.left;
57212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchInnerTop = switchTop + mTempRect.top;
57312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchInnerRight = switchRight - mTempRect.right;
57412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int switchInnerBottom = switchBottom - mTempRect.bottom;
57512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        canvas.clipRect(switchInnerLeft, switchTop, switchInnerRight, switchBottom);
57612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
57712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mThumbDrawable.getPadding(mTempRect);
57812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int thumbPos = (int) (mThumbPosition + 0.5f);
57912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int thumbLeft = switchInnerLeft - mTempRect.left + thumbPos;
58012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int thumbRight = switchInnerLeft + thumbPos + mThumbWidth + mTempRect.right;
58112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
58212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mThumbDrawable.setBounds(thumbLeft, switchTop, thumbRight, switchBottom);
58312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mThumbDrawable.draw(canvas);
58412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
58512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTextPaint.setColor(mTextColors.getColorForState(getDrawableState(),
58612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                mTextColors.getDefaultColor()));
58712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTextPaint.drawableState = getDrawableState();
58812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
58912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
59012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
59112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2,
59212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell                (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
59312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        switchText.draw(canvas);
59412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
59512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        canvas.restore();
59612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
59712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
59812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    @Override
59912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public int getCompoundPaddingRight() {
60012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int padding = super.getCompoundPaddingRight() + mSwitchWidth;
60112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (!TextUtils.isEmpty(getText())) {
60212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            padding += mSwitchPadding;
60312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
60412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        return padding;
60512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
60612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
60712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    private int getThumbScrollRange() {
60812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (mTrackDrawable == null) {
60912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            return 0;
61012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
61112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTrackDrawable.getPadding(mTempRect);
61212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        return mSwitchWidth - mThumbWidth - mTempRect.left - mTempRect.right;
61312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
61412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
61512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    @Override
61612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    protected int[] onCreateDrawableState(int extraSpace) {
61712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
61812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        if (isChecked()) {
61912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell            mergeDrawableStates(drawableState, CHECKED_STATE_SET);
62012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        }
62112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        return drawableState;
62212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
62312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
62412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    @Override
62512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    protected void drawableStateChanged() {
62612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        super.drawableStateChanged();
62712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
62812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        int[] myDrawableState = getDrawableState();
62912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
63012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        // Set the state of the Drawable
63112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mThumbDrawable.setState(myDrawableState);
63212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTrackDrawable.setState(myDrawableState);
63312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
63412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        invalidate();
63512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
63612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
63712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    @Override
63812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    protected boolean verifyDrawable(Drawable who) {
63912190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        return super.verifyDrawable(who) || who == mThumbDrawable || who == mTrackDrawable;
64012190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
64112190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell
64212190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    @Override
64312190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    public void jumpDrawablesToCurrentState() {
64412190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        super.jumpDrawablesToCurrentState();
64512190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mThumbDrawable.jumpToCurrentState();
64612190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell        mTrackDrawable.jumpToCurrentState();
64712190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell    }
64812190b36a9da88f8db7dbd9ce16d127d76a904b7Adam Powell}
649