ExpandableNotificationRow.java revision 6baed9e3a272b09e87f15801a389d7714d0b051f
1/* 2 * Copyright (C) 2013 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.systemui.statusbar; 18 19import android.animation.Animator; 20import android.animation.AnimatorListenerAdapter; 21import android.animation.AnimatorSet; 22import android.animation.ObjectAnimator; 23import android.animation.ValueAnimator; 24import android.animation.ValueAnimator.AnimatorUpdateListener; 25import android.content.Context; 26import android.graphics.drawable.AnimatedVectorDrawable; 27import android.graphics.drawable.AnimationDrawable; 28import android.graphics.drawable.ColorDrawable; 29import android.graphics.drawable.Drawable; 30import android.os.Build; 31import android.service.notification.StatusBarNotification; 32import android.util.AttributeSet; 33import android.util.FloatProperty; 34import android.util.Property; 35import android.view.LayoutInflater; 36import android.view.MotionEvent; 37import android.view.NotificationHeaderView; 38import android.view.View; 39import android.view.ViewStub; 40import android.view.accessibility.AccessibilityEvent; 41import android.widget.Chronometer; 42import android.widget.ImageView; 43 44import com.android.internal.util.NotificationColorUtil; 45import com.android.systemui.R; 46import com.android.systemui.classifier.FalsingManager; 47import com.android.systemui.statusbar.notification.HybridNotificationView; 48import com.android.systemui.statusbar.phone.NotificationGroupManager; 49import com.android.systemui.statusbar.policy.HeadsUpManager; 50import com.android.systemui.statusbar.stack.NotificationChildrenContainer; 51import com.android.systemui.statusbar.stack.StackScrollState; 52import com.android.systemui.statusbar.stack.StackStateAnimator; 53import com.android.systemui.statusbar.stack.StackViewState; 54 55import java.util.ArrayList; 56import java.util.List; 57 58public class ExpandableNotificationRow extends ActivatableNotificationView { 59 60 private static final int DEFAULT_DIVIDER_ALPHA = 0x29; 61 private static final int COLORED_DIVIDER_ALPHA = 0x7B; 62 private int mNotificationMinHeightLegacy; 63 private int mMaxHeadsUpHeightLegacy; 64 private int mMaxHeadsUpHeight; 65 private int mNotificationMinHeight; 66 private int mNotificationMaxHeight; 67 private int mIncreasedPaddingBetweenElements; 68 69 /** Does this row contain layouts that can adapt to row expansion */ 70 private boolean mExpandable; 71 /** Has the user actively changed the expansion state of this row */ 72 private boolean mHasUserChangedExpansion; 73 /** If {@link #mHasUserChangedExpansion}, has the user expanded this row */ 74 private boolean mUserExpanded; 75 76 /** 77 * Has this notification been expanded while it was pinned 78 */ 79 private boolean mExpandedWhenPinned; 80 /** Is the user touching this row */ 81 private boolean mUserLocked; 82 /** Are we showing the "public" version */ 83 private boolean mShowingPublic; 84 private boolean mSensitive; 85 private boolean mSensitiveHiddenInGeneral; 86 private boolean mShowingPublicInitialized; 87 private boolean mHideSensitiveForIntrinsicHeight; 88 89 /** 90 * Is this notification expanded by the system. The expansion state can be overridden by the 91 * user expansion. 92 */ 93 private boolean mIsSystemExpanded; 94 95 /** 96 * Whether the notification is on the keyguard and the expansion is disabled. 97 */ 98 private boolean mOnKeyguard; 99 100 private Animator mTranslateAnim; 101 private ArrayList<View> mTranslateableViews; 102 private NotificationContentView mPublicLayout; 103 private NotificationContentView mPrivateLayout; 104 private int mMaxExpandHeight; 105 private int mHeadsUpHeight; 106 private View mVetoButton; 107 private int mNotificationColor; 108 private boolean mClearable; 109 private ExpansionLogger mLogger; 110 private String mLoggingKey; 111 private NotificationSettingsIconRow mSettingsIconRow; 112 private NotificationGuts mGuts; 113 private NotificationData.Entry mEntry; 114 private StatusBarNotification mStatusBarNotification; 115 private String mAppName; 116 private boolean mIsHeadsUp; 117 private boolean mLastChronometerRunning = true; 118 private ViewStub mChildrenContainerStub; 119 private NotificationGroupManager mGroupManager; 120 private boolean mChildrenExpanded; 121 private boolean mIsSummaryWithChildren; 122 private NotificationChildrenContainer mChildrenContainer; 123 private ViewStub mSettingsIconRowStub; 124 private ViewStub mGutsStub; 125 private boolean mIsSystemChildExpanded; 126 private boolean mIsPinned; 127 private FalsingManager mFalsingManager; 128 private HeadsUpManager mHeadsUpManager; 129 130 private boolean mJustClicked; 131 private boolean mIconAnimationRunning; 132 private boolean mShowNoBackground; 133 private ExpandableNotificationRow mNotificationParent; 134 private OnExpandClickListener mOnExpandClickListener; 135 private boolean mGroupExpansionChanging; 136 137 private OnClickListener mExpandClickListener = new OnClickListener() { 138 @Override 139 public void onClick(View v) { 140 if (!mShowingPublic && mGroupManager.isSummaryOfGroup(mStatusBarNotification)) { 141 mGroupManager.toggleGroupExpansion(mStatusBarNotification); 142 mOnExpandClickListener.onExpandClicked(mEntry, 143 mGroupManager.isGroupExpanded(mStatusBarNotification)); 144 mGroupExpansionChanging = true; 145 updateBackgroundForGroupState(); 146 } else { 147 boolean nowExpanded; 148 if (isPinned()) { 149 nowExpanded = !mExpandedWhenPinned; 150 mExpandedWhenPinned = nowExpanded; 151 } else { 152 nowExpanded = !isExpanded(); 153 setUserExpanded(nowExpanded); 154 } 155 notifyHeightChanged(true); 156 mOnExpandClickListener.onExpandClicked(mEntry, nowExpanded); 157 } 158 } 159 }; 160 private boolean mForceUnlocked; 161 private boolean mDismissed; 162 private boolean mKeepInParent; 163 private boolean mRemoved; 164 private static final Property<ExpandableNotificationRow, Float> TRANSLATE_CONTENT = 165 new FloatProperty<ExpandableNotificationRow>("translate") { 166 @Override 167 public void setValue(ExpandableNotificationRow object, float value) { 168 object.setTranslation(value); 169 } 170 171 @Override 172 public Float get(ExpandableNotificationRow object) { 173 return object.getTranslation(); 174 } 175 }; 176 177 public boolean isGroupExpansionChanging() { 178 if (isChildInGroup()) { 179 return mNotificationParent.isGroupExpansionChanging(); 180 } 181 return mGroupExpansionChanging; 182 } 183 184 public void setGroupExpansionChanging(boolean changing) { 185 mGroupExpansionChanging = changing; 186 } 187 188 public NotificationContentView getPrivateLayout() { 189 return mPrivateLayout; 190 } 191 192 public NotificationContentView getPublicLayout() { 193 return mPublicLayout; 194 } 195 196 public void setIconAnimationRunning(boolean running) { 197 setIconAnimationRunning(running, mPublicLayout); 198 setIconAnimationRunning(running, mPrivateLayout); 199 if (mIsSummaryWithChildren) { 200 setIconAnimationRunningForChild(running, mChildrenContainer.getHeaderView()); 201 List<ExpandableNotificationRow> notificationChildren = 202 mChildrenContainer.getNotificationChildren(); 203 for (int i = 0; i < notificationChildren.size(); i++) { 204 ExpandableNotificationRow child = notificationChildren.get(i); 205 child.setIconAnimationRunning(running); 206 } 207 } 208 mIconAnimationRunning = running; 209 } 210 211 private void setIconAnimationRunning(boolean running, NotificationContentView layout) { 212 if (layout != null) { 213 View contractedChild = layout.getContractedChild(); 214 View expandedChild = layout.getExpandedChild(); 215 View headsUpChild = layout.getHeadsUpChild(); 216 setIconAnimationRunningForChild(running, contractedChild); 217 setIconAnimationRunningForChild(running, expandedChild); 218 setIconAnimationRunningForChild(running, headsUpChild); 219 } 220 } 221 222 private void setIconAnimationRunningForChild(boolean running, View child) { 223 if (child != null) { 224 ImageView icon = (ImageView) child.findViewById(com.android.internal.R.id.icon); 225 setIconRunning(icon, running); 226 ImageView rightIcon = (ImageView) child.findViewById( 227 com.android.internal.R.id.right_icon); 228 setIconRunning(rightIcon, running); 229 } 230 } 231 232 private void setIconRunning(ImageView imageView, boolean running) { 233 if (imageView != null) { 234 Drawable drawable = imageView.getDrawable(); 235 if (drawable instanceof AnimationDrawable) { 236 AnimationDrawable animationDrawable = (AnimationDrawable) drawable; 237 if (running) { 238 animationDrawable.start(); 239 } else { 240 animationDrawable.stop(); 241 } 242 } else if (drawable instanceof AnimatedVectorDrawable) { 243 AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable; 244 if (running) { 245 animationDrawable.start(); 246 } else { 247 animationDrawable.stop(); 248 } 249 } 250 } 251 } 252 253 public void onNotificationUpdated(NotificationData.Entry entry) { 254 mEntry = entry; 255 mStatusBarNotification = entry.notification; 256 mPrivateLayout.onNotificationUpdated(entry); 257 mPublicLayout.onNotificationUpdated(entry); 258 mShowingPublicInitialized = false; 259 updateNotificationColor(); 260 updateClearability(); 261 if (mIsSummaryWithChildren) { 262 mChildrenContainer.recreateNotificationHeader(mExpandClickListener, mEntry.notification); 263 mChildrenContainer.onNotificationUpdated(); 264 } 265 if (mIconAnimationRunning) { 266 setIconAnimationRunning(true); 267 } 268 if (mNotificationParent != null) { 269 mNotificationParent.updateChildrenHeaderAppearance(); 270 } 271 onChildrenCountChanged(); 272 // The public layouts expand button is always visible 273 mPublicLayout.updateExpandButtons(true); 274 updateLimits(); 275 } 276 277 private void updateLimits() { 278 updateLimitsForView(mPrivateLayout); 279 updateLimitsForView(mPublicLayout); 280 } 281 282 private void updateLimitsForView(NotificationContentView layout) { 283 boolean customView = layout.getContractedChild().getId() 284 != com.android.internal.R.id.status_bar_latest_event_content; 285 boolean beforeN = mEntry.targetSdk < Build.VERSION_CODES.N; 286 int minHeight = customView && beforeN && !mIsSummaryWithChildren ? 287 mNotificationMinHeightLegacy : mNotificationMinHeight; 288 boolean headsUpCustom = layout.getHeadsUpChild() != null && 289 layout.getHeadsUpChild().getId() 290 != com.android.internal.R.id.status_bar_latest_event_content; 291 int headsUpheight = headsUpCustom && beforeN ? mMaxHeadsUpHeightLegacy 292 : mMaxHeadsUpHeight; 293 layout.setHeights(minHeight, headsUpheight, mNotificationMaxHeight); 294 } 295 296 public StatusBarNotification getStatusBarNotification() { 297 return mStatusBarNotification; 298 } 299 300 public boolean isHeadsUp() { 301 return mIsHeadsUp; 302 } 303 304 public void setHeadsUp(boolean isHeadsUp) { 305 int intrinsicBefore = getIntrinsicHeight(); 306 mIsHeadsUp = isHeadsUp; 307 mPrivateLayout.setHeadsUp(isHeadsUp); 308 if (mIsSummaryWithChildren) { 309 // The overflow might change since we allow more lines as HUN. 310 mChildrenContainer.updateGroupOverflow(); 311 } 312 if (intrinsicBefore != getIntrinsicHeight()) { 313 notifyHeightChanged(false /* needsAnimation */); 314 } 315 } 316 317 public void setGroupManager(NotificationGroupManager groupManager) { 318 mGroupManager = groupManager; 319 mPrivateLayout.setGroupManager(groupManager); 320 } 321 322 public void setRemoteInputController(RemoteInputController r) { 323 mPrivateLayout.setRemoteInputController(r); 324 } 325 326 public void setAppName(String appName) { 327 mAppName = appName; 328 if (mSettingsIconRow != null) { 329 mSettingsIconRow.setAppName(mAppName); 330 } 331 } 332 333 public void addChildNotification(ExpandableNotificationRow row) { 334 addChildNotification(row, -1); 335 } 336 337 /** 338 * Add a child notification to this view. 339 * 340 * @param row the row to add 341 * @param childIndex the index to add it at, if -1 it will be added at the end 342 */ 343 public void addChildNotification(ExpandableNotificationRow row, int childIndex) { 344 if (mChildrenContainer == null) { 345 mChildrenContainerStub.inflate(); 346 } 347 mChildrenContainer.addNotification(row, childIndex); 348 onChildrenCountChanged(); 349 row.setIsChildInGroup(true, this); 350 } 351 352 public void removeChildNotification(ExpandableNotificationRow row) { 353 if (mChildrenContainer != null) { 354 mChildrenContainer.removeNotification(row); 355 } 356 onChildrenCountChanged(); 357 row.setIsChildInGroup(false, null); 358 } 359 360 public boolean isChildInGroup() { 361 return mNotificationParent != null; 362 } 363 364 public ExpandableNotificationRow getNotificationParent() { 365 return mNotificationParent; 366 } 367 368 /** 369 * @param isChildInGroup Is this notification now in a group 370 * @param parent the new parent notification 371 */ 372 public void setIsChildInGroup(boolean isChildInGroup, ExpandableNotificationRow parent) {; 373 boolean childInGroup = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS && isChildInGroup; 374 mNotificationParent = childInGroup ? parent : null; 375 mPrivateLayout.setIsChildInGroup(childInGroup); 376 updateBackgroundForGroupState(); 377 if (mNotificationParent != null) { 378 mNotificationParent.updateBackgroundForGroupState(); 379 } 380 } 381 382 @Override 383 public boolean onTouchEvent(MotionEvent event) { 384 if (event.getActionMasked() != MotionEvent.ACTION_DOWN 385 || !isChildInGroup() || isGroupExpanded()) { 386 return super.onTouchEvent(event); 387 } else { 388 return false; 389 } 390 } 391 392 @Override 393 protected boolean handleSlideBack() { 394 if (mSettingsIconRow != null && mSettingsIconRow.isVisible()) { 395 animateTranslateNotification(0 /* targetLeft */); 396 return true; 397 } 398 return false; 399 } 400 401 @Override 402 protected boolean shouldHideBackground() { 403 return super.shouldHideBackground() || mShowNoBackground; 404 } 405 406 @Override 407 public boolean isSummaryWithChildren() { 408 return mIsSummaryWithChildren; 409 } 410 411 @Override 412 public boolean areChildrenExpanded() { 413 return mChildrenExpanded; 414 } 415 416 public List<ExpandableNotificationRow> getNotificationChildren() { 417 return mChildrenContainer == null ? null : mChildrenContainer.getNotificationChildren(); 418 } 419 420 public int getNumberOfNotificationChildren() { 421 if (mChildrenContainer == null) { 422 return 0; 423 } 424 return mChildrenContainer.getNotificationChildren().size(); 425 } 426 427 /** 428 * Apply the order given in the list to the children. 429 * 430 * @param childOrder the new list order 431 * @return whether the list order has changed 432 */ 433 public boolean applyChildOrder(List<ExpandableNotificationRow> childOrder) { 434 return mChildrenContainer != null && mChildrenContainer.applyChildOrder(childOrder); 435 } 436 437 public void getChildrenStates(StackScrollState resultState) { 438 if (mIsSummaryWithChildren) { 439 StackViewState parentState = resultState.getViewStateForView(this); 440 mChildrenContainer.getState(resultState, parentState); 441 } 442 } 443 444 public void applyChildrenState(StackScrollState state) { 445 if (mIsSummaryWithChildren) { 446 mChildrenContainer.applyState(state); 447 } 448 } 449 450 public void prepareExpansionChanged(StackScrollState state) { 451 if (mIsSummaryWithChildren) { 452 mChildrenContainer.prepareExpansionChanged(state); 453 } 454 } 455 456 public void startChildAnimation(StackScrollState finalState, 457 StackStateAnimator stateAnimator, long delay, long duration) { 458 if (mIsSummaryWithChildren) { 459 mChildrenContainer.startAnimationToState(finalState, stateAnimator, delay, 460 duration); 461 } 462 } 463 464 public ExpandableNotificationRow getViewAtPosition(float y) { 465 if (!mIsSummaryWithChildren || !mChildrenExpanded) { 466 return this; 467 } else { 468 ExpandableNotificationRow view = mChildrenContainer.getViewAtPosition(y); 469 return view == null ? this : view; 470 } 471 } 472 473 public NotificationGuts getGuts() { 474 return mGuts; 475 } 476 477 /** 478 * Set this notification to be pinned to the top if {@link #isHeadsUp()} is true. By doing this 479 * the notification will be rendered on top of the screen. 480 * 481 * @param pinned whether it is pinned 482 */ 483 public void setPinned(boolean pinned) { 484 int intrinsicHeight = getIntrinsicHeight(); 485 mIsPinned = pinned; 486 if (intrinsicHeight != getIntrinsicHeight()) { 487 notifyHeightChanged(false); 488 } 489 if (pinned) { 490 setIconAnimationRunning(true); 491 mExpandedWhenPinned = false; 492 } else if (mExpandedWhenPinned) { 493 setUserExpanded(true); 494 } 495 setChronometerRunning(mLastChronometerRunning); 496 } 497 498 public boolean isPinned() { 499 return mIsPinned; 500 } 501 502 /** 503 * @param atLeastMinHeight should the value returned be at least the minimum height. 504 * Used to avoid cyclic calls 505 * @return the height of the heads up notification when pinned 506 */ 507 public int getPinnedHeadsUpHeight(boolean atLeastMinHeight) { 508 if (mIsSummaryWithChildren) { 509 return mChildrenContainer.getIntrinsicHeight(); 510 } 511 if(mExpandedWhenPinned) { 512 return Math.max(getMaxExpandHeight(), mHeadsUpHeight); 513 } else if (atLeastMinHeight) { 514 return Math.max(getCollapsedHeight(), mHeadsUpHeight); 515 } else { 516 return mHeadsUpHeight; 517 } 518 } 519 520 /** 521 * Mark whether this notification was just clicked, i.e. the user has just clicked this 522 * notification in this frame. 523 */ 524 public void setJustClicked(boolean justClicked) { 525 mJustClicked = justClicked; 526 } 527 528 /** 529 * @return true if this notification has been clicked in this frame, false otherwise 530 */ 531 public boolean wasJustClicked() { 532 return mJustClicked; 533 } 534 535 public void setChronometerRunning(boolean running) { 536 mLastChronometerRunning = running; 537 setChronometerRunning(running, mPrivateLayout); 538 setChronometerRunning(running, mPublicLayout); 539 if (mChildrenContainer != null) { 540 List<ExpandableNotificationRow> notificationChildren = 541 mChildrenContainer.getNotificationChildren(); 542 for (int i = 0; i < notificationChildren.size(); i++) { 543 ExpandableNotificationRow child = notificationChildren.get(i); 544 child.setChronometerRunning(running); 545 } 546 } 547 } 548 549 private void setChronometerRunning(boolean running, NotificationContentView layout) { 550 if (layout != null) { 551 running = running || isPinned(); 552 View contractedChild = layout.getContractedChild(); 553 View expandedChild = layout.getExpandedChild(); 554 View headsUpChild = layout.getHeadsUpChild(); 555 setChronometerRunningForChild(running, contractedChild); 556 setChronometerRunningForChild(running, expandedChild); 557 setChronometerRunningForChild(running, headsUpChild); 558 } 559 } 560 561 private void setChronometerRunningForChild(boolean running, View child) { 562 if (child != null) { 563 View chronometer = child.findViewById(com.android.internal.R.id.chronometer); 564 if (chronometer instanceof Chronometer) { 565 ((Chronometer) chronometer).setStarted(running); 566 } 567 } 568 } 569 570 public NotificationHeaderView getNotificationHeader() { 571 if (mIsSummaryWithChildren) { 572 return mChildrenContainer.getHeaderView(); 573 } 574 return mPrivateLayout.getNotificationHeader(); 575 } 576 577 private NotificationHeaderView getVisibleNotificationHeader() { 578 if (mIsSummaryWithChildren) { 579 return mChildrenContainer.getHeaderView(); 580 } 581 return getShowingLayout().getVisibleNotificationHeader(); 582 } 583 584 public void setOnExpandClickListener(OnExpandClickListener onExpandClickListener) { 585 mOnExpandClickListener = onExpandClickListener; 586 } 587 588 public void setHeadsUpManager(HeadsUpManager headsUpManager) { 589 mHeadsUpManager = headsUpManager; 590 } 591 592 public void reInflateViews() { 593 initDimens(); 594 if (mIsSummaryWithChildren) { 595 if (mChildrenContainer != null) { 596 mChildrenContainer.reInflateViews(mExpandClickListener, mEntry.notification); 597 } 598 } 599 if (mGuts != null) { 600 View oldGuts = mGuts; 601 int index = indexOfChild(oldGuts); 602 removeView(oldGuts); 603 mGuts = (NotificationGuts) LayoutInflater.from(mContext).inflate( 604 R.layout.notification_guts, this, false); 605 mGuts.setVisibility(oldGuts.getVisibility()); 606 addView(mGuts, index); 607 } 608 if (mSettingsIconRow != null) { 609 View oldSettings = mSettingsIconRow; 610 int settingsIndex = indexOfChild(oldSettings); 611 removeView(oldSettings); 612 mSettingsIconRow = (NotificationSettingsIconRow) LayoutInflater.from(mContext).inflate( 613 R.layout.notification_settings_icon_row, this, false); 614 mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this); 615 mSettingsIconRow.setAppName(mAppName); 616 mSettingsIconRow.setVisibility(oldSettings.getVisibility()); 617 addView(mSettingsIconRow, settingsIndex); 618 619 } 620 mPrivateLayout.reInflateViews(); 621 mPublicLayout.reInflateViews(); 622 } 623 624 public void setContentBackground(int customBackgroundColor, boolean animate, 625 NotificationContentView notificationContentView) { 626 if (getShowingLayout() == notificationContentView) { 627 setTintColor(customBackgroundColor, animate); 628 } 629 } 630 631 public void closeRemoteInput() { 632 mPrivateLayout.closeRemoteInput(); 633 mPublicLayout.closeRemoteInput(); 634 } 635 636 /** 637 * Set by how much the single line view should be indented. 638 */ 639 public void setSingleLineWidthIndention(int indention) { 640 mPrivateLayout.setSingleLineWidthIndention(indention); 641 } 642 643 public int getNotificationColor() { 644 return mNotificationColor; 645 } 646 647 private void updateNotificationColor() { 648 mNotificationColor = NotificationColorUtil.resolveContrastColor(mContext, 649 getStatusBarNotification().getNotification().color); 650 } 651 652 public HybridNotificationView getSingleLineView() { 653 return mPrivateLayout.getSingleLineView(); 654 } 655 656 public boolean isOnKeyguard() { 657 return mOnKeyguard; 658 } 659 660 public void removeAllChildren() { 661 List<ExpandableNotificationRow> notificationChildren 662 = mChildrenContainer.getNotificationChildren(); 663 ArrayList<ExpandableNotificationRow> clonedList = new ArrayList<>(notificationChildren); 664 for (int i = 0; i < clonedList.size(); i++) { 665 ExpandableNotificationRow row = clonedList.get(i); 666 if (row.keepInParent()) { 667 continue; 668 } 669 mChildrenContainer.removeNotification(row); 670 row.setIsChildInGroup(false, null); 671 } 672 onChildrenCountChanged(); 673 } 674 675 public void setForceUnlocked(boolean forceUnlocked) { 676 mForceUnlocked = forceUnlocked; 677 if (mIsSummaryWithChildren) { 678 List<ExpandableNotificationRow> notificationChildren = getNotificationChildren(); 679 for (ExpandableNotificationRow child : notificationChildren) { 680 child.setForceUnlocked(forceUnlocked); 681 } 682 } 683 } 684 685 public void setDismissed(boolean dismissed) { 686 mDismissed = dismissed; 687 } 688 689 public boolean isDismissed() { 690 return mDismissed; 691 } 692 693 public boolean keepInParent() { 694 return mKeepInParent; 695 } 696 697 public void setKeepInParent(boolean keepInParent) { 698 mKeepInParent = keepInParent; 699 } 700 701 public boolean isRemoved() { 702 return mRemoved; 703 } 704 705 public void setRemoved(boolean removed) { 706 mRemoved = removed; 707 } 708 709 public NotificationChildrenContainer getChildrenContainer() { 710 return mChildrenContainer; 711 } 712 713 public interface ExpansionLogger { 714 public void logNotificationExpansion(String key, boolean userAction, boolean expanded); 715 } 716 717 public ExpandableNotificationRow(Context context, AttributeSet attrs) { 718 super(context, attrs); 719 mFalsingManager = FalsingManager.getInstance(context); 720 initDimens(); 721 } 722 723 private void initDimens() { 724 mNotificationMinHeightLegacy = getFontScaledHeight(R.dimen.notification_min_height_legacy); 725 mNotificationMinHeight = getFontScaledHeight(R.dimen.notification_min_height); 726 mNotificationMaxHeight = getFontScaledHeight(R.dimen.notification_max_height); 727 mMaxHeadsUpHeightLegacy = getFontScaledHeight( 728 R.dimen.notification_max_heads_up_height_legacy); 729 mMaxHeadsUpHeight = getFontScaledHeight(R.dimen.notification_max_heads_up_height); 730 mIncreasedPaddingBetweenElements = getResources() 731 .getDimensionPixelSize(R.dimen.notification_divider_height_increased); 732 } 733 734 /** 735 * @param dimenId the dimen to look up 736 * @return the font scaled dimen as if it were in sp but doesn't shrink sizes below dp 737 */ 738 private int getFontScaledHeight(int dimenId) { 739 int dimensionPixelSize = getResources().getDimensionPixelSize(dimenId); 740 float factor = Math.max(1.0f, getResources().getDisplayMetrics().scaledDensity / 741 getResources().getDisplayMetrics().density); 742 return (int) (dimensionPixelSize * factor); 743 } 744 745 /** 746 * Resets this view so it can be re-used for an updated notification. 747 */ 748 @Override 749 public void reset() { 750 super.reset(); 751 final boolean wasExpanded = isExpanded(); 752 mExpandable = false; 753 mHasUserChangedExpansion = false; 754 mUserLocked = false; 755 mShowingPublic = false; 756 mSensitive = false; 757 mShowingPublicInitialized = false; 758 mIsSystemExpanded = false; 759 mOnKeyguard = false; 760 mPublicLayout.reset(); 761 mPrivateLayout.reset(); 762 resetHeight(); 763 resetTranslation(); 764 logExpansionEvent(false, wasExpanded); 765 } 766 767 public void resetHeight() { 768 mMaxExpandHeight = 0; 769 mHeadsUpHeight = 0; 770 onHeightReset(); 771 requestLayout(); 772 } 773 774 @Override 775 protected void onFinishInflate() { 776 super.onFinishInflate(); 777 mPublicLayout = (NotificationContentView) findViewById(R.id.expandedPublic); 778 mPublicLayout.setContainingNotification(this); 779 mPrivateLayout = (NotificationContentView) findViewById(R.id.expanded); 780 mPrivateLayout.setExpandClickListener(mExpandClickListener); 781 mPrivateLayout.setContainingNotification(this); 782 mPublicLayout.setExpandClickListener(mExpandClickListener); 783 mSettingsIconRowStub = (ViewStub) findViewById(R.id.settings_icon_row_stub); 784 mSettingsIconRowStub.setOnInflateListener(new ViewStub.OnInflateListener() { 785 @Override 786 public void onInflate(ViewStub stub, View inflated) { 787 mSettingsIconRow = (NotificationSettingsIconRow) inflated; 788 mSettingsIconRow.setNotificationRowParent(ExpandableNotificationRow.this); 789 mSettingsIconRow.setAppName(mAppName); 790 } 791 }); 792 mGutsStub = (ViewStub) findViewById(R.id.notification_guts_stub); 793 mGutsStub.setOnInflateListener(new ViewStub.OnInflateListener() { 794 @Override 795 public void onInflate(ViewStub stub, View inflated) { 796 mGuts = (NotificationGuts) inflated; 797 mGuts.setClipTopAmount(getClipTopAmount()); 798 mGuts.setActualHeight(getActualHeight()); 799 mGutsStub = null; 800 } 801 }); 802 mChildrenContainerStub = (ViewStub) findViewById(R.id.child_container_stub); 803 mChildrenContainerStub.setOnInflateListener(new ViewStub.OnInflateListener() { 804 805 @Override 806 public void onInflate(ViewStub stub, View inflated) { 807 mChildrenContainer = (NotificationChildrenContainer) inflated; 808 mChildrenContainer.setNotificationParent(ExpandableNotificationRow.this); 809 mChildrenContainer.onNotificationUpdated(); 810 mTranslateableViews.add(mChildrenContainer); 811 } 812 }); 813 mVetoButton = findViewById(R.id.veto); 814 815 // Add the views that we translate to reveal the gear 816 mTranslateableViews = new ArrayList<View>(); 817 for (int i = 0; i < getChildCount(); i++) { 818 mTranslateableViews.add(getChildAt(i)); 819 } 820 // Remove views that don't translate 821 mTranslateableViews.remove(mVetoButton); 822 mTranslateableViews.remove(mSettingsIconRowStub); 823 mTranslateableViews.remove(mChildrenContainerStub); 824 mTranslateableViews.remove(mGutsStub); 825 } 826 827 public void resetTranslation() { 828 if (mTranslateableViews != null) { 829 for (int i = 0; i < mTranslateableViews.size(); i++) { 830 mTranslateableViews.get(i).setTranslationX(0); 831 } 832 } 833 invalidateOutline(); 834 if (mSettingsIconRow != null) { 835 mSettingsIconRow.resetState(); 836 } 837 } 838 839 public void animateTranslateNotification(final float leftTarget) { 840 if (mTranslateAnim != null) { 841 mTranslateAnim.cancel(); 842 } 843 mTranslateAnim = getTranslateViewAnimator(leftTarget, null /* updateListener */); 844 if (mTranslateAnim != null) { 845 mTranslateAnim.start(); 846 } 847 } 848 849 @Override 850 public void setTranslation(float translationX) { 851 if (areGutsExposed()) { 852 // Don't translate if guts are showing. 853 return; 854 } 855 // Translate the group of views 856 for (int i = 0; i < mTranslateableViews.size(); i++) { 857 if (mTranslateableViews.get(i) != null) { 858 mTranslateableViews.get(i).setTranslationX(translationX); 859 } 860 } 861 invalidateOutline(); 862 if (mSettingsIconRow != null) { 863 mSettingsIconRow.updateSettingsIcons(translationX, getMeasuredWidth()); 864 } 865 } 866 867 @Override 868 public float getTranslation() { 869 if (mTranslateableViews != null && mTranslateableViews.size() > 0) { 870 // All of the views in the list should have same translation, just use first one. 871 return mTranslateableViews.get(0).getTranslationX(); 872 } 873 return 0; 874 } 875 876 public Animator getTranslateViewAnimator(final float leftTarget, 877 AnimatorUpdateListener listener) { 878 if (mTranslateAnim != null) { 879 mTranslateAnim.cancel(); 880 } 881 if (areGutsExposed()) { 882 // No translation if guts are exposed. 883 return null; 884 } 885 final ObjectAnimator translateAnim = ObjectAnimator.ofFloat(this, TRANSLATE_CONTENT, 886 leftTarget); 887 if (listener != null) { 888 translateAnim.addUpdateListener(listener); 889 } 890 translateAnim.addListener(new AnimatorListenerAdapter() { 891 boolean cancelled = false; 892 893 @Override 894 public void onAnimationCancel(Animator anim) { 895 cancelled = true; 896 } 897 898 @Override 899 public void onAnimationEnd(Animator anim) { 900 if (!cancelled && mSettingsIconRow != null && leftTarget == 0) { 901 mSettingsIconRow.resetState(); 902 mTranslateAnim = null; 903 } 904 } 905 }); 906 mTranslateAnim = translateAnim; 907 return translateAnim; 908 } 909 910 public float getSpaceForGear() { 911 if (mSettingsIconRow != null) { 912 return mSettingsIconRow.getSpaceForGear(); 913 } 914 return 0; 915 } 916 917 public NotificationSettingsIconRow getSettingsRow() { 918 if (mSettingsIconRow == null) { 919 mSettingsIconRowStub.inflate(); 920 } 921 return mSettingsIconRow; 922 } 923 924 public void inflateGuts() { 925 if (mGuts == null) { 926 mGutsStub.inflate(); 927 } 928 } 929 930 private void updateChildrenVisibility() { 931 mPrivateLayout.setVisibility(!mShowingPublic && !mIsSummaryWithChildren ? VISIBLE 932 : INVISIBLE); 933 if (mChildrenContainer != null) { 934 mChildrenContainer.setVisibility(!mShowingPublic && mIsSummaryWithChildren ? VISIBLE 935 : INVISIBLE); 936 mChildrenContainer.updateHeaderVisibility(!mShowingPublic && mIsSummaryWithChildren 937 ? VISIBLE 938 : INVISIBLE); 939 } 940 // The limits might have changed if the view suddenly became a group or vice versa 941 updateLimits(); 942 } 943 944 @Override 945 public boolean onRequestSendAccessibilityEventInternal(View child, AccessibilityEvent event) { 946 if (super.onRequestSendAccessibilityEventInternal(child, event)) { 947 // Add a record for the entire layout since its content is somehow small. 948 // The event comes from a leaf view that is interacted with. 949 AccessibilityEvent record = AccessibilityEvent.obtain(); 950 onInitializeAccessibilityEvent(record); 951 dispatchPopulateAccessibilityEvent(record); 952 event.appendRecord(record); 953 return true; 954 } 955 return false; 956 } 957 958 @Override 959 public void setDark(boolean dark, boolean fade, long delay) { 960 super.setDark(dark, fade, delay); 961 final NotificationContentView showing = getShowingLayout(); 962 if (showing != null) { 963 showing.setDark(dark, fade, delay); 964 } 965 if (mIsSummaryWithChildren) { 966 mChildrenContainer.setDark(dark, fade, delay); 967 } 968 } 969 970 public boolean isExpandable() { 971 if (mIsSummaryWithChildren && !mShowingPublic) { 972 return !mChildrenExpanded; 973 } 974 return mExpandable; 975 } 976 977 public void setExpandable(boolean expandable) { 978 mExpandable = expandable; 979 mPrivateLayout.updateExpandButtons(isExpandable()); 980 } 981 982 @Override 983 public void setClipToActualHeight(boolean clipToActualHeight) { 984 super.setClipToActualHeight(clipToActualHeight || isUserLocked()); 985 getShowingLayout().setClipToActualHeight(clipToActualHeight || isUserLocked()); 986 } 987 988 /** 989 * @return whether the user has changed the expansion state 990 */ 991 public boolean hasUserChangedExpansion() { 992 return mHasUserChangedExpansion; 993 } 994 995 public boolean isUserExpanded() { 996 return mUserExpanded; 997 } 998 999 /** 1000 * Set this notification to be expanded by the user 1001 * 1002 * @param userExpanded whether the user wants this notification to be expanded 1003 */ 1004 public void setUserExpanded(boolean userExpanded) { 1005 setUserExpanded(userExpanded, false /* allowChildExpansion */); 1006 } 1007 1008 /** 1009 * Set this notification to be expanded by the user 1010 * 1011 * @param userExpanded whether the user wants this notification to be expanded 1012 * @param allowChildExpansion whether a call to this method allows expanding children 1013 */ 1014 public void setUserExpanded(boolean userExpanded, boolean allowChildExpansion) { 1015 mFalsingManager.setNotificationExpanded(); 1016 if (mIsSummaryWithChildren && !mShowingPublic && allowChildExpansion) { 1017 mGroupManager.setGroupExpanded(mStatusBarNotification, userExpanded); 1018 return; 1019 } 1020 if (userExpanded && !mExpandable) return; 1021 final boolean wasExpanded = isExpanded(); 1022 mHasUserChangedExpansion = true; 1023 mUserExpanded = userExpanded; 1024 logExpansionEvent(true, wasExpanded); 1025 } 1026 1027 public void resetUserExpansion() { 1028 mHasUserChangedExpansion = false; 1029 mUserExpanded = false; 1030 } 1031 1032 public boolean isUserLocked() { 1033 return mUserLocked && !mForceUnlocked; 1034 } 1035 1036 public void setUserLocked(boolean userLocked) { 1037 mUserLocked = userLocked; 1038 mPrivateLayout.setUserExpanding(userLocked); 1039 if (mIsSummaryWithChildren) { 1040 mChildrenContainer.setUserLocked(userLocked); 1041 if (userLocked) { 1042 updateBackgroundForGroupState(); 1043 } 1044 } 1045 } 1046 1047 /** 1048 * @return has the system set this notification to be expanded 1049 */ 1050 public boolean isSystemExpanded() { 1051 return mIsSystemExpanded; 1052 } 1053 1054 /** 1055 * Set this notification to be expanded by the system. 1056 * 1057 * @param expand whether the system wants this notification to be expanded. 1058 */ 1059 public void setSystemExpanded(boolean expand) { 1060 if (expand != mIsSystemExpanded) { 1061 final boolean wasExpanded = isExpanded(); 1062 mIsSystemExpanded = expand; 1063 notifyHeightChanged(false /* needsAnimation */); 1064 logExpansionEvent(false, wasExpanded); 1065 if (mChildrenContainer != null) { 1066 mChildrenContainer.updateGroupOverflow(); 1067 } 1068 } 1069 } 1070 1071 /** 1072 * @param onKeyguard whether to prevent notification expansion 1073 */ 1074 public void setOnKeyguard(boolean onKeyguard) { 1075 if (onKeyguard != mOnKeyguard) { 1076 final boolean wasExpanded = isExpanded(); 1077 mOnKeyguard = onKeyguard; 1078 logExpansionEvent(false, wasExpanded); 1079 if (wasExpanded != isExpanded()) { 1080 if (mIsSummaryWithChildren) { 1081 mChildrenContainer.updateGroupOverflow(); 1082 } 1083 notifyHeightChanged(false /* needsAnimation */); 1084 } 1085 } 1086 } 1087 1088 /** 1089 * @return Can the underlying notification be cleared? 1090 */ 1091 public boolean isClearable() { 1092 return mStatusBarNotification != null && mStatusBarNotification.isClearable(); 1093 } 1094 1095 @Override 1096 public int getIntrinsicHeight() { 1097 if (isUserLocked()) { 1098 return getActualHeight(); 1099 } 1100 if (mGuts != null && mGuts.areGutsExposed()) { 1101 return mGuts.getHeight(); 1102 } else if ((isChildInGroup() && !isGroupExpanded())) { 1103 return mPrivateLayout.getMinHeight(); 1104 } else if (mSensitive && mHideSensitiveForIntrinsicHeight) { 1105 return getMinHeight(); 1106 } else if (mIsSummaryWithChildren && !mOnKeyguard) { 1107 return mChildrenContainer.getIntrinsicHeight(); 1108 } else if (mIsHeadsUp) { 1109 if (isPinned()) { 1110 return getPinnedHeadsUpHeight(true /* atLeastMinHeight */); 1111 } else if (isExpanded()) { 1112 return Math.max(getMaxExpandHeight(), mHeadsUpHeight); 1113 } else { 1114 return Math.max(getCollapsedHeight(), mHeadsUpHeight); 1115 } 1116 } else if (isExpanded()) { 1117 return getMaxExpandHeight(); 1118 } else { 1119 return getCollapsedHeight(); 1120 } 1121 } 1122 1123 public boolean isGroupExpanded() { 1124 return mGroupManager.isGroupExpanded(mStatusBarNotification); 1125 } 1126 1127 private void onChildrenCountChanged() { 1128 mIsSummaryWithChildren = BaseStatusBar.ENABLE_CHILD_NOTIFICATIONS 1129 && mChildrenContainer != null && mChildrenContainer.getNotificationChildCount() > 0; 1130 if (mIsSummaryWithChildren && mChildrenContainer.getHeaderView() == null) { 1131 mChildrenContainer.recreateNotificationHeader(mExpandClickListener, 1132 mEntry.notification); 1133 } 1134 getShowingLayout().updateBackgroundColor(false /* animate */); 1135 mPrivateLayout.updateExpandButtons(isExpandable()); 1136 updateChildrenHeaderAppearance(); 1137 updateChildrenVisibility(); 1138 } 1139 1140 public void updateChildrenHeaderAppearance() { 1141 if (mChildrenContainer != null) { 1142 mChildrenContainer.updateChildrenHeaderAppearance(); 1143 } 1144 } 1145 1146 /** 1147 * Check whether the view state is currently expanded. This is given by the system in {@link 1148 * #setSystemExpanded(boolean)} and can be overridden by user expansion or 1149 * collapsing in {@link #setUserExpanded(boolean)}. Note that the visual appearance of this 1150 * view can differ from this state, if layout params are modified from outside. 1151 * 1152 * @return whether the view state is currently expanded. 1153 */ 1154 public boolean isExpanded() { 1155 return isExpanded(false /* allowOnKeyguard */); 1156 } 1157 1158 public boolean isExpanded(boolean allowOnKeyguard) { 1159 return (!mOnKeyguard || allowOnKeyguard) 1160 && (!hasUserChangedExpansion() && (isSystemExpanded() || isSystemChildExpanded()) 1161 || isUserExpanded()); 1162 } 1163 1164 private boolean isSystemChildExpanded() { 1165 return mIsSystemChildExpanded; 1166 } 1167 1168 public void setSystemChildExpanded(boolean expanded) { 1169 mIsSystemChildExpanded = expanded; 1170 } 1171 1172 @Override 1173 protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 1174 super.onLayout(changed, left, top, right, bottom); 1175 updateMaxHeights(); 1176 if (mSettingsIconRow != null) { 1177 mSettingsIconRow.updateVerticalLocation(); 1178 } 1179 } 1180 1181 private void updateMaxHeights() { 1182 int intrinsicBefore = getIntrinsicHeight(); 1183 View expandedChild = mPrivateLayout.getExpandedChild(); 1184 if (expandedChild == null) { 1185 expandedChild = mPrivateLayout.getContractedChild(); 1186 } 1187 mMaxExpandHeight = expandedChild.getHeight(); 1188 View headsUpChild = mPrivateLayout.getHeadsUpChild(); 1189 if (headsUpChild == null) { 1190 headsUpChild = mPrivateLayout.getContractedChild(); 1191 } 1192 mHeadsUpHeight = headsUpChild.getHeight(); 1193 if (intrinsicBefore != getIntrinsicHeight()) { 1194 notifyHeightChanged(false /* needsAnimation */); 1195 } 1196 } 1197 1198 @Override 1199 public void notifyHeightChanged(boolean needsAnimation) { 1200 super.notifyHeightChanged(needsAnimation); 1201 getShowingLayout().requestSelectLayout(needsAnimation || isUserLocked()); 1202 } 1203 1204 public void setSensitive(boolean sensitive, boolean hideSensitive) { 1205 mSensitive = sensitive; 1206 mSensitiveHiddenInGeneral = hideSensitive; 1207 } 1208 1209 public void setHideSensitiveForIntrinsicHeight(boolean hideSensitive) { 1210 mHideSensitiveForIntrinsicHeight = hideSensitive; 1211 if (mIsSummaryWithChildren) { 1212 List<ExpandableNotificationRow> notificationChildren = 1213 mChildrenContainer.getNotificationChildren(); 1214 for (int i = 0; i < notificationChildren.size(); i++) { 1215 ExpandableNotificationRow child = notificationChildren.get(i); 1216 child.setHideSensitiveForIntrinsicHeight(hideSensitive); 1217 } 1218 } 1219 } 1220 1221 public void setHideSensitive(boolean hideSensitive, boolean animated, long delay, 1222 long duration) { 1223 boolean oldShowingPublic = mShowingPublic; 1224 mShowingPublic = mSensitive && hideSensitive; 1225 if (mShowingPublicInitialized && mShowingPublic == oldShowingPublic) { 1226 return; 1227 } 1228 1229 // bail out if no public version 1230 if (mPublicLayout.getChildCount() == 0) return; 1231 1232 if (!animated) { 1233 mPublicLayout.animate().cancel(); 1234 mPrivateLayout.animate().cancel(); 1235 mPublicLayout.setAlpha(1f); 1236 mPrivateLayout.setAlpha(1f); 1237 mPublicLayout.setVisibility(mShowingPublic ? View.VISIBLE : View.INVISIBLE); 1238 updateChildrenVisibility(); 1239 } else { 1240 animateShowingPublic(delay, duration); 1241 } 1242 NotificationContentView showingLayout = getShowingLayout(); 1243 showingLayout.updateBackgroundColor(animated); 1244 mPrivateLayout.updateExpandButtons(isExpandable()); 1245 updateClearability(); 1246 mShowingPublicInitialized = true; 1247 } 1248 1249 private void animateShowingPublic(long delay, long duration) { 1250 View[] privateViews = mIsSummaryWithChildren 1251 ? new View[] {mChildrenContainer} 1252 : new View[] {mPrivateLayout}; 1253 View[] publicViews = new View[] {mPublicLayout}; 1254 View[] hiddenChildren = mShowingPublic ? privateViews : publicViews; 1255 View[] shownChildren = mShowingPublic ? publicViews : privateViews; 1256 for (final View hiddenView : hiddenChildren) { 1257 hiddenView.setVisibility(View.VISIBLE); 1258 hiddenView.animate().cancel(); 1259 hiddenView.animate() 1260 .alpha(0f) 1261 .setStartDelay(delay) 1262 .setDuration(duration) 1263 .withEndAction(new Runnable() { 1264 @Override 1265 public void run() { 1266 hiddenView.setVisibility(View.INVISIBLE); 1267 } 1268 }); 1269 } 1270 for (View showView : shownChildren) { 1271 showView.setVisibility(View.VISIBLE); 1272 showView.setAlpha(0f); 1273 showView.animate().cancel(); 1274 showView.animate() 1275 .alpha(1f) 1276 .setStartDelay(delay) 1277 .setDuration(duration); 1278 } 1279 } 1280 1281 public boolean mustStayOnScreen() { 1282 return mIsHeadsUp; 1283 } 1284 1285 private void updateClearability() { 1286 // public versions cannot be dismissed 1287 mVetoButton.setVisibility(isClearable() && (!mShowingPublic 1288 || !mSensitiveHiddenInGeneral) ? View.VISIBLE : View.GONE); 1289 } 1290 1291 public void makeActionsVisibile() { 1292 setUserExpanded(true, true); 1293 if (isChildInGroup()) { 1294 mGroupManager.setGroupExpanded(mStatusBarNotification, true); 1295 } 1296 notifyHeightChanged(false); 1297 } 1298 1299 public void setChildrenExpanded(boolean expanded, boolean animate) { 1300 mChildrenExpanded = expanded; 1301 if (mChildrenContainer != null) { 1302 mChildrenContainer.setChildrenExpanded(expanded); 1303 } 1304 } 1305 1306 public static void applyTint(View v, int color) { 1307 int alpha; 1308 if (color != 0) { 1309 alpha = COLORED_DIVIDER_ALPHA; 1310 } else { 1311 color = 0xff000000; 1312 alpha = DEFAULT_DIVIDER_ALPHA; 1313 } 1314 if (v.getBackground() instanceof ColorDrawable) { 1315 ColorDrawable background = (ColorDrawable) v.getBackground(); 1316 background.mutate(); 1317 background.setColor(color); 1318 background.setAlpha(alpha); 1319 } 1320 } 1321 1322 public int getMaxExpandHeight() { 1323 return mMaxExpandHeight; 1324 } 1325 1326 public boolean areGutsExposed() { 1327 return (mGuts != null && mGuts.areGutsExposed()); 1328 } 1329 1330 @Override 1331 public boolean isContentExpandable() { 1332 NotificationContentView showingLayout = getShowingLayout(); 1333 return showingLayout.isContentExpandable(); 1334 } 1335 1336 @Override 1337 protected View getContentView() { 1338 if (mIsSummaryWithChildren) { 1339 return mChildrenContainer; 1340 } 1341 return getShowingLayout(); 1342 } 1343 1344 @Override 1345 public int getExtraBottomPadding() { 1346 if (mIsSummaryWithChildren && isGroupExpanded()) { 1347 return mIncreasedPaddingBetweenElements; 1348 } 1349 return 0; 1350 } 1351 1352 @Override 1353 public void setActualHeight(int height, boolean notifyListeners) { 1354 super.setActualHeight(height, notifyListeners); 1355 if (mGuts != null && mGuts.areGutsExposed()) { 1356 mGuts.setActualHeight(height); 1357 return; 1358 } 1359 int contentHeight = Math.max(getMinHeight(), height); 1360 mPrivateLayout.setContentHeight(contentHeight); 1361 mPublicLayout.setContentHeight(contentHeight); 1362 if (mIsSummaryWithChildren) { 1363 mChildrenContainer.setActualHeight(height); 1364 } 1365 if (mGuts != null) { 1366 mGuts.setActualHeight(height); 1367 } 1368 } 1369 1370 @Override 1371 public int getMaxContentHeight() { 1372 if (mIsSummaryWithChildren && !mShowingPublic) { 1373 return mChildrenContainer.getMaxContentHeight(); 1374 } 1375 NotificationContentView showingLayout = getShowingLayout(); 1376 return showingLayout.getMaxHeight(); 1377 } 1378 1379 @Override 1380 public int getMinHeight() { 1381 if (mIsHeadsUp && mHeadsUpManager.isTrackingHeadsUp()) { 1382 return getPinnedHeadsUpHeight(false /* atLeastMinHeight */); 1383 } else if (mIsSummaryWithChildren && !isGroupExpanded() && !mShowingPublic) { 1384 return mChildrenContainer.getMinHeight(); 1385 } else if (mIsHeadsUp) { 1386 return mHeadsUpHeight; 1387 } 1388 NotificationContentView showingLayout = getShowingLayout(); 1389 return showingLayout.getMinHeight(); 1390 } 1391 1392 @Override 1393 public int getCollapsedHeight() { 1394 if (mIsSummaryWithChildren && !mShowingPublic) { 1395 return mChildrenContainer.getCollapsedHeight(); 1396 } 1397 return getMinHeight(); 1398 } 1399 1400 @Override 1401 public void setClipTopAmount(int clipTopAmount) { 1402 super.setClipTopAmount(clipTopAmount); 1403 mPrivateLayout.setClipTopAmount(clipTopAmount); 1404 mPublicLayout.setClipTopAmount(clipTopAmount); 1405 if (mGuts != null) { 1406 mGuts.setClipTopAmount(clipTopAmount); 1407 } 1408 } 1409 1410 public boolean isMaxExpandHeightInitialized() { 1411 return mMaxExpandHeight != 0; 1412 } 1413 1414 public NotificationContentView getShowingLayout() { 1415 return mShowingPublic ? mPublicLayout : mPrivateLayout; 1416 } 1417 1418 @Override 1419 public void setShowingLegacyBackground(boolean showing) { 1420 super.setShowingLegacyBackground(showing); 1421 mPrivateLayout.setShowingLegacyBackground(showing); 1422 mPublicLayout.setShowingLegacyBackground(showing); 1423 } 1424 1425 @Override 1426 protected void updateBackgroundTint() { 1427 super.updateBackgroundTint(); 1428 updateBackgroundForGroupState(); 1429 if (mIsSummaryWithChildren) { 1430 List<ExpandableNotificationRow> notificationChildren = 1431 mChildrenContainer.getNotificationChildren(); 1432 for (int i = 0; i < notificationChildren.size(); i++) { 1433 ExpandableNotificationRow child = notificationChildren.get(i); 1434 child.updateBackgroundForGroupState(); 1435 } 1436 } 1437 } 1438 1439 /** 1440 * Called when a group has finished animating from collapsed or expanded state. 1441 */ 1442 public void onFinishedExpansionChange() { 1443 mGroupExpansionChanging = false; 1444 updateBackgroundForGroupState(); 1445 } 1446 1447 /** 1448 * Updates the parent and children backgrounds in a group based on the expansion state. 1449 */ 1450 public void updateBackgroundForGroupState() { 1451 if (mIsSummaryWithChildren) { 1452 // Only when the group has finished expanding do we hide its background. 1453 mShowNoBackground = isGroupExpanded() && !isGroupExpansionChanging() && !isUserLocked(); 1454 mChildrenContainer.updateHeaderForExpansion(mShowNoBackground); 1455 List<ExpandableNotificationRow> children = mChildrenContainer.getNotificationChildren(); 1456 for (int i = 0; i < children.size(); i++) { 1457 children.get(i).updateBackgroundForGroupState(); 1458 } 1459 } else if (isChildInGroup()) { 1460 final int childColor = getShowingLayout().getBackgroundColorForExpansionState(); 1461 // Only show a background if the group is expanded OR if it is expanding / collapsing 1462 // and has a custom background color 1463 final boolean showBackground = isGroupExpanded() 1464 || ((mNotificationParent.isGroupExpansionChanging() 1465 || mNotificationParent.isUserLocked()) && childColor != 0); 1466 mShowNoBackground = !showBackground; 1467 } else { 1468 // Only children or parents ever need no background. 1469 mShowNoBackground = false; 1470 } 1471 updateOutline(); 1472 updateBackground(); 1473 } 1474 1475 public void setExpansionLogger(ExpansionLogger logger, String key) { 1476 mLogger = logger; 1477 mLoggingKey = key; 1478 } 1479 1480 @Override 1481 public float getIncreasedPaddingAmount() { 1482 if (mIsSummaryWithChildren) { 1483 if (isGroupExpanded()) { 1484 return 1.0f; 1485 } else if (isUserLocked()) { 1486 return mChildrenContainer.getGroupExpandFraction(); 1487 } 1488 } 1489 return 0.0f; 1490 } 1491 1492 @Override 1493 protected boolean disallowSingleClick(MotionEvent event) { 1494 float x = event.getX(); 1495 float y = event.getY(); 1496 NotificationHeaderView header = getVisibleNotificationHeader(); 1497 if (header != null) { 1498 return header.isInTouchRect(x - getTranslation(), y); 1499 } 1500 return super.disallowSingleClick(event); 1501 } 1502 1503 private void logExpansionEvent(boolean userAction, boolean wasExpanded) { 1504 final boolean nowExpanded = isExpanded(); 1505 if (wasExpanded != nowExpanded && mLogger != null) { 1506 mLogger.logNotificationExpansion(mLoggingKey, userAction, nowExpanded) ; 1507 } 1508 } 1509 1510 public interface OnExpandClickListener { 1511 void onExpandClicked(NotificationData.Entry clickedEntry, boolean nowExpanded); 1512 } 1513} 1514