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 
17 package com.android.systemui.statusbar;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.animation.ValueAnimator;
22 import android.util.ArrayMap;
23 import android.util.ArraySet;
24 import android.view.View;
25 import android.view.ViewGroup;
26 import android.view.animation.Interpolator;
27 
28 import com.android.systemui.Interpolators;
29 import com.android.systemui.R;
30 import com.android.systemui.statusbar.notification.TransformState;
31 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
32 
33 import java.util.Stack;
34 
35 /**
36  * A view that can be transformed to and from.
37  */
38 public 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 
addTransformedView(int key, View transformedView)47     public void addTransformedView(int key, View transformedView) {
48         mTransformedViews.put(key, transformedView);
49     }
50 
reset()51     public void reset() {
52         mTransformedViews.clear();
53     }
54 
setCustomTransformation(CustomTransformation transformation, int viewType)55     public void setCustomTransformation(CustomTransformation transformation, int viewType) {
56         mCustomTransformations.put(viewType, transformation);
57     }
58 
59     @Override
getCurrentState(int fadingView)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
transformTo(final TransformableView notification, final Runnable endRunnable)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
transformTo(TransformableView notification, float transformationAmount)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
transformFrom(final TransformableView notification)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
transformFrom(TransformableView notification, float transformationAmount)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
setVisible(boolean visible)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 
abortTransformations()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      */
addRemainingTransformTypes(View viewRoot)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 
resetTransformedView(View view)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      */
getAllTransformingViews()258     public ArraySet<View> getAllTransformingViews() {
259         return new ArraySet<>(mTransformedViews.values());
260     }
261 
262     @Override
isAnimating()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          */
transformTo(TransformState ownState, TransformableView notification, float transformationAmount)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          */
transformFrom(TransformState ownState, TransformableView notification, float transformationAmount)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          */
initTransformation(TransformState ownState, TransformState otherState)297         public boolean initTransformation(TransformState ownState,
298                 TransformState otherState) {
299             return false;
300         }
301 
customTransformTarget(TransformState ownState, TransformState otherState)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          */
getCustomInterpolator(int interpolationType, boolean isFrom)312         public Interpolator getCustomInterpolator(int interpolationType, boolean isFrom) {
313             return null;
314         }
315     }
316 }
317