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