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