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.notification;
18 
19 import android.util.ArraySet;
20 import android.util.Pools;
21 import android.view.View;
22 import android.view.ViewGroup;
23 import android.view.ViewParent;
24 import android.view.animation.Interpolator;
25 import android.widget.ImageView;
26 import android.widget.ProgressBar;
27 import android.widget.TextView;
28 
29 import com.android.systemui.Interpolators;
30 import com.android.systemui.R;
31 import com.android.systemui.statusbar.CrossFadeHelper;
32 import com.android.systemui.statusbar.ExpandableNotificationRow;
33 import com.android.systemui.statusbar.TransformableView;
34 import com.android.systemui.statusbar.ViewTransformationHelper;
35 
36 /**
37  * A transform state of a view.
38 */
39 public class TransformState {
40 
41     public static final int TRANSFORM_X = 0x1;
42     public static final int TRANSFORM_Y = 0x10;
43     public static final int TRANSFORM_ALL = TRANSFORM_X | TRANSFORM_Y;
44 
45     private static final float UNDEFINED = -1f;
46     private static final int CLIP_CLIPPING_SET = R.id.clip_children_set_tag;
47     private static final int CLIP_CHILDREN_TAG = R.id.clip_children_tag;
48     private static final int CLIP_TO_PADDING = R.id.clip_to_padding_tag;
49     private static final int TRANSFORMATION_START_X = R.id.transformation_start_x_tag;
50     private static final int TRANSFORMATION_START_Y = R.id.transformation_start_y_tag;
51     private static final int TRANSFORMATION_START_SCLALE_X = R.id.transformation_start_scale_x_tag;
52     private static final int TRANSFORMATION_START_SCLALE_Y = R.id.transformation_start_scale_y_tag;
53     private static Pools.SimplePool<TransformState> sInstancePool = new Pools.SimplePool<>(40);
54 
55     protected View mTransformedView;
56     private int[] mOwnPosition = new int[2];
57     private float mTransformationEndY = UNDEFINED;
58     private float mTransformationEndX = UNDEFINED;
59 
initFrom(View view)60     public void initFrom(View view) {
61         mTransformedView = view;
62     }
63 
64     /**
65      * Transforms the {@link #mTransformedView} from the given transformviewstate
66      * @param otherState the state to transform from
67      * @param transformationAmount how much to transform
68      */
transformViewFrom(TransformState otherState, float transformationAmount)69     public void transformViewFrom(TransformState otherState, float transformationAmount) {
70         mTransformedView.animate().cancel();
71         if (sameAs(otherState)) {
72             if (mTransformedView.getVisibility() == View.INVISIBLE
73                     || mTransformedView.getAlpha() != 1.0f) {
74                 // We have the same content, lets show ourselves
75                 mTransformedView.setAlpha(1.0f);
76                 mTransformedView.setVisibility(View.VISIBLE);
77             }
78         } else {
79             CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
80         }
81         transformViewFullyFrom(otherState, transformationAmount);
82     }
83 
transformViewFullyFrom(TransformState otherState, float transformationAmount)84     public void transformViewFullyFrom(TransformState otherState, float transformationAmount) {
85         transformViewFrom(otherState, TRANSFORM_ALL, null, transformationAmount);
86     }
87 
transformViewFullyFrom(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)88     public void transformViewFullyFrom(TransformState otherState,
89             ViewTransformationHelper.CustomTransformation customTransformation,
90             float transformationAmount) {
91         transformViewFrom(otherState, TRANSFORM_ALL, customTransformation, transformationAmount);
92     }
93 
transformViewVerticalFrom(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)94     public void transformViewVerticalFrom(TransformState otherState,
95             ViewTransformationHelper.CustomTransformation customTransformation,
96             float transformationAmount) {
97         transformViewFrom(otherState, TRANSFORM_Y, customTransformation, transformationAmount);
98     }
99 
transformViewVerticalFrom(TransformState otherState, float transformationAmount)100     public void transformViewVerticalFrom(TransformState otherState, float transformationAmount) {
101         transformViewFrom(otherState, TRANSFORM_Y, null, transformationAmount);
102     }
103 
transformViewFrom(TransformState otherState, int transformationFlags, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)104     private void transformViewFrom(TransformState otherState, int transformationFlags,
105             ViewTransformationHelper.CustomTransformation customTransformation,
106             float transformationAmount) {
107         final View transformedView = mTransformedView;
108         boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
109         boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
110         boolean transformScale = transformScale();
111         // lets animate the positions correctly
112         if (transformationAmount == 0.0f
113                 || transformX && getTransformationStartX() == UNDEFINED
114                 || transformY && getTransformationStartY() == UNDEFINED
115                 || transformScale && getTransformationStartScaleX() == UNDEFINED
116                 || transformScale && getTransformationStartScaleY() == UNDEFINED) {
117             int[] otherPosition;
118             if (transformationAmount != 0.0f) {
119                 otherPosition = otherState.getLaidOutLocationOnScreen();
120             } else {
121                 otherPosition = otherState.getLocationOnScreen();
122             }
123             int[] ownStablePosition = getLaidOutLocationOnScreen();
124             if (customTransformation == null
125                     || !customTransformation.initTransformation(this, otherState)) {
126                 if (transformX) {
127                     setTransformationStartX(otherPosition[0] - ownStablePosition[0]);
128                 }
129                 if (transformY) {
130                     setTransformationStartY(otherPosition[1] - ownStablePosition[1]);
131                 }
132                 // we also want to animate the scale if we're the same
133                 View otherView = otherState.getTransformedView();
134                 if (transformScale && otherView.getWidth() != transformedView.getWidth()) {
135                     setTransformationStartScaleX(otherView.getWidth() * otherView.getScaleX()
136                             / (float) transformedView.getWidth());
137                     transformedView.setPivotX(0);
138                 } else {
139                     setTransformationStartScaleX(UNDEFINED);
140                 }
141                 if (transformScale && otherView.getHeight() != transformedView.getHeight()) {
142                     setTransformationStartScaleY(otherView.getHeight() * otherView.getScaleY()
143                             / (float) transformedView.getHeight());
144                     transformedView.setPivotY(0);
145                 } else {
146                     setTransformationStartScaleY(UNDEFINED);
147                 }
148             }
149             if (!transformX) {
150                 setTransformationStartX(UNDEFINED);
151             }
152             if (!transformY) {
153                 setTransformationStartY(UNDEFINED);
154             }
155             if (!transformScale) {
156                 setTransformationStartScaleX(UNDEFINED);
157                 setTransformationStartScaleY(UNDEFINED);
158             }
159             setClippingDeactivated(transformedView, true);
160         }
161         float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
162                 transformationAmount);
163         if (transformX) {
164             float interpolation = interpolatedValue;
165             if (customTransformation != null) {
166                 Interpolator customInterpolator =
167                         customTransformation.getCustomInterpolator(TRANSFORM_X, true /* isFrom */);
168                 if (customInterpolator != null) {
169                     interpolation = customInterpolator.getInterpolation(transformationAmount);
170                 }
171             }
172             transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
173                     0.0f,
174                     interpolation));
175         }
176         if (transformY) {
177             float interpolation = interpolatedValue;
178             if (customTransformation != null) {
179                 Interpolator customInterpolator =
180                         customTransformation.getCustomInterpolator(TRANSFORM_Y, true /* isFrom */);
181                 if (customInterpolator != null) {
182                     interpolation = customInterpolator.getInterpolation(transformationAmount);
183                 }
184             }
185             transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
186                     0.0f,
187                     interpolation));
188         }
189         if (transformScale) {
190             float transformationStartScaleX = getTransformationStartScaleX();
191             if (transformationStartScaleX != UNDEFINED) {
192                 transformedView.setScaleX(
193                         NotificationUtils.interpolate(transformationStartScaleX,
194                                 1.0f,
195                                 interpolatedValue));
196             }
197             float transformationStartScaleY = getTransformationStartScaleY();
198             if (transformationStartScaleY != UNDEFINED) {
199                 transformedView.setScaleY(
200                         NotificationUtils.interpolate(transformationStartScaleY,
201                                 1.0f,
202                                 interpolatedValue));
203             }
204         }
205     }
206 
transformScale()207     protected boolean transformScale() {
208         return false;
209     }
210 
211     /**
212      * Transforms the {@link #mTransformedView} to the given transformviewstate
213      * @param otherState the state to transform from
214      * @param transformationAmount how much to transform
215      * @return whether an animation was started
216      */
transformViewTo(TransformState otherState, float transformationAmount)217     public boolean transformViewTo(TransformState otherState, float transformationAmount) {
218         mTransformedView.animate().cancel();
219         if (sameAs(otherState)) {
220             // We have the same text, lets show ourselfs
221             if (mTransformedView.getVisibility() == View.VISIBLE) {
222                 mTransformedView.setAlpha(0.0f);
223                 mTransformedView.setVisibility(View.INVISIBLE);
224             }
225             return false;
226         } else {
227             CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
228         }
229         transformViewFullyTo(otherState, transformationAmount);
230         return true;
231     }
232 
transformViewFullyTo(TransformState otherState, float transformationAmount)233     public void transformViewFullyTo(TransformState otherState, float transformationAmount) {
234         transformViewTo(otherState, TRANSFORM_ALL, null, transformationAmount);
235     }
236 
transformViewFullyTo(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)237     public void transformViewFullyTo(TransformState otherState,
238             ViewTransformationHelper.CustomTransformation customTransformation,
239             float transformationAmount) {
240         transformViewTo(otherState, TRANSFORM_ALL, customTransformation, transformationAmount);
241     }
242 
transformViewVerticalTo(TransformState otherState, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)243     public void transformViewVerticalTo(TransformState otherState,
244             ViewTransformationHelper.CustomTransformation customTransformation,
245             float transformationAmount) {
246         transformViewTo(otherState, TRANSFORM_Y, customTransformation, transformationAmount);
247     }
248 
transformViewVerticalTo(TransformState otherState, float transformationAmount)249     public void transformViewVerticalTo(TransformState otherState, float transformationAmount) {
250         transformViewTo(otherState, TRANSFORM_Y, null, transformationAmount);
251     }
252 
transformViewTo(TransformState otherState, int transformationFlags, ViewTransformationHelper.CustomTransformation customTransformation, float transformationAmount)253     private void transformViewTo(TransformState otherState, int transformationFlags,
254             ViewTransformationHelper.CustomTransformation customTransformation,
255             float transformationAmount) {
256         // lets animate the positions correctly
257 
258         final View transformedView = mTransformedView;
259         boolean transformX = (transformationFlags & TRANSFORM_X) != 0;
260         boolean transformY = (transformationFlags & TRANSFORM_Y) != 0;
261         boolean transformScale = transformScale();
262         // lets animate the positions correctly
263         if (transformationAmount == 0.0f) {
264             if (transformX) {
265                 float transformationStartX = getTransformationStartX();
266                 float start = transformationStartX != UNDEFINED ? transformationStartX
267                         : transformedView.getTranslationX();
268                 setTransformationStartX(start);
269             }
270             if (transformY) {
271                 float transformationStartY = getTransformationStartY();
272                 float start = transformationStartY != UNDEFINED ? transformationStartY
273                         : transformedView.getTranslationY();
274                 setTransformationStartY(start);
275             }
276             View otherView = otherState.getTransformedView();
277             if (transformScale && otherView.getWidth() != transformedView.getWidth()) {
278                 setTransformationStartScaleX(transformedView.getScaleX());
279                 transformedView.setPivotX(0);
280             } else {
281                 setTransformationStartScaleX(UNDEFINED);
282             }
283             if (transformScale && otherView.getHeight() != transformedView.getHeight()) {
284                 setTransformationStartScaleY(transformedView.getScaleY());
285                 transformedView.setPivotY(0);
286             } else {
287                 setTransformationStartScaleY(UNDEFINED);
288             }
289             setClippingDeactivated(transformedView, true);
290         }
291         float interpolatedValue = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(
292                 transformationAmount);
293         int[] otherStablePosition = otherState.getLaidOutLocationOnScreen();
294         int[] ownPosition = getLaidOutLocationOnScreen();
295         if (transformX) {
296             float endX = otherStablePosition[0] - ownPosition[0];
297             float interpolation = interpolatedValue;
298             if (customTransformation != null) {
299                 if (customTransformation.customTransformTarget(this, otherState)) {
300                     endX = mTransformationEndX;
301                 }
302                 Interpolator customInterpolator =
303                         customTransformation.getCustomInterpolator(TRANSFORM_X, false /* isFrom */);
304                 if (customInterpolator != null) {
305                     interpolation = customInterpolator.getInterpolation(transformationAmount);
306                 }
307             }
308             transformedView.setTranslationX(NotificationUtils.interpolate(getTransformationStartX(),
309                     endX,
310                     interpolation));
311         }
312         if (transformY) {
313             float endY = otherStablePosition[1] - ownPosition[1];
314             float interpolation = interpolatedValue;
315             if (customTransformation != null) {
316                 if (customTransformation.customTransformTarget(this, otherState)) {
317                     endY = mTransformationEndY;
318                 }
319                 Interpolator customInterpolator =
320                         customTransformation.getCustomInterpolator(TRANSFORM_Y, false /* isFrom */);
321                 if (customInterpolator != null) {
322                     interpolation = customInterpolator.getInterpolation(transformationAmount);
323                 }
324             }
325             transformedView.setTranslationY(NotificationUtils.interpolate(getTransformationStartY(),
326                     endY,
327                     interpolation));
328         }
329         if (transformScale) {
330             View otherView = otherState.getTransformedView();
331             float transformationStartScaleX = getTransformationStartScaleX();
332             if (transformationStartScaleX != UNDEFINED) {
333                 transformedView.setScaleX(
334                         NotificationUtils.interpolate(transformationStartScaleX,
335                                 (otherView.getWidth() / (float) transformedView.getWidth()),
336                                 interpolatedValue));
337             }
338             float transformationStartScaleY = getTransformationStartScaleY();
339             if (transformationStartScaleY != UNDEFINED) {
340                 transformedView.setScaleY(
341                         NotificationUtils.interpolate(transformationStartScaleY,
342                                 (otherView.getHeight() / (float) transformedView.getHeight()),
343                                 interpolatedValue));
344             }
345         }
346     }
347 
setClippingDeactivated(final View transformedView, boolean deactivated)348     public static void setClippingDeactivated(final View transformedView, boolean deactivated) {
349         if (!(transformedView.getParent() instanceof ViewGroup)) {
350             return;
351         }
352         ViewGroup view = (ViewGroup) transformedView.getParent();
353         while (true) {
354             ArraySet<View> clipSet = (ArraySet<View>) view.getTag(CLIP_CLIPPING_SET);
355             if (clipSet == null) {
356                 clipSet = new ArraySet<>();
357                 view.setTag(CLIP_CLIPPING_SET, clipSet);
358             }
359             Boolean clipChildren = (Boolean) view.getTag(CLIP_CHILDREN_TAG);
360             if (clipChildren == null) {
361                 clipChildren = view.getClipChildren();
362                 view.setTag(CLIP_CHILDREN_TAG, clipChildren);
363             }
364             Boolean clipToPadding = (Boolean) view.getTag(CLIP_TO_PADDING);
365             if (clipToPadding == null) {
366                 clipToPadding = view.getClipToPadding();
367                 view.setTag(CLIP_TO_PADDING, clipToPadding);
368             }
369             ExpandableNotificationRow row = view instanceof ExpandableNotificationRow
370                     ? (ExpandableNotificationRow) view
371                     : null;
372             if (!deactivated) {
373                 clipSet.remove(transformedView);
374                 if (clipSet.isEmpty()) {
375                     view.setClipChildren(clipChildren);
376                     view.setClipToPadding(clipToPadding);
377                     view.setTag(CLIP_CLIPPING_SET, null);
378                     if (row != null) {
379                         row.setClipToActualHeight(true);
380                     }
381                 }
382             } else {
383                 clipSet.add(transformedView);
384                 view.setClipChildren(false);
385                 view.setClipToPadding(false);
386                 if (row != null && row.isChildInGroup()) {
387                     // We still want to clip to the parent's height
388                     row.setClipToActualHeight(false);
389                 }
390             }
391             if (row != null && !row.isChildInGroup()) {
392                 return;
393             }
394             final ViewParent parent = view.getParent();
395             if (parent instanceof ViewGroup) {
396                 view = (ViewGroup) parent;
397             } else {
398                 return;
399             }
400         }
401     }
402 
getLaidOutLocationOnScreen()403     public int[] getLaidOutLocationOnScreen() {
404         int[] location = getLocationOnScreen();
405         // remove translation
406         location[0] -= mTransformedView.getTranslationX();
407         location[1] -= mTransformedView.getTranslationY();
408         return location;
409     }
410 
getLocationOnScreen()411     public int[] getLocationOnScreen() {
412         mTransformedView.getLocationOnScreen(mOwnPosition);
413 
414         // remove scale
415         mOwnPosition[0] -= (1.0f - mTransformedView.getScaleX()) * mTransformedView.getPivotX();
416         mOwnPosition[1] -= (1.0f - mTransformedView.getScaleY()) * mTransformedView.getPivotY();
417         return mOwnPosition;
418     }
419 
sameAs(TransformState otherState)420     protected boolean sameAs(TransformState otherState) {
421         return false;
422     }
423 
appear(float transformationAmount, TransformableView otherView)424     public void appear(float transformationAmount, TransformableView otherView) {
425         // There's no other view, lets fade us in
426         // Certain views need to prepare the fade in and make sure its children are
427         // completely visible. An example is the notification header.
428         if (transformationAmount == 0.0f) {
429             prepareFadeIn();
430         }
431         CrossFadeHelper.fadeIn(mTransformedView, transformationAmount);
432     }
433 
disappear(float transformationAmount, TransformableView otherView)434     public void disappear(float transformationAmount, TransformableView otherView) {
435         CrossFadeHelper.fadeOut(mTransformedView, transformationAmount);
436     }
437 
createFrom(View view)438     public static TransformState createFrom(View view) {
439         if (view instanceof TextView) {
440             TextViewTransformState result = TextViewTransformState.obtain();
441             result.initFrom(view);
442             return result;
443         }
444         if (view.getId() == com.android.internal.R.id.actions_container) {
445             ActionListTransformState result = ActionListTransformState.obtain();
446             result.initFrom(view);
447             return result;
448         }
449         if (view instanceof ImageView) {
450             ImageTransformState result = ImageTransformState.obtain();
451             result.initFrom(view);
452             return result;
453         }
454         if (view instanceof ProgressBar) {
455             ProgressTransformState result = ProgressTransformState.obtain();
456             result.initFrom(view);
457             return result;
458         }
459         TransformState result = obtain();
460         result.initFrom(view);
461         return result;
462     }
463 
recycle()464     public void recycle() {
465         reset();
466         if (getClass() == TransformState.class) {
467             sInstancePool.release(this);
468         }
469     }
470 
setTransformationEndY(float transformationEndY)471     public void setTransformationEndY(float transformationEndY) {
472         mTransformationEndY = transformationEndY;
473     }
474 
setTransformationEndX(float transformationEndX)475     public void setTransformationEndX(float transformationEndX) {
476         mTransformationEndX = transformationEndX;
477     }
478 
getTransformationStartX()479     public float getTransformationStartX() {
480         Object tag = mTransformedView.getTag(TRANSFORMATION_START_X);
481         return tag == null ? UNDEFINED : (float) tag;
482     }
483 
getTransformationStartY()484     public float getTransformationStartY() {
485         Object tag = mTransformedView.getTag(TRANSFORMATION_START_Y);
486         return tag == null ? UNDEFINED : (float) tag;
487     }
488 
getTransformationStartScaleX()489     public float getTransformationStartScaleX() {
490         Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_X);
491         return tag == null ? UNDEFINED : (float) tag;
492     }
493 
getTransformationStartScaleY()494     public float getTransformationStartScaleY() {
495         Object tag = mTransformedView.getTag(TRANSFORMATION_START_SCLALE_Y);
496         return tag == null ? UNDEFINED : (float) tag;
497     }
498 
setTransformationStartX(float transformationStartX)499     public void setTransformationStartX(float transformationStartX) {
500         mTransformedView.setTag(TRANSFORMATION_START_X, transformationStartX);
501     }
502 
setTransformationStartY(float transformationStartY)503     public void setTransformationStartY(float transformationStartY) {
504         mTransformedView.setTag(TRANSFORMATION_START_Y, transformationStartY);
505     }
506 
setTransformationStartScaleX(float startScaleX)507     private void setTransformationStartScaleX(float startScaleX) {
508         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, startScaleX);
509     }
510 
setTransformationStartScaleY(float startScaleY)511     private void setTransformationStartScaleY(float startScaleY) {
512         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, startScaleY);
513     }
514 
reset()515     protected void reset() {
516         mTransformedView = null;
517         mTransformationEndX = UNDEFINED;
518         mTransformationEndY = UNDEFINED;
519     }
520 
setVisible(boolean visible, boolean force)521     public void setVisible(boolean visible, boolean force) {
522         if (!force && mTransformedView.getVisibility() == View.GONE) {
523             return;
524         }
525         if (mTransformedView.getVisibility() != View.GONE) {
526             mTransformedView.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
527         }
528         mTransformedView.animate().cancel();
529         mTransformedView.setAlpha(visible ? 1.0f : 0.0f);
530         resetTransformedView();
531     }
532 
prepareFadeIn()533     public void prepareFadeIn() {
534         resetTransformedView();
535     }
536 
resetTransformedView()537     protected void resetTransformedView() {
538         mTransformedView.setTranslationX(0);
539         mTransformedView.setTranslationY(0);
540         mTransformedView.setScaleX(1.0f);
541         mTransformedView.setScaleY(1.0f);
542         setClippingDeactivated(mTransformedView, false);
543         abortTransformation();
544     }
545 
abortTransformation()546     public void abortTransformation() {
547         mTransformedView.setTag(TRANSFORMATION_START_X, UNDEFINED);
548         mTransformedView.setTag(TRANSFORMATION_START_Y, UNDEFINED);
549         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_X, UNDEFINED);
550         mTransformedView.setTag(TRANSFORMATION_START_SCLALE_Y, UNDEFINED);
551     }
552 
obtain()553     public static TransformState obtain() {
554         TransformState instance = sInstancePool.acquire();
555         if (instance != null) {
556             return instance;
557         }
558         return new TransformState();
559     }
560 
getTransformedView()561     public View getTransformedView() {
562         return mTransformedView;
563     }
564 }
565