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