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