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