1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.animation.ObjectAnimator;
20import android.annotation.Nullable;
21import android.content.Context;
22import android.content.res.ColorStateList;
23import android.content.res.TypedArray;
24import android.graphics.Canvas;
25import android.graphics.Insets;
26import android.graphics.PorterDuff;
27import android.graphics.Rect;
28import android.graphics.Region.Op;
29import android.graphics.drawable.Drawable;
30import android.os.Bundle;
31import android.util.AttributeSet;
32import android.view.KeyEvent;
33import android.view.MotionEvent;
34import android.view.ViewConfiguration;
35import android.view.accessibility.AccessibilityEvent;
36import android.view.accessibility.AccessibilityNodeInfo;
37
38import com.android.internal.R;
39
40public abstract class AbsSeekBar extends ProgressBar {
41    private final Rect mTempRect = new Rect();
42
43    private Drawable mThumb;
44    private ColorStateList mThumbTintList = null;
45    private PorterDuff.Mode mThumbTintMode = null;
46    private boolean mHasThumbTint = false;
47    private boolean mHasThumbTintMode = false;
48
49    private int mThumbOffset;
50    private boolean mSplitTrack;
51
52    /**
53     * On touch, this offset plus the scaled value from the position of the
54     * touch will form the progress value. Usually 0.
55     */
56    float mTouchProgressOffset;
57
58    /**
59     * Whether this is user seekable.
60     */
61    boolean mIsUserSeekable = true;
62
63    /**
64     * On key presses (right or left), the amount to increment/decrement the
65     * progress.
66     */
67    private int mKeyProgressIncrement = 1;
68    private ObjectAnimator mPositionAnimator;
69    private static final int PROGRESS_ANIMATION_DURATION = 250;
70
71
72    private static final int NO_ALPHA = 0xFF;
73    private float mDisabledAlpha;
74
75    private int mScaledTouchSlop;
76    private float mTouchDownX;
77    private boolean mIsDragging;
78
79    public AbsSeekBar(Context context) {
80        super(context);
81    }
82
83    public AbsSeekBar(Context context, AttributeSet attrs) {
84        super(context, attrs);
85    }
86
87    public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr) {
88        this(context, attrs, defStyleAttr, 0);
89    }
90
91    public AbsSeekBar(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
92        super(context, attrs, defStyleAttr, defStyleRes);
93
94        TypedArray a = context.obtainStyledAttributes(
95                attrs, com.android.internal.R.styleable.SeekBar, defStyleAttr, defStyleRes);
96
97        final Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb);
98        setThumb(thumb);
99
100        if (a.hasValue(R.styleable.SeekBar_thumbTintMode)) {
101            mThumbTintMode = Drawable.parseTintMode(a.getInt(
102                    R.styleable.SeekBar_thumbTintMode, -1), mThumbTintMode);
103            mHasThumbTintMode = true;
104        }
105
106        if (a.hasValue(R.styleable.SeekBar_thumbTint)) {
107            mThumbTintList = a.getColorStateList(R.styleable.SeekBar_thumbTint);
108            mHasThumbTint = true;
109        }
110
111        // Guess thumb offset if thumb != null, but allow layout to override.
112        final int thumbOffset = a.getDimensionPixelOffset(
113                com.android.internal.R.styleable.SeekBar_thumbOffset, getThumbOffset());
114        setThumbOffset(thumbOffset);
115
116        mSplitTrack = a.getBoolean(com.android.internal.R.styleable.SeekBar_splitTrack, false);
117        a.recycle();
118
119        a = context.obtainStyledAttributes(attrs,
120                com.android.internal.R.styleable.Theme, 0, 0);
121        mDisabledAlpha = a.getFloat(com.android.internal.R.styleable.Theme_disabledAlpha, 0.5f);
122        a.recycle();
123
124        applyThumbTint();
125
126        mScaledTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
127    }
128
129    /**
130     * Sets the thumb that will be drawn at the end of the progress meter within the SeekBar.
131     * <p>
132     * If the thumb is a valid drawable (i.e. not null), half its width will be
133     * used as the new thumb offset (@see #setThumbOffset(int)).
134     *
135     * @param thumb Drawable representing the thumb
136     */
137    public void setThumb(Drawable thumb) {
138        final boolean needUpdate;
139        // This way, calling setThumb again with the same bitmap will result in
140        // it recalcuating mThumbOffset (if for example it the bounds of the
141        // drawable changed)
142        if (mThumb != null && thumb != mThumb) {
143            mThumb.setCallback(null);
144            needUpdate = true;
145        } else {
146            needUpdate = false;
147        }
148
149        if (thumb != null) {
150            thumb.setCallback(this);
151            if (canResolveLayoutDirection()) {
152                thumb.setLayoutDirection(getLayoutDirection());
153            }
154
155            // Assuming the thumb drawable is symmetric, set the thumb offset
156            // such that the thumb will hang halfway off either edge of the
157            // progress bar.
158            mThumbOffset = thumb.getIntrinsicWidth() / 2;
159
160            // If we're updating get the new states
161            if (needUpdate &&
162                    (thumb.getIntrinsicWidth() != mThumb.getIntrinsicWidth()
163                        || thumb.getIntrinsicHeight() != mThumb.getIntrinsicHeight())) {
164                requestLayout();
165            }
166        }
167
168        mThumb = thumb;
169
170        applyThumbTint();
171        invalidate();
172
173        if (needUpdate) {
174            updateThumbAndTrackPos(getWidth(), getHeight());
175            if (thumb != null && thumb.isStateful()) {
176                // Note that if the states are different this won't work.
177                // For now, let's consider that an app bug.
178                int[] state = getDrawableState();
179                thumb.setState(state);
180            }
181        }
182    }
183
184    /**
185     * Return the drawable used to represent the scroll thumb - the component that
186     * the user can drag back and forth indicating the current value by its position.
187     *
188     * @return The current thumb drawable
189     */
190    public Drawable getThumb() {
191        return mThumb;
192    }
193
194    /**
195     * Applies a tint to the thumb drawable. Does not modify the current tint
196     * mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
197     * <p>
198     * Subsequent calls to {@link #setThumb(Drawable)} will automatically
199     * mutate the drawable and apply the specified tint and tint mode using
200     * {@link Drawable#setTintList(ColorStateList)}.
201     *
202     * @param tint the tint to apply, may be {@code null} to clear tint
203     *
204     * @attr ref android.R.styleable#SeekBar_thumbTint
205     * @see #getThumbTintList()
206     * @see Drawable#setTintList(ColorStateList)
207     */
208    public void setThumbTintList(@Nullable ColorStateList tint) {
209        mThumbTintList = tint;
210        mHasThumbTint = true;
211
212        applyThumbTint();
213    }
214
215    /**
216     * Returns the tint applied to the thumb drawable, if specified.
217     *
218     * @return the tint applied to the thumb drawable
219     * @attr ref android.R.styleable#SeekBar_thumbTint
220     * @see #setThumbTintList(ColorStateList)
221     */
222    @Nullable
223    public ColorStateList getThumbTintList() {
224        return mThumbTintList;
225    }
226
227    /**
228     * Specifies the blending mode used to apply the tint specified by
229     * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable. The
230     * default mode is {@link PorterDuff.Mode#SRC_IN}.
231     *
232     * @param tintMode the blending mode used to apply the tint, may be
233     *                 {@code null} to clear tint
234     *
235     * @attr ref android.R.styleable#SeekBar_thumbTintMode
236     * @see #getThumbTintMode()
237     * @see Drawable#setTintMode(PorterDuff.Mode)
238     */
239    public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) {
240        mThumbTintMode = tintMode;
241        mHasThumbTintMode = true;
242
243        applyThumbTint();
244    }
245
246    /**
247     * Returns the blending mode used to apply the tint to the thumb drawable,
248     * if specified.
249     *
250     * @return the blending mode used to apply the tint to the thumb drawable
251     * @attr ref android.R.styleable#SeekBar_thumbTintMode
252     * @see #setThumbTintMode(PorterDuff.Mode)
253     */
254    @Nullable
255    public PorterDuff.Mode getThumbTintMode() {
256        return mThumbTintMode;
257    }
258
259    private void applyThumbTint() {
260        if (mThumb != null && (mHasThumbTint || mHasThumbTintMode)) {
261            mThumb = mThumb.mutate();
262
263            if (mHasThumbTint) {
264                mThumb.setTintList(mThumbTintList);
265            }
266
267            if (mHasThumbTintMode) {
268                mThumb.setTintMode(mThumbTintMode);
269            }
270        }
271    }
272
273    /**
274     * @see #setThumbOffset(int)
275     */
276    public int getThumbOffset() {
277        return mThumbOffset;
278    }
279
280    /**
281     * Sets the thumb offset that allows the thumb to extend out of the range of
282     * the track.
283     *
284     * @param thumbOffset The offset amount in pixels.
285     */
286    public void setThumbOffset(int thumbOffset) {
287        mThumbOffset = thumbOffset;
288        invalidate();
289    }
290
291    /**
292     * Specifies whether the track should be split by the thumb. When true,
293     * the thumb's optical bounds will be clipped out of the track drawable,
294     * then the thumb will be drawn into the resulting gap.
295     *
296     * @param splitTrack Whether the track should be split by the thumb
297     */
298    public void setSplitTrack(boolean splitTrack) {
299        mSplitTrack = splitTrack;
300        invalidate();
301    }
302
303    /**
304     * Returns whether the track should be split by the thumb.
305     */
306    public boolean getSplitTrack() {
307        return mSplitTrack;
308    }
309
310    /**
311     * Sets the amount of progress changed via the arrow keys.
312     *
313     * @param increment The amount to increment or decrement when the user
314     *            presses the arrow keys.
315     */
316    public void setKeyProgressIncrement(int increment) {
317        mKeyProgressIncrement = increment < 0 ? -increment : increment;
318    }
319
320    /**
321     * Returns the amount of progress changed via the arrow keys.
322     * <p>
323     * By default, this will be a value that is derived from the max progress.
324     *
325     * @return The amount to increment or decrement when the user presses the
326     *         arrow keys. This will be positive.
327     */
328    public int getKeyProgressIncrement() {
329        return mKeyProgressIncrement;
330    }
331
332    @Override
333    public synchronized void setMax(int max) {
334        super.setMax(max);
335
336        if ((mKeyProgressIncrement == 0) || (getMax() / mKeyProgressIncrement > 20)) {
337            // It will take the user too long to change this via keys, change it
338            // to something more reasonable
339            setKeyProgressIncrement(Math.max(1, Math.round((float) getMax() / 20)));
340        }
341    }
342
343    @Override
344    protected boolean verifyDrawable(Drawable who) {
345        return who == mThumb || super.verifyDrawable(who);
346    }
347
348    @Override
349    public void jumpDrawablesToCurrentState() {
350        super.jumpDrawablesToCurrentState();
351
352        if (mThumb != null) {
353            mThumb.jumpToCurrentState();
354        }
355    }
356
357    @Override
358    protected void drawableStateChanged() {
359        super.drawableStateChanged();
360
361        final Drawable progressDrawable = getProgressDrawable();
362        if (progressDrawable != null) {
363            progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha));
364        }
365
366        final Drawable thumb = mThumb;
367        if (thumb != null && thumb.isStateful()) {
368            thumb.setState(getDrawableState());
369        }
370    }
371
372    @Override
373    public void drawableHotspotChanged(float x, float y) {
374        super.drawableHotspotChanged(x, y);
375
376        if (mThumb != null) {
377            mThumb.setHotspot(x, y);
378        }
379    }
380
381    @Override
382    void onProgressRefresh(float scale, boolean fromUser) {
383        super.onProgressRefresh(scale, fromUser);
384
385        if (!isAnimationRunning()) {
386            setThumbPos(scale);
387        }
388    }
389
390    @Override
391    void onAnimatePosition(float scale, boolean fromUser) {
392        setThumbPos(scale);
393    }
394
395    @Override
396    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
397        super.onSizeChanged(w, h, oldw, oldh);
398
399        updateThumbAndTrackPos(w, h);
400    }
401
402    private void updateThumbAndTrackPos(int w, int h) {
403        final Drawable track = getCurrentDrawable();
404        final Drawable thumb = mThumb;
405
406        // The max height does not incorporate padding, whereas the height
407        // parameter does.
408        final int trackHeight = Math.min(mMaxHeight, h - mPaddingTop - mPaddingBottom);
409        final int thumbHeight = thumb == null ? 0 : thumb.getIntrinsicHeight();
410
411        // Apply offset to whichever item is taller.
412        final int trackOffset;
413        final int thumbOffset;
414        if (thumbHeight > trackHeight) {
415            trackOffset = (thumbHeight - trackHeight) / 2;
416            thumbOffset = 0;
417        } else {
418            trackOffset = 0;
419            thumbOffset = (trackHeight - thumbHeight) / 2;
420        }
421
422        if (track != null) {
423            track.setBounds(0, trackOffset, w - mPaddingRight - mPaddingLeft,
424                    h - mPaddingBottom - trackOffset - mPaddingTop);
425        }
426
427        if (thumb != null) {
428            setThumbPos(w, thumb, getScale(), thumbOffset);
429        }
430    }
431
432    private float getScale() {
433        final int max = getMax();
434        return max > 0 ? getProgress() / (float) max : 0;
435    }
436
437    private void setThumbPos(float scale) {
438        final Drawable thumb = mThumb;
439        if (thumb != null) {
440            setThumbPos(getWidth(), thumb, scale, Integer.MIN_VALUE);
441            // Since we draw translated, the drawable's bounds that it signals
442            // for invalidation won't be the actual bounds we want invalidated,
443            // so just invalidate this whole view.
444            invalidate();
445
446        }
447    }
448
449    /**
450     * Updates the thumb drawable bounds.
451     *
452     * @param w Width of the view, including padding
453     * @param thumb Drawable used for the thumb
454     * @param scale Current progress between 0 and 1
455     * @param offset Vertical offset for centering. If set to
456     *            {@link Integer#MIN_VALUE}, the current offset will be used.
457     */
458    private void setThumbPos(int w, Drawable thumb, float scale, int offset) {
459        int available = w - mPaddingLeft - mPaddingRight;
460        final int thumbWidth = thumb.getIntrinsicWidth();
461        final int thumbHeight = thumb.getIntrinsicHeight();
462        available -= thumbWidth;
463
464        // The extra space for the thumb to move on the track
465        available += mThumbOffset * 2;
466
467        final int thumbPos = (int) (scale * available + 0.5f);
468
469        final int top, bottom;
470        if (offset == Integer.MIN_VALUE) {
471            final Rect oldBounds = thumb.getBounds();
472            top = oldBounds.top;
473            bottom = oldBounds.bottom;
474        } else {
475            top = offset;
476            bottom = offset + thumbHeight;
477        }
478
479        final int left = (isLayoutRtl() && mMirrorForRtl) ? available - thumbPos : thumbPos;
480        final int right = left + thumbWidth;
481
482        final Drawable background = getBackground();
483        if (background != null) {
484            final Rect bounds = thumb.getBounds();
485            final int offsetX = mPaddingLeft - mThumbOffset;
486            final int offsetY = mPaddingTop;
487            background.setHotspotBounds(left + offsetX, top + offsetY,
488                    right + offsetX, bottom + offsetY);
489        }
490
491        // Canvas will be translated, so 0,0 is where we start drawing
492        thumb.setBounds(left, top, right, bottom);
493    }
494
495    /**
496     * @hide
497     */
498    @Override
499    public void onResolveDrawables(int layoutDirection) {
500        super.onResolveDrawables(layoutDirection);
501
502        if (mThumb != null) {
503            mThumb.setLayoutDirection(layoutDirection);
504        }
505    }
506
507    @Override
508    protected synchronized void onDraw(Canvas canvas) {
509        super.onDraw(canvas);
510        drawThumb(canvas);
511
512    }
513
514    @Override
515    void drawTrack(Canvas canvas) {
516        final Drawable thumbDrawable = mThumb;
517        if (thumbDrawable != null && mSplitTrack) {
518            final Insets insets = thumbDrawable.getOpticalInsets();
519            final Rect tempRect = mTempRect;
520            thumbDrawable.copyBounds(tempRect);
521            tempRect.offset(mPaddingLeft - mThumbOffset, mPaddingTop);
522            tempRect.left += insets.left;
523            tempRect.right -= insets.right;
524
525            final int saveCount = canvas.save();
526            canvas.clipRect(tempRect, Op.DIFFERENCE);
527            super.drawTrack(canvas);
528            canvas.restoreToCount(saveCount);
529        } else {
530            super.drawTrack(canvas);
531        }
532    }
533
534    /**
535     * Draw the thumb.
536     */
537    void drawThumb(Canvas canvas) {
538        if (mThumb != null) {
539            canvas.save();
540            // Translate the padding. For the x, we need to allow the thumb to
541            // draw in its extra space
542            canvas.translate(mPaddingLeft - mThumbOffset, mPaddingTop);
543            mThumb.draw(canvas);
544            canvas.restore();
545        }
546    }
547
548    @Override
549    protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
550        Drawable d = getCurrentDrawable();
551
552        int thumbHeight = mThumb == null ? 0 : mThumb.getIntrinsicHeight();
553        int dw = 0;
554        int dh = 0;
555        if (d != null) {
556            dw = Math.max(mMinWidth, Math.min(mMaxWidth, d.getIntrinsicWidth()));
557            dh = Math.max(mMinHeight, Math.min(mMaxHeight, d.getIntrinsicHeight()));
558            dh = Math.max(thumbHeight, dh);
559        }
560        dw += mPaddingLeft + mPaddingRight;
561        dh += mPaddingTop + mPaddingBottom;
562
563        setMeasuredDimension(resolveSizeAndState(dw, widthMeasureSpec, 0),
564                resolveSizeAndState(dh, heightMeasureSpec, 0));
565    }
566
567    @Override
568    public boolean onTouchEvent(MotionEvent event) {
569        if (!mIsUserSeekable || !isEnabled()) {
570            return false;
571        }
572
573        switch (event.getAction()) {
574            case MotionEvent.ACTION_DOWN:
575                if (isInScrollingContainer()) {
576                    mTouchDownX = event.getX();
577                } else {
578                    setPressed(true);
579                    if (mThumb != null) {
580                        invalidate(mThumb.getBounds()); // This may be within the padding region
581                    }
582                    onStartTrackingTouch();
583                    trackTouchEvent(event);
584                    attemptClaimDrag();
585                }
586                break;
587
588            case MotionEvent.ACTION_MOVE:
589                if (mIsDragging) {
590                    trackTouchEvent(event);
591                } else {
592                    final float x = event.getX();
593                    if (Math.abs(x - mTouchDownX) > mScaledTouchSlop) {
594                        setPressed(true);
595                        if (mThumb != null) {
596                            invalidate(mThumb.getBounds()); // This may be within the padding region
597                        }
598                        onStartTrackingTouch();
599                        trackTouchEvent(event);
600                        attemptClaimDrag();
601                    }
602                }
603                break;
604
605            case MotionEvent.ACTION_UP:
606                if (mIsDragging) {
607                    trackTouchEvent(event);
608                    onStopTrackingTouch();
609                    setPressed(false);
610                } else {
611                    // Touch up when we never crossed the touch slop threshold should
612                    // be interpreted as a tap-seek to that location.
613                    onStartTrackingTouch();
614                    trackTouchEvent(event);
615                    onStopTrackingTouch();
616                }
617                // ProgressBar doesn't know to repaint the thumb drawable
618                // in its inactive state when the touch stops (because the
619                // value has not apparently changed)
620                invalidate();
621                break;
622
623            case MotionEvent.ACTION_CANCEL:
624                if (mIsDragging) {
625                    onStopTrackingTouch();
626                    setPressed(false);
627                }
628                invalidate(); // see above explanation
629                break;
630        }
631        return true;
632    }
633
634    private void setHotspot(float x, float y) {
635        final Drawable bg = getBackground();
636        if (bg != null) {
637            bg.setHotspot(x, y);
638        }
639    }
640
641    private void trackTouchEvent(MotionEvent event) {
642        final int width = getWidth();
643        final int available = width - mPaddingLeft - mPaddingRight;
644        final int x = (int) event.getX();
645        float scale;
646        float progress = 0;
647        if (isLayoutRtl() && mMirrorForRtl) {
648            if (x > width - mPaddingRight) {
649                scale = 0.0f;
650            } else if (x < mPaddingLeft) {
651                scale = 1.0f;
652            } else {
653                scale = (float)(available - x + mPaddingLeft) / (float)available;
654                progress = mTouchProgressOffset;
655            }
656        } else {
657            if (x < mPaddingLeft) {
658                scale = 0.0f;
659            } else if (x > width - mPaddingRight) {
660                scale = 1.0f;
661            } else {
662                scale = (float)(x - mPaddingLeft) / (float)available;
663                progress = mTouchProgressOffset;
664            }
665        }
666        final int max = getMax();
667        progress += scale * max;
668
669        setHotspot(x, (int) event.getY());
670        setProgress((int) progress, true);
671    }
672
673    /**
674     * Tries to claim the user's drag motion, and requests disallowing any
675     * ancestors from stealing events in the drag.
676     */
677    private void attemptClaimDrag() {
678        if (mParent != null) {
679            mParent.requestDisallowInterceptTouchEvent(true);
680        }
681    }
682
683    /**
684     * This is called when the user has started touching this widget.
685     */
686    void onStartTrackingTouch() {
687        mIsDragging = true;
688    }
689
690    /**
691     * This is called when the user either releases his touch or the touch is
692     * canceled.
693     */
694    void onStopTrackingTouch() {
695        mIsDragging = false;
696    }
697
698    /**
699     * Called when the user changes the seekbar's progress by using a key event.
700     */
701    void onKeyChange() {
702    }
703
704    @Override
705    public boolean onKeyDown(int keyCode, KeyEvent event) {
706        if (isEnabled()) {
707            int progress = getProgress();
708            switch (keyCode) {
709                case KeyEvent.KEYCODE_DPAD_LEFT:
710                    if (progress <= 0) break;
711                    animateSetProgress(progress - mKeyProgressIncrement);
712                    onKeyChange();
713                    return true;
714
715                case KeyEvent.KEYCODE_DPAD_RIGHT:
716                    if (progress >= getMax()) break;
717                    animateSetProgress(progress + mKeyProgressIncrement);
718                    onKeyChange();
719                    return true;
720            }
721        }
722
723        return super.onKeyDown(keyCode, event);
724    }
725
726    boolean isAnimationRunning() {
727        return mPositionAnimator != null && mPositionAnimator.isRunning();
728    }
729
730    /**
731     * @hide
732     */
733    @Override
734    public void setProgress(int progress, boolean fromUser) {
735        if (isAnimationRunning()) {
736            mPositionAnimator.cancel();
737        }
738        super.setProgress(progress, fromUser);
739    }
740
741    void animateSetProgress(int progress) {
742        float curProgress = isAnimationRunning() ? getAnimationPosition() : getProgress();
743
744        if (progress < 0) {
745            progress = 0;
746        } else if (progress > getMax()) {
747            progress = getMax();
748        }
749        setProgressValueOnly(progress);
750
751        mPositionAnimator = ObjectAnimator.ofFloat(this, "animationPosition", curProgress,
752                progress);
753        mPositionAnimator.setDuration(PROGRESS_ANIMATION_DURATION);
754        mPositionAnimator.setAutoCancel(true);
755        mPositionAnimator.start();
756    }
757
758    @Override
759    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
760        super.onInitializeAccessibilityEvent(event);
761        event.setClassName(AbsSeekBar.class.getName());
762    }
763
764    @Override
765    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
766        super.onInitializeAccessibilityNodeInfo(info);
767        info.setClassName(AbsSeekBar.class.getName());
768
769        if (isEnabled()) {
770            final int progress = getProgress();
771            if (progress > 0) {
772                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD);
773            }
774            if (progress < getMax()) {
775                info.addAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
776            }
777        }
778    }
779
780    @Override
781    public boolean performAccessibilityAction(int action, Bundle arguments) {
782        if (super.performAccessibilityAction(action, arguments)) {
783            return true;
784        }
785        if (!isEnabled()) {
786            return false;
787        }
788        final int progress = getProgress();
789        final int increment = Math.max(1, Math.round((float) getMax() / 5));
790        switch (action) {
791            case AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD: {
792                if (progress <= 0) {
793                    return false;
794                }
795                setProgress(progress - increment, true);
796                onKeyChange();
797                return true;
798            }
799            case AccessibilityNodeInfo.ACTION_SCROLL_FORWARD: {
800                if (progress >= getMax()) {
801                    return false;
802                }
803                setProgress(progress + increment, true);
804                onKeyChange();
805                return true;
806            }
807        }
808        return false;
809    }
810
811    @Override
812    public void onRtlPropertiesChanged(int layoutDirection) {
813        super.onRtlPropertiesChanged(layoutDirection);
814
815        final Drawable thumb = mThumb;
816        if (thumb != null) {
817            setThumbPos(getWidth(), thumb, getScale(), Integer.MIN_VALUE);
818
819            // Since we draw translated, the drawable's bounds that it signals
820            // for invalidation won't be the actual bounds we want invalidated,
821            // so just invalidate this whole view.
822            invalidate();
823        }
824    }
825}
826