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                     child.setAlpha(newAlpha);
    123                 }
    124 
    125                 // apply visibility
    126                 int oldVisibility = child.getVisibility();
    127                 int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE;
    128                 if (newVisibility != oldVisibility) {
    129                     child.setVisibility(newVisibility);
    130                 }
    131 
    132                 // apply yTranslation
    133                 if (yTranslation != newYTranslation) {
    134                     child.setTranslationY(newYTranslation);
    135                 }
    136 
    137                 // apply zTranslation
    138                 if (zTranslation != newZTranslation) {
    139                     child.setTranslationZ(newZTranslation);
    140                 }
    141 
    142                 // apply scale
    143                 if (scale != newScale) {
    144                     child.setScaleX(newScale);
    145                     child.setScaleY(newScale);
    146                 }
    147 
    148                 // apply height
    149                 if (height != newHeight) {
    150                     child.setActualHeight(newHeight, false /* notifyListeners */);
    151                 }
    152 
    153                 // apply dimming
    154                 child.setDimmed(state.dimmed, false /* animate */);
    155 
    156                 // apply dark
    157                 child.setDark(state.dark, false /* animate */, 0 /* delay */);
    158 
    159                 // apply hiding sensitive
    160                 child.setHideSensitive(
    161                         state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */);
    162 
    163                 // apply speed bump state
    164                 child.setBelowSpeedBump(state.belowSpeedBump);
    165 
    166                 // apply clipping
    167                 float oldClipTopAmount = child.getClipTopAmount();
    168                 if (oldClipTopAmount != state.clipTopAmount) {
    169                     child.setClipTopAmount(state.clipTopAmount);
    170                 }
    171                 updateChildClip(child, newHeight, state.topOverLap);
    172 
    173                 if(child instanceof SpeedBumpView) {
    174                     performSpeedBumpAnimation(i, (SpeedBumpView) child, state, 0);
    175                 } else if (child instanceof DismissView) {
    176                     DismissView dismissView = (DismissView) child;
    177                     boolean visible = state.topOverLap < mClearAllTopPadding;
    178                     dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone());
    179                 } else if (child instanceof EmptyShadeView) {
    180                     EmptyShadeView emptyShadeView = (EmptyShadeView) child;
    181                     boolean visible = state.topOverLap <= 0;
    182                     emptyShadeView.performVisibilityAnimation(
    183                             visible && !emptyShadeView.willBeGone());
    184                 }
    185             }
    186         }
    187     }
    188 
    189     /**
    190      * Updates the clipping of a view
    191      *
    192      * @param child the view to update
    193      * @param height the currently applied height of the view
    194      * @param clipInset how much should this view be clipped from the top
    195      */
    196     private void updateChildClip(View child, int height, int clipInset) {
    197         mClipRect.set(0,
    198                 clipInset,
    199                 child.getWidth(),
    200                 height);
    201         child.setClipBounds(mClipRect);
    202     }
    203 
    204     public void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, ViewState state,
    205             long delay) {
    206         View nextChild = getNextChildNotGone(i);
    207         if (nextChild != null) {
    208             float lineEnd = state.yTranslation + state.height / 2;
    209             ViewState nextState = getViewStateForView(nextChild);
    210             boolean startIsAboveNext = nextState.yTranslation > lineEnd;
    211             speedBump.animateDivider(startIsAboveNext, delay, null /* onFinishedRunnable */);
    212         }
    213     }
    214 
    215     private View getNextChildNotGone(int childIndex) {
    216         int childCount = mHostView.getChildCount();
    217         for (int i = childIndex + 1; i < childCount; i++) {
    218             View child = mHostView.getChildAt(i);
    219             if (child.getVisibility() != View.GONE) {
    220                 return child;
    221             }
    222         }
    223         return null;
    224     }
    225 
    226     public static class ViewState {
    227 
    228         // These are flags such that we can create masks for filtering.
    229 
    230         public static final int LOCATION_UNKNOWN = 0x00;
    231         public static final int LOCATION_FIRST_CARD = 0x01;
    232         public static final int LOCATION_TOP_STACK_HIDDEN = 0x02;
    233         public static final int LOCATION_TOP_STACK_PEEKING = 0x04;
    234         public static final int LOCATION_MAIN_AREA = 0x08;
    235         public static final int LOCATION_BOTTOM_STACK_PEEKING = 0x10;
    236         public static final int LOCATION_BOTTOM_STACK_HIDDEN = 0x20;
    237         /** The view isn't layouted at all. */
    238         public static final int LOCATION_GONE = 0x40;
    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