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 17 package com.android.systemui.statusbar.stack; 18 19 import android.app.Notification; 20 import android.content.Context; 21 import android.content.res.Configuration; 22 import android.content.res.Resources; 23 import android.graphics.drawable.ColorDrawable; 24 import android.service.notification.StatusBarNotification; 25 import android.util.AttributeSet; 26 import android.view.LayoutInflater; 27 import android.view.NotificationHeaderView; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.RemoteViews; 31 import android.widget.TextView; 32 33 import com.android.internal.annotations.VisibleForTesting; 34 import com.android.systemui.R; 35 import com.android.systemui.statusbar.CrossFadeHelper; 36 import com.android.systemui.statusbar.ExpandableNotificationRow; 37 import com.android.systemui.statusbar.NotificationHeaderUtil; 38 import com.android.systemui.statusbar.notification.HybridGroupManager; 39 import com.android.systemui.statusbar.notification.HybridNotificationView; 40 import com.android.systemui.statusbar.notification.NotificationUtils; 41 import com.android.systemui.statusbar.notification.NotificationViewWrapper; 42 import com.android.systemui.statusbar.notification.VisualStabilityManager; 43 44 import java.util.ArrayList; 45 import java.util.List; 46 47 /** 48 * A container containing child notifications 49 */ 50 public class NotificationChildrenContainer extends ViewGroup { 51 52 private static final int NUMBER_OF_CHILDREN_WHEN_COLLAPSED = 2; 53 private static final int NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED = 5; 54 private static final int NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED = 8; 55 private static final int NUMBER_OF_CHILDREN_WHEN_AMBIENT = 1; 56 private static final AnimationProperties ALPHA_FADE_IN = new AnimationProperties() { 57 private AnimationFilter mAnimationFilter = new AnimationFilter().animateAlpha(); 58 59 @Override 60 public AnimationFilter getAnimationFilter() { 61 return mAnimationFilter; 62 } 63 }.setDuration(200); 64 65 private final List<View> mDividers = new ArrayList<>(); 66 private final List<ExpandableNotificationRow> mChildren = new ArrayList<>(); 67 private final HybridGroupManager mHybridGroupManager; 68 private int mChildPadding; 69 private int mDividerHeight; 70 private float mDividerAlpha; 71 private int mNotificationHeaderMargin; 72 73 private int mNotificatonTopPadding; 74 private float mCollapsedBottompadding; 75 private boolean mChildrenExpanded; 76 private ExpandableNotificationRow mContainingNotification; 77 private TextView mOverflowNumber; 78 private ViewState mGroupOverFlowState; 79 private int mRealHeight; 80 private boolean mUserLocked; 81 private int mActualHeight; 82 private boolean mNeverAppliedGroupState; 83 private int mHeaderHeight; 84 85 /** 86 * Whether or not individual notifications that are part of this container will have shadows. 87 */ 88 private boolean mEnableShadowOnChildNotifications; 89 90 private NotificationHeaderView mNotificationHeader; 91 private NotificationViewWrapper mNotificationHeaderWrapper; 92 private NotificationHeaderView mNotificationHeaderLowPriority; 93 private NotificationViewWrapper mNotificationHeaderWrapperLowPriority; 94 private ViewGroup mNotificationHeaderAmbient; 95 private NotificationViewWrapper mNotificationHeaderWrapperAmbient; 96 private NotificationHeaderUtil mHeaderUtil; 97 private ViewState mHeaderViewState; 98 private int mClipBottomAmount; 99 private boolean mIsLowPriority; 100 private OnClickListener mHeaderClickListener; 101 private ViewGroup mCurrentHeader; 102 103 private boolean mShowDividersWhenExpanded; 104 private boolean mHideDividersDuringExpand; 105 private int mTranslationForHeader; 106 private int mCurrentHeaderTranslation = 0; 107 private float mHeaderVisibleAmount = 1.0f; 108 109 public NotificationChildrenContainer(Context context) { 110 this(context, null); 111 } 112 113 public NotificationChildrenContainer(Context context, AttributeSet attrs) { 114 this(context, attrs, 0); 115 } 116 117 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr) { 118 this(context, attrs, defStyleAttr, 0); 119 } 120 121 public NotificationChildrenContainer(Context context, AttributeSet attrs, int defStyleAttr, 122 int defStyleRes) { 123 super(context, attrs, defStyleAttr, defStyleRes); 124 mHybridGroupManager = new HybridGroupManager(getContext(), this); 125 initDimens(); 126 setClipChildren(false); 127 } 128 129 private void initDimens() { 130 Resources res = getResources(); 131 mChildPadding = res.getDimensionPixelSize(R.dimen.notification_children_padding); 132 mDividerHeight = res.getDimensionPixelSize( 133 R.dimen.notification_children_container_divider_height); 134 mDividerAlpha = res.getFloat(R.dimen.notification_divider_alpha); 135 mNotificationHeaderMargin = res.getDimensionPixelSize( 136 R.dimen.notification_children_container_margin_top); 137 mNotificatonTopPadding = res.getDimensionPixelSize( 138 R.dimen.notification_children_container_top_padding); 139 mHeaderHeight = mNotificationHeaderMargin + mNotificatonTopPadding; 140 mCollapsedBottompadding = res.getDimensionPixelSize( 141 com.android.internal.R.dimen.notification_content_margin); 142 mEnableShadowOnChildNotifications = 143 res.getBoolean(R.bool.config_enableShadowOnChildNotifications); 144 mShowDividersWhenExpanded = 145 res.getBoolean(R.bool.config_showDividersWhenGroupNotificationExpanded); 146 mHideDividersDuringExpand = 147 res.getBoolean(R.bool.config_hideDividersDuringExpand); 148 mTranslationForHeader = res.getDimensionPixelSize( 149 com.android.internal.R.dimen.notification_content_margin) 150 - mNotificationHeaderMargin; 151 mHybridGroupManager.initDimens(); 152 } 153 154 @Override 155 protected void onLayout(boolean changed, int l, int t, int r, int b) { 156 int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED); 157 for (int i = 0; i < childCount; i++) { 158 View child = mChildren.get(i); 159 // We need to layout all children even the GONE ones, such that the heights are 160 // calculated correctly as they are used to calculate how many we can fit on the screen 161 child.layout(0, 0, child.getMeasuredWidth(), child.getMeasuredHeight()); 162 mDividers.get(i).layout(0, 0, getWidth(), mDividerHeight); 163 } 164 if (mOverflowNumber != null) { 165 boolean isRtl = getLayoutDirection() == LAYOUT_DIRECTION_RTL; 166 int left = (isRtl ? 0 : getWidth() - mOverflowNumber.getMeasuredWidth()); 167 int right = left + mOverflowNumber.getMeasuredWidth(); 168 mOverflowNumber.layout(left, 0, right, mOverflowNumber.getMeasuredHeight()); 169 } 170 if (mNotificationHeader != null) { 171 mNotificationHeader.layout(0, 0, mNotificationHeader.getMeasuredWidth(), 172 mNotificationHeader.getMeasuredHeight()); 173 } 174 if (mNotificationHeaderLowPriority != null) { 175 mNotificationHeaderLowPriority.layout(0, 0, 176 mNotificationHeaderLowPriority.getMeasuredWidth(), 177 mNotificationHeaderLowPriority.getMeasuredHeight()); 178 } 179 if (mNotificationHeaderAmbient != null) { 180 mNotificationHeaderAmbient.layout(0, 0, 181 mNotificationHeaderAmbient.getMeasuredWidth(), 182 mNotificationHeaderAmbient.getMeasuredHeight()); 183 } 184 } 185 186 @Override 187 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 188 int heightMode = MeasureSpec.getMode(heightMeasureSpec); 189 boolean hasFixedHeight = heightMode == MeasureSpec.EXACTLY; 190 boolean isHeightLimited = heightMode == MeasureSpec.AT_MOST; 191 int size = MeasureSpec.getSize(heightMeasureSpec); 192 int newHeightSpec = heightMeasureSpec; 193 if (hasFixedHeight || isHeightLimited) { 194 newHeightSpec = MeasureSpec.makeMeasureSpec(size, MeasureSpec.AT_MOST); 195 } 196 int width = MeasureSpec.getSize(widthMeasureSpec); 197 if (mOverflowNumber != null) { 198 mOverflowNumber.measure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST), 199 newHeightSpec); 200 } 201 int dividerHeightSpec = MeasureSpec.makeMeasureSpec(mDividerHeight, MeasureSpec.EXACTLY); 202 int height = mNotificationHeaderMargin + mNotificatonTopPadding; 203 int childCount = Math.min(mChildren.size(), NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED); 204 int collapsedChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */); 205 int overflowIndex = childCount > collapsedChildren ? collapsedChildren - 1 : -1; 206 for (int i = 0; i < childCount; i++) { 207 ExpandableNotificationRow child = mChildren.get(i); 208 // We need to measure all children even the GONE ones, such that the heights are 209 // calculated correctly as they are used to calculate how many we can fit on the screen. 210 boolean isOverflow = i == overflowIndex; 211 child.setSingleLineWidthIndention(isOverflow && mOverflowNumber != null && 212 !mContainingNotification.isShowingAmbient() 213 ? mOverflowNumber.getMeasuredWidth() : 0); 214 child.measure(widthMeasureSpec, newHeightSpec); 215 // layout the divider 216 View divider = mDividers.get(i); 217 divider.measure(widthMeasureSpec, dividerHeightSpec); 218 if (child.getVisibility() != GONE) { 219 height += child.getMeasuredHeight() + mDividerHeight; 220 } 221 } 222 mRealHeight = height; 223 if (heightMode != MeasureSpec.UNSPECIFIED) { 224 height = Math.min(height, size); 225 } 226 227 int headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY); 228 if (mNotificationHeader != null) { 229 mNotificationHeader.measure(widthMeasureSpec, headerHeightSpec); 230 } 231 if (mNotificationHeaderLowPriority != null) { 232 headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY); 233 mNotificationHeaderLowPriority.measure(widthMeasureSpec, headerHeightSpec); 234 } 235 if (mNotificationHeaderAmbient != null) { 236 headerHeightSpec = MeasureSpec.makeMeasureSpec(mHeaderHeight, MeasureSpec.EXACTLY); 237 mNotificationHeaderAmbient.measure(widthMeasureSpec, headerHeightSpec); 238 } 239 240 setMeasuredDimension(width, height); 241 } 242 243 @Override 244 public boolean hasOverlappingRendering() { 245 return false; 246 } 247 248 @Override 249 public boolean pointInView(float localX, float localY, float slop) { 250 return localX >= -slop && localY >= -slop && localX < ((mRight - mLeft) + slop) && 251 localY < (mRealHeight + slop); 252 } 253 254 /** 255 * Add a child notification to this view. 256 * 257 * @param row the row to add 258 * @param childIndex the index to add it at, if -1 it will be added at the end 259 */ 260 public void addNotification(ExpandableNotificationRow row, int childIndex) { 261 int newIndex = childIndex < 0 ? mChildren.size() : childIndex; 262 mChildren.add(newIndex, row); 263 addView(row); 264 row.setUserLocked(mUserLocked); 265 266 View divider = inflateDivider(); 267 addView(divider); 268 mDividers.add(newIndex, divider); 269 270 updateGroupOverflow(); 271 row.setContentTransformationAmount(0, false /* isLastChild */); 272 // It doesn't make sense to keep old animations around, lets cancel them! 273 ExpandableNotificationRow.NotificationViewState viewState = row.getViewState(); 274 if (viewState != null) { 275 viewState.cancelAnimations(row); 276 row.cancelAppearDrawing(); 277 } 278 } 279 280 public void removeNotification(ExpandableNotificationRow row) { 281 int childIndex = mChildren.indexOf(row); 282 mChildren.remove(row); 283 removeView(row); 284 285 final View divider = mDividers.remove(childIndex); 286 removeView(divider); 287 getOverlay().add(divider); 288 CrossFadeHelper.fadeOut(divider, new Runnable() { 289 @Override 290 public void run() { 291 getOverlay().remove(divider); 292 } 293 }); 294 295 row.setSystemChildExpanded(false); 296 row.setUserLocked(false); 297 updateGroupOverflow(); 298 if (!row.isRemoved()) { 299 mHeaderUtil.restoreNotificationHeader(row); 300 } 301 } 302 303 /** 304 * @return The number of notification children in the container. 305 */ 306 public int getNotificationChildCount() { 307 return mChildren.size(); 308 } 309 310 public void recreateNotificationHeader(OnClickListener listener) { 311 mHeaderClickListener = listener; 312 StatusBarNotification notification = mContainingNotification.getStatusBarNotification(); 313 final Notification.Builder builder = Notification.Builder.recoverBuilder(getContext(), 314 notification.getNotification()); 315 RemoteViews header = builder.makeNotificationHeader(false /* ambient */); 316 if (mNotificationHeader == null) { 317 mNotificationHeader = (NotificationHeaderView) header.apply(getContext(), this); 318 final View expandButton = mNotificationHeader.findViewById( 319 com.android.internal.R.id.expand_button); 320 expandButton.setVisibility(VISIBLE); 321 mNotificationHeader.setOnClickListener(mHeaderClickListener); 322 mNotificationHeaderWrapper = NotificationViewWrapper.wrap(getContext(), 323 mNotificationHeader, mContainingNotification); 324 addView(mNotificationHeader, 0); 325 invalidate(); 326 } else { 327 header.reapply(getContext(), mNotificationHeader); 328 } 329 mNotificationHeaderWrapper.onContentUpdated(mContainingNotification); 330 recreateLowPriorityHeader(builder); 331 recreateAmbientHeader(builder); 332 updateHeaderVisibility(false /* animate */); 333 updateChildrenHeaderAppearance(); 334 } 335 336 private void recreateAmbientHeader(Notification.Builder builder) { 337 RemoteViews header; 338 StatusBarNotification notification = mContainingNotification.getStatusBarNotification(); 339 if (builder == null) { 340 builder = Notification.Builder.recoverBuilder(getContext(), 341 notification.getNotification()); 342 } 343 header = builder.makeNotificationHeader(true /* ambient */); 344 if (mNotificationHeaderAmbient == null) { 345 mNotificationHeaderAmbient = (ViewGroup) header.apply(getContext(), this); 346 mNotificationHeaderWrapperAmbient = NotificationViewWrapper.wrap(getContext(), 347 mNotificationHeaderAmbient, mContainingNotification); 348 mNotificationHeaderWrapperAmbient.onContentUpdated(mContainingNotification); 349 addView(mNotificationHeaderAmbient, 0); 350 invalidate(); 351 } else { 352 header.reapply(getContext(), mNotificationHeaderAmbient); 353 } 354 resetHeaderVisibilityIfNeeded(mNotificationHeaderAmbient, calculateDesiredHeader()); 355 mNotificationHeaderWrapperAmbient.onContentUpdated(mContainingNotification); 356 } 357 358 /** 359 * Recreate the low-priority header. 360 * 361 * @param builder a builder to reuse. Otherwise the builder will be recovered. 362 */ 363 private void recreateLowPriorityHeader(Notification.Builder builder) { 364 RemoteViews header; 365 StatusBarNotification notification = mContainingNotification.getStatusBarNotification(); 366 if (mIsLowPriority) { 367 if (builder == null) { 368 builder = Notification.Builder.recoverBuilder(getContext(), 369 notification.getNotification()); 370 } 371 header = builder.makeLowPriorityContentView(true /* useRegularSubtext */); 372 if (mNotificationHeaderLowPriority == null) { 373 mNotificationHeaderLowPriority = (NotificationHeaderView) header.apply(getContext(), 374 this); 375 final View expandButton = mNotificationHeaderLowPriority.findViewById( 376 com.android.internal.R.id.expand_button); 377 expandButton.setVisibility(VISIBLE); 378 mNotificationHeaderLowPriority.setOnClickListener(mHeaderClickListener); 379 mNotificationHeaderWrapperLowPriority = NotificationViewWrapper.wrap(getContext(), 380 mNotificationHeaderLowPriority, mContainingNotification); 381 addView(mNotificationHeaderLowPriority, 0); 382 invalidate(); 383 } else { 384 header.reapply(getContext(), mNotificationHeaderLowPriority); 385 } 386 mNotificationHeaderWrapperLowPriority.onContentUpdated(mContainingNotification); 387 resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, calculateDesiredHeader()); 388 } else { 389 removeView(mNotificationHeaderLowPriority); 390 mNotificationHeaderLowPriority = null; 391 mNotificationHeaderWrapperLowPriority = null; 392 } 393 } 394 395 public void updateChildrenHeaderAppearance() { 396 mHeaderUtil.updateChildrenHeaderAppearance(); 397 } 398 399 public void updateGroupOverflow() { 400 int childCount = mChildren.size(); 401 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* likeCollapsed */); 402 if (childCount > maxAllowedVisibleChildren) { 403 int number = childCount - maxAllowedVisibleChildren; 404 mOverflowNumber = mHybridGroupManager.bindOverflowNumber(mOverflowNumber, number); 405 if (mContainingNotification.isShowingAmbient()) { 406 ExpandableNotificationRow overflowView = mChildren.get(0); 407 HybridNotificationView ambientSingleLineView = overflowView == null ? null 408 : overflowView.getAmbientSingleLineView(); 409 if (ambientSingleLineView != null) { 410 mHybridGroupManager.bindOverflowNumberAmbient( 411 ambientSingleLineView.getTitleView(), 412 mContainingNotification.getStatusBarNotification().getNotification(), 413 number); 414 } 415 } 416 if (mGroupOverFlowState == null) { 417 mGroupOverFlowState = new ViewState(); 418 mNeverAppliedGroupState = true; 419 } 420 } else if (mOverflowNumber != null) { 421 removeView(mOverflowNumber); 422 if (isShown() && isAttachedToWindow()) { 423 final View removedOverflowNumber = mOverflowNumber; 424 addTransientView(removedOverflowNumber, getTransientViewCount()); 425 CrossFadeHelper.fadeOut(removedOverflowNumber, new Runnable() { 426 @Override 427 public void run() { 428 removeTransientView(removedOverflowNumber); 429 } 430 }); 431 } 432 mOverflowNumber = null; 433 mGroupOverFlowState = null; 434 } 435 } 436 437 @Override 438 protected void onConfigurationChanged(Configuration newConfig) { 439 super.onConfigurationChanged(newConfig); 440 updateGroupOverflow(); 441 } 442 443 private View inflateDivider() { 444 return LayoutInflater.from(mContext).inflate( 445 R.layout.notification_children_divider, this, false); 446 } 447 448 public List<ExpandableNotificationRow> getNotificationChildren() { 449 return mChildren; 450 } 451 452 /** 453 * Apply the order given in the list to the children. 454 * 455 * @param childOrder the new list order 456 * @param visualStabilityManager 457 * @param callback 458 * @return whether the list order has changed 459 */ 460 public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder, 461 VisualStabilityManager visualStabilityManager, 462 VisualStabilityManager.Callback callback) { 463 if (childOrder == null) { 464 return false; 465 } 466 boolean result = false; 467 for (int i = 0; i < mChildren.size() && i < childOrder.size(); i++) { 468 ExpandableNotificationRow child = mChildren.get(i); 469 ExpandableNotificationRow desiredChild = childOrder.get(i); 470 if (child != desiredChild) { 471 if (visualStabilityManager.canReorderNotification(desiredChild)) { 472 mChildren.remove(desiredChild); 473 mChildren.add(i, desiredChild); 474 result = true; 475 } else { 476 visualStabilityManager.addReorderingAllowedCallback(callback); 477 } 478 } 479 } 480 updateExpansionStates(); 481 return result; 482 } 483 484 private void updateExpansionStates() { 485 if (mChildrenExpanded || mUserLocked) { 486 // we don't modify it the group is expanded or if we are expanding it 487 return; 488 } 489 int size = mChildren.size(); 490 for (int i = 0; i < size; i++) { 491 ExpandableNotificationRow child = mChildren.get(i); 492 child.setSystemChildExpanded(i == 0 && size == 1); 493 } 494 } 495 496 /** 497 * 498 * @return the intrinsic size of this children container, i.e the natural fully expanded state 499 */ 500 public int getIntrinsicHeight() { 501 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(); 502 return getIntrinsicHeight(maxAllowedVisibleChildren); 503 } 504 505 /** 506 * @return the intrinsic height with a number of children given 507 * in @param maxAllowedVisibleChildren 508 */ 509 private int getIntrinsicHeight(float maxAllowedVisibleChildren) { 510 if (showingAsLowPriority()) { 511 return mNotificationHeaderLowPriority.getHeight(); 512 } 513 int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation; 514 int visibleChildren = 0; 515 int childCount = mChildren.size(); 516 boolean firstChild = true; 517 float expandFactor = 0; 518 if (mUserLocked) { 519 expandFactor = getGroupExpandFraction(); 520 } 521 boolean childrenExpanded = mChildrenExpanded || mContainingNotification.isShowingAmbient(); 522 for (int i = 0; i < childCount; i++) { 523 if (visibleChildren >= maxAllowedVisibleChildren) { 524 break; 525 } 526 if (!firstChild) { 527 if (mUserLocked) { 528 intrinsicHeight += NotificationUtils.interpolate(mChildPadding, mDividerHeight, 529 expandFactor); 530 } else { 531 intrinsicHeight += childrenExpanded ? mDividerHeight : mChildPadding; 532 } 533 } else { 534 if (mUserLocked) { 535 intrinsicHeight += NotificationUtils.interpolate( 536 0, 537 mNotificatonTopPadding + mDividerHeight, 538 expandFactor); 539 } else { 540 intrinsicHeight += childrenExpanded 541 ? mNotificatonTopPadding + mDividerHeight 542 : 0; 543 } 544 firstChild = false; 545 } 546 ExpandableNotificationRow child = mChildren.get(i); 547 intrinsicHeight += child.getIntrinsicHeight(); 548 visibleChildren++; 549 } 550 if (mUserLocked) { 551 intrinsicHeight += NotificationUtils.interpolate(mCollapsedBottompadding, 0.0f, 552 expandFactor); 553 } else if (!childrenExpanded) { 554 intrinsicHeight += mCollapsedBottompadding; 555 } 556 return intrinsicHeight; 557 } 558 559 /** 560 * Update the state of all its children based on a linear layout algorithm. 561 * @param resultState the state to update 562 * @param parentState the state of the parent 563 * @param ambientState 564 */ 565 public void getState(StackScrollState resultState, ExpandableViewState parentState, 566 AmbientState ambientState) { 567 int childCount = mChildren.size(); 568 int yPosition = mNotificationHeaderMargin + mCurrentHeaderTranslation; 569 boolean firstChild = true; 570 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(); 571 int lastVisibleIndex = maxAllowedVisibleChildren - 1; 572 int firstOverflowIndex = lastVisibleIndex + 1; 573 float expandFactor = 0; 574 boolean expandingToExpandedGroup = mUserLocked && !showingAsLowPriority(); 575 if (mUserLocked) { 576 expandFactor = getGroupExpandFraction(); 577 firstOverflowIndex = getMaxAllowedVisibleChildren(true /* likeCollapsed */); 578 } 579 580 boolean childrenExpandedAndNotAnimating = mChildrenExpanded 581 && !mContainingNotification.isGroupExpansionChanging(); 582 int launchTransitionCompensation = 0; 583 for (int i = 0; i < childCount; i++) { 584 ExpandableNotificationRow child = mChildren.get(i); 585 if (!firstChild) { 586 if (expandingToExpandedGroup) { 587 yPosition += NotificationUtils.interpolate(mChildPadding, mDividerHeight, 588 expandFactor); 589 } else { 590 yPosition += mChildrenExpanded ? mDividerHeight : mChildPadding; 591 } 592 } else { 593 if (expandingToExpandedGroup) { 594 yPosition += NotificationUtils.interpolate( 595 0, 596 mNotificatonTopPadding + mDividerHeight, 597 expandFactor); 598 } else { 599 yPosition += mChildrenExpanded ? mNotificatonTopPadding + mDividerHeight : 0; 600 } 601 firstChild = false; 602 } 603 604 ExpandableViewState childState = resultState.getViewStateForView(child); 605 int intrinsicHeight = child.getIntrinsicHeight(); 606 childState.height = intrinsicHeight; 607 childState.yTranslation = yPosition + launchTransitionCompensation; 608 childState.hidden = false; 609 // When the group is expanded, the children cast the shadows rather than the parent 610 // so use the parent's elevation here. 611 childState.zTranslation = 612 (childrenExpandedAndNotAnimating && mEnableShadowOnChildNotifications) 613 ? parentState.zTranslation 614 : 0; 615 childState.dimmed = parentState.dimmed; 616 childState.dark = parentState.dark; 617 childState.hideSensitive = parentState.hideSensitive; 618 childState.belowSpeedBump = parentState.belowSpeedBump; 619 childState.clipTopAmount = 0; 620 childState.alpha = 0; 621 if (i < firstOverflowIndex) { 622 childState.alpha = showingAsLowPriority() ? expandFactor : 1.0f; 623 } else if (expandFactor == 1.0f && i <= lastVisibleIndex) { 624 childState.alpha = (mActualHeight - childState.yTranslation) / childState.height; 625 childState.alpha = Math.max(0.0f, Math.min(1.0f, childState.alpha)); 626 } 627 childState.location = parentState.location; 628 childState.inShelf = parentState.inShelf; 629 yPosition += intrinsicHeight; 630 if (child.isExpandAnimationRunning()) { 631 launchTransitionCompensation = -ambientState.getExpandAnimationTopChange(); 632 } 633 634 } 635 if (mOverflowNumber != null) { 636 ExpandableNotificationRow overflowView = mChildren.get(Math.min( 637 getMaxAllowedVisibleChildren(true /* likeCollapsed */), childCount) - 1); 638 mGroupOverFlowState.copyFrom(resultState.getViewStateForView(overflowView)); 639 640 if (mContainingNotification.isShowingAmbient()) { 641 mGroupOverFlowState.alpha = 0.0f; 642 } else if (!mChildrenExpanded) { 643 HybridNotificationView alignView = overflowView.getSingleLineView(); 644 if (alignView != null) { 645 View mirrorView = alignView.getTextView(); 646 if (mirrorView.getVisibility() == GONE) { 647 mirrorView = alignView.getTitleView(); 648 } 649 if (mirrorView.getVisibility() == GONE) { 650 mirrorView = alignView; 651 } 652 mGroupOverFlowState.alpha = mirrorView.getAlpha(); 653 mGroupOverFlowState.yTranslation += NotificationUtils.getRelativeYOffset( 654 mirrorView, overflowView); 655 } 656 } else { 657 mGroupOverFlowState.yTranslation += mNotificationHeaderMargin; 658 mGroupOverFlowState.alpha = 0.0f; 659 } 660 } 661 if (mNotificationHeader != null) { 662 if (mHeaderViewState == null) { 663 mHeaderViewState = new ViewState(); 664 } 665 mHeaderViewState.initFrom(mNotificationHeader); 666 mHeaderViewState.zTranslation = childrenExpandedAndNotAnimating 667 ? parentState.zTranslation 668 : 0; 669 mHeaderViewState.yTranslation = mCurrentHeaderTranslation; 670 mHeaderViewState.alpha = mHeaderVisibleAmount; 671 // The hiding is done automatically by the alpha, otherwise we'll pick it up again 672 // in the next frame with the initFrom call above and have an invisible header 673 mHeaderViewState.hidden = false; 674 } 675 } 676 677 /** 678 * When moving into the bottom stack, the bottom visible child in an expanded group adjusts its 679 * height, children in the group after this are gone. 680 * 681 * @param child the child who's height to adjust. 682 * @param parentHeight the height of the parent. 683 * @param childState the state to update. 684 * @param yPosition the yPosition of the view. 685 * @return true if children after this one should be hidden. 686 */ 687 private boolean updateChildStateForExpandedGroup(ExpandableNotificationRow child, 688 int parentHeight, ExpandableViewState childState, int yPosition) { 689 final int top = yPosition + child.getClipTopAmount(); 690 final int intrinsicHeight = child.getIntrinsicHeight(); 691 final int bottom = top + intrinsicHeight; 692 int newHeight = intrinsicHeight; 693 if (bottom >= parentHeight) { 694 // Child is either clipped or gone 695 newHeight = Math.max((parentHeight - top), 0); 696 } 697 childState.hidden = newHeight == 0; 698 childState.height = newHeight; 699 return childState.height != intrinsicHeight && !childState.hidden; 700 } 701 702 private int getMaxAllowedVisibleChildren() { 703 return getMaxAllowedVisibleChildren(false /* likeCollapsed */); 704 } 705 706 private int getMaxAllowedVisibleChildren(boolean likeCollapsed) { 707 if (mContainingNotification.isShowingAmbient()) { 708 return NUMBER_OF_CHILDREN_WHEN_AMBIENT; 709 } 710 if (!likeCollapsed && (mChildrenExpanded || mContainingNotification.isUserLocked())) { 711 return NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED; 712 } 713 if (mIsLowPriority || !mContainingNotification.isOnKeyguard() 714 && (mContainingNotification.isExpanded() || mContainingNotification.isHeadsUp())) { 715 return NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED; 716 } 717 return NUMBER_OF_CHILDREN_WHEN_COLLAPSED; 718 } 719 720 public void applyState(StackScrollState state) { 721 int childCount = mChildren.size(); 722 ViewState tmpState = new ViewState(); 723 float expandFraction = 0.0f; 724 if (mUserLocked) { 725 expandFraction = getGroupExpandFraction(); 726 } 727 final boolean dividersVisible = mUserLocked && !showingAsLowPriority() 728 || (mChildrenExpanded && mShowDividersWhenExpanded) 729 || (mContainingNotification.isGroupExpansionChanging() 730 && !mHideDividersDuringExpand); 731 for (int i = 0; i < childCount; i++) { 732 ExpandableNotificationRow child = mChildren.get(i); 733 ExpandableViewState viewState = state.getViewStateForView(child); 734 viewState.applyToView(child); 735 736 // layout the divider 737 View divider = mDividers.get(i); 738 tmpState.initFrom(divider); 739 tmpState.yTranslation = viewState.yTranslation - mDividerHeight; 740 float alpha = mChildrenExpanded && viewState.alpha != 0 ? mDividerAlpha : 0; 741 if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) { 742 alpha = NotificationUtils.interpolate(0, 0.5f, 743 Math.min(viewState.alpha, expandFraction)); 744 } 745 tmpState.hidden = !dividersVisible; 746 tmpState.alpha = alpha; 747 tmpState.applyToView(divider); 748 // There is no fake shadow to be drawn on the children 749 child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0); 750 } 751 if (mGroupOverFlowState != null) { 752 mGroupOverFlowState.applyToView(mOverflowNumber); 753 mNeverAppliedGroupState = false; 754 } 755 if (mHeaderViewState != null) { 756 mHeaderViewState.applyToView(mNotificationHeader); 757 } 758 updateChildrenClipping(); 759 } 760 761 private void updateChildrenClipping() { 762 if (mContainingNotification.hasExpandingChild()) { 763 return; 764 } 765 int childCount = mChildren.size(); 766 int layoutEnd = mContainingNotification.getActualHeight() - mClipBottomAmount; 767 for (int i = 0; i < childCount; i++) { 768 ExpandableNotificationRow child = mChildren.get(i); 769 if (child.getVisibility() == GONE) { 770 continue; 771 } 772 float childTop = child.getTranslationY(); 773 float childBottom = childTop + child.getActualHeight(); 774 boolean visible = true; 775 int clipBottomAmount = 0; 776 if (childTop > layoutEnd) { 777 visible = false; 778 } else if (childBottom > layoutEnd) { 779 clipBottomAmount = (int) (childBottom - layoutEnd); 780 } 781 782 boolean isVisible = child.getVisibility() == VISIBLE; 783 if (visible != isVisible) { 784 child.setVisibility(visible ? VISIBLE : INVISIBLE); 785 } 786 787 child.setClipBottomAmount(clipBottomAmount); 788 } 789 } 790 791 /** 792 * This is called when the children expansion has changed and positions the children properly 793 * for an appear animation. 794 * 795 * @param state the new state we animate to 796 */ 797 public void prepareExpansionChanged(StackScrollState state) { 798 // TODO: do something that makes sense, like placing the invisible views correctly 799 return; 800 } 801 802 public void startAnimationToState(StackScrollState state, AnimationProperties properties) { 803 int childCount = mChildren.size(); 804 ViewState tmpState = new ViewState(); 805 float expandFraction = getGroupExpandFraction(); 806 final boolean dividersVisible = mUserLocked && !showingAsLowPriority() 807 || (mChildrenExpanded && mShowDividersWhenExpanded) 808 || (mContainingNotification.isGroupExpansionChanging() 809 && !mHideDividersDuringExpand); 810 for (int i = childCount - 1; i >= 0; i--) { 811 ExpandableNotificationRow child = mChildren.get(i); 812 ExpandableViewState viewState = state.getViewStateForView(child); 813 viewState.animateTo(child, properties); 814 815 // layout the divider 816 View divider = mDividers.get(i); 817 tmpState.initFrom(divider); 818 tmpState.yTranslation = viewState.yTranslation - mDividerHeight; 819 float alpha = mChildrenExpanded && viewState.alpha != 0 ? 0.5f : 0; 820 if (mUserLocked && !showingAsLowPriority() && viewState.alpha != 0) { 821 alpha = NotificationUtils.interpolate(0, 0.5f, 822 Math.min(viewState.alpha, expandFraction)); 823 } 824 tmpState.hidden = !dividersVisible; 825 tmpState.alpha = alpha; 826 tmpState.animateTo(divider, properties); 827 // There is no fake shadow to be drawn on the children 828 child.setFakeShadowIntensity(0.0f, 0.0f, 0, 0); 829 } 830 if (mOverflowNumber != null) { 831 if (mNeverAppliedGroupState) { 832 float alpha = mGroupOverFlowState.alpha; 833 mGroupOverFlowState.alpha = 0; 834 mGroupOverFlowState.applyToView(mOverflowNumber); 835 mGroupOverFlowState.alpha = alpha; 836 mNeverAppliedGroupState = false; 837 } 838 mGroupOverFlowState.animateTo(mOverflowNumber, properties); 839 } 840 if (mNotificationHeader != null) { 841 mHeaderViewState.applyToView(mNotificationHeader); 842 } 843 updateChildrenClipping(); 844 } 845 846 public ExpandableNotificationRow getViewAtPosition(float y) { 847 // find the view under the pointer, accounting for GONE views 848 final int count = mChildren.size(); 849 for (int childIdx = 0; childIdx < count; childIdx++) { 850 ExpandableNotificationRow slidingChild = mChildren.get(childIdx); 851 float childTop = slidingChild.getTranslationY(); 852 float top = childTop + slidingChild.getClipTopAmount(); 853 float bottom = childTop + slidingChild.getActualHeight(); 854 if (y >= top && y <= bottom) { 855 return slidingChild; 856 } 857 } 858 return null; 859 } 860 861 public void setChildrenExpanded(boolean childrenExpanded) { 862 mChildrenExpanded = childrenExpanded; 863 updateExpansionStates(); 864 if (mNotificationHeader != null) { 865 mNotificationHeader.setExpanded(childrenExpanded); 866 } 867 final int count = mChildren.size(); 868 for (int childIdx = 0; childIdx < count; childIdx++) { 869 ExpandableNotificationRow child = mChildren.get(childIdx); 870 child.setChildrenExpanded(childrenExpanded, false); 871 } 872 } 873 874 public void setContainingNotification(ExpandableNotificationRow parent) { 875 mContainingNotification = parent; 876 mHeaderUtil = new NotificationHeaderUtil(mContainingNotification); 877 } 878 879 public ExpandableNotificationRow getContainingNotification() { 880 return mContainingNotification; 881 } 882 883 public NotificationHeaderView getHeaderView() { 884 return mNotificationHeader; 885 } 886 887 public NotificationHeaderView getLowPriorityHeaderView() { 888 return mNotificationHeaderLowPriority; 889 } 890 891 @VisibleForTesting 892 public ViewGroup getCurrentHeaderView() { 893 return mCurrentHeader; 894 } 895 896 public void notifyShowAmbientChanged() { 897 updateHeaderVisibility(false); 898 updateGroupOverflow(); 899 } 900 901 private void updateHeaderVisibility(boolean animate) { 902 ViewGroup desiredHeader; 903 ViewGroup currentHeader = mCurrentHeader; 904 desiredHeader = calculateDesiredHeader(); 905 906 if (currentHeader == desiredHeader) { 907 return; 908 } 909 if (desiredHeader == mNotificationHeaderAmbient 910 || currentHeader == mNotificationHeaderAmbient) { 911 animate = false; 912 } 913 914 if (animate) { 915 if (desiredHeader != null && currentHeader != null) { 916 currentHeader.setVisibility(VISIBLE); 917 desiredHeader.setVisibility(VISIBLE); 918 NotificationViewWrapper visibleWrapper = getWrapperForView(desiredHeader); 919 NotificationViewWrapper hiddenWrapper = getWrapperForView(currentHeader); 920 visibleWrapper.transformFrom(hiddenWrapper); 921 hiddenWrapper.transformTo(visibleWrapper, () -> updateHeaderVisibility(false)); 922 startChildAlphaAnimations(desiredHeader == mNotificationHeader); 923 } else { 924 animate = false; 925 } 926 } 927 if (!animate) { 928 if (desiredHeader != null) { 929 getWrapperForView(desiredHeader).setVisible(true); 930 desiredHeader.setVisibility(VISIBLE); 931 } 932 if (currentHeader != null) { 933 // Wrapper can be null if we were a low priority notification 934 // and just destroyed it by calling setIsLowPriority(false) 935 NotificationViewWrapper wrapper = getWrapperForView(currentHeader); 936 if (wrapper != null) { 937 wrapper.setVisible(false); 938 } 939 currentHeader.setVisibility(INVISIBLE); 940 } 941 } 942 943 resetHeaderVisibilityIfNeeded(mNotificationHeader, desiredHeader); 944 resetHeaderVisibilityIfNeeded(mNotificationHeaderAmbient, desiredHeader); 945 resetHeaderVisibilityIfNeeded(mNotificationHeaderLowPriority, desiredHeader); 946 947 mCurrentHeader = desiredHeader; 948 } 949 950 private void resetHeaderVisibilityIfNeeded(View header, View desiredHeader) { 951 if (header == null) { 952 return; 953 } 954 if (header != mCurrentHeader && header != desiredHeader) { 955 getWrapperForView(header).setVisible(false); 956 header.setVisibility(INVISIBLE); 957 } 958 if (header == desiredHeader && header.getVisibility() != VISIBLE) { 959 getWrapperForView(header).setVisible(true); 960 header.setVisibility(VISIBLE); 961 } 962 } 963 964 private ViewGroup calculateDesiredHeader() { 965 ViewGroup desiredHeader; 966 if (mContainingNotification.isShowingAmbient()) { 967 desiredHeader = mNotificationHeaderAmbient; 968 } else if (showingAsLowPriority()) { 969 desiredHeader = mNotificationHeaderLowPriority; 970 } else { 971 desiredHeader = mNotificationHeader; 972 } 973 return desiredHeader; 974 } 975 976 private void startChildAlphaAnimations(boolean toVisible) { 977 float target = toVisible ? 1.0f : 0.0f; 978 float start = 1.0f - target; 979 int childCount = mChildren.size(); 980 for (int i = 0; i < childCount; i++) { 981 if (i >= NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED) { 982 break; 983 } 984 ExpandableNotificationRow child = mChildren.get(i); 985 child.setAlpha(start); 986 ViewState viewState = new ViewState(); 987 viewState.initFrom(child); 988 viewState.alpha = target; 989 ALPHA_FADE_IN.setDelay(i * 50); 990 viewState.animateTo(child, ALPHA_FADE_IN); 991 } 992 } 993 994 995 private void updateHeaderTransformation() { 996 if (mUserLocked && showingAsLowPriority()) { 997 float fraction = getGroupExpandFraction(); 998 mNotificationHeaderWrapper.transformFrom(mNotificationHeaderWrapperLowPriority, 999 fraction); 1000 mNotificationHeader.setVisibility(VISIBLE); 1001 mNotificationHeaderWrapperLowPriority.transformTo(mNotificationHeaderWrapper, 1002 fraction); 1003 } 1004 1005 } 1006 1007 private NotificationViewWrapper getWrapperForView(View visibleHeader) { 1008 if (visibleHeader == mNotificationHeader) { 1009 return mNotificationHeaderWrapper; 1010 } 1011 if (visibleHeader == mNotificationHeaderAmbient) { 1012 return mNotificationHeaderWrapperAmbient; 1013 } 1014 return mNotificationHeaderWrapperLowPriority; 1015 } 1016 1017 /** 1018 * Called when a groups expansion changes to adjust the background of the header view. 1019 * 1020 * @param expanded whether the group is expanded. 1021 */ 1022 public void updateHeaderForExpansion(boolean expanded) { 1023 if (mNotificationHeader != null) { 1024 if (expanded) { 1025 ColorDrawable cd = new ColorDrawable(); 1026 cd.setColor(mContainingNotification.calculateBgColor()); 1027 mNotificationHeader.setHeaderBackgroundDrawable(cd); 1028 } else { 1029 mNotificationHeader.setHeaderBackgroundDrawable(null); 1030 } 1031 } 1032 } 1033 1034 public int getMaxContentHeight() { 1035 if (showingAsLowPriority()) { 1036 return getMinHeight(NUMBER_OF_CHILDREN_WHEN_SYSTEM_EXPANDED, true 1037 /* likeHighPriority */); 1038 } 1039 int maxContentHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation 1040 + mNotificatonTopPadding; 1041 int visibleChildren = 0; 1042 int childCount = mChildren.size(); 1043 for (int i = 0; i < childCount; i++) { 1044 if (visibleChildren >= NUMBER_OF_CHILDREN_WHEN_CHILDREN_EXPANDED) { 1045 break; 1046 } 1047 ExpandableNotificationRow child = mChildren.get(i); 1048 float childHeight = child.isExpanded(true /* allowOnKeyguard */) 1049 ? child.getMaxExpandHeight() 1050 : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */); 1051 maxContentHeight += childHeight; 1052 visibleChildren++; 1053 } 1054 if (visibleChildren > 0) { 1055 maxContentHeight += visibleChildren * mDividerHeight; 1056 } 1057 return maxContentHeight; 1058 } 1059 1060 public void setActualHeight(int actualHeight) { 1061 if (!mUserLocked) { 1062 return; 1063 } 1064 mActualHeight = actualHeight; 1065 float fraction = getGroupExpandFraction(); 1066 boolean showingLowPriority = showingAsLowPriority(); 1067 updateHeaderTransformation(); 1068 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */); 1069 int childCount = mChildren.size(); 1070 for (int i = 0; i < childCount; i++) { 1071 ExpandableNotificationRow child = mChildren.get(i); 1072 float childHeight; 1073 if (showingLowPriority) { 1074 childHeight = child.getShowingLayout().getMinHeight(false /* likeGroupExpanded */); 1075 } else if (child.isExpanded(true /* allowOnKeyguard */)) { 1076 childHeight = child.getMaxExpandHeight(); 1077 } else { 1078 childHeight = child.getShowingLayout().getMinHeight( 1079 true /* likeGroupExpanded */); 1080 } 1081 if (i < maxAllowedVisibleChildren) { 1082 float singleLineHeight = child.getShowingLayout().getMinHeight( 1083 false /* likeGroupExpanded */); 1084 child.setActualHeight((int) NotificationUtils.interpolate(singleLineHeight, 1085 childHeight, fraction), false); 1086 } else { 1087 child.setActualHeight((int) childHeight, false); 1088 } 1089 } 1090 } 1091 1092 public float getGroupExpandFraction() { 1093 int visibleChildrenExpandedHeight = showingAsLowPriority() ? getMaxContentHeight() 1094 : getVisibleChildrenExpandHeight(); 1095 int minExpandHeight = getCollapsedHeight(); 1096 float factor = (mActualHeight - minExpandHeight) 1097 / (float) (visibleChildrenExpandedHeight - minExpandHeight); 1098 return Math.max(0.0f, Math.min(1.0f, factor)); 1099 } 1100 1101 private int getVisibleChildrenExpandHeight() { 1102 int intrinsicHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation 1103 + mNotificatonTopPadding + mDividerHeight; 1104 int visibleChildren = 0; 1105 int childCount = mChildren.size(); 1106 int maxAllowedVisibleChildren = getMaxAllowedVisibleChildren(true /* forceCollapsed */); 1107 for (int i = 0; i < childCount; i++) { 1108 if (visibleChildren >= maxAllowedVisibleChildren) { 1109 break; 1110 } 1111 ExpandableNotificationRow child = mChildren.get(i); 1112 float childHeight = child.isExpanded(true /* allowOnKeyguard */) 1113 ? child.getMaxExpandHeight() 1114 : child.getShowingLayout().getMinHeight(true /* likeGroupExpanded */); 1115 intrinsicHeight += childHeight; 1116 visibleChildren++; 1117 } 1118 return intrinsicHeight; 1119 } 1120 1121 public int getMinHeight() { 1122 return getMinHeight(mContainingNotification.isShowingAmbient() 1123 ? NUMBER_OF_CHILDREN_WHEN_AMBIENT 1124 : NUMBER_OF_CHILDREN_WHEN_COLLAPSED, false /* likeHighPriority */); 1125 } 1126 1127 public int getCollapsedHeight() { 1128 return getMinHeight(getMaxAllowedVisibleChildren(true /* forceCollapsed */), 1129 false /* likeHighPriority */); 1130 } 1131 1132 /** 1133 * Get the minimum Height for this group. 1134 * 1135 * @param maxAllowedVisibleChildren the number of children that should be visible 1136 * @param likeHighPriority if the height should be calculated as if it were not low priority 1137 */ 1138 private int getMinHeight(int maxAllowedVisibleChildren, boolean likeHighPriority) { 1139 if (!likeHighPriority && showingAsLowPriority()) { 1140 return mNotificationHeaderLowPriority.getHeight(); 1141 } 1142 int minExpandHeight = mNotificationHeaderMargin + mCurrentHeaderTranslation; 1143 int visibleChildren = 0; 1144 boolean firstChild = true; 1145 int childCount = mChildren.size(); 1146 for (int i = 0; i < childCount; i++) { 1147 if (visibleChildren >= maxAllowedVisibleChildren) { 1148 break; 1149 } 1150 if (!firstChild) { 1151 minExpandHeight += mChildPadding; 1152 } else { 1153 firstChild = false; 1154 } 1155 ExpandableNotificationRow child = mChildren.get(i); 1156 minExpandHeight += child.getSingleLineView().getHeight(); 1157 visibleChildren++; 1158 } 1159 minExpandHeight += mCollapsedBottompadding; 1160 return minExpandHeight; 1161 } 1162 1163 public boolean showingAsLowPriority() { 1164 return mIsLowPriority && !mContainingNotification.isExpanded(); 1165 } 1166 1167 public void setDark(boolean dark, boolean fade, long delay) { 1168 if (mOverflowNumber != null) { 1169 mHybridGroupManager.setOverflowNumberDark(mOverflowNumber, dark, fade, delay); 1170 } 1171 } 1172 1173 public void reInflateViews(OnClickListener listener, StatusBarNotification notification) { 1174 if (mNotificationHeader != null) { 1175 removeView(mNotificationHeader); 1176 mNotificationHeader = null; 1177 } 1178 if (mNotificationHeaderLowPriority != null) { 1179 removeView(mNotificationHeaderLowPriority); 1180 mNotificationHeaderLowPriority = null; 1181 } 1182 if (mNotificationHeaderAmbient != null) { 1183 removeView(mNotificationHeaderAmbient); 1184 mNotificationHeaderAmbient = null; 1185 } 1186 recreateNotificationHeader(listener); 1187 initDimens(); 1188 for (int i = 0; i < mDividers.size(); i++) { 1189 View prevDivider = mDividers.get(i); 1190 int index = indexOfChild(prevDivider); 1191 removeView(prevDivider); 1192 View divider = inflateDivider(); 1193 addView(divider, index); 1194 mDividers.set(i, divider); 1195 } 1196 removeView(mOverflowNumber); 1197 mOverflowNumber = null; 1198 mGroupOverFlowState = null; 1199 updateGroupOverflow(); 1200 } 1201 1202 public void setUserLocked(boolean userLocked) { 1203 mUserLocked = userLocked; 1204 if (!mUserLocked) { 1205 updateHeaderVisibility(false /* animate */); 1206 } 1207 int childCount = mChildren.size(); 1208 for (int i = 0; i < childCount; i++) { 1209 ExpandableNotificationRow child = mChildren.get(i); 1210 child.setUserLocked(userLocked && !showingAsLowPriority()); 1211 } 1212 } 1213 1214 public void onNotificationUpdated() { 1215 mHybridGroupManager.setOverflowNumberColor(mOverflowNumber, 1216 mContainingNotification.getNotificationColor(), 1217 mContainingNotification.getNotificationColorAmbient()); 1218 } 1219 1220 public int getPositionInLinearLayout(View childInGroup) { 1221 int position = mNotificationHeaderMargin + mCurrentHeaderTranslation 1222 + mNotificatonTopPadding; 1223 1224 for (int i = 0; i < mChildren.size(); i++) { 1225 ExpandableNotificationRow child = mChildren.get(i); 1226 boolean notGone = child.getVisibility() != View.GONE; 1227 if (notGone) { 1228 position += mDividerHeight; 1229 } 1230 if (child == childInGroup) { 1231 return position; 1232 } 1233 if (notGone) { 1234 position += child.getIntrinsicHeight(); 1235 } 1236 } 1237 return 0; 1238 } 1239 1240 public void setIconsVisible(boolean iconsVisible) { 1241 if (mNotificationHeaderWrapper != null) { 1242 NotificationHeaderView header = mNotificationHeaderWrapper.getNotificationHeader(); 1243 if (header != null) { 1244 header.getIcon().setForceHidden(!iconsVisible); 1245 } 1246 } 1247 if (mNotificationHeaderWrapperLowPriority != null) { 1248 NotificationHeaderView header 1249 = mNotificationHeaderWrapperLowPriority.getNotificationHeader(); 1250 if (header != null) { 1251 header.getIcon().setForceHidden(!iconsVisible); 1252 } 1253 } 1254 } 1255 1256 public void setClipBottomAmount(int clipBottomAmount) { 1257 mClipBottomAmount = clipBottomAmount; 1258 updateChildrenClipping(); 1259 } 1260 1261 public void setIsLowPriority(boolean isLowPriority) { 1262 mIsLowPriority = isLowPriority; 1263 if (mContainingNotification != null) { /* we're not yet set up yet otherwise */ 1264 recreateLowPriorityHeader(null /* existingBuilder */); 1265 updateHeaderVisibility(false /* animate */); 1266 } 1267 if (mUserLocked) { 1268 setUserLocked(mUserLocked); 1269 } 1270 } 1271 1272 public NotificationHeaderView getVisibleHeader() { 1273 NotificationHeaderView header = mNotificationHeader; 1274 if (showingAsLowPriority()) { 1275 header = mNotificationHeaderLowPriority; 1276 } 1277 return header; 1278 } 1279 1280 public void onExpansionChanged() { 1281 if (mIsLowPriority) { 1282 if (mUserLocked) { 1283 setUserLocked(mUserLocked); 1284 } 1285 updateHeaderVisibility(true /* animate */); 1286 } 1287 } 1288 1289 public float getIncreasedPaddingAmount() { 1290 if (showingAsLowPriority()) { 1291 return 0.0f; 1292 } 1293 return getGroupExpandFraction(); 1294 } 1295 1296 @VisibleForTesting 1297 public boolean isUserLocked() { 1298 return mUserLocked; 1299 } 1300 1301 public void setCurrentBottomRoundness(float currentBottomRoundness) { 1302 boolean last = true; 1303 for (int i = mChildren.size() - 1; i >= 0; i--) { 1304 ExpandableNotificationRow child = mChildren.get(i); 1305 if (child.getVisibility() == View.GONE) { 1306 continue; 1307 } 1308 float bottomRoundness = last ? currentBottomRoundness : 0.0f; 1309 child.setBottomRoundness(bottomRoundness, isShown() /* animate */); 1310 last = false; 1311 } 1312 } 1313 1314 public void setHeaderVisibleAmount(float headerVisibleAmount) { 1315 mHeaderVisibleAmount = headerVisibleAmount; 1316 mCurrentHeaderTranslation = (int) ((1.0f - headerVisibleAmount) * mTranslationForHeader); 1317 } 1318 } 1319