1d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/*
2d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Copyright (C) 2015 The Android Open Source Project
3d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *
4d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Licensed under the Apache License, Version 2.0 (the "License");
5d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * you may not use this file except in compliance with the License.
6d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * You may obtain a copy of the License at
7d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *
8d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *      http://www.apache.org/licenses/LICENSE-2.0
9d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd *
10d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Unless required by applicable law or agreed to in writing, software
11d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * distributed under the License is distributed on an "AS IS" BASIS,
12d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * See the License for the specific language governing permissions and
14d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * limitations under the License.
15d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */
16d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
17d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpackage com.android.messaging.ui.conversation;
18d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
19d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.animation.AnimatorSet;
20d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.animation.ObjectAnimator;
21d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.content.Context;
22d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.content.res.Resources;
23d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.graphics.Rect;
24d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.graphics.drawable.StateListDrawable;
25d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.os.Handler;
26d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.support.v7.widget.LinearLayoutManager;
27d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.support.v7.widget.RecyclerView;
28d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.support.v7.widget.RecyclerView.AdapterDataObserver;
29d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.support.v7.widget.RecyclerView.ViewHolder;
30d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.util.StateSet;
31d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.LayoutInflater;
32d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.MotionEvent;
33d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.View;
34d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.View.MeasureSpec;
35d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.View.OnLayoutChangeListener;
36d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.view.ViewGroupOverlay;
37d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.widget.ImageView;
38d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport android.widget.TextView;
39d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
40d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.R;
41d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.datamodel.data.ConversationMessageData;
42d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.ui.ConversationDrawables;
43d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.Dates;
44d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddimport com.android.messaging.util.OsUtil;
45d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
46d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd/**
47d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * Adds a "fast-scroll" bar to the conversation RecyclerView that shows the current position within
48d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * the conversation and allows quickly moving to another position by dragging the scrollbar thumb
49d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * up or down. As the thumb is dragged, we show a floating bubble alongside it that shows the
50d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd * date/time of the first visible message at the current position.
51d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd */
52d3b009ae55651f1e60950342468e3c37fdeb0796Mike Doddpublic class ConversationFastScroller extends RecyclerView.OnScrollListener implements
53d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        OnLayoutChangeListener, RecyclerView.OnItemTouchListener {
54d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
55d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
56d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Creates a {@link ConversationFastScroller} instance, attached to the provided
57d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * {@link RecyclerView}.
58d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *
59d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param rv the conversation RecyclerView
60d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @param position where the scrollbar should appear (either {@code POSITION_RIGHT_SIDE} or
61d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *            {@code POSITION_LEFT_SIDE})
62d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * @return a new ConversationFastScroller, or {@code null} if fast-scrolling is not supported
63d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     *         (the feature requires Jellybean MR2 or newer)
64d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
65d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static ConversationFastScroller addTo(RecyclerView rv, int position) {
66d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (OsUtil.isAtLeastJB_MR2()) {
67d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return new ConversationFastScroller(rv, position);
68d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
69d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return null;
70d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
71d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
72d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static final int POSITION_RIGHT_SIDE = 0;
73d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public static final int POSITION_LEFT_SIDE = 1;
74d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
75d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final int MIN_PAGES_TO_ENABLE = 7;
76d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final int SHOW_ANIMATION_DURATION_MS = 150;
77d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final int HIDE_ANIMATION_DURATION_MS = 300;
78d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private static final int HIDE_DELAY_MS = 1500;
79d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
80d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final Context mContext;
81d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final RecyclerView mRv;
82d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final ViewGroupOverlay mOverlay;
83d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final ImageView mTrackImageView;
84d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final ImageView mThumbImageView;
85d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final TextView mPreviewTextView;
86d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
87d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final int mTrackWidth;
88d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final int mThumbHeight;
89d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final int mPreviewHeight;
90d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final int mPreviewMinWidth;
91d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final int mPreviewMarginTop;
92d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final int mPreviewMarginLeftRight;
93d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final int mTouchSlop;
94d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
95d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final Rect mContainer = new Rect();
96d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final Handler mHandler = new Handler();
97d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
98d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // Whether to render the scrollbar on the right side (otherwise it'll be on the left).
99d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final boolean mPosRight;
100d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
101d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // Whether the scrollbar is currently visible (it may still be animating).
102d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private boolean mVisible = false;
103d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
104d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // Whether we are waiting to hide the scrollbar (i.e. scrolling has stopped).
105d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private boolean mPendingHide = false;
106d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
107d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // Whether the user is currently dragging the thumb up or down.
108d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private boolean mDragging = false;
109d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
110d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    // Animations responsible for hiding the scrollbar & preview. May be null.
111d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private AnimatorSet mHideAnimation;
112d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private ObjectAnimator mHidePreviewAnimation;
113d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
114d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private final Runnable mHideTrackRunnable = new Runnable() {
115d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        @Override
116d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        public void run() {
117d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            hide(true /* animate */);
118d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mPendingHide = false;
119d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
120d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    };
121d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
122d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private ConversationFastScroller(RecyclerView rv, int position) {
123d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mContext = rv.getContext();
124d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mRv = rv;
125d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mRv.addOnLayoutChangeListener(this);
126d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mRv.addOnScrollListener(this);
127d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mRv.addOnItemTouchListener(this);
128d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mRv.getAdapter().registerAdapterDataObserver(new AdapterDataObserver() {
129d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            @Override
130d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            public void onChanged() {
131d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                updateScrollPos();
132d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
133d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        });
134d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPosRight = (position == POSITION_RIGHT_SIDE);
135d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
136d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Cache the dimensions we'll need during layout
137d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final Resources res = mContext.getResources();
138d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mTrackWidth = res.getDimensionPixelSize(R.dimen.fastscroll_track_width);
139d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mThumbHeight = res.getDimensionPixelSize(R.dimen.fastscroll_thumb_height);
140d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPreviewHeight = res.getDimensionPixelSize(R.dimen.fastscroll_preview_height);
141d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPreviewMinWidth = res.getDimensionPixelSize(R.dimen.fastscroll_preview_min_width);
142d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPreviewMarginTop = res.getDimensionPixelOffset(R.dimen.fastscroll_preview_margin_top);
143d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPreviewMarginLeftRight = res.getDimensionPixelOffset(
144d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                R.dimen.fastscroll_preview_margin_left_right);
145d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mTouchSlop = res.getDimensionPixelOffset(R.dimen.fastscroll_touch_slop);
146d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
147d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final LayoutInflater inflator = LayoutInflater.from(mContext);
148d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mTrackImageView = (ImageView) inflator.inflate(R.layout.fastscroll_track, null);
149d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mThumbImageView = (ImageView) inflator.inflate(R.layout.fastscroll_thumb, null);
150d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPreviewTextView = (TextView) inflator.inflate(R.layout.fastscroll_preview, null);
151d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
152d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        refreshConversationThemeColor();
153d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
154d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Add the fast scroll views to the overlay, so they are rendered above the list
155d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mOverlay = rv.getOverlay();
156d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mOverlay.add(mTrackImageView);
157d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mOverlay.add(mThumbImageView);
158d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mOverlay.add(mPreviewTextView);
159d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
160d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        hide(false /* animate */);
161d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPreviewTextView.setAlpha(0f);
162d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
163d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
164d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public void refreshConversationThemeColor() {
165d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPreviewTextView.setBackground(
166d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                ConversationDrawables.get().getFastScrollPreviewDrawable(mPosRight));
167d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (OsUtil.isAtLeastL()) {
168d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            final StateListDrawable drawable = new StateListDrawable();
169d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            drawable.addState(new int[]{ android.R.attr.state_pressed },
170d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ConversationDrawables.get().getFastScrollThumbDrawable(true /* pressed */));
171d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            drawable.addState(StateSet.WILD_CARD,
172d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */));
173d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mThumbImageView.setImageDrawable(drawable);
174d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } else {
175d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Android pre-L doesn't seem to handle a StateListDrawable containing a tinted
176d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // drawable (it's rendered in the filter base color, which is red), so fall back to
177d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // just the regular (non-pressed) drawable.
178d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mThumbImageView.setImageDrawable(
179d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    ConversationDrawables.get().getFastScrollThumbDrawable(false /* pressed */));
180d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
181d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
182d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
183d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @Override
184d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public void onScrollStateChanged(final RecyclerView view, final int newState) {
185d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
186d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Only show the scrollbar once the user starts scrolling
187d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            if (!mVisible && isEnabled()) {
188d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                show();
189d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            }
190d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            cancelAnyPendingHide();
191d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } else if (newState == RecyclerView.SCROLL_STATE_IDLE && !mDragging) {
192d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Hide the scrollbar again after scrolling stops
193d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            hideAfterDelay();
194d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
195d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
196d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
197d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private boolean isEnabled() {
198d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int range = mRv.computeVerticalScrollRange();
199d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int extent = mRv.computeVerticalScrollExtent();
200d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
201d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (range == 0 || extent == 0) {
202d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return false; // Conversation isn't long enough to scroll
203d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
204d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Only enable scrollbars for conversations long enough that they would require several
205d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // flings to scroll through.
206d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final float pages = (float) range / extent;
207d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return (pages > MIN_PAGES_TO_ENABLE);
208d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
209d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
210d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void show() {
211d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (mHideAnimation != null && mHideAnimation.isRunning()) {
212d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mHideAnimation.cancel();
213d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
214d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Slide the scrollbar in from the side
215d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X, 0);
216d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X, 0);
217d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        AnimatorSet animation = new AnimatorSet();
218d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        animation.playTogether(trackSlide, thumbSlide);
219d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        animation.setDuration(SHOW_ANIMATION_DURATION_MS);
220d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        animation.start();
221d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
222d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mVisible = true;
223d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateScrollPos();
224d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
225d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
226d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void hideAfterDelay() {
227d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        cancelAnyPendingHide();
228d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mHandler.postDelayed(mHideTrackRunnable, HIDE_DELAY_MS);
229d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPendingHide = true;
230d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
231d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
232d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void cancelAnyPendingHide() {
233d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (mPendingHide) {
234d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mHandler.removeCallbacks(mHideTrackRunnable);
235d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
236d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
237d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
238d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void hide(boolean animate) {
239d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int hiddenTranslationX = mPosRight ? mTrackWidth : -mTrackWidth;
240d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (animate) {
241d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // Slide the scrollbar off to the side
242d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            ObjectAnimator trackSlide = ObjectAnimator.ofFloat(mTrackImageView, View.TRANSLATION_X,
243d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    hiddenTranslationX);
244d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            ObjectAnimator thumbSlide = ObjectAnimator.ofFloat(mThumbImageView, View.TRANSLATION_X,
245d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    hiddenTranslationX);
246d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mHideAnimation = new AnimatorSet();
247d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mHideAnimation.playTogether(trackSlide, thumbSlide);
248d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mHideAnimation.setDuration(HIDE_ANIMATION_DURATION_MS);
249d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mHideAnimation.start();
250d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } else {
251d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mTrackImageView.setTranslationX(hiddenTranslationX);
252d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mThumbImageView.setTranslationX(hiddenTranslationX);
253d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
254d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
255d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mVisible = false;
256d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
257d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
258d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void showPreview() {
259d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (mHidePreviewAnimation != null && mHidePreviewAnimation.isRunning()) {
260d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mHidePreviewAnimation.cancel();
261d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
262d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPreviewTextView.setAlpha(1f);
263d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
264d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
265d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void hidePreview() {
266d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mHidePreviewAnimation = ObjectAnimator.ofFloat(mPreviewTextView, View.ALPHA, 0f);
267d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mHidePreviewAnimation.setDuration(HIDE_ANIMATION_DURATION_MS);
268d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mHidePreviewAnimation.start();
269d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
270d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
271d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @Override
272d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public void onScrolled(final RecyclerView view, final int dx, final int dy) {
273d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateScrollPos();
274d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
275d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
276d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void updateScrollPos() {
277d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!mVisible) {
278d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return;
279d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
280d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int verticalScrollLength = mContainer.height() - mThumbHeight;
281d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int verticalScrollStart = mContainer.top + mThumbHeight / 2;
282d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
283d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final float scrollRatio = computeScrollRatio();
284d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int thumbCenterY = verticalScrollStart + (int)(verticalScrollLength * scrollRatio);
285d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        layoutThumb(thumbCenterY);
286d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
287d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (mDragging) {
288d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            updatePreviewText();
289d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            layoutPreview(thumbCenterY);
290d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
291d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
292d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
293d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    /**
294d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * Returns the current position in the conversation, as a value between 0 and 1, inclusive.
295d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     * The top of the conversation is 0, the bottom is 1, the exact middle is 0.5, and so on.
296d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd     */
297d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private float computeScrollRatio() {
298d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int range = mRv.computeVerticalScrollRange();
299d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int extent = mRv.computeVerticalScrollExtent();
300d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int offset = mRv.computeVerticalScrollOffset();
301d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
302d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (range == 0 || extent == 0) {
303d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // If the conversation doesn't scroll, we're at the bottom.
304d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return 1.0f;
305d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
306d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int scrollRange = range - extent;
307d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        offset = Math.min(offset, scrollRange);
308d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return offset / (float) scrollRange;
309d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
310d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
311d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void updatePreviewText() {
312d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final LinearLayoutManager lm = (LinearLayoutManager) mRv.getLayoutManager();
313d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int pos = lm.findFirstVisibleItemPosition();
314d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (pos == RecyclerView.NO_POSITION) {
315d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return;
316d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
317d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ViewHolder vh = mRv.findViewHolderForAdapterPosition(pos);
318d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (vh == null) {
319d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            // This can happen if the messages update while we're dragging the thumb.
320d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return;
321d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
322d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ConversationMessageView messageView = (ConversationMessageView) vh.itemView;
323d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final ConversationMessageData messageData = messageView.getData();
324d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final long timestamp = messageData.getReceivedTimeStamp();
325d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final CharSequence timestampText = Dates.getFastScrollPreviewTimeString(timestamp);
326d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPreviewTextView.setText(timestampText);
327d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
328d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
329d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @Override
330d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
331d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!mVisible) {
332d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return false;
333d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
334d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // If the user presses down on the scroll thumb, we'll start intercepting events from the
335d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // RecyclerView so we can handle the move events while they're dragging it up/down.
336d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int action = e.getActionMasked();
337d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        switch (action) {
338d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            case MotionEvent.ACTION_DOWN:
339d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (isInsideThumb(e.getX(), e.getY())) {
340d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    startDrag();
341d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    return true;
342d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
343d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                break;
344d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            case MotionEvent.ACTION_MOVE:
345d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (mDragging) {
346d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    return true;
347d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
348d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            case MotionEvent.ACTION_CANCEL:
349d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            case MotionEvent.ACTION_UP:
350d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                if (mDragging) {
351d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                    cancelDrag();
352d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                }
353d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                return false;
354d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
355d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return false;
356d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
357d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
358d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private boolean isInsideThumb(float x, float y) {
359d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int hitTargetLeft = mThumbImageView.getLeft() - mTouchSlop;
360d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int hitTargetRight = mThumbImageView.getRight() + mTouchSlop;
361d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
362d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (x < hitTargetLeft || x > hitTargetRight) {
363d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return false;
364d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
365d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (y < mThumbImageView.getTop() || y > mThumbImageView.getBottom()) {
366d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return false;
367d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
368d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        return true;
369d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
370d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
371d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @Override
372d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public void onTouchEvent(RecyclerView rv, MotionEvent e) {
373d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!mDragging) {
374d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            return;
375d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
376d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int action = e.getActionMasked();
377d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        switch (action) {
378d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            case MotionEvent.ACTION_MOVE:
379d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                handleDragMove(e.getY());
380d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                break;
381d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            case MotionEvent.ACTION_CANCEL:
382d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            case MotionEvent.ACTION_UP:
383d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                cancelDrag();
384d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd                break;
385d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
386d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
387d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
388d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @Override
389d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {
390d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
391d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
392d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void startDrag() {
393d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mDragging = true;
394d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mThumbImageView.setPressed(true);
395d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateScrollPos();
396d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        showPreview();
397d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        cancelAnyPendingHide();
398d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
399d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
400d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void handleDragMove(float y) {
401d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int verticalScrollLength = mContainer.height() - mThumbHeight;
402d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int verticalScrollStart = mContainer.top + (mThumbHeight / 2);
403d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
404d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Convert the desired position from px to a scroll position in the conversation.
405d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        float dragScrollRatio = (y - verticalScrollStart) / verticalScrollLength;
406d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        dragScrollRatio = Math.max(dragScrollRatio, 0.0f);
407d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        dragScrollRatio = Math.min(dragScrollRatio, 1.0f);
408d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
409d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Scroll the RecyclerView to a new position.
410d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int itemCount = mRv.getAdapter().getItemCount();
411d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int itemPos = (int)((itemCount - 1) * dragScrollRatio);
412d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mRv.scrollToPosition(itemPos);
413d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
414d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
415d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void cancelDrag() {
416d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mDragging = false;
417d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mThumbImageView.setPressed(false);
418d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        hidePreview();
419d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        hideAfterDelay();
420d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
421d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
422d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    @Override
423d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    public void onLayoutChange(View v, int left, int top, int right, int bottom,
424d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            int oldLeft, int oldTop, int oldRight, int oldBottom) {
425d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (!mVisible) {
426d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            hide(false /* animate */);
427d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
428d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // The container is the size of the RecyclerView that's visible on screen. We have to
429d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // exclude the top padding, because it's usually hidden behind the conversation action bar.
430d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mContainer.set(left, top + mRv.getPaddingTop(), right, bottom);
431d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        layoutTrack();
432d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        updateScrollPos();
433d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
434d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
435d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void layoutTrack() {
436d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int trackHeight = Math.max(0, mContainer.height());
437d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY);
438d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(trackHeight, MeasureSpec.EXACTLY);
439d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mTrackImageView.measure(widthMeasureSpec, heightMeasureSpec);
440d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
441d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left;
442d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int top = mContainer.top;
443d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth);
444d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int bottom = mContainer.bottom;
445d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mTrackImageView.layout(left, top, right, bottom);
446d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
447d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
448d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void layoutThumb(int centerY) {
449d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTrackWidth, MeasureSpec.EXACTLY);
450d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mThumbHeight, MeasureSpec.EXACTLY);
451d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mThumbImageView.measure(widthMeasureSpec, heightMeasureSpec);
452d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
453d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int left = mPosRight ? (mContainer.right - mTrackWidth) : mContainer.left;
454d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int top = centerY - (mThumbImageView.getHeight() / 2);
455d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int right = mPosRight ? mContainer.right : (mContainer.left + mTrackWidth);
456d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int bottom = top + mThumbHeight;
457d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mThumbImageView.layout(left, top, right, bottom);
458d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
459d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
460d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    private void layoutPreview(int centerY) {
461d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int widthMeasureSpec = MeasureSpec.makeMeasureSpec(mContainer.width(), MeasureSpec.AT_MOST);
462d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int heightMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewHeight, MeasureSpec.EXACTLY);
463d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec);
464d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
465d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        // Ensure that the preview bubble is at least as wide as it is tall
466d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (mPreviewTextView.getMeasuredWidth() < mPreviewMinWidth) {
467d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            widthMeasureSpec = MeasureSpec.makeMeasureSpec(mPreviewMinWidth, MeasureSpec.EXACTLY);
468d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            mPreviewTextView.measure(widthMeasureSpec, heightMeasureSpec);
469d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
470d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int previewMinY = mContainer.top + mPreviewMarginTop;
471d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
472d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        final int left, right;
473d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (mPosRight) {
474d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            right = mContainer.right - mTrackWidth - mPreviewMarginLeftRight;
475d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            left = right - mPreviewTextView.getMeasuredWidth();
476d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        } else {
477d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            left = mContainer.left + mTrackWidth + mPreviewMarginLeftRight;
478d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            right = left + mPreviewTextView.getMeasuredWidth();
479d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
480d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd
481d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int bottom = centerY;
482d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        int top = bottom - mPreviewTextView.getMeasuredHeight();
483d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        if (top < previewMinY) {
484d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            top = previewMinY;
485d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd            bottom = top + mPreviewTextView.getMeasuredHeight();
486d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        }
487d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd        mPreviewTextView.layout(left, top, right, bottom);
488d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd    }
489d3b009ae55651f1e60950342468e3c37fdeb0796Mike Dodd}
490