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 29 import java.util.List; 30 import java.util.WeakHashMap; 31 32 /** 33 * A state of a {@link com.android.systemui.statusbar.stack.NotificationStackScrollLayout} which 34 * can be applied to a viewGroup. 35 */ 36 public class StackScrollState { 37 38 private static final String CHILD_NOT_FOUND_TAG = "StackScrollStateNoSuchChild"; 39 40 private final ViewGroup mHostView; 41 private WeakHashMap<ExpandableView, StackViewState> mStateMap; 42 private final int mClearAllTopPadding; 43 44 public StackScrollState(ViewGroup hostView) { 45 mHostView = hostView; 46 mStateMap = new WeakHashMap<>(); 47 mClearAllTopPadding = hostView.getContext().getResources().getDimensionPixelSize( 48 R.dimen.clear_all_padding_top); 49 } 50 51 public ViewGroup getHostView() { 52 return mHostView; 53 } 54 55 public void resetViewStates() { 56 int numChildren = mHostView.getChildCount(); 57 for (int i = 0; i < numChildren; i++) { 58 ExpandableView child = (ExpandableView) mHostView.getChildAt(i); 59 resetViewState(child); 60 61 // handling reset for child notifications 62 if (child instanceof ExpandableNotificationRow) { 63 ExpandableNotificationRow row = (ExpandableNotificationRow) child; 64 List<ExpandableNotificationRow> children = 65 row.getNotificationChildren(); 66 if (row.isSummaryWithChildren() && children != null) { 67 for (ExpandableNotificationRow childRow : children) { 68 resetViewState(childRow); 69 } 70 } 71 } 72 } 73 } 74 75 private void resetViewState(ExpandableView view) { 76 StackViewState viewState = mStateMap.get(view); 77 if (viewState == null) { 78 viewState = new StackViewState(); 79 mStateMap.put(view, viewState); 80 } 81 // initialize with the default values of the view 82 viewState.height = view.getIntrinsicHeight(); 83 viewState.gone = view.getVisibility() == View.GONE; 84 viewState.alpha = 1f; 85 viewState.shadowAlpha = 1f; 86 viewState.notGoneIndex = -1; 87 viewState.hidden = false; 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 DismissView) { 111 DismissView dismissView = (DismissView) child; 112 boolean visible = state.clipTopAmount < mClearAllTopPadding; 113 dismissView.performVisibilityAnimation(visible && !dismissView.willBeGone()); 114 } else if (child instanceof EmptyShadeView) { 115 EmptyShadeView emptyShadeView = (EmptyShadeView) child; 116 boolean visible = state.clipTopAmount <= 0; 117 emptyShadeView.performVisibilityAnimation( 118 visible && !emptyShadeView.willBeGone()); 119 } 120 } 121 } 122 123 /** 124 * Applies a {@link StackViewState} to an {@link ExpandableView}. 125 * 126 * @return whether the state was applied correctly 127 */ 128 public boolean applyState(ExpandableView view, StackViewState state) { 129 if (state == null) { 130 Log.wtf(CHILD_NOT_FOUND_TAG, "No child state was found when applying this state " + 131 "to the hostView"); 132 return false; 133 } 134 if (state.gone) { 135 return false; 136 } 137 applyViewState(view, state); 138 139 int height = view.getActualHeight(); 140 int newHeight = state.height; 141 142 // apply height 143 if (height != newHeight) { 144 view.setActualHeight(newHeight, false /* notifyListeners */); 145 } 146 147 float shadowAlpha = view.getShadowAlpha(); 148 float newShadowAlpha = state.shadowAlpha; 149 150 // apply shadowAlpha 151 if (shadowAlpha != newShadowAlpha) { 152 view.setShadowAlpha(newShadowAlpha); 153 } 154 155 // apply dimming 156 view.setDimmed(state.dimmed, false /* animate */); 157 158 // apply hiding sensitive 159 view.setHideSensitive( 160 state.hideSensitive, false /* animated */, 0 /* delay */, 0 /* duration */); 161 162 // apply speed bump state 163 view.setBelowSpeedBump(state.belowSpeedBump); 164 165 // apply dark 166 view.setDark(state.dark, false /* animate */, 0 /* delay */); 167 168 // apply clipping 169 float oldClipTopAmount = view.getClipTopAmount(); 170 if (oldClipTopAmount != state.clipTopAmount) { 171 view.setClipTopAmount(state.clipTopAmount); 172 } 173 if (view instanceof ExpandableNotificationRow) { 174 ExpandableNotificationRow row = (ExpandableNotificationRow) view; 175 if (state.isBottomClipped) { 176 row.setClipToActualHeight(true); 177 } 178 row.applyChildrenState(this); 179 } 180 return true; 181 } 182 183 /** 184 * Applies a {@link ViewState} to a normal view. 185 */ 186 public void applyViewState(View view, ViewState state) { 187 float alpha = view.getAlpha(); 188 float yTranslation = view.getTranslationY(); 189 float xTranslation = view.getTranslationX(); 190 float zTranslation = view.getTranslationZ(); 191 float newAlpha = state.alpha; 192 float newYTranslation = state.yTranslation; 193 float newZTranslation = state.zTranslation; 194 boolean becomesInvisible = newAlpha == 0.0f || state.hidden; 195 if (alpha != newAlpha && xTranslation == 0) { 196 // apply layer type 197 boolean becomesFullyVisible = newAlpha == 1.0f; 198 boolean newLayerTypeIsHardware = !becomesInvisible && !becomesFullyVisible 199 && view.hasOverlappingRendering(); 200 int layerType = view.getLayerType(); 201 int newLayerType = newLayerTypeIsHardware 202 ? View.LAYER_TYPE_HARDWARE 203 : View.LAYER_TYPE_NONE; 204 if (layerType != newLayerType) { 205 view.setLayerType(newLayerType, null); 206 } 207 208 // apply alpha 209 view.setAlpha(newAlpha); 210 } 211 212 // apply visibility 213 int oldVisibility = view.getVisibility(); 214 int newVisibility = becomesInvisible ? View.INVISIBLE : View.VISIBLE; 215 if (newVisibility != oldVisibility) { 216 if (!(view instanceof ExpandableView) || !((ExpandableView) view).willBeGone()) { 217 // We don't want views to change visibility when they are animating to GONE 218 view.setVisibility(newVisibility); 219 } 220 } 221 222 // apply yTranslation 223 if (yTranslation != newYTranslation) { 224 view.setTranslationY(newYTranslation); 225 } 226 227 // apply zTranslation 228 if (zTranslation != newZTranslation) { 229 view.setTranslationZ(newZTranslation); 230 } 231 } 232 } 233