1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5 * except in compliance with the License. You may obtain a copy of the License at
6 *
7 *      http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software distributed under the
10 * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
11 * KIND, either express or implied. See the License for the specific language governing
12 * permissions and limitations under the License.
13 */
14
15package com.android.systemui.qs;
16
17import android.util.FloatProperty;
18import android.util.MathUtils;
19import android.util.Property;
20import android.view.View;
21import android.view.animation.Interpolator;
22
23import java.util.ArrayList;
24import java.util.List;
25
26/**
27 * Helper class, that handles similar properties as animators (delay, interpolators)
28 * but can have a float input as to the amount they should be in effect.  This allows
29 * easier animation that tracks input.
30 *
31 * All "delays" and "times" are as fractions from 0-1.
32 */
33public class TouchAnimator {
34
35    private final Object[] mTargets;
36    private final KeyframeSet[] mKeyframeSets;
37    private final float mStartDelay;
38    private final float mEndDelay;
39    private final float mSpan;
40    private final Interpolator mInterpolator;
41    private final Listener mListener;
42    private float mLastT = -1;
43
44    private TouchAnimator(Object[] targets, KeyframeSet[] keyframeSets,
45            float startDelay, float endDelay, Interpolator interpolator, Listener listener) {
46        mTargets = targets;
47        mKeyframeSets = keyframeSets;
48        mStartDelay = startDelay;
49        mEndDelay = endDelay;
50        mSpan = (1 - mEndDelay - mStartDelay);
51        mInterpolator = interpolator;
52        mListener = listener;
53    }
54
55    public void setPosition(float fraction) {
56        float t = MathUtils.constrain((fraction - mStartDelay) / mSpan, 0, 1);
57        if (mInterpolator != null) {
58            t = mInterpolator.getInterpolation(t);
59        }
60        if (t == mLastT) {
61            return;
62        }
63        if (mListener != null) {
64            if (t == 1) {
65                mListener.onAnimationAtEnd();
66            } else if (t == 0) {
67                mListener.onAnimationAtStart();
68            } else if (mLastT <= 0 || mLastT == 1) {
69                mListener.onAnimationStarted();
70            }
71            mLastT = t;
72        }
73        for (int i = 0; i < mTargets.length; i++) {
74            mKeyframeSets[i].setValue(t, mTargets[i]);
75        }
76    }
77
78    private static final FloatProperty<TouchAnimator> POSITION =
79            new FloatProperty<TouchAnimator>("position") {
80        @Override
81        public void setValue(TouchAnimator touchAnimator, float value) {
82            touchAnimator.setPosition(value);
83        }
84
85        @Override
86        public Float get(TouchAnimator touchAnimator) {
87            return touchAnimator.mLastT;
88        }
89    };
90
91    public static class ListenerAdapter implements Listener {
92        @Override
93        public void onAnimationAtStart() { }
94
95        @Override
96        public void onAnimationAtEnd() { }
97
98        @Override
99        public void onAnimationStarted() { }
100    }
101
102    public interface Listener {
103        /**
104         * Called when the animator moves into a position of "0". Start and end delays are
105         * taken into account, so this position may cover a range of fractional inputs.
106         */
107        void onAnimationAtStart();
108
109        /**
110         * Called when the animator moves into a position of "0". Start and end delays are
111         * taken into account, so this position may cover a range of fractional inputs.
112         */
113        void onAnimationAtEnd();
114
115        /**
116         * Called when the animator moves out of the start or end position and is in a transient
117         * state.
118         */
119        void onAnimationStarted();
120    }
121
122    public static class Builder {
123        private List<Object> mTargets = new ArrayList<>();
124        private List<KeyframeSet> mValues = new ArrayList<>();
125
126        private float mStartDelay;
127        private float mEndDelay;
128        private Interpolator mInterpolator;
129        private Listener mListener;
130
131        public Builder addFloat(Object target, String property, float... values) {
132            add(target, KeyframeSet.ofFloat(getProperty(target, property, float.class), values));
133            return this;
134        }
135
136        public Builder addInt(Object target, String property, int... values) {
137            add(target, KeyframeSet.ofInt(getProperty(target, property, int.class), values));
138            return this;
139        }
140
141        private void add(Object target, KeyframeSet keyframeSet) {
142            mTargets.add(target);
143            mValues.add(keyframeSet);
144        }
145
146        private static Property getProperty(Object target, String property, Class<?> cls) {
147            if (target instanceof View) {
148                switch (property) {
149                    case "translationX":
150                        return View.TRANSLATION_X;
151                    case "translationY":
152                        return View.TRANSLATION_Y;
153                    case "translationZ":
154                        return View.TRANSLATION_Z;
155                    case "alpha":
156                        return View.ALPHA;
157                    case "rotation":
158                        return View.ROTATION;
159                    case "x":
160                        return View.X;
161                    case "y":
162                        return View.Y;
163                    case "scaleX":
164                        return View.SCALE_X;
165                    case "scaleY":
166                        return View.SCALE_Y;
167                }
168            }
169            if (target instanceof TouchAnimator && "position".equals(property)) {
170                return POSITION;
171            }
172            return Property.of(target.getClass(), cls, property);
173        }
174
175        public Builder setStartDelay(float startDelay) {
176            mStartDelay = startDelay;
177            return this;
178        }
179
180        public Builder setEndDelay(float endDelay) {
181            mEndDelay = endDelay;
182            return this;
183        }
184
185        public Builder setInterpolator(Interpolator intepolator) {
186            mInterpolator = intepolator;
187            return this;
188        }
189
190        public Builder setListener(Listener listener) {
191            mListener = listener;
192            return this;
193        }
194
195        public TouchAnimator build() {
196            return new TouchAnimator(mTargets.toArray(new Object[mTargets.size()]),
197                    mValues.toArray(new KeyframeSet[mValues.size()]),
198                    mStartDelay, mEndDelay, mInterpolator, mListener);
199        }
200    }
201
202    private static abstract class KeyframeSet {
203
204        private final float mFrameWidth;
205        private final int mSize;
206
207        public KeyframeSet(int size) {
208            mSize = size;
209            mFrameWidth = 1 / (float) (size - 1);
210        }
211
212        void setValue(float fraction, Object target) {
213            int i;
214            for (i = 1; i < mSize - 1 && fraction > mFrameWidth; i++);
215            float amount = fraction / mFrameWidth;
216            interpolate(i, amount, target);
217        }
218
219        protected abstract void interpolate(int index, float amount, Object target);
220
221        public static KeyframeSet ofInt(Property property, int... values) {
222            return new IntKeyframeSet((Property<?, Integer>) property, values);
223        }
224
225        public static KeyframeSet ofFloat(Property property, float... values) {
226            return new FloatKeyframeSet((Property<?, Float>) property, values);
227        }
228    }
229
230    private static class FloatKeyframeSet<T> extends KeyframeSet {
231        private final float[] mValues;
232        private final Property<T, Float> mProperty;
233
234        public FloatKeyframeSet(Property<T, Float> property, float[] values) {
235            super(values.length);
236            mProperty = property;
237            mValues = values;
238        }
239
240        @Override
241        protected void interpolate(int index, float amount, Object target) {
242            float firstFloat = mValues[index - 1];
243            float secondFloat = mValues[index];
244            mProperty.set((T) target, firstFloat + (secondFloat - firstFloat) * amount);
245        }
246    }
247
248    private static class IntKeyframeSet<T> extends KeyframeSet {
249        private final int[] mValues;
250        private final Property<T, Integer> mProperty;
251
252        public IntKeyframeSet(Property<T, Integer> property, int[] values) {
253            super(values.length);
254            mProperty = property;
255            mValues = values;
256        }
257
258        @Override
259        protected void interpolate(int index, float amount, Object target) {
260            int firstFloat = mValues[index - 1];
261            int secondFloat = mValues[index];
262            mProperty.set((T) target, (int) (firstFloat + (secondFloat - firstFloat) * amount));
263        }
264    }
265}
266