1/*
2 * Copyright (C) 2016 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.systemui.statusbar;
18
19import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ValueAnimator;
22import android.util.ArrayMap;
23import android.util.ArraySet;
24import android.view.View;
25import android.view.ViewGroup;
26import android.view.animation.Interpolator;
27
28import com.android.systemui.Interpolators;
29import com.android.systemui.R;
30import com.android.systemui.statusbar.notification.TransformState;
31import com.android.systemui.statusbar.stack.StackStateAnimator;
32
33import java.util.Stack;
34
35/**
36 * A view that can be transformed to and from.
37 */
38public class ViewTransformationHelper implements TransformableView,
39        TransformState.TransformInfo {
40
41    private static final int TAG_CONTAINS_TRANSFORMED_VIEW = R.id.contains_transformed_view;
42
43    private ArrayMap<Integer, View> mTransformedViews = new ArrayMap<>();
44    private ArrayMap<Integer, CustomTransformation> mCustomTransformations = new ArrayMap<>();
45    private ValueAnimator mViewTransformationAnimation;
46
47    public void addTransformedView(int key, View transformedView) {
48        mTransformedViews.put(key, transformedView);
49    }
50
51    public void reset() {
52        mTransformedViews.clear();
53    }
54
55    public void setCustomTransformation(CustomTransformation transformation, int viewType) {
56        mCustomTransformations.put(viewType, transformation);
57    }
58
59    @Override
60    public TransformState getCurrentState(int fadingView) {
61        View view = mTransformedViews.get(fadingView);
62        if (view != null && view.getVisibility() != View.GONE) {
63            return TransformState.createFrom(view, this);
64        }
65        return null;
66    }
67
68    @Override
69    public void transformTo(final TransformableView notification, final Runnable endRunnable) {
70        if (mViewTransformationAnimation != null) {
71            mViewTransformationAnimation.cancel();
72        }
73        mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
74        mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
75            @Override
76            public void onAnimationUpdate(ValueAnimator animation) {
77                transformTo(notification, animation.getAnimatedFraction());
78            }
79        });
80        mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
81        mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
82        mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
83            public boolean mCancelled;
84
85            @Override
86            public void onAnimationEnd(Animator animation) {
87                if (!mCancelled) {
88                    if (endRunnable != null) {
89                        endRunnable.run();
90                    }
91                    setVisible(false);
92                    mViewTransformationAnimation = null;
93                } else {
94                    abortTransformations();
95                }
96            }
97
98            @Override
99            public void onAnimationCancel(Animator animation) {
100                mCancelled = true;
101            }
102        });
103        mViewTransformationAnimation.start();
104    }
105
106    @Override
107    public void transformTo(TransformableView notification, float transformationAmount) {
108        for (Integer viewType : mTransformedViews.keySet()) {
109            TransformState ownState = getCurrentState(viewType);
110            if (ownState != null) {
111                CustomTransformation customTransformation = mCustomTransformations.get(viewType);
112                if (customTransformation != null && customTransformation.transformTo(
113                        ownState, notification, transformationAmount)) {
114                    ownState.recycle();
115                    continue;
116                }
117                TransformState otherState = notification.getCurrentState(viewType);
118                if (otherState != null) {
119                    ownState.transformViewTo(otherState, transformationAmount);
120                    otherState.recycle();
121                } else {
122                    ownState.disappear(transformationAmount, notification);
123                }
124                ownState.recycle();
125            }
126        }
127    }
128
129    @Override
130    public void transformFrom(final TransformableView notification) {
131        if (mViewTransformationAnimation != null) {
132            mViewTransformationAnimation.cancel();
133        }
134        mViewTransformationAnimation = ValueAnimator.ofFloat(0.0f, 1.0f);
135        mViewTransformationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
136            @Override
137            public void onAnimationUpdate(ValueAnimator animation) {
138                transformFrom(notification, animation.getAnimatedFraction());
139            }
140        });
141        mViewTransformationAnimation.addListener(new AnimatorListenerAdapter() {
142            public boolean mCancelled;
143
144            @Override
145            public void onAnimationEnd(Animator animation) {
146                if (!mCancelled) {
147                    setVisible(true);
148                } else {
149                    abortTransformations();
150                }
151            }
152
153            @Override
154            public void onAnimationCancel(Animator animation) {
155                mCancelled = true;
156            }
157        });
158        mViewTransformationAnimation.setInterpolator(Interpolators.LINEAR);
159        mViewTransformationAnimation.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
160        mViewTransformationAnimation.start();
161    }
162
163    @Override
164    public void transformFrom(TransformableView notification, float transformationAmount) {
165        for (Integer viewType : mTransformedViews.keySet()) {
166            TransformState ownState = getCurrentState(viewType);
167            if (ownState != null) {
168                CustomTransformation customTransformation = mCustomTransformations.get(viewType);
169                if (customTransformation != null && customTransformation.transformFrom(
170                        ownState, notification, transformationAmount)) {
171                    ownState.recycle();
172                    continue;
173                }
174                TransformState otherState = notification.getCurrentState(viewType);
175                if (otherState != null) {
176                    ownState.transformViewFrom(otherState, transformationAmount);
177                    otherState.recycle();
178                } else {
179                    ownState.appear(transformationAmount, notification);
180                }
181                ownState.recycle();
182            }
183        }
184    }
185
186    @Override
187    public void setVisible(boolean visible) {
188        if (mViewTransformationAnimation != null) {
189            mViewTransformationAnimation.cancel();
190        }
191        for (Integer viewType : mTransformedViews.keySet()) {
192            TransformState ownState = getCurrentState(viewType);
193            if (ownState != null) {
194                ownState.setVisible(visible, false /* force */);
195                ownState.recycle();
196            }
197        }
198    }
199
200    private void abortTransformations() {
201        for (Integer viewType : mTransformedViews.keySet()) {
202            TransformState ownState = getCurrentState(viewType);
203            if (ownState != null) {
204                ownState.abortTransformation();
205                ownState.recycle();
206            }
207        }
208    }
209
210    /**
211     * Add the remaining transformation views such that all views are being transformed correctly
212     * @param viewRoot the root below which all elements need to be transformed
213     */
214    public void addRemainingTransformTypes(View viewRoot) {
215        // lets now tag the right views
216        int numValues = mTransformedViews.size();
217        for (int i = 0; i < numValues; i++) {
218            View view = mTransformedViews.valueAt(i);
219            while (view != viewRoot.getParent()) {
220                view.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, true);
221                view = (View) view.getParent();
222            }
223        }
224        Stack<View> stack = new Stack<>();
225        // Add the right views now
226        stack.push(viewRoot);
227        while (!stack.isEmpty()) {
228            View child = stack.pop();
229            Boolean containsView = (Boolean) child.getTag(TAG_CONTAINS_TRANSFORMED_VIEW);
230            if (containsView == null) {
231                // This one is unhandled, let's add it to our list.
232                int id = child.getId();
233                if (id != View.NO_ID) {
234                    // We only fade views with an id
235                    addTransformedView(id, child);
236                    continue;
237                }
238            }
239            child.setTag(TAG_CONTAINS_TRANSFORMED_VIEW, null);
240            if (child instanceof ViewGroup && !mTransformedViews.containsValue(child)){
241                ViewGroup group = (ViewGroup) child;
242                for (int i = 0; i < group.getChildCount(); i++) {
243                    stack.push(group.getChildAt(i));
244                }
245            }
246        }
247    }
248
249    public void resetTransformedView(View view) {
250        TransformState state = TransformState.createFrom(view, this);
251        state.setVisible(true /* visible */, true /* force */);
252        state.recycle();
253    }
254
255    /**
256     * @return a set of all views are being transformed.
257     */
258    public ArraySet<View> getAllTransformingViews() {
259        return new ArraySet<>(mTransformedViews.values());
260    }
261
262    @Override
263    public boolean isAnimating() {
264        return mViewTransformationAnimation != null && mViewTransformationAnimation.isRunning();
265    }
266
267    public static abstract class CustomTransformation {
268        /**
269         * Transform a state to the given view
270         * @param ownState the state to transform
271         * @param notification the view to transform to
272         * @param transformationAmount how much transformation should be done
273         * @return whether a custom transformation is performed
274         */
275        public abstract boolean transformTo(TransformState ownState,
276                TransformableView notification,
277                float transformationAmount);
278
279        /**
280         * Transform to this state from the given view
281         * @param ownState the state to transform to
282         * @param notification the view to transform from
283         * @param transformationAmount how much transformation should be done
284         * @return whether a custom transformation is performed
285         */
286        public abstract boolean transformFrom(TransformState ownState,
287                TransformableView notification,
288                float transformationAmount);
289
290        /**
291         * Perform a custom initialisation before transforming.
292         *
293         * @param ownState our own state
294         * @param otherState the other state
295         * @return whether a custom initialization is done
296         */
297        public boolean initTransformation(TransformState ownState,
298                TransformState otherState) {
299            return false;
300        }
301
302        public boolean customTransformTarget(TransformState ownState,
303                TransformState otherState) {
304            return false;
305        }
306
307        /**
308         * Get a custom interpolator for this animation
309         * @param interpolationType the type of the interpolation, i.e TranslationX / TranslationY
310         * @param isFrom true if this transformation from the other view
311         */
312        public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) {
313            return null;
314        }
315    }
316}
317