Home | History | Annotate | Download | only in stack
      1 /*
      2  * Copyright (C) 2014 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.graphics.Rect;
     20 import android.util.Log;
     21 import android.view.View;
     22 import android.view.ViewGroup;
     23 
     24 import com.android.systemui.R;
     25 import com.android.systemui.statusbar.DismissView;
     26 import com.android.systemui.statusbar.EmptyShadeView;
     27 import com.android.systemui.statusbar.ExpandableView;
     28 import com.android.systemui.statusbar.SpeedBumpView;
     29 
     30 import java.util.HashMap;
     31 import java.util.Map;
     32 
     33 /**
     34  * A state of a {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout} which
     35  * can be applied to a viewGroup.
     36  */
     37 public class StackScrollState {
     38 
     39     private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild";
     40 
     41     private final ViewGroup mHostView;
     42     private Map<ExpandableView, ViewState> mStateMap;
     43     private final Rect mClipRect = new Rect();
     44     private final int mClearAllTopPadding;
     45 
     46     public StackScrollState(ViewGroup hostView) {
     47         mHostView = hostView;
     48         mStateMap = new HashMap<ExpandableView, ViewState>();
     49         mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize(
     50                 R.dimen.clear_all_padding_top);
     51     }
     52 
     53     public ViewGroup getHostView() {
     54         return mHostView;
     55     }
     56 
     57     public void resetViewStates() {
     58         int numChildren = mHostView.getChildCount();
     59         for (int i = 0; i < numChildren; i++) {
     60             ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
     61             ViewState viewState = mStateMap.get(child);
     62             if (viewState == null) {
     63                 viewState = new ViewState();
     64                 mStateMap.put(child, viewState);
     65             }
     66             // initialize with the default values of the view
     67             viewState.height = child.getIntrinsicHeight();
     68             viewState.gone = child.getVisibility() == View.GONE;
     69             viewState.alpha = 1;
     70             viewState.notGoneIndex = -1;
     71         }
     72     }
     73 
     74     public ViewState getViewStateForView(View requestedView) {
     75         return mStateMap.get(requestedView);
     76     }
     77 
     78     public void removeViewStateForView(View child) {
     79         mStateMap.remove(child);
     80     }
     81 
     82     /**
     83      * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}.
     84      * The properties are only applied if they effectively changed.
     85      */
     86     public void apply() {
     87         int numChildren = mHostView.getChildCount();
     88         for (int i = 0; i < numChildren; i++) {
     89             ExpandableView child = (ExpandableView) mHostView.getChildAt(i);
     90             ViewState state = mStateMap.get(child);
     91             if (state == null) {
     92                 Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " +
     93                         "to the hostView");
     94                 continue;
     95             }
     96             if (!state.gone) {
     97                 float alpha = child.getAlpha();
     98                 float yTranslation = child.getTranslationY();
     99                 float xTranslation = child.getTranslationX();
    100                 float zTranslation = child.getTranslationZ();
    101                 float scale = child.getScaleX();
    102                 int height = child.getActualHeight();
    103                 float newAlpha = state.alpha;
    104                 float newYTranslation = state.yTranslation;
    105                 float newZTranslation = state.zTranslation;
    106                 float newScale = state.scale;
    107                 int newHeight = state.height;
    108                 boolean becomesInvisible = newAlpha == 0.0f;
    109                 if (alpha != newAlpha && xTranslation == 0) {
    110                     // apply layer type
    111                     boolean becomesFullyVisible = newAlpha == 1.0f;
    112                     boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible;
    113                     int layerType = child.getLayerType();
    114                     int newLayerType = newLayerTypeIsHardware
    115                             ? View.LAYER_TYPE_HARDWARE
    116                             : View.LAYER_TYPE_NONE;
    117                     if (layerType != newLayerType) {
    118                         child.setLayerType(newLayerType, null);
    119                     }
    120 
    121                     // apply alpha
    122                     if (!becomesInvisible) {
    123                         child.setAlpha(newAlpha);
    124                     }
    125                 }
    126 
    127                 // apply visibility
    128                 int oldVisibility = child.getVisibility();
    129                 int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
    130                 if (newVisibility != oldVisibility) {
    131                     child.setVisibility(newVisibility);
    132                 }
    133 
    134                 // apply yTranslation
    135                 if (yTranslation != newYTranslation) {
    136                     child.setTranslationY(newYTranslation);
    137                 }
    138 
    139                 // apply zTranslation
    140                 if (zTranslation != newZTranslation) {
    141                     child.setTranslationZ(newZTranslation);
    142                 }
    143 
    144                 // apply scale
    145                 if (scale != newScale) {
    146                     child.setScaleX(newScale);
    147                     child.setScaleY(newScale);
    148                 }
    149 
    150                 // apply height
    151                 if (height != newHeight) {
    152                     child.setActualHeight(newHeight, false /* notifyListeners */);
    153                 }
    154 
    155                 // apply dimming
    156                 child.setDimmed(state.dimmed, false /* animate */);
    157 
    158                 // apply dark
    159                 child.setDark(state.dark, false /* animate */);
    160 
    161                 // apply hiding sensitive
    162                 child.setHideSensitive(
    163                         state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
    164 
    165                 // apply speed bump state
    166                 child.setBelowSpeedBump(state.belowSpeedBump);
    167 
    168                 // apply clipping
    169                 float oldClipTopAmount = child.getClipTopAmount();
    170                 if (oldClipTopAmount != state.clipTopAmount) {
    171                     child.setClipTopAmount(state.clipTopAmount);
    172                 }
    173                 updateChildClip(child, newHeight, state.topOverLap);
    174 
    175                 if(child instanceof SpeedBumpView) {
    176                     performSpeedBumpAnimation(i, (SpeedBumpView) child, state, 0);
    177                 } else if (child instanceof DismissView) {
    178                     DismissView dismissView = (DismissView) child;
    179                     boolean visible = state.topOverLap < mClearAllTopPadding;
    180                     dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone());
    181                 } else if (child instanceof EmptyShadeView) {
    182                     EmptyShadeView emptyShadeView = (EmptyShadeView) child;
    183                     boolean visible = state.topOverLap <= 0;
    184                     emptyShadeView.performVisibilityAnimation(
    185                             visible && !emptyShadeView.willBeGone());
    186                 }
    187             }
    188         }
    189     }
    190 
    191     /**
    192      * Updates the clipping of a view
    193      *
    194      * @param child the view to update
    195      * @param height the currently applied height of the view
    196      * @param clipInset how much should this view be clipped from the top
    197      */
    198     private void updateChildClip(View child, int height, int clipInset) {
    199         mClipRect.set(0,
    200                 clipInset,
    201                 child.getWidth(),
    202                 height);
    203         child.setClipBounds(mClipRect);
    204     }
    205 
    206     public void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, ViewState state,
    207             long delay) {
    208         View nextChild = getNextChildNotGone(i);
    209         if (nextChild != null) {
    210             float lineEnd = state.yTranslation + state.height / 2;
    211             ViewState nextState = getViewStateForView(nextChild);
    212             boolean startIsAboveNext = nextState.yTranslation > lineEnd;
    213             speedBump.animateDivider(startIsAboveNext, delay, null /* onFinishedRunnable */);
    214         }
    215     }
    216 
    217     private View getNextChildNotGone(int childIndex) {
    218         int childCount = mHostView.getChildCount();
    219         for (int i = childIndex + 1; i < childCount; i++) {
    220             View child = mHostView.getChildAt(i);
    221             if (child.getVisibility() != View.GONE) {
    222                 return child;
    223             }
    224         }
    225         return null;
    226     }
    227 
    228     public static class ViewState {
    229 
    230         // These are flags such that we can create masks for filtering.
    231 
    232         public static final int LOCATION_UNKNOWN = 0x00;
    233         public static final int LOCATION_FIRST_CARD = 0x01;
    234         public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
    235         public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
    236         public static final int LOCATION_MAIN_AREA = 0x08;
    237         public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
    238         public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
    239 
    240         float alpha;
    241         float yTranslation;
    242         float zTranslation;
    243         int height;
    244         boolean gone;
    245         float scale;
    246         boolean dimmed;
    247         boolean dark;
    248         boolean hideSensitive;
    249         boolean belowSpeedBump;
    250 
    251         /**
    252          * The amount which the view should be clipped from the top. This is calculated to
    253          * perceive consistent shadows.
    254          */
    255         int clipTopAmount;
    256 
    257         /**
    258          * How much does the child overlap with the previous view on the top? Can be used for
    259          * a clipping optimization
    260          */
    261         int topOverLap;
    262 
    263         /**
    264          * The index of the view, only accounting for views not equal to GONE
    265          */
    266         int notGoneIndex;
    267 
    268         /**
    269          * The location this view is currently rendered at.
    270          *
    271          * <p>See <code>LOCATION_</code> flags.</p>
    272          */
    273         int location;
    274     }
    275 }
    276