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