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