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.util.Log; 20 import android.view.View; 21 import android.view.ViewGroup; 22 23 import com.android.systemui.R; 24 import com.android.systemui.statusbar.DismissView; 25 import com.android.systemui.statusbar.EmptyShadeView; 26 import com.android.systemui.statusbar.ExpandableNotificationRow; 27 import com.android.systemui.statusbar.ExpandableView; 28 import com.android.systemui.statusbar.SpeedBumpView; 29 30 import java.util.HashMap; 31 import java.util.List; 32 import java.util.Map; 33 34 /** 35 * A state of a {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout} which 36 * can be applied to a viewGroup. 37 */ 38 public class StackScrollState { 39 40 private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; 41 42 private final ViewGroup mHostView; 43 private Map<ExpandableView, StackViewState> mStateMap; 44 private final int mClearAllTopPadding; 45 46 public StackScrollState(ViewGroup hostView) { 47 mHostView = hostView; 48 mStateMap = new HashMap<ExpandableView, StackViewState>(); 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 resetViewState(child); 62 63 // handling reset for child notifications 64 if (child instanceof ExpandableNotificationRow) { 65 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 66 List<ExpandableNotificationRow> children = 67 row.getNotificationChildren(); 68 if (row.areChildrenExpanded() && children != null) { 69 for (ExpandableNotificationRow childRow : children) { 70 resetViewState(childRow); 71 } 72 } 73 } 74 } 75 } 76 77 private void resetViewState(ExpandableView view) { 78 StackViewState viewState = mStateMap.get(view); 79 if (viewState == null) { 80 viewState = new StackViewState(); 81 mStateMap.put(view, viewState); 82 } 83 // initialize with the default values of the view 84 viewState.height = view.getIntrinsicHeight(); 85 viewState.gone = view.getVisibility() == View.GONE; 86 viewState.alpha = 1; 87 viewState.notGoneIndex = -1; 88 } 89 90 public StackViewState getViewStateForView(View requestedView) { 91 return mStateMap.get(requestedView); 92 } 93 94 public void removeViewStateForView(View child) { 95 mStateMap.remove(child); 96 } 97 98 /** 99 * Apply the properties saved in {@link #mStateMap} to the children of the {@link #mHostView}. 100 * The properties are only applied if they effectively changed. 101 */ 102 public void apply() { 103 int numChildren = mHostView.getChildCount(); 104 for (int i = 0; i < numChildren; i++) { 105 ExpandableView child = (ExpandableView) mHostView.getChildAt(i); 106 StackViewState state = mStateMap.get(child); 107 if (!applyState(child, state)) { 108 continue; 109 } 110 if(child instanceof SpeedBumpView) { 111 performSpeedBumpAnimation(i, (SpeedBumpView) child, state, 0); 112 } else if (child instanceof DismissView) { 113 DismissView dismissView = (DismissView) child; 114 boolean visible = state.topOverLap < mClearAllTopPadding; 115 dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone()); 116 } else if (child instanceof EmptyShadeView) { 117 EmptyShadeView emptyShadeView = (EmptyShadeView) child; 118 boolean visible = state.topOverLap <= 0; 119 emptyShadeView.performVisibilityAnimation( 120 visible && !emptyShadeView.willBeGone()); 121 } 122 } 123 } 124 125 /** 126 * Applies a {@link StackViewState} to an {@link ExpandableView}. 127 * 128 * @return whether the state was applied correctly 129 */ 130 public boolean applyState(ExpandableView view, StackViewState state) { 131 if (state == null) { 132 Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " + 133 "to the hostView"); 134 return false; 135 } 136 if (state.gone) { 137 return false; 138 } 139 applyViewState(view, state); 140 141 int height = view.getActualHeight(); 142 int newHeight = state.height; 143 144 // apply height 145 if (height != newHeight) { 146 view.setActualHeight(newHeight, false /* notifyListeners */); 147 } 148 149 // apply dimming 150 view.setDimmed(state.dimmed, false /* animate */); 151 152 // apply hiding sensitive 153 view.setHideSensitive( 154 state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */); 155 156 // apply speed bump state 157 view.setBelowSpeedBump(state.belowSpeedBump); 158 159 // apply dark 160 view.setDark(state.dark, false /* animate */, 0 /* delay */); 161 162 // apply clipping 163 float oldClipTopAmount = view.getClipTopAmount(); 164 if (oldClipTopAmount != state.clipTopAmount) { 165 view.setClipTopAmount(state.clipTopAmount); 166 } 167 float oldClipTopOptimization = view.getClipTopOptimization(); 168 if (oldClipTopOptimization != state.topOverLap) { 169 view.setClipTopOptimization(state.topOverLap); 170 } 171 if (view instanceof ExpandableNotificationRow) { 172 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 173 row.applyChildrenState(this); 174 } 175 return true; 176 } 177 178 /** 179 * Applies a {@link ViewState} to a normal view. 180 */ 181 public void applyViewState(View view, ViewState state) { 182 float alpha = view.getAlpha(); 183 float yTranslation = view.getTranslationY(); 184 float xTranslation = view.getTranslationX(); 185 float zTranslation = view.getTranslationZ(); 186 float scale = view.getScaleX(); 187 float newAlpha = state.alpha; 188 float newYTranslation = state.yTranslation; 189 float newZTranslation = state.zTranslation; 190 float newScale = state.scale; 191 boolean becomesInvisible = newAlpha == 0.0f; 192 if (alpha != newAlpha && xTranslation == 0) { 193 // apply layer type 194 boolean becomesFullyVisible = newAlpha == 1.0f; 195 boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible 196 && view.hasOverlappingRendering(); 197 int layerType = view.getLayerType(); 198 int newLayerType = newLayerTypeIsHardware 199 ? View.LAYER_TYPE_HARDWARE 200 : View.LAYER_TYPE_NONE; 201 if (layerType != newLayerType) { 202 view.setLayerType(newLayerType, null); 203 } 204 205 // apply alpha 206 view.setAlpha(newAlpha); 207 } 208 209 // apply visibility 210 int oldVisibility = view.getVisibility(); 211 int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; 212 if (newVisibility != oldVisibility) { 213 if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) { 214 // We don't want views to change visibility when they are animating to GONE 215 view.setVisibility(newVisibility); 216 } 217 } 218 219 // apply yTranslation 220 if (yTranslation != newYTranslation) { 221 view.setTranslationY(newYTranslation); 222 } 223 224 // apply zTranslation 225 if (zTranslation != newZTranslation) { 226 view.setTranslationZ(newZTranslation); 227 } 228 229 // apply scale 230 if (scale != newScale) { 231 view.setScaleX(newScale); 232 view.setScaleY(newScale); 233 } 234 } 235 236 public void performSpeedBumpAnimation(int i, SpeedBumpView speedBump, StackViewState state, 237 long delay) { 238 View nextChild = getNextChildNotGone(i); 239 if (nextChild != null) { 240 float lineEnd = state.yTranslation + state.height / 2; 241 StackViewState nextState = getViewStateForView(nextChild); 242 boolean startIsAboveNext = nextState.yTranslation > lineEnd; 243 speedBump.animateDivider(startIsAboveNext, delay, null /* onFinishedRunnable */); 244 } 245 } 246 247 private View getNextChildNotGone(int childIndex) { 248 int childCount = mHostView.getChildCount(); 249 for (int i = childIndex + 1; i < childCount; i++) { 250 View child = mHostView.getChildAt(i); 251 if (child.getVisibility() != View.GONE) { 252 return child; 253 } 254 } 255 return null; 256 } 257 258 } 259