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