1package com.android.launcher3.pageindicators;
2
3import android.animation.Animator;
4import android.animation.AnimatorListenerAdapter;
5import android.animation.ObjectAnimator;
6import android.animation.ValueAnimator;
7import android.content.Context;
8import android.content.res.Resources;
9import android.graphics.Canvas;
10import android.graphics.Color;
11import android.graphics.Paint;
12import android.os.Handler;
13import android.os.Looper;
14import android.support.v4.graphics.ColorUtils;
15import android.util.AttributeSet;
16import android.util.Log;
17import android.util.Property;
18import android.view.ViewConfiguration;
19import android.widget.ImageView;
20
21import com.android.launcher3.Launcher;
22import com.android.launcher3.R;
23import com.android.launcher3.Utilities;
24import com.android.launcher3.dynamicui.ExtractedColors;
25
26/**
27 * A PageIndicator that briefly shows a fraction of a line when moving between pages.
28 *
29 * The fraction is 1 / number of pages and the position is based on the progress of the page scroll.
30 */
31public class PageIndicatorLineCaret extends PageIndicator {
32    private static final String TAG = "PageIndicatorLine";
33
34    private static final int[] sTempCoords = new int[2];
35
36    private static final int LINE_ANIMATE_DURATION = ViewConfiguration.getScrollBarFadeDuration();
37    private static final int LINE_FADE_DELAY = ViewConfiguration.getScrollDefaultDelay();
38    public static final int WHITE_ALPHA = (int) (0.70f * 255);
39    public static final int BLACK_ALPHA = (int) (0.65f * 255);
40
41    private static final int LINE_ALPHA_ANIMATOR_INDEX = 0;
42    private static final int NUM_PAGES_ANIMATOR_INDEX = 1;
43    private static final int TOTAL_SCROLL_ANIMATOR_INDEX = 2;
44
45    private ValueAnimator[] mAnimators = new ValueAnimator[3];
46
47    private final Handler mDelayedLineFadeHandler = new Handler(Looper.getMainLooper());
48
49    private boolean mShouldAutoHide = true;
50
51    // The alpha of the line when it is showing.
52    private int mActiveAlpha = 0;
53    // The alpha that the line is being animated to or already at (either 0 or mActiveAlpha).
54    private int mToAlpha;
55    // A float value representing the number of pages, to allow for an animation when it changes.
56    private float mNumPagesFloat;
57    private int mCurrentScroll;
58    private int mTotalScroll;
59    private Paint mLinePaint;
60    private Launcher mLauncher;
61    private final int mLineHeight;
62    private ImageView mAllAppsHandle;
63
64    private static final Property<PageIndicatorLineCaret, Integer> PAINT_ALPHA
65            = new Property<PageIndicatorLineCaret, Integer>(Integer.class, "paint_alpha") {
66        @Override
67        public Integer get(PageIndicatorLineCaret obj) {
68            return obj.mLinePaint.getAlpha();
69        }
70
71        @Override
72        public void set(PageIndicatorLineCaret obj, Integer alpha) {
73            obj.mLinePaint.setAlpha(alpha);
74            obj.invalidate();
75        }
76    };
77
78    private static final Property<PageIndicatorLineCaret, Float> NUM_PAGES
79            = new Property<PageIndicatorLineCaret, Float>(Float.class, "num_pages") {
80        @Override
81        public Float get(PageIndicatorLineCaret obj) {
82            return obj.mNumPagesFloat;
83        }
84
85        @Override
86        public void set(PageIndicatorLineCaret obj, Float numPages) {
87            obj.mNumPagesFloat = numPages;
88            obj.invalidate();
89        }
90    };
91
92    private static final Property<PageIndicatorLineCaret, Integer> TOTAL_SCROLL
93            = new Property<PageIndicatorLineCaret, Integer>(Integer.class, "total_scroll") {
94        @Override
95        public Integer get(PageIndicatorLineCaret obj) {
96            return obj.mTotalScroll;
97        }
98
99        @Override
100        public void set(PageIndicatorLineCaret obj, Integer totalScroll) {
101            obj.mTotalScroll = totalScroll;
102            obj.invalidate();
103        }
104    };
105
106    private Runnable mHideLineRunnable = new Runnable() {
107        @Override
108        public void run() {
109            animateLineToAlpha(0);
110        }
111    };
112
113    public PageIndicatorLineCaret(Context context) {
114        this(context, null);
115    }
116
117    public PageIndicatorLineCaret(Context context, AttributeSet attrs) {
118        this(context, attrs, 0);
119    }
120
121    public PageIndicatorLineCaret(Context context, AttributeSet attrs, int defStyle) {
122        super(context, attrs, defStyle);
123
124        Resources res = context.getResources();
125        mLinePaint = new Paint();
126        mLinePaint.setAlpha(0);
127
128        mLauncher = Launcher.getLauncher(context);
129        mLineHeight = res.getDimensionPixelSize(R.dimen.dynamic_grid_page_indicator_line_height);
130        setCaretDrawable(new CaretDrawable(context));
131    }
132
133    @Override
134    protected void onFinishInflate() {
135        super.onFinishInflate();
136        mAllAppsHandle = (ImageView) findViewById(R.id.all_apps_handle);
137        mAllAppsHandle.setImageDrawable(getCaretDrawable());
138        mAllAppsHandle.setOnTouchListener(mLauncher.getHapticFeedbackTouchListener());
139        mAllAppsHandle.setOnClickListener(mLauncher);
140        mAllAppsHandle.setOnLongClickListener(mLauncher);
141        mAllAppsHandle.setOnFocusChangeListener(mLauncher.mFocusHandler);
142        mLauncher.setAllAppsButton(mAllAppsHandle);
143    }
144
145    @Override
146    public void setAccessibilityDelegate(AccessibilityDelegate delegate) {
147        mAllAppsHandle.setAccessibilityDelegate(delegate);
148    }
149
150    @Override
151    protected void onDraw(Canvas canvas) {
152        if (mTotalScroll == 0 || mNumPagesFloat == 0) {
153            return;
154        }
155
156        // Compute and draw line rect.
157        float progress = Utilities.boundToRange(((float) mCurrentScroll) / mTotalScroll, 0f, 1f);
158        int availableWidth = canvas.getWidth();
159        int lineWidth = (int) (availableWidth / mNumPagesFloat);
160        int lineLeft = (int) (progress * (availableWidth - lineWidth));
161        int lineRight = lineLeft + lineWidth;
162        canvas.drawRect(lineLeft, canvas.getHeight() - mLineHeight, lineRight, canvas.getHeight(),
163                mLinePaint);
164    }
165
166    @Override
167    public void setContentDescription(CharSequence contentDescription) {
168        mAllAppsHandle.setContentDescription(contentDescription);
169    }
170
171    @Override
172    public void setScroll(int currentScroll, int totalScroll) {
173        if (getAlpha() == 0) {
174            return;
175        }
176        animateLineToAlpha(mActiveAlpha);
177
178        mCurrentScroll = currentScroll;
179        if (mTotalScroll == 0) {
180            mTotalScroll = totalScroll;
181        } else if (mTotalScroll != totalScroll) {
182            animateToTotalScroll(totalScroll);
183        } else {
184            invalidate();
185        }
186
187        if (mShouldAutoHide) {
188            hideAfterDelay();
189        }
190    }
191
192    private void hideAfterDelay() {
193        mDelayedLineFadeHandler.removeCallbacksAndMessages(null);
194        mDelayedLineFadeHandler.postDelayed(mHideLineRunnable, LINE_FADE_DELAY);
195    }
196
197    @Override
198    public void setActiveMarker(int activePage) {
199    }
200
201    @Override
202    protected void onPageCountChanged() {
203        if (Float.compare(mNumPages, mNumPagesFloat) != 0) {
204            animateToNumPages(mNumPages);
205        }
206    }
207
208    public void setShouldAutoHide(boolean shouldAutoHide) {
209        mShouldAutoHide = shouldAutoHide;
210        if (shouldAutoHide && mLinePaint.getAlpha() > 0) {
211            hideAfterDelay();
212        } else if (!shouldAutoHide) {
213            mDelayedLineFadeHandler.removeCallbacksAndMessages(null);
214        }
215    }
216
217    /**
218     * The line's color will be:
219     * - mostly opaque white if the hotseat is white (ignoring alpha)
220     * - mostly opaque black if the hotseat is black (ignoring alpha)
221     */
222    public void updateColor(ExtractedColors extractedColors) {
223        int originalLineAlpha = mLinePaint.getAlpha();
224        int color = extractedColors.getColor(ExtractedColors.HOTSEAT_INDEX, Color.TRANSPARENT);
225        if (color != Color.TRANSPARENT) {
226            color = ColorUtils.setAlphaComponent(color, 255);
227            if (color == Color.BLACK) {
228                mActiveAlpha = BLACK_ALPHA;
229            } else if (color == Color.WHITE) {
230                mActiveAlpha = WHITE_ALPHA;
231            } else {
232                Log.e(TAG, "Setting workspace page indicators to an unsupported color: #"
233                        + Integer.toHexString(color));
234            }
235            mLinePaint.setColor(color);
236            mLinePaint.setAlpha(originalLineAlpha);
237        }
238    }
239
240    private void animateLineToAlpha(int alpha) {
241        if (alpha == mToAlpha) {
242            // Ignore the new animation if it is going to the same alpha as the current animation.
243            return;
244        }
245        mToAlpha = alpha;
246        setupAndRunAnimation(ObjectAnimator.ofInt(this, PAINT_ALPHA, alpha),
247                LINE_ALPHA_ANIMATOR_INDEX);
248    }
249
250    private void animateToNumPages(int numPages) {
251        setupAndRunAnimation(ObjectAnimator.ofFloat(this, NUM_PAGES, numPages),
252                NUM_PAGES_ANIMATOR_INDEX);
253    }
254
255    private void animateToTotalScroll(int totalScroll) {
256        setupAndRunAnimation(ObjectAnimator.ofInt(this, TOTAL_SCROLL, totalScroll),
257                TOTAL_SCROLL_ANIMATOR_INDEX);
258    }
259
260    /**
261     * Starts the given animator and stores it in the provided index in {@link #mAnimators} until
262     * the animation ends.
263     *
264     * If an animator is already at the index (i.e. it is already playing), it is canceled and
265     * replaced with the new animator.
266     */
267    private void setupAndRunAnimation(ValueAnimator animator, final int animatorIndex) {
268        if (mAnimators[animatorIndex] != null) {
269            mAnimators[animatorIndex].cancel();
270        }
271        mAnimators[animatorIndex] = animator;
272        mAnimators[animatorIndex].addListener(new AnimatorListenerAdapter() {
273            @Override
274            public void onAnimationEnd(Animator animation) {
275                mAnimators[animatorIndex] = null;
276            }
277        });
278        mAnimators[animatorIndex].setDuration(LINE_ANIMATE_DURATION);
279        mAnimators[animatorIndex].start();
280    }
281}
282