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      * A state indicating whether a headsup is currently fully visible, even when not scrolled.
     99      * Only valid if the view is heads upped.
    100      */
    101     public boolean headsUpIsVisible;
    102 
    103     /**
    104      * How much the child overlaps with the previous child on top. This is used to
    105      * show the background properly when the child on top is translating away.
    106      */
    107     public int clipTopAmount;
    108 
    109     /**
    110      * The index of the view, only accounting for views not equal to GONE
    111      */
    112     public int notGoneIndex;
    113 
    114     /**
    115      * The location this view is currently rendered at.
    116      *
    117      * <p>See <code>LOCATION_</code> flags.</p>
    118      */
    119     public int location;
    120 
    121     @Override
    122     public void copyFrom(ViewState viewState) {
    123         super.copyFrom(viewState);
    124         if (viewState instanceof ExpandableViewState) {
    125             ExpandableViewState svs = (ExpandableViewState) viewState;
    126             height = svs.height;
    127             dimmed = svs.dimmed;
    128             shadowAlpha = svs.shadowAlpha;
    129             dark = svs.dark;
    130             hideSensitive = svs.hideSensitive;
    131             belowSpeedBump = svs.belowSpeedBump;
    132             clipTopAmount = svs.clipTopAmount;
    133             notGoneIndex = svs.notGoneIndex;
    134             location = svs.location;
    135             headsUpIsVisible = svs.headsUpIsVisible;
    136         }
    137     }
    138 
    139     /**
    140      * Applies a {@link ExpandableViewState} to a {@link ExpandableView}.
    141      */
    142     @Override
    143     public void applyToView(View view) {
    144         super.applyToView(view);
    145         if (view instanceof ExpandableView) {
    146             ExpandableView expandableView = (ExpandableView) view;
    147 
    148             int height = expandableView.getActualHeight();
    149             int newHeight = this.height;
    150 
    151             // apply height
    152             if (height != newHeight) {
    153                 expandableView.setActualHeight(newHeight, false /* notifyListeners */);
    154             }
    155 
    156             float shadowAlpha = expandableView.getShadowAlpha();
    157             float newShadowAlpha = this.shadowAlpha;
    158 
    159             // apply shadowAlpha
    160             if (shadowAlpha != newShadowAlpha) {
    161                 expandableView.setShadowAlpha(newShadowAlpha);
    162             }
    163 
    164             // apply dimming
    165             expandableView.setDimmed(this.dimmed, false /* animate */);
    166 
    167             // apply hiding sensitive
    168             expandableView.setHideSensitive(
    169                     this.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
    170 
    171             // apply below shelf speed bump
    172             expandableView.setBelowSpeedBump(this.belowSpeedBump);
    173 
    174             // apply dark
    175             expandableView.setDark(this.dark, false /* animate */, 0 /* delay */);
    176 
    177             // apply clipping
    178             float oldClipTopAmount = expandableView.getClipTopAmount();
    179             if (oldClipTopAmount != this.clipTopAmount) {
    180                 expandableView.setClipTopAmount(this.clipTopAmount);
    181             }
    182 
    183             expandableView.setTransformingInShelf(false);
    184             expandableView.setInShelf(inShelf);
    185 
    186             if (headsUpIsVisible) {
    187                 expandableView.setHeadsUpIsVisible();
    188             }
    189         }
    190     }
    191 
    192     @Override
    193     public void animateTo(View child, AnimationProperties properties) {
    194         super.animateTo(child, properties);
    195         if (!(child instanceof ExpandableView)) {
    196             return;
    197         }
    198         ExpandableView expandableView = (ExpandableView) child;
    199         AnimationFilter animationFilter = properties.getAnimationFilter();
    200 
    201         // start height animation
    202         if (this.height != expandableView.getActualHeight()) {
    203             startHeightAnimation(expandableView, properties);
    204         }  else {
    205             abortAnimation(child, TAG_ANIMATOR_HEIGHT);
    206         }
    207 
    208         // start shadow alpha animation
    209         if (this.shadowAlpha != expandableView.getShadowAlpha()) {
    210             startShadowAlphaAnimation(expandableView, properties);
    211         } else {
    212             abortAnimation(child, TAG_ANIMATOR_SHADOW_ALPHA);
    213         }
    214 
    215         // start top inset animation
    216         if (this.clipTopAmount != expandableView.getClipTopAmount()) {
    217             startInsetAnimation(expandableView, properties);
    218         } else {
    219             abortAnimation(child, TAG_ANIMATOR_TOP_INSET);
    220         }
    221 
    222         // start dimmed animation
    223         expandableView.setDimmed(this.dimmed, animationFilter.animateDimmed);
    224 
    225         // apply below the speed bump
    226         expandableView.setBelowSpeedBump(this.belowSpeedBump);
    227 
    228         // start hiding sensitive animation
    229         expandableView.setHideSensitive(this.hideSensitive, animationFilter.animateHideSensitive,
    230                 properties.delay, properties.duration);
    231 
    232         // start dark animation
    233         expandableView.setDark(this.dark, animationFilter.animateDark, properties.delay);
    234 
    235         if (properties.wasAdded(child) && !hidden) {
    236             expandableView.performAddAnimation(properties.delay, properties.duration,
    237                     false /* isHeadsUpAppear */);
    238         }
    239 
    240         if (!expandableView.isInShelf() && this.inShelf) {
    241             expandableView.setTransformingInShelf(true);
    242         }
    243         expandableView.setInShelf(this.inShelf);
    244 
    245         if (headsUpIsVisible) {
    246             expandableView.setHeadsUpIsVisible();
    247         }
    248     }
    249 
    250     private void startHeightAnimation(final ExpandableView child, AnimationProperties properties) {
    251         Integer previousStartValue = getChildTag(child, TAG_START_HEIGHT);
    252         Integer previousEndValue = getChildTag(child, TAG_END_HEIGHT);
    253         int newEndValue = this.height;
    254         if (previousEndValue != null && previousEndValue == newEndValue) {
    255             return;
    256         }
    257         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_HEIGHT);
    258         AnimationFilter filter = properties.getAnimationFilter();
    259         if (!filter.animateHeight) {
    260             // just a local update was performed
    261             if (previousAnimator != null) {
    262                 // we need to increase all animation keyframes of the previous animator by the
    263                 // relative change to the end value
    264                 PropertyValuesHolder[] values = previousAnimator.getValues();
    265                 int relativeDiff = newEndValue - previousEndValue;
    266                 int newStartValue = previousStartValue + relativeDiff;
    267                 values[0].setIntValues(newStartValue, newEndValue);
    268                 child.setTag(TAG_START_HEIGHT, newStartValue);
    269                 child.setTag(TAG_END_HEIGHT, newEndValue);
    270                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
    271                 return;
    272             } else {
    273                 // no new animation needed, let's just apply the value
    274                 child.setActualHeight(newEndValue, false);
    275                 return;
    276             }
    277         }
    278 
    279         ValueAnimator animator = ValueAnimator.ofInt(child.getActualHeight(), newEndValue);
    280         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    281             @Override
    282             public void onAnimationUpdate(ValueAnimator animation) {
    283                 child.setActualHeight((int) animation.getAnimatedValue(),
    284                         false /* notifyListeners */);
    285             }
    286         });
    287         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
    288         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
    289         animator.setDuration(newDuration);
    290         if (properties.delay > 0 && (previousAnimator == null
    291                 || previousAnimator.getAnimatedFraction() == 0)) {
    292             animator.setStartDelay(properties.delay);
    293         }
    294         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
    295         if (listener != null) {
    296             animator.addListener(listener);
    297         }
    298         // remove the tag when the animation is finished
    299         animator.addListener(new AnimatorListenerAdapter() {
    300             boolean mWasCancelled;
    301 
    302             @Override
    303             public void onAnimationEnd(Animator animation) {
    304                 child.setTag(TAG_ANIMATOR_HEIGHT, null);
    305                 child.setTag(TAG_START_HEIGHT, null);
    306                 child.setTag(TAG_END_HEIGHT, null);
    307                 child.setActualHeightAnimating(false);
    308                 if (!mWasCancelled && child instanceof ExpandableNotificationRow) {
    309                     ((ExpandableNotificationRow) child).setGroupExpansionChanging(
    310                             false /* isExpansionChanging */);
    311                 }
    312             }
    313 
    314             @Override
    315             public void onAnimationStart(Animator animation) {
    316                 mWasCancelled = false;
    317             }
    318 
    319             @Override
    320             public void onAnimationCancel(Animator animation) {
    321                 mWasCancelled = true;
    322             }
    323         });
    324         startAnimator(animator, listener);
    325         child.setTag(TAG_ANIMATOR_HEIGHT, animator);
    326         child.setTag(TAG_START_HEIGHT, child.getActualHeight());
    327         child.setTag(TAG_END_HEIGHT, newEndValue);
    328         child.setActualHeightAnimating(true);
    329     }
    330 
    331     private void startShadowAlphaAnimation(final ExpandableView child,
    332             AnimationProperties properties) {
    333         Float previousStartValue = getChildTag(child, TAG_START_SHADOW_ALPHA);
    334         Float previousEndValue = getChildTag(child, TAG_END_SHADOW_ALPHA);
    335         float newEndValue = this.shadowAlpha;
    336         if (previousEndValue != null && previousEndValue == newEndValue) {
    337             return;
    338         }
    339         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_SHADOW_ALPHA);
    340         AnimationFilter filter = properties.getAnimationFilter();
    341         if (!filter.animateShadowAlpha) {
    342             // just a local update was performed
    343             if (previousAnimator != null) {
    344                 // we need to increase all animation keyframes of the previous animator by the
    345                 // relative change to the end value
    346                 PropertyValuesHolder[] values = previousAnimator.getValues();
    347                 float relativeDiff = newEndValue - previousEndValue;
    348                 float newStartValue = previousStartValue + relativeDiff;
    349                 values[0].setFloatValues(newStartValue, newEndValue);
    350                 child.setTag(TAG_START_SHADOW_ALPHA, newStartValue);
    351                 child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
    352                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
    353                 return;
    354             } else {
    355                 // no new animation needed, let's just apply the value
    356                 child.setShadowAlpha(newEndValue);
    357                 return;
    358             }
    359         }
    360 
    361         ValueAnimator animator = ValueAnimator.ofFloat(child.getShadowAlpha(), newEndValue);
    362         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    363             @Override
    364             public void onAnimationUpdate(ValueAnimator animation) {
    365                 child.setShadowAlpha((float) animation.getAnimatedValue());
    366             }
    367         });
    368         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
    369         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
    370         animator.setDuration(newDuration);
    371         if (properties.delay > 0 && (previousAnimator == null
    372                 || previousAnimator.getAnimatedFraction() == 0)) {
    373             animator.setStartDelay(properties.delay);
    374         }
    375         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
    376         if (listener != null) {
    377             animator.addListener(listener);
    378         }
    379         // remove the tag when the animation is finished
    380         animator.addListener(new AnimatorListenerAdapter() {
    381             @Override
    382             public void onAnimationEnd(Animator animation) {
    383                 child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, null);
    384                 child.setTag(TAG_START_SHADOW_ALPHA, null);
    385                 child.setTag(TAG_END_SHADOW_ALPHA, null);
    386             }
    387         });
    388         startAnimator(animator, listener);
    389         child.setTag(TAG_ANIMATOR_SHADOW_ALPHA, animator);
    390         child.setTag(TAG_START_SHADOW_ALPHA, child.getShadowAlpha());
    391         child.setTag(TAG_END_SHADOW_ALPHA, newEndValue);
    392     }
    393 
    394     private void startInsetAnimation(final ExpandableView child, AnimationProperties properties) {
    395         Integer previousStartValue = getChildTag(child, TAG_START_TOP_INSET);
    396         Integer previousEndValue = getChildTag(child, TAG_END_TOP_INSET);
    397         int newEndValue = this.clipTopAmount;
    398         if (previousEndValue != null && previousEndValue == newEndValue) {
    399             return;
    400         }
    401         ValueAnimator previousAnimator = getChildTag(child, TAG_ANIMATOR_TOP_INSET);
    402         AnimationFilter filter = properties.getAnimationFilter();
    403         if (!filter.animateTopInset) {
    404             // just a local update was performed
    405             if (previousAnimator != null) {
    406                 // we need to increase all animation keyframes of the previous animator by the
    407                 // relative change to the end value
    408                 PropertyValuesHolder[] values = previousAnimator.getValues();
    409                 int relativeDiff = newEndValue - previousEndValue;
    410                 int newStartValue = previousStartValue + relativeDiff;
    411                 values[0].setIntValues(newStartValue, newEndValue);
    412                 child.setTag(TAG_START_TOP_INSET, newStartValue);
    413                 child.setTag(TAG_END_TOP_INSET, newEndValue);
    414                 previousAnimator.setCurrentPlayTime(previousAnimator.getCurrentPlayTime());
    415                 return;
    416             } else {
    417                 // no new animation needed, let's just apply the value
    418                 child.setClipTopAmount(newEndValue);
    419                 return;
    420             }
    421         }
    422 
    423         ValueAnimator animator = ValueAnimator.ofInt(child.getClipTopAmount(), newEndValue);
    424         animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    425             @Override
    426             public void onAnimationUpdate(ValueAnimator animation) {
    427                 child.setClipTopAmount((int) animation.getAnimatedValue());
    428             }
    429         });
    430         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
    431         long newDuration = cancelAnimatorAndGetNewDuration(properties.duration, previousAnimator);
    432         animator.setDuration(newDuration);
    433         if (properties.delay > 0 && (previousAnimator == null
    434                 || previousAnimator.getAnimatedFraction() == 0)) {
    435             animator.setStartDelay(properties.delay);
    436         }
    437         AnimatorListenerAdapter listener = properties.getAnimationFinishListener();
    438         if (listener != null) {
    439             animator.addListener(listener);
    440         }
    441         // remove the tag when the animation is finished
    442         animator.addListener(new AnimatorListenerAdapter() {
    443             @Override
    444             public void onAnimationEnd(Animator animation) {
    445                 child.setTag(TAG_ANIMATOR_TOP_INSET, null);
    446                 child.setTag(TAG_START_TOP_INSET, null);
    447                 child.setTag(TAG_END_TOP_INSET, null);
    448             }
    449         });
    450         startAnimator(animator, listener);
    451         child.setTag(TAG_ANIMATOR_TOP_INSET, animator);
    452         child.setTag(TAG_START_TOP_INSET, child.getClipTopAmount());
    453         child.setTag(TAG_END_TOP_INSET, newEndValue);
    454     }
    455 
    456     /**
    457      * Get the end value of the height animation running on a view or the actualHeight
    458      * if no animation is running.
    459      */
    460     public static int getFinalActualHeight(ExpandableView view) {
    461         if (view == null) {
    462             return 0;
    463         }
    464         ValueAnimator heightAnimator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
    465         if (heightAnimator == null) {
    466             return view.getActualHeight();
    467         } else {
    468             return getChildTag(view, TAG_END_HEIGHT);
    469         }
    470     }
    471 
    472     @Override
    473     public void cancelAnimations(View view) {
    474         super.cancelAnimations(view);
    475         Animator animator = getChildTag(view, TAG_ANIMATOR_HEIGHT);
    476         if (animator != null) {
    477             animator.cancel();
    478         }
    479         animator = getChildTag(view, TAG_ANIMATOR_SHADOW_ALPHA);
    480         if (animator != null) {
    481             animator.cancel();
    482         }
    483         animator = getChildTag(view, TAG_ANIMATOR_TOP_INSET);
    484         if (animator != null) {
    485             animator.cancel();
    486         }
    487     }
    488 }
    489