1package com.android.systemui.qs;
2
3import android.content.Context;
4import android.graphics.drawable.AnimatedVectorDrawable;
5import android.util.AttributeSet;
6import android.util.Log;
7import android.view.View;
8import android.view.ViewGroup;
9import android.widget.ImageView;
10import com.android.systemui.R;
11
12import java.util.ArrayList;
13
14public class PageIndicator extends ViewGroup {
15
16    private static final String TAG = "PageIndicator";
17    private static final boolean DEBUG = false;
18
19    private static final long ANIMATION_DURATION = 250;
20
21    // The size of a single dot in relation to the whole animation.
22    private static final float SINGLE_SCALE = .4f;
23
24    private static final float MINOR_ALPHA = .3f;
25
26    private final ArrayList<Integer> mQueuedPositions = new ArrayList<>();
27
28    private final int mPageIndicatorWidth;
29    private final int mPageIndicatorHeight;
30    private final int mPageDotWidth;
31
32    private int mPosition = -1;
33    private boolean mAnimating;
34
35    public PageIndicator(Context context, AttributeSet attrs) {
36        super(context, attrs);
37        mPageIndicatorWidth =
38                (int) mContext.getResources().getDimension(R.dimen.qs_page_indicator_width);
39        mPageIndicatorHeight =
40                (int) mContext.getResources().getDimension(R.dimen.qs_page_indicator_height);
41        mPageDotWidth = (int) (mPageIndicatorWidth * SINGLE_SCALE);
42    }
43
44    public void setNumPages(int numPages) {
45        setVisibility(numPages > 1 ? View.VISIBLE : View.INVISIBLE);
46        if (mAnimating) {
47            Log.w(TAG, "setNumPages during animation");
48        }
49        while (numPages < getChildCount()) {
50            removeViewAt(getChildCount() - 1);
51        }
52        while (numPages > getChildCount()) {
53            ImageView v = new ImageView(mContext);
54            v.setImageResource(R.drawable.minor_a_b);
55            addView(v, new LayoutParams(mPageIndicatorWidth, mPageIndicatorHeight));
56        }
57        // Refresh state.
58        setIndex(mPosition >> 1);
59    }
60
61    public void setLocation(float location) {
62        int index = (int) location;
63        setContentDescription(getContext().getString(R.string.accessibility_quick_settings_page,
64                (index + 1), getChildCount()));
65        int position = index << 1 | ((location != index) ? 1 : 0);
66        if (DEBUG) Log.d(TAG, "setLocation " + location + " " + index + " " + position);
67
68        int lastPosition = mPosition;
69        if (mQueuedPositions.size() != 0) {
70            lastPosition = mQueuedPositions.get(mQueuedPositions.size() - 1);
71        }
72        if (position == lastPosition) return;
73        if (mAnimating) {
74            if (DEBUG) Log.d(TAG, "Queueing transition to " + Integer.toHexString(position));
75            mQueuedPositions.add(position);
76            return;
77        }
78
79        setPosition(position);
80    }
81
82    private void setPosition(int position) {
83        if (isVisibleToUser() && Math.abs(mPosition - position) == 1) {
84            animate(mPosition, position);
85        } else {
86            if (DEBUG) Log.d(TAG, "Skipping animation " + isVisibleToUser() + " " + mPosition
87                    + " " + position);
88            setIndex(position >> 1);
89        }
90        mPosition = position;
91    }
92
93    private void setIndex(int index) {
94        final int N = getChildCount();
95        for (int i = 0; i < N; i++) {
96            ImageView v = (ImageView) getChildAt(i);
97            // Clear out any animation positioning.
98            v.setTranslationX(0);
99            v.setImageResource(R.drawable.major_a_b);
100            v.setAlpha(getAlpha(i == index));
101        }
102    }
103
104    private void animate(int from, int to) {
105        if (DEBUG) Log.d(TAG, "Animating from " + Integer.toHexString(from) + " to "
106                + Integer.toHexString(to));
107        int fromIndex = from >> 1;
108        int toIndex = to >> 1;
109
110        // Set the position of everything, then we will manually control the two views involved
111        // in the animation.
112        setIndex(fromIndex);
113
114        boolean fromTransition = (from & 1) != 0;
115        boolean isAState = fromTransition ? from > to : from < to;
116        int firstIndex = Math.min(fromIndex, toIndex);
117        int secondIndex = Math.max(fromIndex, toIndex);
118        if (secondIndex == firstIndex) {
119            secondIndex++;
120        }
121        ImageView first = (ImageView) getChildAt(firstIndex);
122        ImageView second = (ImageView) getChildAt(secondIndex);
123        if (first == null || second == null) {
124            // may happen during reInflation or other weird cases
125            return;
126        }
127        // Lay the two views on top of each other.
128        second.setTranslationX(first.getX() - second.getX());
129
130        playAnimation(first, getTransition(fromTransition, isAState, false));
131        first.setAlpha(getAlpha(false));
132
133        playAnimation(second, getTransition(fromTransition, isAState, true));
134        second.setAlpha(getAlpha(true));
135
136        mAnimating = true;
137    }
138
139    private float getAlpha(boolean isMajor) {
140        return isMajor ? 1 : MINOR_ALPHA;
141    }
142
143    private void playAnimation(ImageView imageView, int res) {
144        final AnimatedVectorDrawable avd = (AnimatedVectorDrawable) getContext().getDrawable(res);
145        imageView.setImageDrawable(avd);
146        avd.forceAnimationOnUI();
147        avd.start();
148        // TODO: Figure out how to user an AVD animation callback instead, which doesn't
149        // seem to be working right now...
150        postDelayed(mAnimationDone, ANIMATION_DURATION);
151    }
152
153    private int getTransition(boolean fromB, boolean isMajorAState, boolean isMajor) {
154        if (isMajor) {
155            if (fromB) {
156                if (isMajorAState) {
157                    return R.drawable.major_b_a_animation;
158                } else {
159                    return R.drawable.major_b_c_animation;
160                }
161            } else {
162                if (isMajorAState) {
163                    return R.drawable.major_a_b_animation;
164                } else {
165                    return R.drawable.major_c_b_animation;
166                }
167            }
168        } else {
169            if (fromB) {
170                if (isMajorAState) {
171                    return R.drawable.minor_b_c_animation;
172                } else {
173                    return R.drawable.minor_b_a_animation;
174                }
175            } else {
176                if (isMajorAState) {
177                    return R.drawable.minor_c_b_animation;
178                } else {
179                    return R.drawable.minor_a_b_animation;
180                }
181            }
182        }
183    }
184
185    @Override
186    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
187        final int N = getChildCount();
188        if (N == 0) {
189            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
190            return;
191        }
192        final int widthChildSpec = MeasureSpec.makeMeasureSpec(mPageIndicatorWidth,
193                MeasureSpec.EXACTLY);
194        final int heightChildSpec = MeasureSpec.makeMeasureSpec(mPageIndicatorHeight,
195                MeasureSpec.EXACTLY);
196        for (int i = 0; i < N; i++) {
197            getChildAt(i).measure(widthChildSpec, heightChildSpec);
198        }
199        int width = (mPageIndicatorWidth - mPageDotWidth) * N + mPageDotWidth;
200        setMeasuredDimension(width, mPageIndicatorHeight);
201    }
202
203    @Override
204    protected void onLayout(boolean changed, int l, int t, int r, int b) {
205        final int N = getChildCount();
206        if (N == 0) {
207            return;
208        }
209        for (int i = 0; i < N; i++) {
210            int left = (mPageIndicatorWidth - mPageDotWidth) * i;
211            getChildAt(i).layout(left, 0, mPageIndicatorWidth + left, mPageIndicatorHeight);
212        }
213    }
214
215    private final Runnable mAnimationDone = new Runnable() {
216        @Override
217        public void run() {
218            if (DEBUG) Log.d(TAG, "onAnimationEnd - queued: " + mQueuedPositions.size());
219            mAnimating = false;
220            if (mQueuedPositions.size() != 0) {
221                setPosition(mQueuedPositions.remove(0));
222            }
223        }
224    };
225}
226