NotificationChildrenContainer.java revision b0a824687f56b6950338aad169d8d837f8ed657b
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.app.Notification; 20import android.content.Context; 21import android.content.res.Configuration; 22import android.graphics.drawable.ColorDrawable; 23import android.service.notification.StatusBarNotification; 24import android.util.AttributeSet; 25import android.view.LayoutInflater; 26import android.view.NotificationHeaderView; 27import android.view.View; 28import android.view.ViewGroup; 29import android.widget.RemoteViews; 30import android.widget.TextView; 31 32import com.android.systemui.R; 33import com.android.systemui.ViewInvertHelper; 34import com.android.systemui.statusbar.CrossFadeHelper; 35import com.android.systemui.statusbar.ExpandableNotificationRow; 36import com.android.systemui.statusbar.NotificationHeaderUtil; 37import com.android.systemui.statusbar.notification.HybridGroupManager; 38import com.android.systemui.statusbar.notification.HybridNotificationView; 39import com.android.systemui.statusbar.notification.NotificationUtils; 40import com.android.systemui.statusbar.notification.NotificationViewWrapper; 41import com.android.systemui.statusbar.phone.NotificationPanelView; 42 43import java.util.ArrayList; 44import java.util.List; 45 46/** 47 * A container containing child notifications 48 */ 49public class NotificationChildrenContainer extends ViewGroup { 50 51 private static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2; 52 private static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5; 53 private static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8; 54 55 private final List<View> mDividers = new ArrayList<>(); 56 private final List<ExpandableNotificationRow> mChildren = new ArrayList<>(); 57 private final HybridGroupManager mHybridGroupManager; 58 private int mChildPadding; 59 private int mDividerHeight; 60 private int mMaxNotificationHeight; 61 private int mNotificationHeaderMargin; 62 private int mNotificatonTopPadding; 63 private float mCollapsedBottompadding; 64 private ViewInvertHelper mOverflowInvertHelper; 65 private boolean mChildrenExpanded; 66 private ExpandableNotificationRow mNotificationParent; 67 private TextView mOverflowNumber; 68 private ViewState mGroupOverFlowState; 69 private int mRealHeight; 70 private boolean mUserLocked; 71 private int mActualHeight; 72 private boolean mNeverAppliedGroupState; 73 private int mHeaderHeight; 74 75 private NotificationHeaderView mNotificationHeader; 76 private NotificationViewWrapper mNotificationHeaderWrapper; 77 private NotificationHeaderUtil mHeaderUtil; 78 private ViewState mHeaderViewState; 79 80 public NotificationChildrenContainer(Context context) { 81 this(context, null); 82 } 83 84 public NotificationChildrenContainer(Context context, AttributeSet attrs) { 85 this(context, attrs, 0); 86 } 87 88 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) { 89 this(context, attrs, defStyleAttr, 0); 90 } 91 92 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, 93 int defStyleRes) { 94 super(context, attrs, defStyleAttr, defStyleRes); 95 initDimens(); 96 mHybridGroupManager = new HybridGroupManager(getContext(), this); 97 } 98 99 private void initDimens() { 100 mChildPadding = getResources().getDimensionPixelSize( 101 R.dimen.notification_children_padding); 102 mDividerHeight = Math.max(1, getResources().getDimensionPixelSize( 103 R.dimen.notification_divider_height)); 104 mHeaderHeight = getResources().getDimensionPixelSize(R.dimen.notification_header_height); 105 mMaxNotificationHeight = getResources().getDimensionPixelSize( 106 R.dimen.notification_max_height); 107 mNotificationHeaderMargin = getResources().getDimensionPixelSize( 108 com.android.internal.R.dimen.notification_content_margin_top); 109 mNotificatonTopPadding = getResources().getDimensionPixelSize( 110 R.dimen.notification_children_container_top_padding); 111 mCollapsedBottompadding = getResources().getDimensionPixelSize( 112 com.android.internal.R.dimen.notification_content_margin_bottom); 113 } 114 115 @Override 116 protected void onLayout(boolean changed, int l, int t, int r, int b) { 117 int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED); 118 for (int i = 0; i < childCount; i++) { 119 View child = mChildren.get(i); 120 // We need to layout all children even the GONE ones, such that the heights are 121 // calculated correctly as they are used to calculate how many we can fit on the screen 122 child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); 123 mDividers.get(i).layout(0, 0, getWidth(), mDividerHeight); 124 } 125 if (mOverflowNumber != null) { 126 mOverflowNumber.layout(getWidth() - mOverflowNumber.getMeasuredWidth(), 0, getWidth(), 127 mOverflowNumber.getMeasuredHeight()); 128 } 129 if (mNotificationHeader != null) { 130 mNotificationHeader.layout(0, 0, mNotificationHeader.getMeasuredWidth(), 131 mNotificationHeader.getMeasuredHeight()); 132 } 133 } 134 135 @Override 136 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 137 int ownMaxHeight = mMaxNotificationHeight; 138 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 139 boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; 140 boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; 141 int size = MeasureSpec.getSize(heightMeasureSpec); 142 if (hasFixedHeight || isHeightLimited) { 143 ownMaxHeight = Math.min(ownMaxHeight, size); 144 } 145 int newHeightSpec = MeasureSpec.makeMeasureSpec(ownMaxHeight, MeasureSpec.AT_MOST); 146 int width = MeasureSpec.getSize(widthMeasureSpec); 147 if (mOverflowNumber != null) { 148 mOverflowNumber.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), 149 newHeightSpec); 150 } 151 int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY); 152 int height = mNotificationHeaderMargin + mNotificatonTopPadding; 153 int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED); 154 int collapsedChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */); 155 int overflowIndex = childCount > collapsedChildren ? collapsedChildren - 1 : -1; 156 for (int i = 0; i < childCount; i++) { 157 ExpandableNotificationRow child = mChildren.get(i); 158 // We need to measure all children even the GONE ones, such that the heights are 159 // calculated correctly as they are used to calculate how many we can fit on the screen. 160 boolean isOverflow = i == overflowIndex; 161 child.setSingleLineWidthIndention(isOverflow && mOverflowNumber != null 162 ? mOverflowNumber.getMeasuredWidth() 163 : 0); 164 child.measure(widthMeasureSpec, newHeightSpec); 165 // layout the divider 166 View divider = mDividers.get(i); 167 divider.measure(widthMeasureSpec, dividerHeightSpec); 168 if (child.getVisibility() != GONE) { 169 height += child.getMeasuredHeight() + mDividerHeight; 170 } 171 } 172 mRealHeight = height; 173 if (heightMode != MeasureSpec.UNSPECIFIED) { 174 height = Math.min(height, size); 175 } 176 177 if (mNotificationHeader != null) { 178 int headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY); 179 mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec); 180 } 181 182 setMeasuredDimension(width, height); 183 } 184 185 @Override 186 public boolean pointInView(float localX, float localY, float slop) { 187 return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) && 188 localY < (mRealHeight + slop); 189 } 190 191 /** 192 * Add a child notification to this view. 193 * 194 * @param row the row to add 195 * @param childIndex the index to add it at, if -1 it will be added at the end 196 */ 197 public void addNotification(ExpandableNotificationRow row, int childIndex) { 198 int newIndex = childIndex < 0 ? mChildren.size() : childIndex; 199 mChildren.add(newIndex, row); 200 addView(row); 201 row.setUserLocked(mUserLocked); 202 203 View divider = inflateDivider(); 204 addView(divider); 205 mDividers.add(newIndex, divider); 206 207 updateGroupOverflow(); 208 } 209 210 public void removeNotification(ExpandableNotificationRow row) { 211 int childIndex = mChildren.indexOf(row); 212 mChildren.remove(row); 213 removeView(row); 214 215 final View divider = mDividers.remove(childIndex); 216 removeView(divider); 217 getOverlay().add(divider); 218 CrossFadeHelper.fadeOut(divider, new Runnable() { 219 @Override 220 public void run() { 221 getOverlay().remove(divider); 222 } 223 }); 224 225 row.setSystemChildExpanded(false); 226 row.setUserLocked(false); 227 updateGroupOverflow(); 228 if (!row.isRemoved()) { 229 mHeaderUtil.restoreNotificationHeader(row); 230 } 231 } 232 233 /** 234 * @return The number of notification children in the container. 235 */ 236 public int getNotificationChildCount() { 237 return mChildren.size(); 238 } 239 240 public void recreateNotificationHeader(OnClickListener listener, StatusBarNotification notification) { 241 final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(), 242 mNotificationParent.getStatusBarNotification().getNotification()); 243 final RemoteViews header = builder.makeNotificationHeader(); 244 if (mNotificationHeader == null) { 245 mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this); 246 final View expandButton = mNotificationHeader.findViewById( 247 com.android.internal.R.id.expand_button); 248 expandButton.setVisibility(VISIBLE); 249 mNotificationHeader.setOnClickListener(listener); 250 mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(), 251 mNotificationHeader, mNotificationParent); 252 addView(mNotificationHeader, 0); 253 invalidate(); 254 } else { 255 header.reapply(getContext(), mNotificationHeader); 256 mNotificationHeaderWrapper.notifyContentUpdated(notification); 257 } 258 updateChildrenHeaderAppearance(); 259 } 260 261 public void updateChildrenHeaderAppearance() { 262 mHeaderUtil.updateChildrenHeaderAppearance(); 263 } 264 265 public void updateGroupOverflow() { 266 int childCount = mChildren.size(); 267 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */); 268 if (childCount > maxAllowedVisibleChildren) { 269 mOverflowNumber = mHybridGroupManager.bindOverflowNumber( 270 mOverflowNumber, childCount - maxAllowedVisibleChildren); 271 if (mOverflowInvertHelper == null) { 272 mOverflowInvertHelper = new ViewInvertHelper(mOverflowNumber, 273 NotificationPanelView.DOZE_ANIMATION_DURATION); 274 } 275 if (mGroupOverFlowState == null) { 276 mGroupOverFlowState = new ViewState(); 277 mNeverAppliedGroupState = true; 278 } 279 } else if (mOverflowNumber != null) { 280 removeView(mOverflowNumber); 281 if (isShown()) { 282 final View removedOverflowNumber = mOverflowNumber; 283 addTransientView(removedOverflowNumber, getTransientViewCount()); 284 CrossFadeHelper.fadeOut(removedOverflowNumber, new Runnable() { 285 @Override 286 public void run() { 287 removeTransientView(removedOverflowNumber); 288 } 289 }); 290 } 291 mOverflowNumber = null; 292 mOverflowInvertHelper = null; 293 mGroupOverFlowState = null; 294 } 295 } 296 297 @Override 298 protected void onConfigurationChanged(Configuration newConfig) { 299 super.onConfigurationChanged(newConfig); 300 updateGroupOverflow(); 301 } 302 303 private View inflateDivider() { 304 return LayoutInflater.from(mContext).inflate( 305 R.layout.notification_children_divider, this, false); 306 } 307 308 public List<ExpandableNotificationRow> getNotificationChildren() { 309 return mChildren; 310 } 311 312 /** 313 * Apply the order given in the list to the children. 314 * 315 * @param childOrder the new list order 316 * @return whether the list order has changed 317 */ 318 public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { 319 if (childOrder == null) { 320 return false; 321 } 322 boolean result = false; 323 for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) { 324 ExpandableNotificationRow child = mChildren.get(i); 325 ExpandableNotificationRow desiredChild = childOrder.get(i); 326 if (child != desiredChild) { 327 mChildren.remove(desiredChild); 328 mChildren.add(i, desiredChild); 329 result = true; 330 } 331 } 332 updateExpansionStates(); 333 return result; 334 } 335 336 private void updateExpansionStates() { 337 if (mChildrenExpanded || mUserLocked) { 338 // we don't modify it the group is expanded or if we are expanding it 339 return; 340 } 341 int size = mChildren.size(); 342 for (int i = 0; i < size; i++) { 343 ExpandableNotificationRow child = mChildren.get(i); 344 child.setSystemChildExpanded(i == 0 && size == 1); 345 } 346 } 347 348 /** 349 * 350 * @return the intrinsic size of this children container, i.e the natural fully expanded state 351 */ 352 public int getIntrinsicHeight() { 353 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(); 354 return getIntrinsicHeight(maxAllowedVisibleChildren); 355 } 356 357 /** 358 * @return the intrinsic height with a number of children given 359 * in @param maxAllowedVisibleChildren 360 */ 361 private int getIntrinsicHeight(float maxAllowedVisibleChildren) { 362 int intrinsicHeight = mNotificationHeaderMargin; 363 int visibleChildren = 0; 364 int childCount = mChildren.size(); 365 boolean firstChild = true; 366 float expandFactor = 0; 367 if (mUserLocked) { 368 expandFactor = getGroupExpandFraction(); 369 } 370 for (int i = 0; i < childCount; i++) { 371 if (visibleChildren >= maxAllowedVisibleChildren) { 372 break; 373 } 374 if (!firstChild) { 375 if (mUserLocked) { 376 intrinsicHeight += NotificationUtils.interpolate(mChildPadding, mDividerHeight, 377 expandFactor); 378 } else { 379 intrinsicHeight += mChildrenExpanded ? mDividerHeight : mChildPadding; 380 } 381 } else { 382 if (mUserLocked) { 383 intrinsicHeight += NotificationUtils.interpolate( 384 0, 385 mNotificatonTopPadding + mDividerHeight, 386 expandFactor); 387 } else { 388 intrinsicHeight += mChildrenExpanded 389 ? mNotificatonTopPadding + mDividerHeight 390 : 0; 391 } 392 firstChild = false; 393 } 394 ExpandableNotificationRow child = mChildren.get(i); 395 intrinsicHeight += child.getIntrinsicHeight(); 396 visibleChildren++; 397 } 398 if (mUserLocked) { 399 intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottompadding, 0.0f, 400 expandFactor); 401 } else if (!mChildrenExpanded) { 402 intrinsicHeight += mCollapsedBottompadding; 403 } 404 return intrinsicHeight; 405 } 406 407 /** 408 * Update the state of all its children based on a linear layout algorithm. 409 * 410 * @param resultState the state to update 411 * @param parentState the state of the parent 412 */ 413 public void getState(StackScrollState resultState, StackViewState parentState) { 414 int childCount = mChildren.size(); 415 int yPosition = mNotificationHeaderMargin; 416 boolean firstChild = true; 417 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(); 418 int lastVisibleIndex = maxAllowedVisibleChildren - 1; 419 int firstOverflowIndex = lastVisibleIndex + 1; 420 float expandFactor = 0; 421 if (mUserLocked) { 422 expandFactor = getGroupExpandFraction(); 423 firstOverflowIndex = getMaxAllowedVisibleChildren(true /* likeCollapsed */); 424 } 425 426 boolean childrenExpanded = !mNotificationParent.isGroupExpansionChanging() 427 && mChildrenExpanded; 428 int parentHeight = parentState.height; 429 for (int i = 0; i < childCount; i++) { 430 ExpandableNotificationRow child = mChildren.get(i); 431 if (!firstChild) { 432 if (mUserLocked) { 433 yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight, 434 expandFactor); 435 } else { 436 yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding; 437 } 438 } else { 439 if (mUserLocked) { 440 yPosition += NotificationUtils.interpolate( 441 0, 442 mNotificatonTopPadding + mDividerHeight, 443 expandFactor); 444 } else { 445 yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0; 446 } 447 firstChild = false; 448 } 449 450 StackViewState childState = resultState.getViewStateForView(child); 451 int intrinsicHeight = child.getIntrinsicHeight(); 452 if (childrenExpanded) { 453 // When a group is expanded and moving into bottom stack, the bottom visible child 454 // adjusts its height to move into it. Children after it are hidden. 455 if (updateChildStateForExpandedGroup(child, parentHeight, childState, yPosition)) { 456 // Clipping might be deactivated if the view is transforming, however, clipping 457 // the child into the bottom stack should take precedent over this. 458 childState.isBottomClipped = true; 459 } 460 } else { 461 childState.hidden = false; 462 childState.height = intrinsicHeight; 463 childState.isBottomClipped = false; 464 } 465 childState.yTranslation = yPosition; 466 // When the group is expanded, the children cast the shadows rather than the parent 467 // so use the parent's elevation here. 468 childState.zTranslation = childrenExpanded 469 ? mNotificationParent.getTranslationZ() 470 : 0; 471 childState.dimmed = parentState.dimmed; 472 childState.dark = parentState.dark; 473 childState.hideSensitive = parentState.hideSensitive; 474 childState.belowSpeedBump = parentState.belowSpeedBump; 475 childState.clipTopAmount = 0; 476 childState.alpha = 0; 477 if (i < firstOverflowIndex) { 478 childState.alpha = 1; 479 } else if (expandFactor == 1.0f && i <= lastVisibleIndex) { 480 childState.alpha = (mActualHeight - childState.yTranslation) / childState.height; 481 childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha)); 482 } 483 childState.location = parentState.location; 484 yPosition += intrinsicHeight; 485 } 486 if (mOverflowNumber != null) { 487 ExpandableNotificationRow overflowView = mChildren.get(Math.min( 488 getMaxAllowedVisibleChildren(true /* likeCollpased */), childCount) - 1); 489 mGroupOverFlowState.copyFrom(resultState.getViewStateForView(overflowView)); 490 if (!mChildrenExpanded) { 491 if (mUserLocked) { 492 HybridNotificationView singleLineView = overflowView.getSingleLineView(); 493 View mirrorView = singleLineView.getTextView(); 494 if (mirrorView.getVisibility() == GONE) { 495 mirrorView = singleLineView.getTitleView(); 496 } 497 if (mirrorView.getVisibility() == GONE) { 498 mirrorView = singleLineView; 499 } 500 mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset( 501 mirrorView, overflowView); 502 mGroupOverFlowState.alpha = mirrorView.getAlpha(); 503 } 504 } else { 505 mGroupOverFlowState.yTranslation += mNotificationHeaderMargin; 506 mGroupOverFlowState.alpha = 0.0f; 507 } 508 } 509 if (mNotificationHeader != null) { 510 if (mHeaderViewState == null) { 511 mHeaderViewState = new ViewState(); 512 } 513 mHeaderViewState.initFrom(mNotificationHeader); 514 mHeaderViewState.zTranslation = childrenExpanded 515 ? mNotificationParent.getTranslationZ() 516 : 0; 517 } 518 } 519 520 /** 521 * When moving into the bottom stack, the bottom visible child in an expanded group adjusts its 522 * height, children in the group after this are gone. 523 * 524 * @param child the child who's height to adjust. 525 * @param parentHeight the height of the parent. 526 * @param childState the state to update. 527 * @param yPosition the yPosition of the view. 528 * @return true if children after this one should be hidden. 529 */ 530 private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child, 531 int parentHeight, StackViewState childState, int yPosition) { 532 final int top = yPosition + child.getClipTopAmount(); 533 final int intrinsicHeight = child.getIntrinsicHeight(); 534 final int bottom = top + intrinsicHeight; 535 int newHeight = intrinsicHeight; 536 if (bottom >= parentHeight) { 537 // Child is either clipped or gone 538 newHeight = Math.max((parentHeight - top), 0); 539 } 540 childState.hidden = newHeight == 0; 541 childState.height = newHeight; 542 return childState.height != intrinsicHeight && !childState.hidden; 543 } 544 545 private int getMaxAllowedVisibleChildren() { 546 return getMaxAllowedVisibleChildren(false /* likeCollapsed */); 547 } 548 549 private int getMaxAllowedVisibleChildren(boolean likeCollapsed) { 550 if (!likeCollapsed && (mChildrenExpanded || mNotificationParent.isUserLocked())) { 551 return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED; 552 } 553 if (!mNotificationParent.isOnKeyguard() 554 && (mNotificationParent.isExpanded() || mNotificationParent.isHeadsUp())) { 555 return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED; 556 } 557 return NUMBER_OF_CHILDREN_WHEN_COLLAPSED; 558 } 559 560 public void applyState(StackScrollState state) { 561 int childCount = mChildren.size(); 562 ViewState tmpState = new ViewState(); 563 float expandFraction = 0.0f; 564 if (mUserLocked) { 565 expandFraction = getGroupExpandFraction(); 566 } 567 final boolean dividersVisible = mUserLocked 568 || mNotificationParent.isGroupExpansionChanging(); 569 for (int i = 0; i < childCount; i++) { 570 ExpandableNotificationRow child = mChildren.get(i); 571 StackViewState viewState = state.getViewStateForView(child); 572 state.applyState(child, viewState); 573 574 // layout the divider 575 View divider = mDividers.get(i); 576 tmpState.initFrom(divider); 577 tmpState.yTranslation = viewState.yTranslation - mDividerHeight; 578 float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0; 579 if (mUserLocked && viewState.alpha != 0) { 580 alpha = NotificationUtils.interpolate(0, 0.5f, 581 Math.min(viewState.alpha, expandFraction)); 582 } 583 tmpState.hidden = !dividersVisible; 584 tmpState.alpha = alpha; 585 state.applyViewState(divider, tmpState); 586 // There is no fake shadow to be drawn on the children 587 child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0); 588 } 589 if (mOverflowNumber != null) { 590 state.applyViewState(mOverflowNumber, mGroupOverFlowState); 591 mNeverAppliedGroupState = false; 592 } 593 if (mNotificationHeader != null) { 594 state.applyViewState(mNotificationHeader, mHeaderViewState); 595 } 596 } 597 598 /** 599 * This is called when the children expansion has changed and positions the children properly 600 * for an appear animation. 601 * 602 * @param state the new state we animate to 603 */ 604 public void prepareExpansionChanged(StackScrollState state) { 605 // TODO: do something that makes sense, like placing the invisible views correctly 606 return; 607 } 608 609 public void startAnimationToState(StackScrollState state, StackStateAnimator stateAnimator, 610 long baseDelay, long duration) { 611 int childCount = mChildren.size(); 612 ViewState tmpState = new ViewState(); 613 float expandFraction = getGroupExpandFraction(); 614 final boolean dividersVisible = mUserLocked 615 || mNotificationParent.isGroupExpansionChanging(); 616 for (int i = childCount - 1; i >= 0; i--) { 617 ExpandableNotificationRow child = mChildren.get(i); 618 StackViewState viewState = state.getViewStateForView(child); 619 stateAnimator.startStackAnimations(child, viewState, state, -1, baseDelay); 620 621 // layout the divider 622 View divider = mDividers.get(i); 623 tmpState.initFrom(divider); 624 tmpState.yTranslation = viewState.yTranslation - mDividerHeight; 625 float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0; 626 if (mUserLocked && viewState.alpha != 0) { 627 alpha = NotificationUtils.interpolate(0, 0.5f, 628 Math.min(viewState.alpha, expandFraction)); 629 } 630 tmpState.hidden = !dividersVisible; 631 tmpState.alpha = alpha; 632 stateAnimator.startViewAnimations(divider, tmpState, baseDelay, duration); 633 // There is no fake shadow to be drawn on the children 634 child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0); 635 } 636 if (mOverflowNumber != null) { 637 if (mNeverAppliedGroupState) { 638 float alpha = mGroupOverFlowState.alpha; 639 mGroupOverFlowState.alpha = 0; 640 state.applyViewState(mOverflowNumber, mGroupOverFlowState); 641 mGroupOverFlowState.alpha = alpha; 642 mNeverAppliedGroupState = false; 643 } 644 stateAnimator.startViewAnimations(mOverflowNumber, mGroupOverFlowState, 645 baseDelay, duration); 646 } 647 if (mNotificationHeader != null) { 648 state.applyViewState(mNotificationHeader, mHeaderViewState); 649 } 650 } 651 652 public ExpandableNotificationRow getViewAtPosition(float y) { 653 // find the view under the pointer, accounting for GONE views 654 final int count = mChildren.size(); 655 for (int childIdx = 0; childIdx < count; childIdx++) { 656 ExpandableNotificationRow slidingChild = mChildren.get(childIdx); 657 float childTop = slidingChild.getTranslationY(); 658 float top = childTop + slidingChild.getClipTopAmount(); 659 float bottom = childTop + slidingChild.getActualHeight(); 660 if (y >= top && y <= bottom) { 661 return slidingChild; 662 } 663 } 664 return null; 665 } 666 667 public void setChildrenExpanded(boolean childrenExpanded) { 668 mChildrenExpanded = childrenExpanded; 669 updateExpansionStates(); 670 if (mNotificationHeader != null) { 671 mNotificationHeader.setExpanded(childrenExpanded); 672 } 673 } 674 675 public void setNotificationParent(ExpandableNotificationRow parent) { 676 mNotificationParent = parent; 677 mHeaderUtil = new NotificationHeaderUtil(mNotificationParent); 678 } 679 680 public NotificationHeaderView getHeaderView() { 681 return mNotificationHeader; 682 } 683 684 public void updateHeaderVisibility(int visiblity) { 685 if (mNotificationHeader != null) { 686 mNotificationHeader.setVisibility(visiblity); 687 } 688 } 689 690 /** 691 * Called when a groups expansion changes to adjust the background of the header view. 692 * 693 * @param expanded whether the group is expanded. 694 */ 695 public void updateHeaderForExpansion(boolean expanded) { 696 if (mNotificationHeader != null) { 697 if (expanded) { 698 ColorDrawable cd = new ColorDrawable(); 699 cd.setColor(mNotificationParent.calculateBgColor()); 700 mNotificationHeader.setHeaderBackgroundDrawable(cd); 701 } else { 702 mNotificationHeader.setHeaderBackgroundDrawable(null); 703 } 704 } 705 } 706 707 public int getMaxContentHeight() { 708 int maxContentHeight = mNotificationHeaderMargin + mNotificatonTopPadding; 709 int visibleChildren = 0; 710 int childCount = mChildren.size(); 711 for (int i = 0; i < childCount; i++) { 712 if (visibleChildren >= NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED) { 713 break; 714 } 715 ExpandableNotificationRow child = mChildren.get(i); 716 float childHeight = child.isExpanded(true /* allowOnKeyguard */) 717 ? child.getMaxExpandHeight() 718 : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */); 719 maxContentHeight += childHeight; 720 visibleChildren++; 721 } 722 if (visibleChildren > 0) { 723 maxContentHeight += visibleChildren * mDividerHeight; 724 } 725 return maxContentHeight; 726 } 727 728 public void setActualHeight(int actualHeight) { 729 if (!mUserLocked) { 730 return; 731 } 732 mActualHeight = actualHeight; 733 float fraction = getGroupExpandFraction(); 734 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */); 735 int childCount = mChildren.size(); 736 for (int i = 0; i < childCount; i++) { 737 ExpandableNotificationRow child = mChildren.get(i); 738 float childHeight = child.isExpanded(true /* allowOnKeyguard */) 739 ? child.getMaxExpandHeight() 740 : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */); 741 if (i < maxAllowedVisibleChildren) { 742 float singleLineHeight = child.getShowingLayout().getMinHeight( 743 false /* likeGroupExpanded */); 744 child.setActualHeight((int) NotificationUtils.interpolate(singleLineHeight, 745 childHeight, fraction), false); 746 } else { 747 child.setActualHeight((int) childHeight, false); 748 } 749 } 750 } 751 752 public float getGroupExpandFraction() { 753 int visibleChildrenExpandedHeight = getVisibleChildrenExpandHeight(); 754 int minExpandHeight = getCollapsedHeight(); 755 float factor = (mActualHeight - minExpandHeight) 756 / (float) (visibleChildrenExpandedHeight - minExpandHeight); 757 return Math.max(0.0f, Math.min(1.0f, factor)); 758 } 759 760 private int getVisibleChildrenExpandHeight() { 761 int intrinsicHeight = mNotificationHeaderMargin + mNotificatonTopPadding + mDividerHeight; 762 int visibleChildren = 0; 763 int childCount = mChildren.size(); 764 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */); 765 for (int i = 0; i < childCount; i++) { 766 if (visibleChildren >= maxAllowedVisibleChildren) { 767 break; 768 } 769 ExpandableNotificationRow child = mChildren.get(i); 770 float childHeight = child.isExpanded(true /* allowOnKeyguard */) 771 ? child.getMaxExpandHeight() 772 : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */); 773 intrinsicHeight += childHeight; 774 visibleChildren++; 775 } 776 return intrinsicHeight; 777 } 778 779 public int getMinHeight() { 780 return getMinHeight(NUMBER_OF_CHILDREN_WHEN_COLLAPSED); 781 } 782 783 public int getCollapsedHeight() { 784 return getMinHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */)); 785 } 786 787 private int getMinHeight(int maxAllowedVisibleChildren) { 788 int minExpandHeight = mNotificationHeaderMargin; 789 int visibleChildren = 0; 790 boolean firstChild = true; 791 int childCount = mChildren.size(); 792 for (int i = 0; i < childCount; i++) { 793 if (visibleChildren >= maxAllowedVisibleChildren) { 794 break; 795 } 796 if (!firstChild) { 797 minExpandHeight += mChildPadding; 798 } else { 799 firstChild = false; 800 } 801 ExpandableNotificationRow child = mChildren.get(i); 802 minExpandHeight += child.getSingleLineView().getHeight(); 803 visibleChildren++; 804 } 805 minExpandHeight += mCollapsedBottompadding; 806 return minExpandHeight; 807 } 808 809 public void setDark(boolean dark, boolean fade, long delay) { 810 if (mOverflowNumber != null) { 811 mOverflowInvertHelper.setInverted(dark, fade, delay); 812 } 813 mNotificationHeaderWrapper.setDark(dark, fade, delay); 814 } 815 816 public void reInflateViews(OnClickListener listener, StatusBarNotification notification) { 817 removeView(mNotificationHeader); 818 mNotificationHeader = null; 819 recreateNotificationHeader(listener, notification); 820 initDimens(); 821 for (int i = 0; i < mDividers.size(); i++) { 822 View prevDivider = mDividers.get(i); 823 int index = indexOfChild(prevDivider); 824 removeView(prevDivider); 825 View divider = inflateDivider(); 826 addView(divider, index); 827 mDividers.set(i, divider); 828 } 829 removeView(mOverflowNumber); 830 mOverflowNumber = null; 831 mOverflowInvertHelper = null; 832 mGroupOverFlowState = null; 833 updateGroupOverflow(); 834 } 835 836 public void setUserLocked(boolean userLocked) { 837 mUserLocked = userLocked; 838 int childCount = mChildren.size(); 839 for (int i = 0; i < childCount; i++) { 840 ExpandableNotificationRow child = mChildren.get(i); 841 child.setUserLocked(userLocked); 842 } 843 } 844 845 public void onNotificationUpdated() { 846 mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, 847 mNotificationParent.getNotificationColor()); 848 } 849} 850