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