NotificationChildrenContainer.java revision e812652fba04abc32fd6eeefee3463270f3b9d8e
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 17package com.android.systemui.statusbar.stack; 18 19import android.content.Context; 20import android.util.AttributeSet; 21import android.view.LayoutInflater; 22import android.view.View; 23import android.view.ViewGroup; 24 25import com.android.systemui.R; 26import com.android.systemui.ViewInvertHelper; 27import com.android.systemui.statusbar.ExpandableNotificationRow; 28import com.android.systemui.statusbar.notification.HybridNotificationView; 29import com.android.systemui.statusbar.notification.HybridNotificationViewManager; 30import com.android.systemui.statusbar.phone.NotificationPanelView; 31 32import java.util.ArrayList; 33import java.util.List; 34 35/** 36 * A container containing child notifications 37 */ 38public class NotificationChildrenContainer extends ViewGroup { 39 40 private static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2; 41 private static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5; 42 private static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8; 43 44 private final int mChildPadding; 45 private final int mDividerHeight; 46 private final int mMaxNotificationHeight; 47 private final List<View> mDividers = new ArrayList<>(); 48 private final List<ExpandableNotificationRow> mChildren = new ArrayList<>(); 49 private final int mNotificationHeaderHeight; 50 private final int mNotificationAppearDistance; 51 private final int mNotificatonTopPadding; 52 private final HybridNotificationViewManager mHybridViewManager; 53 private final float mCollapsedBottompadding; 54 private ViewInvertHelper mOverflowInvertHelper; 55 private boolean mChildrenExpanded; 56 private ExpandableNotificationRow mNotificationParent; 57 private HybridNotificationView mGroupOverflowContainer; 58 private ViewState mGroupOverFlowState; 59 60 public NotificationChildrenContainer(Context context) { 61 this(context, null); 62 } 63 64 public NotificationChildrenContainer(Context context, AttributeSet attrs) { 65 this(context, attrs, 0); 66 } 67 68 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) { 69 this(context, attrs, defStyleAttr, 0); 70 } 71 72 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, 73 int defStyleRes) { 74 super(context, attrs, defStyleAttr, defStyleRes); 75 mChildPadding = getResources().getDimensionPixelSize( 76 R.dimen.notification_children_padding); 77 mDividerHeight = getResources().getDimensionPixelSize( 78 R.dimen.notification_children_divider_height); 79 mMaxNotificationHeight = getResources().getDimensionPixelSize( 80 R.dimen.notification_max_height); 81 mNotificationAppearDistance = getResources().getDimensionPixelSize( 82 R.dimen.notification_appear_distance); 83 mNotificationHeaderHeight = getResources().getDimensionPixelSize( 84 com.android.internal.R.dimen.notification_content_margin_top); 85 mNotificatonTopPadding = getResources().getDimensionPixelSize( 86 R.dimen.notification_children_container_top_padding); 87 mCollapsedBottompadding = 11.5f * getResources().getDisplayMetrics().density; 88 mHybridViewManager = new HybridNotificationViewManager(getContext(), this); 89 } 90 91 @Override 92 protected void onLayout(boolean changed, int l, int t, int r, int b) { 93 int childCount = mChildren.size(); 94 for (int i = 0; i < childCount; i++) { 95 View child = mChildren.get(i); 96 if (child.getVisibility() == View.GONE) { 97 continue; 98 } 99 child.layout(0, 0, getWidth(), child.getMeasuredHeight()); 100 mDividers.get(i).layout(0, 0, getWidth(), mDividerHeight); 101 } 102 if (mGroupOverflowContainer != null) { 103 mGroupOverflowContainer.layout(0, 0, getWidth(), 104 mGroupOverflowContainer.getMeasuredHeight()); 105 } 106 } 107 108 @Override 109 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 110 int ownMaxHeight = mMaxNotificationHeight; 111 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 112 boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; 113 boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; 114 if (hasFixedHeight || isHeightLimited) { 115 int size = MeasureSpec.getSize(heightMeasureSpec); 116 ownMaxHeight = Math.min(ownMaxHeight, size); 117 } 118 int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST); 119 int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY); 120 int height = mNotificationHeaderHeight + mNotificatonTopPadding; 121 int childCount = mChildren.size(); 122 for (int i = 0; i < childCount; i++) { 123 View child = mChildren.get(i); 124 child.measure(widthMeasureSpec, newHeightSpec); 125 height += child.getMeasuredHeight(); 126 127 // layout the divider 128 View divider = mDividers.get(i); 129 divider.measure(widthMeasureSpec, dividerHeightSpec); 130 height += mDividerHeight; 131 } 132 int width = MeasureSpec.getSize(widthMeasureSpec); 133 if (mGroupOverflowContainer != null) { 134 mGroupOverflowContainer.measure(widthMeasureSpec, newHeightSpec); 135 } 136 setMeasuredDimension(width, height); 137 } 138 139 /** 140 * Add a child notification to this view. 141 * 142 * @param row the row to add 143 * @param childIndex the index to add it at, if -1 it will be added at the end 144 */ 145 public void addNotification(ExpandableNotificationRow row, int childIndex) { 146 int newIndex = childIndex < 0 ? mChildren.size() : childIndex; 147 mChildren.add(newIndex, row); 148 addView(row); 149 150 View divider = inflateDivider(); 151 addView(divider); 152 mDividers.add(newIndex, divider); 153 154 updateGroupOverflow(); 155 } 156 157 public void removeNotification(ExpandableNotificationRow row) { 158 int childIndex = mChildren.indexOf(row); 159 mChildren.remove(row); 160 removeView(row); 161 162 View divider = mDividers.remove(childIndex); 163 removeView(divider); 164 165 row.setSystemChildExpanded(false); 166 updateGroupOverflow(); 167 } 168 169 public void updateGroupOverflow() { 170 int childCount = mChildren.size(); 171 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */); 172 boolean hasOverflow = childCount > maxAllowedVisibleChildren; 173 int lastVisibleIndex = hasOverflow ? maxAllowedVisibleChildren - 2 174 : maxAllowedVisibleChildren - 1; 175 if (hasOverflow) { 176 mGroupOverflowContainer = mHybridViewManager.bindFromNotificationGroup( 177 mGroupOverflowContainer, mChildren, lastVisibleIndex + 1); 178 if (mOverflowInvertHelper == null) { 179 mOverflowInvertHelper= new ViewInvertHelper(mGroupOverflowContainer, 180 NotificationPanelView.DOZE_ANIMATION_DURATION); 181 } 182 if (mGroupOverFlowState == null) { 183 mGroupOverFlowState = new ViewState(); 184 } 185 } else if (mGroupOverflowContainer != null) { 186 removeView(mGroupOverflowContainer); 187 mGroupOverflowContainer = null; 188 mOverflowInvertHelper = null; 189 mGroupOverFlowState = null; 190 } 191 } 192 193 private View inflateDivider() { 194 return LayoutInflater.from(mContext).inflate( 195 R.layout.notification_children_divider, this, false); 196 } 197 198 public List<ExpandableNotificationRow> getNotificationChildren() { 199 return mChildren; 200 } 201 202 /** 203 * Apply the order given in the list to the children. 204 * 205 * @param childOrder the new list order 206 * @return whether the list order has changed 207 */ 208 public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { 209 if (childOrder == null) { 210 return false; 211 } 212 boolean result = false; 213 for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) { 214 ExpandableNotificationRow child = mChildren.get(i); 215 ExpandableNotificationRow desiredChild = childOrder.get(i); 216 if (child != desiredChild) { 217 mChildren.remove(desiredChild); 218 mChildren.add(i, desiredChild); 219 result = true; 220 } 221 } 222 updateExpansionStates(); 223 return result; 224 } 225 226 private void updateExpansionStates() { 227 // Let's make the first child expanded if the parent is 228 for (int i = 0; i < mChildren.size(); i++) { 229 ExpandableNotificationRow child = mChildren.get(i); 230 child.setSystemChildExpanded(false); 231 } 232 } 233 234 /** 235 * 236 * @return the intrinsic size of this children container, i.e the natural fully expanded state 237 */ 238 public int getIntrinsicHeight() { 239 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(); 240 return getIntrinsicHeight(maxAllowedVisibleChildren); 241 } 242 243 /** 244 * @return the intrinsic height with a number of children given 245 * in @param maxAllowedVisibleChildren 246 */ 247 private int getIntrinsicHeight(float maxAllowedVisibleChildren) { 248 int intrinsicHeight = mNotificationHeaderHeight; 249 if (mChildrenExpanded) { 250 intrinsicHeight += mNotificatonTopPadding; 251 } 252 int visibleChildren = 0; 253 int childCount = mChildren.size(); 254 for (int i = 0; i < childCount; i++) { 255 if (visibleChildren >= maxAllowedVisibleChildren) { 256 break; 257 } 258 ExpandableNotificationRow child = mChildren.get(i); 259 intrinsicHeight += child.getIntrinsicHeight(); 260 visibleChildren++; 261 } 262 if (visibleChildren > 0) { 263 if (mChildrenExpanded) { 264 intrinsicHeight += visibleChildren * mDividerHeight; 265 } else { 266 intrinsicHeight += (visibleChildren - 1) * mChildPadding; 267 } 268 } 269 if (!mChildrenExpanded) { 270 intrinsicHeight += mCollapsedBottompadding; 271 } 272 return intrinsicHeight; 273 } 274 275 /** 276 * Update the state of all its children based on a linear layout algorithm. 277 * 278 * @param resultState the state to update 279 * @param parentState the state of the parent 280 */ 281 public void getState(StackScrollState resultState, StackViewState parentState) { 282 int childCount = mChildren.size(); 283 int yPosition = mNotificationHeaderHeight; 284 boolean firstChild = true; 285 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(); 286 boolean hasOverflow = !mChildrenExpanded && childCount > maxAllowedVisibleChildren 287 && maxAllowedVisibleChildren != NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED; 288 int lastVisibleIndex = hasOverflow 289 ? maxAllowedVisibleChildren - 2 290 : maxAllowedVisibleChildren - 1; 291 for (int i = 0; i < childCount; i++) { 292 ExpandableNotificationRow child = mChildren.get(i); 293 if (!firstChild) { 294 yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding; 295 } else { 296 yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0; 297 firstChild = false; 298 } 299 StackViewState childState = resultState.getViewStateForView(child); 300 int intrinsicHeight = child.getIntrinsicHeight(); 301 childState.yTranslation = yPosition; 302 childState.zTranslation = 0; 303 childState.height = intrinsicHeight; 304 childState.dimmed = parentState.dimmed; 305 childState.dark = parentState.dark; 306 childState.hideSensitive = parentState.hideSensitive; 307 childState.belowSpeedBump = parentState.belowSpeedBump; 308 childState.scale = 1.0f; 309 childState.clipTopAmount = 0; 310 childState.topOverLap = 0; 311 boolean visible = i <= lastVisibleIndex; 312 childState.alpha = visible ? 1 : 0; 313 childState.location = parentState.location; 314 yPosition += intrinsicHeight; 315 } 316 if (mGroupOverflowContainer != null) { 317 mGroupOverFlowState.initFrom(mGroupOverflowContainer); 318 if (hasOverflow) { 319 StackViewState firstOverflowState = 320 resultState.getViewStateForView(mChildren.get(lastVisibleIndex + 1)); 321 mGroupOverFlowState.yTranslation = firstOverflowState.yTranslation; 322 } 323 mGroupOverFlowState.alpha = mChildrenExpanded || !hasOverflow ? 0.0f : 1.0f; 324 } 325 } 326 327 private int getMaxAllowedVisibleChildren() { 328 return getMaxAllowedVisibleChildren(false /* likeCollapsed */); 329 } 330 331 private int getMaxAllowedVisibleChildren(boolean likeCollapsed) { 332 if (!likeCollapsed && (mChildrenExpanded || mNotificationParent.isUserLocked())) { 333 return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED; 334 } 335 if (mNotificationParent.isExpanded()) { 336 return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED; 337 } 338 return NUMBER_OF_CHILDREN_WHEN_COLLAPSED; 339 } 340 341 public void applyState(StackScrollState state) { 342 int childCount = mChildren.size(); 343 ViewState tmpState = new ViewState(); 344 for (int i = 0; i < childCount; i++) { 345 ExpandableNotificationRow child = mChildren.get(i); 346 StackViewState viewState = state.getViewStateForView(child); 347 state.applyState(child, viewState); 348 349 // layout the divider 350 View divider = mDividers.get(i); 351 tmpState.initFrom(divider); 352 tmpState.yTranslation = viewState.yTranslation - mDividerHeight; 353 tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0; 354 state.applyViewState(divider, tmpState); 355 } 356 if (mGroupOverflowContainer != null) { 357 state.applyViewState(mGroupOverflowContainer, mGroupOverFlowState); 358 } 359 } 360 361 /** 362 * This is called when the children expansion has changed and positions the children properly 363 * for an appear animation. 364 * 365 * @param state the new state we animate to 366 */ 367 public void prepareExpansionChanged(StackScrollState state) { 368 if (true) { 369 // TODO: do something that makes sense 370 return; 371 } 372 int childCount = mChildren.size(); 373 StackViewState sourceState = new StackViewState(); 374 ViewState dividerState = new ViewState(); 375 for (int i = 0; i < childCount; i++) { 376 ExpandableNotificationRow child = mChildren.get(i); 377 StackViewState viewState = state.getViewStateForView(child); 378 sourceState.copyFrom(viewState); 379 sourceState.alpha = 0; 380 sourceState.yTranslation += mNotificationAppearDistance; 381 state.applyState(child, sourceState); 382 383 // layout the divider 384 View divider = mDividers.get(i); 385 dividerState.initFrom(divider); 386 dividerState.yTranslation = viewState.yTranslation - mDividerHeight 387 + mNotificationAppearDistance; 388 dividerState.alpha = 0; 389 state.applyViewState(divider, dividerState); 390 391 } 392 } 393 394 public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator, 395 boolean withDelays, long baseDelay, long duration) { 396 int childCount = mChildren.size(); 397 ViewState tmpState = new ViewState(); 398 int delayIndex = 0; 399 int maxAllowChildCount = getMaxAllowedVisibleChildren(true /* likeCollapsed */); 400 for (int i = childCount - 1; i >= 0; i--) { 401 ExpandableNotificationRow child = mChildren.get(i); 402 StackViewState viewState = state.getViewStateForView(child); 403 int difference = Math.min(StackStateAnimator.DELAY_EFFECT_MAX_INDEX_DIFFERENCE_CHILDREN, 404 delayIndex); 405 long delay = withDelays 406 ? difference * StackStateAnimator.ANIMATION_DELAY_PER_ELEMENT_EXPAND_CHILDREN 407 : 0; 408 delay = (long) (delay * (mChildrenExpanded ? 1.0f : 0.5f) + baseDelay); 409 stateAnimator.startStackAnimations(child, viewState, state, -1, delay); 410 411 // layout the divider 412 View divider = mDividers.get(i); 413 tmpState.initFrom(divider); 414 tmpState.yTranslation = viewState.yTranslation - mDividerHeight; 415 tmpState.alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0; 416 stateAnimator.startViewAnimations(divider, tmpState, delay, duration); 417 if (i < maxAllowChildCount) { 418 delayIndex++; 419 } 420 } 421 if (mGroupOverflowContainer != null) { 422 stateAnimator.startViewAnimations(mGroupOverflowContainer, mGroupOverFlowState, 423 baseDelay, duration); 424 } 425 } 426 427 public ExpandableNotificationRow getViewAtPosition(float y) { 428 // find the view under the pointer, accounting for GONE views 429 final int count = mChildren.size(); 430 for (int childIdx = 0; childIdx < count; childIdx++) { 431 ExpandableNotificationRow slidingChild = mChildren.get(childIdx); 432 float childTop = slidingChild.getTranslationY(); 433 float top = childTop + slidingChild.getClipTopAmount(); 434 float bottom = childTop + slidingChild.getActualHeight(); 435 if (y >= top && y <= bottom) { 436 return slidingChild; 437 } 438 } 439 return null; 440 } 441 442 public void setChildrenExpanded(boolean childrenExpanded) { 443 mChildrenExpanded = childrenExpanded; 444 } 445 446 public void setNotificationParent(ExpandableNotificationRow parent) { 447 mNotificationParent = parent; 448 } 449 450 public int getMaxContentHeight() { 451 return getIntrinsicHeight(NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED); 452 } 453 454 public int getMinHeight() { 455 return getIntrinsicHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */)); 456 } 457 458 public void setDark(boolean dark, boolean fade, long delay) { 459 if (mGroupOverflowContainer != null) { 460 mOverflowInvertHelper.setInverted(dark, fade, delay); 461 } 462 } 463} 464