Home | History | Annotate | Download | only in stack
      1 /*
      2  * Copyright (C) 2015 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.stack;
     18 
     19 import android.animation.Animator;
     20 import android.animation.AnimatorListenerAdapter;
     21 import android.animation.PropertyValuesHolder;
     22 import android.animation.ValueAnimator;
     23 import android.view.View;
     24 
     25 import com.android.systemui.Interpolators;
     26 import com.android.systemui.R;
     27 import com.android.systemui.statusbar.ExpandableNotificationRow;
     28 import com.android.systemui.statusbar.ExpandableView;
     29 
     30 /**
     31 * A state of an expandable view
     32 */
     33 public class ExpandableViewState extends ViewState {
     34 
     35     private static final int TAG_ANIMATOR_HEIGHT = R.id.height_animator_tag;
     36     private static final int TAG_ANIMATOR_TOP_INSET = R.id.top_inset_animator_tag;
     37     private static final int TAG_ANIMATOR_SHADOW_ALPHA = R.id.shadow_alpha_animator_tag;
     38     private static final int TAG_END_HEIGHT = R.id.height_animator_end_value_tag;
     39     private static final int TAG_END_TOP_INSET = R.id.top_inset_animator_end_value_tag;
     40     private static final int TAG_END_SHADOW_ALPHA = R.id.shadow_alpha_animator_end_value_tag;
     41     private static final int TAG_START_HEIGHT = R.id.height_animator_start_value_tag;
     42     private static final int TAG_START_TOP_INSET = R.id.top_inset_animator_start_value_tag;
     43     private static final int TAG_START_SHADOW_ALPHA = R.id.shadow_alpha_animator_start_value_tag;
     44 
     45     // These are flags such that we can create masks for filtering.
     46 
     47     /**
     48      * No known location. This is the default and should not be set after an invocation of the
     49      * algorithm.
     50      */
     51     public static final int LOCATION_UNKNOWN = 0x00;
     52 
     53     /**
     54      * The location is the first heads up notification, so on the very top.
     55      */
     56     public static final int LOCATION_FIRST_HUN = 0x01;
     57 
     58     /**
     59      * The location is hidden / scrolled away on the top.
     60      */
     61     public static final int LOCATION_HIDDEN_TOP = 0x02;
     62 
     63     /**
     64      * The location is in the main area of the screen and visible.
     65      */
     66     public static final int LOCATION_MAIN_AREA = 0x04;
     67 
     68     /**
     69      * The location is in the bottom stack and it's peeking
     70      */
     71     public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x08;
     72 
     73     /**
     74      * The location is in the bottom stack and it's hidden.
     75      */
     76     public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x10;
     77 
     78     /**
     79      * The view isn't laid out at all.
     80      */
     81     public static final int LOCATION_GONE = 0x40;
     82 
     83     /**
     84      * The visible locations of a view.
     85      */
     86     public static final int VISIBLE_LOCATIONS = ExpandableViewState.LOCATION_FIRST_HUN
     87             | ExpandableViewState.LOCATION_MAIN_AREA;
     88 
     89     public int height;
     90     public boolean dimmed;
     91     public boolean dark;
     92     public boolean hideSensitive;
     93     public boolean belowSpeedBump;
     94     public float shadowAlpha;
     95     public boolean inShelf;
     96 
     97     /**
     98      * How much the child overlaps with the previous child on top. This is used to
     99      * show the background properly when the child on top is translating away.
    100      */
    101     public int clipTopAmount;
    102 
    103     /**
    104      * The index of the view, only accounting for views not equal to GONE
    105      */
    106     public int notGoneIndex;
    107 
    108     /**
    109      * The location this view is currently rendered at.
    110      *
    111      * <p>See <code>LOCATION_</code> flags.</p>
    112      */
    113     public int location;
    114 
    115     @Override
    116     public void copyFrom(ViewState viewState) {
    117         super.copyFrom(viewState);
    118         if (viewState instanceof ExpandableViewState) {
    119             ExpandableViewState svs = (ExpandableViewState) viewState;
    120             height = svs.height;
    121             dimmed = svs.dimmed;
    122             shadowAlpha = svs.shadowAlpha;
    123             dark = svs.dark;
    124             hideSensitive = svs.hideSensitive;
    125             belowSpeedBump = svs.belowSpeedBump;
    126             clipTopAmount = svs.clipTopAmount;
    127             notGoneIndex = svs.notGoneIndex;
    128             location = svs.location;
    129         }
    130     }
    131 
    132     /**
    133      * Applies a {@link ExpandableViewState} to a {@link ExpandableView}.
    134      */
    135     @Override
    136     public void applyToView(View view) {
    137         super.applyToView(view);
    138         if (view instanceof ExpandableView) {
    139             ExpandableView expandableView = (ExpandableView) view;
    140 
    141             int height = expandableView.getActualHeight();
    142             int newHeight = this.height;
    143 
    144             // apply height
    145             if (height != newHeight) {
    146                 expandableView.setActualHeight(newHeight, false /* notifyListeners */);
    147             }
    148 
    149             float shadowAlpha = expandableView.getShadowAlpha();
    150             float newShadowAlpha = this.shadowAlpha;
    151 
    152             // apply shadowAlpha
    153             if (shadowAlpha != newShadowAlpha) {
    154                 expandableView.setShadowAlpha(newShadowAlpha);
    155             }
    156 
    157             // apply dimming
    158             expandableView.setDimmed(this.dimmed, false /* animate */);
    159 
    160             // apply hiding sensitive
    161             expandableView.setHideSensitive(
    162                     this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
    163 
    164             // apply below shelf speed bump
    165             expandableView.setBelowSpeedBump(this.belowSpeedBump);
    166 
    167             // apply dark
    168             expandableView.setDark(this.dark, false /* animate */, 0 /* delay */);
    169 
    170             // apply clipping
    171             float oldClipTopAmount = expandableView.getClipTopAmount();
    172             if (oldClipTopAmount != this.clipTopAmount) {
    173                 expandableView.setClipTopAmount(this.clipTopAmount);
    174             }
    175 
    176             expandableView.setTransformingInShelf(false);
    177             expandableView.setInShelf(inShelf);
    178         }
    179     }
    180 
    181     @Override
    182     public void animateTo(View child, AnimationProperties properties) {
    183         super.animateTo(child, properties);
    184         if (!(child instanceof ExpandableView)) {
    185             return;
    186         }
    187         ExpandableView expandableView = (ExpandableView) child;
    188         AnimationFilter animationFilter = properties.getAnimationFilter();
    189 
    190         // start height animation
    191         if (this.height != expandableView.getActualHeight()) {
    192             startHeightAnimation(expandableView, properties);
    193         }  else {
    194             abortAnimation(child, TAG_ANIMATOR_HEIGHT);
    195         }
    196 
    197         // start shadow alpha animation
    198         if (this.shadowAlpha != expandableView.getShadowAlpha()) {
    199             startShadowAlphaAnimation(expandableView, properties);
    200         } else {
    201             abortAnimation(child, TAG_ANIMATOR_SHADOW_ALPHA);
    202         }
    203 
    204         // start top inset animation
    205         if (this.clipTopAmount != expandableView.getClipTopAmount()) {
    206             startInsetAnimation(expandableView, properties);
    207         } else {
    208             abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
    209         }
    210 
    211         // start dimmed animation
    212         expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
    213 
    214         // apply below the speed bump
    215         expandableView.setBelowSpeedBump(this.belowSpeedBump);
    216 
    217         // start hiding sensitive animation
    218         expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive,
    219                 properties.delay, properties.duration);
    220 
    221         // start dark animation
    222         expandableView.setDark(this.dark, animationFilter.animateDark, properties.delay);
    223 
    224         if (properties.wasAdded(child) && !hidden) {
    225             expandableView.performAddAnimation(properties.delay, properties.duration);
    226         }
    227 
    228         if (!expandableView.isInShelf() && this.inShelf) {
    229             expandableView.setTransformingInShelf(true);
    230         }
    231         expandableView.setInShelf(this.inShelf);
    232     }
    233 
    234     private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) {
    235         Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
    236         Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
    237         int newEndValue = this.height;
    238         if (previousEndValue != null && previousEndValue == newEndValue) {
    239             return;
    240         }
    241         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
    242         AnimationFilter filter = properties.getAnimationFilter();
    243         if (!filter.animateHeight) {
    244             // just a local update was performed
    245             if (previousAnimator != null) {
    246                 // we need to increase all animation keyframes of the previous animator by the
    247                 // relative change to the end value
    248                 PropertyValuesHolder[] values = previousAnimator.getValues();
    249                 int relativeDiff = newEndValue - previousEndValue;
    250                 int newStartValue = previousStartValue + relativeDiff;
    251                 values[0].setIntValues(newStartValue, newEndValue);
    252                 child.setTag(TAG_START_HEIGHT, newStartValue);
    253                 child.setTag(TAG_END_HEIGHT, newEndValue);
    254                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
    255                 return;
    256             } else {
    257                 // no new animation needed, let's just apply the value
    258                 child.setActualHeight(newEndValue, false);
    259                 return;
    260             }
    261         }
    262 
    263         ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
    264         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    265             @Override
    266             public void onAnimationUpdate(ValueAnimator animation) {
    267                 child.setActualHeight((int) animation.getAnimatedValue(),
    268                         false /* notifyListeners */);
    269             }
    270         });
    271         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
    272         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
    273         animator.setDuration(newDuration);
    274         if (properties.delay > 0 && (previousAnimator == null
    275                 || previousAnimator.getAnimatedFraction() == 0)) {
    276             animator.setStartDelay(properties.delay);
    277         }
    278         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
    279         if (listener != null) {
    280             animator.addListener(listener);
    281         }
    282         // remove the tag when the animation is finished
    283         animator.addListener(new AnimatorListenerAdapter() {
    284             boolean mWasCancelled;
    285 
    286             @Override
    287             public void onAnimationEnd(Animator animation) {
    288                 child.setTag(TAG_ANIMATOR_HEIGHT, null);
    289                 child.setTag(TAG_START_HEIGHT, null);
    290                 child.setTag(TAG_END_HEIGHT, null);
    291                 child.setActualHeightAnimating(false);
    292                 if (!mWasCancelled && child instanceof ExpandableNotificationRow) {
    293                     ((ExpandableNotificationRow) child).setGroupExpansionChanging(
    294                             false /* isExpansionChanging */);
    295                 }
    296             }
    297 
    298             @Override
    299             public void onAnimationStart(Animator animation) {
    300                 mWasCancelled = false;
    301             }
    302 
    303             @Override
    304             public void onAnimationCancel(Animator animation) {
    305                 mWasCancelled = true;
    306             }
    307         });
    308         startAnimator(animator, listener);
    309         child.setTag(TAG_ANIMATOR_HEIGHT, animator);
    310         child.setTag(TAG_START_HEIGHT, child.getActualHeight());
    311         child.setTag(TAG_END_HEIGHT, newEndValue);
    312         child.setActualHeightAnimating(true);
    313     }
    314 
    315     private void startShadowAlphaAnimation(final ExpandableView child,
    316             AnimationProperties properties) {
    317         Float previousStartValue = getChildTag(child, TAG_START_SHADOW_ALPHA);
    318         Float previousEndValue = getChildTag(child, TAG_END_SHADOW_ALPHA);
    319         float newEndValue = this.shadowAlpha;
    320         if (previousEndValue != null && previousEndValue == newEndValue) {
    321             return;
    322         }
    323         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SHADOW_ALPHA);
    324         AnimationFilter filter = properties.getAnimationFilter();
    325         if (!filter.animateShadowAlpha) {
    326             // just a local update was performed
    327             if (previousAnimator != null) {
    328                 // we need to increase all animation keyframes of the previous animator by the
    329                 // relative change to the end value
    330                 PropertyValuesHolder[] values = previousAnimator.getValues();
    331                 float relativeDiff = newEndValue - previousEndValue;
    332                 float newStartValue = previousStartValue + relativeDiff;
    333                 values[0].setFloatValues(newStartValue, newEndValue);
    334                 child.setTag(TAG_START_SHADOW_ALPHA, newStartValue);
    335                 child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
    336                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
    337                 return;
    338             } else {
    339                 // no new animation needed, let's just apply the value
    340                 child.setShadowAlpha(newEndValue);
    341                 return;
    342             }
    343         }
    344 
    345         ValueAnimator animator = ValueAnimator.ofFloat(child.getShadowAlpha(), newEndValue);
    346         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    347             @Override
    348             public void onAnimationUpdate(ValueAnimator animation) {
    349                 child.setShadowAlpha((float) animation.getAnimatedValue());
    350             }
    351         });
    352         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
    353         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
    354         animator.setDuration(newDuration);
    355         if (properties.delay > 0 && (previousAnimator == null
    356                 || previousAnimator.getAnimatedFraction() == 0)) {
    357             animator.setStartDelay(properties.delay);
    358         }
    359         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
    360         if (listener != null) {
    361             animator.addListener(listener);
    362         }
    363         // remove the tag when the animation is finished
    364         animator.addListener(new AnimatorListenerAdapter() {
    365             @Override
    366             public void onAnimationEnd(Animator animation) {
    367                 child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, null);
    368                 child.setTag(TAG_START_SHADOW_ALPHA, null);
    369                 child.setTag(TAG_END_SHADOW_ALPHA, null);
    370             }
    371         });
    372         startAnimator(animator, listener);
    373         child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, animator);
    374         child.setTag(TAG_START_SHADOW_ALPHA, child.getShadowAlpha());
    375         child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
    376     }
    377 
    378     private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) {
    379         Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
    380         Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
    381         int newEndValue = this.clipTopAmount;
    382         if (previousEndValue != null && previousEndValue == newEndValue) {
    383             return;
    384         }
    385         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
    386         AnimationFilter filter = properties.getAnimationFilter();
    387         if (!filter.animateTopInset) {
    388             // just a local update was performed
    389             if (previousAnimator != null) {
    390                 // we need to increase all animation keyframes of the previous animator by the
    391                 // relative change to the end value
    392                 PropertyValuesHolder[] values = previousAnimator.getValues();
    393                 int relativeDiff = newEndValue - previousEndValue;
    394                 int newStartValue = previousStartValue + relativeDiff;
    395                 values[0].setIntValues(newStartValue, newEndValue);
    396                 child.setTag(TAG_START_TOP_INSET, newStartValue);
    397                 child.setTag(TAG_END_TOP_INSET, newEndValue);
    398                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
    399                 return;
    400             } else {
    401                 // no new animation needed, let's just apply the value
    402                 child.setClipTopAmount(newEndValue);
    403                 return;
    404             }
    405         }
    406 
    407         ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
    408         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    409             @Override
    410             public void onAnimationUpdate(ValueAnimator animation) {
    411                 child.setClipTopAmount((int) animation.getAnimatedValue());
    412             }
    413         });
    414         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
    415         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
    416         animator.setDuration(newDuration);
    417         if (properties.delay > 0 && (previousAnimator == null
    418                 || previousAnimator.getAnimatedFraction() == 0)) {
    419             animator.setStartDelay(properties.delay);
    420         }
    421         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
    422         if (listener != null) {
    423             animator.addListener(listener);
    424         }
    425         // remove the tag when the animation is finished
    426         animator.addListener(new AnimatorListenerAdapter() {
    427             @Override
    428             public void onAnimationEnd(Animator animation) {
    429                 child.setTag(TAG_ANIMATOR_TOP_INSET, null);
    430                 child.setTag(TAG_START_TOP_INSET, null);
    431                 child.setTag(TAG_END_TOP_INSET, null);
    432             }
    433         });
    434         startAnimator(animator, listener);
    435         child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
    436         child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
    437         child.setTag(TAG_END_TOP_INSET, newEndValue);
    438     }
    439 
    440     /**
    441      * Get the end value of the height animation running on a view or the actualHeight
    442      * if no animation is running.
    443      */
    444     public static int getFinalActualHeight(ExpandableView view) {
    445         if (view == null) {
    446             return 0;
    447         }
    448         ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
    449         if (heightAnimator == null) {
    450             return view.getActualHeight();
    451         } else {
    452             return getChildTag(view, TAG_END_HEIGHT);
    453         }
    454     }
    455 }
    456