Home | History | Annotate | Download | only in notification
      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.NotificationHeaderView;
     22 import android.view.View;
     23 import android.view.ViewGroup;
     24 import android.view.ViewParent;
     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     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