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