1 /* 2 * Copyright (C) 2018 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.bubbles; 18 19 import static android.view.Display.INVALID_DISPLAY; 20 21 import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_BUBBLE_EXPANDED_VIEW; 22 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; 23 import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; 24 25 import android.app.ActivityOptions; 26 import android.app.ActivityTaskManager; 27 import android.app.ActivityView; 28 import android.app.PendingIntent; 29 import android.content.ComponentName; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.content.pm.ApplicationInfo; 33 import android.content.pm.PackageManager; 34 import android.content.res.Resources; 35 import android.content.res.TypedArray; 36 import android.graphics.Color; 37 import android.graphics.Insets; 38 import android.graphics.Point; 39 import android.graphics.Rect; 40 import android.graphics.drawable.Drawable; 41 import android.graphics.drawable.ShapeDrawable; 42 import android.os.RemoteException; 43 import android.service.notification.StatusBarNotification; 44 import android.util.AttributeSet; 45 import android.util.Log; 46 import android.util.StatsLog; 47 import android.view.View; 48 import android.view.WindowInsets; 49 import android.view.WindowManager; 50 import android.widget.LinearLayout; 51 52 import com.android.internal.policy.ScreenDecorationsUtils; 53 import com.android.systemui.Dependency; 54 import com.android.systemui.R; 55 import com.android.systemui.recents.TriangleShape; 56 import com.android.systemui.statusbar.AlphaOptimizedButton; 57 58 /** 59 * Container for the expanded bubble view, handles rendering the caret and settings icon. 60 */ 61 public class BubbleExpandedView extends LinearLayout implements View.OnClickListener { 62 private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleExpandedView" : TAG_BUBBLES; 63 64 private enum ActivityViewStatus { 65 // ActivityView is being initialized, cannot start an activity yet. 66 INITIALIZING, 67 // ActivityView is initialized, and ready to start an activity. 68 INITIALIZED, 69 // Activity runs in the ActivityView. 70 ACTIVITY_STARTED, 71 // ActivityView is released, so activity launching will no longer be permitted. 72 RELEASED, 73 } 74 75 // The triangle pointing to the expanded view 76 private View mPointerView; 77 private int mPointerMargin; 78 79 private AlphaOptimizedButton mSettingsIcon; 80 81 // Views for expanded state 82 private ActivityView mActivityView; 83 84 private ActivityViewStatus mActivityViewStatus = ActivityViewStatus.INITIALIZING; 85 private int mTaskId = -1; 86 87 private PendingIntent mBubbleIntent; 88 89 private boolean mKeyboardVisible; 90 private boolean mNeedsNewHeight; 91 92 private Point mDisplaySize; 93 private int mMinHeight; 94 private int mSettingsIconHeight; 95 private int mPointerWidth; 96 private int mPointerHeight; 97 private ShapeDrawable mPointerDrawable; 98 private Rect mTempRect = new Rect(); 99 private int[] mTempLoc = new int[2]; 100 private int mExpandedViewTouchSlop; 101 102 private Bubble mBubble; 103 private PackageManager mPm; 104 private String mAppName; 105 private Drawable mAppIcon; 106 107 private BubbleController mBubbleController = Dependency.get(BubbleController.class); 108 private WindowManager mWindowManager; 109 110 private BubbleStackView mStackView; 111 112 private ActivityView.StateCallback mStateCallback = new ActivityView.StateCallback() { 113 @Override 114 public void onActivityViewReady(ActivityView view) { 115 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 116 Log.d(TAG, "onActivityViewReady: mActivityViewStatus=" + mActivityViewStatus 117 + " bubble=" + getBubbleKey()); 118 } 119 switch (mActivityViewStatus) { 120 case INITIALIZING: 121 case INITIALIZED: 122 // Custom options so there is no activity transition animation 123 ActivityOptions options = ActivityOptions.makeCustomAnimation(getContext(), 124 0 /* enterResId */, 0 /* exitResId */); 125 // Post to keep the lifecycle normal 126 post(() -> { 127 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 128 Log.d(TAG, "onActivityViewReady: calling startActivity, " 129 + "bubble=" + getBubbleKey()); 130 } 131 try { 132 mActivityView.startActivity(mBubbleIntent, options); 133 } catch (RuntimeException e) { 134 // If there's a runtime exception here then there's something 135 // wrong with the intent, we can't really recover / try to populate 136 // the bubble again so we'll just remove it. 137 Log.w(TAG, "Exception while displaying bubble: " + getBubbleKey() 138 + ", " + e.getMessage() + "; removing bubble"); 139 mBubbleController.removeBubble(mBubble.getKey(), 140 BubbleController.DISMISS_INVALID_INTENT); 141 } 142 }); 143 mActivityViewStatus = ActivityViewStatus.ACTIVITY_STARTED; 144 } 145 } 146 147 @Override 148 public void onActivityViewDestroyed(ActivityView view) { 149 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 150 Log.d(TAG, "onActivityViewDestroyed: mActivityViewStatus=" + mActivityViewStatus 151 + " bubble=" + getBubbleKey()); 152 } 153 mActivityViewStatus = ActivityViewStatus.RELEASED; 154 } 155 156 @Override 157 public void onTaskCreated(int taskId, ComponentName componentName) { 158 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 159 Log.d(TAG, "onTaskCreated: taskId=" + taskId 160 + " bubble=" + getBubbleKey()); 161 } 162 // Since Bubble ActivityView applies singleTaskDisplay this is 163 // guaranteed to only be called once per ActivityView. The taskId is 164 // saved to use for removeTask, preventing appearance in recent tasks. 165 mTaskId = taskId; 166 } 167 168 /** 169 * This is only called for tasks on this ActivityView, which is also set to 170 * single-task mode -- meaning never more than one task on this display. If a task 171 * is being removed, it's the top Activity finishing and this bubble should 172 * be removed or collapsed. 173 */ 174 @Override 175 public void onTaskRemovalStarted(int taskId) { 176 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 177 Log.d(TAG, "onTaskRemovalStarted: taskId=" + taskId 178 + " mActivityViewStatus=" + mActivityViewStatus 179 + " bubble=" + getBubbleKey()); 180 } 181 if (mBubble != null) { 182 // Must post because this is called from a binder thread. 183 post(() -> mBubbleController.removeBubble(mBubble.getKey(), 184 BubbleController.DISMISS_TASK_FINISHED)); 185 } 186 } 187 }; 188 BubbleExpandedView(Context context)189 public BubbleExpandedView(Context context) { 190 this(context, null); 191 } 192 BubbleExpandedView(Context context, AttributeSet attrs)193 public BubbleExpandedView(Context context, AttributeSet attrs) { 194 this(context, attrs, 0); 195 } 196 BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr)197 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr) { 198 this(context, attrs, defStyleAttr, 0); 199 } 200 BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)201 public BubbleExpandedView(Context context, AttributeSet attrs, int defStyleAttr, 202 int defStyleRes) { 203 super(context, attrs, defStyleAttr, defStyleRes); 204 mPm = context.getPackageManager(); 205 mDisplaySize = new Point(); 206 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); 207 // Get the real size -- this includes screen decorations (notches, statusbar, navbar). 208 mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize); 209 Resources res = getResources(); 210 mMinHeight = res.getDimensionPixelSize(R.dimen.bubble_expanded_default_height); 211 mPointerMargin = res.getDimensionPixelSize(R.dimen.bubble_pointer_margin); 212 mExpandedViewTouchSlop = res.getDimensionPixelSize(R.dimen.bubble_expanded_view_slop); 213 } 214 215 @Override onFinishInflate()216 protected void onFinishInflate() { 217 super.onFinishInflate(); 218 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 219 Log.d(TAG, "onFinishInflate: bubble=" + getBubbleKey()); 220 } 221 222 Resources res = getResources(); 223 mPointerView = findViewById(R.id.pointer_view); 224 mPointerWidth = res.getDimensionPixelSize(R.dimen.bubble_pointer_width); 225 mPointerHeight = res.getDimensionPixelSize(R.dimen.bubble_pointer_height); 226 227 228 mPointerDrawable = new ShapeDrawable(TriangleShape.create( 229 mPointerWidth, mPointerHeight, true /* pointUp */)); 230 mPointerView.setBackground(mPointerDrawable); 231 mPointerView.setVisibility(INVISIBLE); 232 233 mSettingsIconHeight = getContext().getResources().getDimensionPixelSize( 234 R.dimen.bubble_settings_size); 235 mSettingsIcon = findViewById(R.id.settings_button); 236 mSettingsIcon.setOnClickListener(this); 237 238 mActivityView = new ActivityView(mContext, null /* attrs */, 0 /* defStyle */, 239 true /* singleTaskInstance */); 240 // Set ActivityView's alpha value as zero, since there is no view content to be shown. 241 setContentVisibility(false); 242 addView(mActivityView); 243 244 // Expanded stack layout, top to bottom: 245 // Expanded view container 246 // ==> bubble row 247 // ==> expanded view 248 // ==> activity view 249 // ==> manage button 250 bringChildToFront(mActivityView); 251 bringChildToFront(mSettingsIcon); 252 253 applyThemeAttrs(); 254 255 setOnApplyWindowInsetsListener((View view, WindowInsets insets) -> { 256 // Keep track of IME displaying because we should not make any adjustments that might 257 // cause a config change while the IME is displayed otherwise it'll loose focus. 258 final int keyboardHeight = insets.getSystemWindowInsetBottom() 259 - insets.getStableInsetBottom(); 260 mKeyboardVisible = keyboardHeight != 0; 261 if (!mKeyboardVisible && mNeedsNewHeight) { 262 updateHeight(); 263 } 264 return view.onApplyWindowInsets(insets); 265 }); 266 } 267 getBubbleKey()268 private String getBubbleKey() { 269 return mBubble != null ? mBubble.getKey() : "null"; 270 } 271 applyThemeAttrs()272 void applyThemeAttrs() { 273 TypedArray ta = getContext().obtainStyledAttributes(R.styleable.BubbleExpandedView); 274 int bgColor = ta.getColor( 275 R.styleable.BubbleExpandedView_android_colorBackgroundFloating, Color.WHITE); 276 float cornerRadius = ta.getDimension( 277 R.styleable.BubbleExpandedView_android_dialogCornerRadius, 0); 278 ta.recycle(); 279 280 // Update triangle color. 281 mPointerDrawable.setTint(bgColor); 282 283 // Update ActivityView cornerRadius 284 if (ScreenDecorationsUtils.supportsRoundedCornersOnWindows(mContext.getResources())) { 285 mActivityView.setCornerRadius(cornerRadius); 286 } 287 } 288 289 @Override onDetachedFromWindow()290 protected void onDetachedFromWindow() { 291 super.onDetachedFromWindow(); 292 mKeyboardVisible = false; 293 mNeedsNewHeight = false; 294 if (mActivityView != null) { 295 mActivityView.setForwardedInsets(Insets.of(0, 0, 0, 0)); 296 } 297 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 298 Log.d(TAG, "onDetachedFromWindow: bubble=" + getBubbleKey()); 299 } 300 } 301 302 /** 303 * Set visibility of contents in the expanded state. 304 * 305 * @param visibility {@code true} if the contents should be visible on the screen. 306 * 307 * Note that this contents visibility doesn't affect visibility at {@link android.view.View}, 308 * and setting {@code false} actually means rendering the contents in transparent. 309 */ setContentVisibility(boolean visibility)310 void setContentVisibility(boolean visibility) { 311 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 312 Log.d(TAG, "setContentVisibility: visibility=" + visibility 313 + " bubble=" + getBubbleKey()); 314 } 315 final float alpha = visibility ? 1f : 0f; 316 mPointerView.setAlpha(alpha); 317 if (mActivityView != null) { 318 mActivityView.setAlpha(alpha); 319 } 320 } 321 322 /** 323 * Called by {@link BubbleStackView} when the insets for the expanded state should be updated. 324 * This should be done post-move and post-animation. 325 */ updateInsets(WindowInsets insets)326 void updateInsets(WindowInsets insets) { 327 if (usingActivityView()) { 328 int[] screenLoc = mActivityView.getLocationOnScreen(); 329 final int activityViewBottom = screenLoc[1] + mActivityView.getHeight(); 330 final int keyboardTop = mDisplaySize.y - Math.max(insets.getSystemWindowInsetBottom(), 331 insets.getDisplayCutout() != null 332 ? insets.getDisplayCutout().getSafeInsetBottom() 333 : 0); 334 final int insetsBottom = Math.max(activityViewBottom - keyboardTop, 0); 335 mActivityView.setForwardedInsets(Insets.of(0, 0, 0, insetsBottom)); 336 } 337 } 338 339 /** 340 * Sets the bubble used to populate this view. 341 */ setBubble(Bubble bubble, BubbleStackView stackView, String appName)342 public void setBubble(Bubble bubble, BubbleStackView stackView, String appName) { 343 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 344 Log.d(TAG, "setBubble: bubble=" + (bubble != null ? bubble.getKey() : "null")); 345 } 346 347 mStackView = stackView; 348 mBubble = bubble; 349 mAppName = appName; 350 351 try { 352 ApplicationInfo info = mPm.getApplicationInfo( 353 bubble.getPackageName(), 354 PackageManager.MATCH_UNINSTALLED_PACKAGES 355 | PackageManager.MATCH_DISABLED_COMPONENTS 356 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE 357 | PackageManager.MATCH_DIRECT_BOOT_AWARE); 358 mAppIcon = mPm.getApplicationIcon(info); 359 } catch (PackageManager.NameNotFoundException e) { 360 // Do nothing. 361 } 362 if (mAppIcon == null) { 363 mAppIcon = mPm.getDefaultActivityIcon(); 364 } 365 applyThemeAttrs(); 366 showSettingsIcon(); 367 updateExpandedView(); 368 } 369 370 /** 371 * Lets activity view know it should be shown / populated. 372 */ populateExpandedView()373 public void populateExpandedView() { 374 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 375 Log.d(TAG, "populateExpandedView: " 376 + "bubble=" + getBubbleKey()); 377 } 378 379 if (usingActivityView()) { 380 mActivityView.setCallback(mStateCallback); 381 } else { 382 Log.e(TAG, "Cannot populate expanded view."); 383 } 384 } 385 386 /** 387 * Updates the bubble backing this view. This will not re-populate ActivityView, it will 388 * only update the deep-links in the title, and the height of the view. 389 */ update(Bubble bubble)390 public void update(Bubble bubble) { 391 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 392 Log.d(TAG, "update: bubble=" + (bubble != null ? bubble.getKey() : "null")); 393 } 394 if (bubble.getKey().equals(mBubble.getKey())) { 395 mBubble = bubble; 396 updateSettingsContentDescription(); 397 updateHeight(); 398 } else { 399 Log.w(TAG, "Trying to update entry with different key, new bubble: " 400 + bubble.getKey() + " old bubble: " + bubble.getKey()); 401 } 402 } 403 updateExpandedView()404 private void updateExpandedView() { 405 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 406 Log.d(TAG, "updateExpandedView: bubble=" 407 + getBubbleKey()); 408 } 409 410 mBubbleIntent = mBubble.getBubbleIntent(mContext); 411 if (mBubbleIntent != null) { 412 setContentVisibility(false); 413 mActivityView.setVisibility(VISIBLE); 414 } 415 updateView(); 416 } 417 performBackPressIfNeeded()418 boolean performBackPressIfNeeded() { 419 if (!usingActivityView()) { 420 return false; 421 } 422 mActivityView.performBackPress(); 423 return true; 424 } 425 updateHeight()426 void updateHeight() { 427 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 428 Log.d(TAG, "updateHeight: bubble=" + getBubbleKey()); 429 } 430 if (usingActivityView()) { 431 float desiredHeight = Math.max(mBubble.getDesiredHeight(mContext), mMinHeight); 432 float height = Math.min(desiredHeight, getMaxExpandedHeight()); 433 height = Math.max(height, mMinHeight); 434 LayoutParams lp = (LayoutParams) mActivityView.getLayoutParams(); 435 mNeedsNewHeight = lp.height != height; 436 if (!mKeyboardVisible) { 437 // If the keyboard is visible... don't adjust the height because that will cause 438 // a configuration change and the keyboard will be lost. 439 lp.height = (int) height; 440 mActivityView.setLayoutParams(lp); 441 mNeedsNewHeight = false; 442 } 443 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 444 Log.d(TAG, "updateHeight: bubble=" + getBubbleKey() + " height=" + height 445 + " mNeedsNewHeight=" + mNeedsNewHeight); 446 } 447 } 448 } 449 getMaxExpandedHeight()450 private int getMaxExpandedHeight() { 451 mWindowManager.getDefaultDisplay().getRealSize(mDisplaySize); 452 int[] windowLocation = mActivityView.getLocationOnScreen(); 453 int bottomInset = getRootWindowInsets() != null 454 ? getRootWindowInsets().getStableInsetBottom() 455 : 0; 456 return mDisplaySize.y - windowLocation[1] - mSettingsIconHeight - mPointerHeight 457 - mPointerMargin - bottomInset; 458 } 459 460 /** 461 * Whether the provided x, y values (in raw coordinates) are in a touchable area of the 462 * expanded view. 463 * 464 * The touchable areas are the ActivityView (plus some slop around it) and the manage button. 465 */ intersectingTouchableContent(int rawX, int rawY)466 boolean intersectingTouchableContent(int rawX, int rawY) { 467 mTempRect.setEmpty(); 468 if (mActivityView != null) { 469 mTempLoc = mActivityView.getLocationOnScreen(); 470 mTempRect.set(mTempLoc[0] - mExpandedViewTouchSlop, 471 mTempLoc[1] - mExpandedViewTouchSlop, 472 mTempLoc[0] + mActivityView.getWidth() + mExpandedViewTouchSlop, 473 mTempLoc[1] + mActivityView.getHeight() + mExpandedViewTouchSlop); 474 } 475 if (mTempRect.contains(rawX, rawY)) { 476 return true; 477 } 478 mTempLoc = mSettingsIcon.getLocationOnScreen(); 479 mTempRect.set(mTempLoc[0], 480 mTempLoc[1], 481 mTempLoc[0] + mSettingsIcon.getWidth(), 482 mTempLoc[1] + mSettingsIcon.getHeight()); 483 if (mTempRect.contains(rawX, rawY)) { 484 return true; 485 } 486 return false; 487 } 488 489 @Override onClick(View view)490 public void onClick(View view) { 491 if (mBubble == null) { 492 return; 493 } 494 int id = view.getId(); 495 if (id == R.id.settings_button) { 496 Intent intent = mBubble.getSettingsIntent(); 497 mStackView.collapseStack(() -> { 498 mContext.startActivityAsUser(intent, mBubble.getEntry().notification.getUser()); 499 logBubbleClickEvent(mBubble, 500 StatsLog.BUBBLE_UICHANGED__ACTION__HEADER_GO_TO_SETTINGS); 501 }); 502 } 503 } 504 updateSettingsContentDescription()505 private void updateSettingsContentDescription() { 506 mSettingsIcon.setContentDescription(getResources().getString( 507 R.string.bubbles_settings_button_description, mAppName)); 508 } 509 showSettingsIcon()510 void showSettingsIcon() { 511 updateSettingsContentDescription(); 512 mSettingsIcon.setVisibility(VISIBLE); 513 } 514 515 /** 516 * Update appearance of the expanded view being displayed. 517 */ updateView()518 public void updateView() { 519 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 520 Log.d(TAG, "updateView: bubble=" 521 + getBubbleKey()); 522 } 523 if (usingActivityView() 524 && mActivityView.getVisibility() == VISIBLE 525 && mActivityView.isAttachedToWindow()) { 526 mActivityView.onLocationChanged(); 527 } 528 updateHeight(); 529 } 530 531 /** 532 * Set the x position that the tip of the triangle should point to. 533 */ setPointerPosition(float x)534 public void setPointerPosition(float x) { 535 float halfPointerWidth = mPointerWidth / 2f; 536 float pointerLeft = x - halfPointerWidth; 537 mPointerView.setTranslationX(pointerLeft); 538 mPointerView.setVisibility(VISIBLE); 539 } 540 541 /** 542 * Removes and releases an ActivityView if one was previously created for this bubble. 543 */ cleanUpExpandedState()544 public void cleanUpExpandedState() { 545 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 546 Log.d(TAG, "cleanUpExpandedState: mActivityViewStatus=" + mActivityViewStatus 547 + ", bubble=" + getBubbleKey()); 548 } 549 if (mActivityView == null) { 550 return; 551 } 552 switch (mActivityViewStatus) { 553 case INITIALIZED: 554 case ACTIVITY_STARTED: 555 mActivityView.release(); 556 } 557 if (mTaskId != -1) { 558 try { 559 ActivityTaskManager.getService().removeTask(mTaskId); 560 } catch (RemoteException e) { 561 Log.w(TAG, "Failed to remove taskId " + mTaskId); 562 } 563 mTaskId = -1; 564 } 565 removeView(mActivityView); 566 567 mActivityView = null; 568 } 569 570 /** 571 * Called when the last task is removed from a {@link android.hardware.display.VirtualDisplay} 572 * which {@link ActivityView} uses. 573 */ notifyDisplayEmpty()574 void notifyDisplayEmpty() { 575 if (DEBUG_BUBBLE_EXPANDED_VIEW) { 576 Log.d(TAG, "notifyDisplayEmpty: bubble=" 577 + getBubbleKey() 578 + " mActivityViewStatus=" + mActivityViewStatus); 579 } 580 if (mActivityViewStatus == ActivityViewStatus.ACTIVITY_STARTED) { 581 mActivityViewStatus = ActivityViewStatus.INITIALIZED; 582 } 583 } 584 usingActivityView()585 private boolean usingActivityView() { 586 return mBubbleIntent != null && mActivityView != null; 587 } 588 589 /** 590 * @return the display id of the virtual display. 591 */ getVirtualDisplayId()592 public int getVirtualDisplayId() { 593 if (usingActivityView()) { 594 return mActivityView.getVirtualDisplayId(); 595 } 596 return INVALID_DISPLAY; 597 } 598 599 /** 600 * Logs bubble UI click event. 601 * 602 * @param bubble the bubble notification entry that user is interacting with. 603 * @param action the user interaction enum. 604 */ logBubbleClickEvent(Bubble bubble, int action)605 private void logBubbleClickEvent(Bubble bubble, int action) { 606 StatusBarNotification notification = bubble.getEntry().notification; 607 StatsLog.write(StatsLog.BUBBLE_UI_CHANGED, 608 notification.getPackageName(), 609 notification.getNotification().getChannelId(), 610 notification.getId(), 611 mStackView.getBubbleIndex(mStackView.getExpandedBubble()), 612 mStackView.getBubbleCount(), 613 action, 614 mStackView.getNormalizedXPosition(), 615 mStackView.getNormalizedYPosition(), 616 bubble.showInShadeWhenBubble(), 617 bubble.isOngoing(), 618 false /* isAppForeground (unused) */); 619 } 620 } 621