1 /* 2 * Copyright (C) 2020 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.wm.shell.bubbles; 18 19 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_DELETED; 20 import static android.service.notification.NotificationListenerService.NOTIFICATION_CHANNEL_OR_GROUP_UPDATED; 21 import static android.service.notification.NotificationListenerService.REASON_CANCEL; 22 import static android.view.View.INVISIBLE; 23 import static android.view.View.VISIBLE; 24 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 25 import static android.view.WindowManager.TRANSIT_CHANGE; 26 27 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES; 28 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; 29 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_BLOCKED; 30 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_GROUP_CANCELLED; 31 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_INVALID_INTENT; 32 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NOTIF_CANCEL; 33 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_BUBBLE_UP; 34 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_NO_LONGER_BUBBLE; 35 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_PACKAGE_REMOVED; 36 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED; 37 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED; 38 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES; 39 40 import android.annotation.BinderThread; 41 import android.annotation.NonNull; 42 import android.annotation.UserIdInt; 43 import android.app.ActivityManager; 44 import android.app.ActivityOptions; 45 import android.app.Notification; 46 import android.app.NotificationChannel; 47 import android.app.PendingIntent; 48 import android.app.TaskInfo; 49 import android.content.BroadcastReceiver; 50 import android.content.ClipDescription; 51 import android.content.Context; 52 import android.content.Intent; 53 import android.content.IntentFilter; 54 import android.content.pm.LauncherApps; 55 import android.content.pm.PackageManager; 56 import android.content.pm.ShortcutInfo; 57 import android.content.pm.UserInfo; 58 import android.content.res.Configuration; 59 import android.graphics.PixelFormat; 60 import android.graphics.Point; 61 import android.graphics.Rect; 62 import android.graphics.drawable.Icon; 63 import android.os.Binder; 64 import android.os.Bundle; 65 import android.os.Handler; 66 import android.os.RemoteException; 67 import android.os.ServiceManager; 68 import android.os.UserHandle; 69 import android.os.UserManager; 70 import android.service.notification.NotificationListenerService; 71 import android.service.notification.NotificationListenerService.RankingMap; 72 import android.util.Log; 73 import android.util.Pair; 74 import android.util.SparseArray; 75 import android.view.IWindowManager; 76 import android.view.SurfaceControl; 77 import android.view.View; 78 import android.view.ViewGroup; 79 import android.view.ViewRootImpl; 80 import android.view.WindowInsets; 81 import android.view.WindowManager; 82 import android.window.ScreenCapture; 83 import android.window.ScreenCapture.SynchronousScreenCaptureListener; 84 import android.window.TransitionInfo; 85 import android.window.WindowContainerToken; 86 import android.window.WindowContainerTransaction; 87 88 import androidx.annotation.MainThread; 89 import androidx.annotation.Nullable; 90 91 import com.android.internal.annotations.VisibleForTesting; 92 import com.android.internal.protolog.ProtoLog; 93 import com.android.internal.statusbar.IStatusBarService; 94 import com.android.internal.util.CollectionUtils; 95 import com.android.launcher3.icons.BubbleIconFactory; 96 import com.android.wm.shell.Flags; 97 import com.android.wm.shell.R; 98 import com.android.wm.shell.ShellTaskOrganizer; 99 import com.android.wm.shell.bubbles.bar.BubbleBarDragListener; 100 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView; 101 import com.android.wm.shell.bubbles.shortcut.BubbleShortcutHelper; 102 import com.android.wm.shell.common.DisplayController; 103 import com.android.wm.shell.common.DisplayImeController; 104 import com.android.wm.shell.common.DisplayInsetsController; 105 import com.android.wm.shell.common.ExternalInterfaceBinder; 106 import com.android.wm.shell.common.FloatingContentCoordinator; 107 import com.android.wm.shell.common.ImeListener; 108 import com.android.wm.shell.common.RemoteCallable; 109 import com.android.wm.shell.common.ShellExecutor; 110 import com.android.wm.shell.common.SingleInstanceRemoteListener; 111 import com.android.wm.shell.common.SyncTransactionQueue; 112 import com.android.wm.shell.common.TaskStackListenerCallback; 113 import com.android.wm.shell.common.TaskStackListenerImpl; 114 import com.android.wm.shell.draganddrop.DragAndDropController; 115 import com.android.wm.shell.onehanded.OneHandedController; 116 import com.android.wm.shell.onehanded.OneHandedTransitionCallback; 117 import com.android.wm.shell.shared.annotations.ShellBackgroundThread; 118 import com.android.wm.shell.shared.annotations.ShellMainThread; 119 import com.android.wm.shell.shared.bubbles.BubbleAnythingFlagHelper; 120 import com.android.wm.shell.shared.bubbles.BubbleBarLocation; 121 import com.android.wm.shell.shared.bubbles.BubbleBarLocation.UpdateSource; 122 import com.android.wm.shell.shared.bubbles.BubbleBarUpdate; 123 import com.android.wm.shell.shared.bubbles.BubbleDropTargetBoundsProvider; 124 import com.android.wm.shell.shared.bubbles.ContextUtils; 125 import com.android.wm.shell.shared.bubbles.DeviceConfig; 126 import com.android.wm.shell.shared.draganddrop.DragAndDropConstants; 127 import com.android.wm.shell.sysui.ConfigurationChangeListener; 128 import com.android.wm.shell.sysui.ShellCommandHandler; 129 import com.android.wm.shell.sysui.ShellController; 130 import com.android.wm.shell.sysui.ShellInit; 131 import com.android.wm.shell.taskview.TaskView; 132 import com.android.wm.shell.taskview.TaskViewController; 133 import com.android.wm.shell.taskview.TaskViewRepository; 134 import com.android.wm.shell.taskview.TaskViewTaskController; 135 import com.android.wm.shell.taskview.TaskViewTransitions; 136 import com.android.wm.shell.transition.Transitions; 137 138 import java.io.PrintWriter; 139 import java.util.ArrayList; 140 import java.util.HashMap; 141 import java.util.HashSet; 142 import java.util.List; 143 import java.util.Locale; 144 import java.util.Map; 145 import java.util.Objects; 146 import java.util.Optional; 147 import java.util.Set; 148 import java.util.concurrent.Executor; 149 import java.util.function.Consumer; 150 import java.util.function.IntConsumer; 151 152 /** 153 * Bubbles are a special type of content that can "float" on top of other apps or System UI. 154 * Bubbles can be expanded to show more content. 155 * 156 * The controller manages addition, removal, and visible state of bubbles on screen. 157 */ 158 public class BubbleController implements ConfigurationChangeListener, 159 RemoteCallable<BubbleController>, Bubbles.SysuiProxy.Provider, 160 BubbleBarDragListener, BubbleTaskUnfoldTransitionMerger { 161 162 private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES; 163 164 // Should match with PhoneWindowManager 165 private static final String SYSTEM_DIALOG_REASON_KEY = "reason"; 166 private static final String SYSTEM_DIALOG_REASON_GESTURE_NAV = "gestureNav"; 167 private static final String SYSTEM_DIALOG_REASON_RECENT_APPS = "recentapps"; 168 private static final String SYSTEM_DIALOG_REASON_HOME_KEY = "homekey"; 169 170 /** 171 * Common interface to send updates to bubble views. 172 */ 173 public interface BubbleViewCallback { 174 /** Called when the provided bubble should be removed. */ removeBubble(Bubble removedBubble)175 void removeBubble(Bubble removedBubble); 176 /** Called when the provided bubble should be added. */ addBubble(Bubble addedBubble)177 void addBubble(Bubble addedBubble); 178 /** Called when the provided bubble should be updated. */ updateBubble(Bubble updatedBubble)179 void updateBubble(Bubble updatedBubble); 180 /** Called when the provided bubble should be selected. */ selectionChanged(BubbleViewProvider selectedBubble)181 void selectionChanged(BubbleViewProvider selectedBubble); 182 /** Called when the provided bubble's suppression state has changed. */ suppressionChanged(Bubble bubble, boolean isSuppressed)183 void suppressionChanged(Bubble bubble, boolean isSuppressed); 184 /** Called when the expansion state of bubbles has changed. */ expansionChanged(boolean isExpanded)185 void expansionChanged(boolean isExpanded); 186 /** 187 * Called when the order of the bubble list has changed. Depending on the expanded state 188 * the pointer might need to be updated. 189 */ bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer)190 void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer); 191 /** Called when the bubble overflow empty state changes, used to show/hide the overflow. */ bubbleOverflowChanged(boolean hasBubbles)192 void bubbleOverflowChanged(boolean hasBubbles); 193 } 194 195 private final Context mContext; 196 private final BubblesImpl mImpl = new BubblesImpl(); 197 private Bubbles.BubbleExpandListener mExpandListener; 198 @Nullable private final BubbleStackView.SurfaceSynchronizer mSurfaceSynchronizer; 199 private final FloatingContentCoordinator mFloatingContentCoordinator; 200 private final BubbleDataRepository mDataRepository; 201 private final DisplayInsetsController mDisplayInsetsController; 202 private final DisplayImeController mDisplayImeController; 203 private final UserManager mUserManager; 204 private final LauncherApps mLauncherApps; 205 private final IStatusBarService mBarService; 206 private final WindowManager mWindowManager; 207 private final TaskStackListenerImpl mTaskStackListener; 208 private final ShellTaskOrganizer mTaskOrganizer; 209 private final DisplayController mDisplayController; 210 private final TaskViewController mTaskViewController; 211 private final Transitions mTransitions; 212 private final SyncTransactionQueue mSyncQueue; 213 private final ShellController mShellController; 214 private final ShellCommandHandler mShellCommandHandler; 215 private final IWindowManager mWmService; 216 private final BubbleTaskViewFactory mBubbleTaskViewFactory; 217 private final BubbleExpandedViewManager mExpandedViewManager; 218 private final ResizabilityChecker mResizabilityChecker; 219 220 // Used to post to main UI thread 221 private final ShellExecutor mMainExecutor; 222 private final Handler mMainHandler; 223 private final ShellExecutor mBackgroundExecutor; 224 225 private final BubbleLogger mLogger; 226 private final BubbleData mBubbleData; 227 @Nullable private BubbleStackView mStackView; 228 @Nullable private BubbleBarLayerView mLayerView; 229 private BubbleIconFactory mBubbleIconFactory; 230 private final BubblePositioner mBubblePositioner; 231 private Bubbles.SysuiProxy mSysuiProxy; 232 233 @Nullable private Runnable mOnImeHidden; 234 235 // Tracks the id of the current (foreground) user. 236 private int mCurrentUserId; 237 // Current profiles of the user (e.g. user with a workprofile) 238 private SparseArray<UserInfo> mCurrentProfiles; 239 // Saves data about active bubbles when users are switched. 240 private final SparseArray<UserBubbleData> mSavedUserBubbleData; 241 242 // Used when ranking updates occur and we check if things should bubble / unbubble 243 private NotificationListenerService.Ranking mTmpRanking; 244 245 // Callback that updates BubbleOverflowActivity on data change. 246 @Nullable private BubbleData.Listener mOverflowListener = null; 247 248 // Typically only load once & after user switches 249 private boolean mOverflowDataLoadNeeded = true; 250 251 /** 252 * When the shade status changes to SHADE (from anything but SHADE, like LOCKED) we'll select 253 * this bubble and expand the stack. 254 */ 255 @Nullable private BubbleEntry mNotifEntryToExpandOnShadeUnlock; 256 257 /** LayoutParams used to add the BubbleStackView to the window manager. */ 258 private WindowManager.LayoutParams mWmLayoutParams; 259 /** Whether or not the BubbleStackView has been added to the WindowManager. */ 260 private boolean mAddedToWindowManager = false; 261 262 /** 263 * Saved screen density, used to detect display size changes in {@link #onConfigurationChanged}. 264 */ 265 private int mDensityDpi = Configuration.DENSITY_DPI_UNDEFINED; 266 267 /** 268 * Saved screen bounds, used to detect screen size changes in {@link #onConfigurationChanged}. 269 */ 270 private final Rect mScreenBounds = new Rect(); 271 272 /** Saved font scale, used to detect font size changes in {@link #onConfigurationChanged}. */ 273 private float mFontScale = 0; 274 275 /** Saved locale, used to detect local changes in {@link #onConfigurationChanged}. */ 276 private Locale mLocale = null; 277 278 /** Saved direction, used to detect layout direction changes @link #onConfigChanged}. */ 279 private int mLayoutDirection = View.LAYOUT_DIRECTION_UNDEFINED; 280 281 /** Saved insets, used to detect WindowInset changes. */ 282 private WindowInsets mWindowInsets; 283 284 private boolean mInflateSynchronously; 285 286 /** True when user is in status bar unlock shade. */ 287 private boolean mIsStatusBarShade = true; 288 289 /** One handed mode controller to register transition listener. */ 290 private final Optional<OneHandedController> mOneHandedOptional; 291 /** Drag and drop controller to register listener for onDragStarted. */ 292 private final DragAndDropController mDragAndDropController; 293 /** Used to send bubble events to launcher. */ 294 private Bubbles.BubbleStateListener mBubbleStateListener; 295 /** 296 * Used to track previous navigation mode to detect switch to buttons navigation. Set to 297 * true to switch the bubble bar to the opposite side for 3 nav buttons mode on device boot. 298 */ 299 private boolean mIsPrevNavModeGestures = true; 300 /** Used to send updates to the views from {@link #mBubbleDataListener}. */ 301 private BubbleViewCallback mBubbleViewCallback; 302 303 private final BubbleTransitions mBubbleTransitions; 304 BubbleController(Context context, ShellInit shellInit, ShellCommandHandler shellCommandHandler, ShellController shellController, BubbleData data, @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, FloatingContentCoordinator floatingContentCoordinator, BubbleDataRepository dataRepository, @Nullable IStatusBarService statusBarService, WindowManager windowManager, DisplayInsetsController displayInsetsController, DisplayImeController displayImeController, UserManager userManager, LauncherApps launcherApps, BubbleLogger bubbleLogger, TaskStackListenerImpl taskStackListener, ShellTaskOrganizer organizer, BubblePositioner positioner, DisplayController displayController, Optional<OneHandedController> oneHandedOptional, DragAndDropController dragAndDropController, @ShellMainThread ShellExecutor mainExecutor, @ShellMainThread Handler mainHandler, @ShellBackgroundThread ShellExecutor bgExecutor, TaskViewRepository taskViewRepository, TaskViewTransitions taskViewTransitions, Transitions transitions, SyncTransactionQueue syncQueue, IWindowManager wmService, ResizabilityChecker resizabilityChecker)305 public BubbleController(Context context, 306 ShellInit shellInit, 307 ShellCommandHandler shellCommandHandler, 308 ShellController shellController, 309 BubbleData data, 310 @Nullable BubbleStackView.SurfaceSynchronizer synchronizer, 311 FloatingContentCoordinator floatingContentCoordinator, 312 BubbleDataRepository dataRepository, 313 @Nullable IStatusBarService statusBarService, 314 WindowManager windowManager, 315 DisplayInsetsController displayInsetsController, 316 DisplayImeController displayImeController, 317 UserManager userManager, 318 LauncherApps launcherApps, 319 BubbleLogger bubbleLogger, 320 TaskStackListenerImpl taskStackListener, 321 ShellTaskOrganizer organizer, 322 BubblePositioner positioner, 323 DisplayController displayController, 324 Optional<OneHandedController> oneHandedOptional, 325 DragAndDropController dragAndDropController, 326 @ShellMainThread ShellExecutor mainExecutor, 327 @ShellMainThread Handler mainHandler, 328 @ShellBackgroundThread ShellExecutor bgExecutor, 329 TaskViewRepository taskViewRepository, 330 TaskViewTransitions taskViewTransitions, 331 Transitions transitions, 332 SyncTransactionQueue syncQueue, 333 IWindowManager wmService, 334 ResizabilityChecker resizabilityChecker) { 335 mContext = context; 336 mShellCommandHandler = shellCommandHandler; 337 mShellController = shellController; 338 mLauncherApps = launcherApps; 339 mBarService = statusBarService == null 340 ? IStatusBarService.Stub.asInterface( 341 ServiceManager.getService(Context.STATUS_BAR_SERVICE)) 342 : statusBarService; 343 mWindowManager = windowManager; 344 mDisplayInsetsController = displayInsetsController; 345 mDisplayImeController = displayImeController; 346 mUserManager = userManager; 347 mFloatingContentCoordinator = floatingContentCoordinator; 348 mDataRepository = dataRepository; 349 mLogger = bubbleLogger; 350 mMainExecutor = mainExecutor; 351 mMainHandler = mainHandler; 352 mBackgroundExecutor = bgExecutor; 353 mTaskStackListener = taskStackListener; 354 mTaskOrganizer = organizer; 355 mSurfaceSynchronizer = synchronizer; 356 mCurrentUserId = ActivityManager.getCurrentUser(); 357 mBubblePositioner = positioner; 358 mBubbleData = data; 359 mSavedUserBubbleData = new SparseArray<>(); 360 mBubbleIconFactory = new BubbleIconFactory(context, 361 context.getResources().getDimensionPixelSize(R.dimen.bubble_size), 362 context.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size), 363 context.getResources().getColor( 364 com.android.launcher3.icons.R.color.important_conversation), 365 context.getResources().getDimensionPixelSize( 366 com.android.internal.R.dimen.importance_ring_stroke_width)); 367 mDisplayController = displayController; 368 final TaskViewTransitions tvTransitions; 369 if (TaskViewTransitions.useRepo()) { 370 tvTransitions = new TaskViewTransitions(transitions, taskViewRepository, organizer, 371 syncQueue); 372 } else { 373 tvTransitions = taskViewTransitions; 374 } 375 mTaskViewController = new BubbleTaskViewController(tvTransitions); 376 mBubbleTransitions = new BubbleTransitions(transitions, organizer, taskViewRepository, data, 377 tvTransitions, context); 378 mTransitions = transitions; 379 mOneHandedOptional = oneHandedOptional; 380 mDragAndDropController = dragAndDropController; 381 mSyncQueue = syncQueue; 382 mWmService = wmService; 383 shellInit.addInitCallback(this::onInit, this); 384 mBubbleTaskViewFactory = new BubbleTaskViewFactory() { 385 @Override 386 public BubbleTaskView create() { 387 TaskViewTaskController taskViewTaskController = new TaskViewTaskController( 388 context, organizer, mTaskViewController, syncQueue); 389 TaskView taskView = new TaskView(context, mTaskViewController, 390 taskViewTaskController); 391 return new BubbleTaskView(taskView, mainExecutor); 392 } 393 }; 394 mExpandedViewManager = BubbleExpandedViewManager.fromBubbleController(this); 395 mResizabilityChecker = resizabilityChecker; 396 } 397 registerOneHandedState(OneHandedController oneHanded)398 private void registerOneHandedState(OneHandedController oneHanded) { 399 oneHanded.registerTransitionCallback( 400 new OneHandedTransitionCallback() { 401 @Override 402 public void onStartFinished(Rect bounds) { 403 mMainExecutor.execute(() -> { 404 if (mStackView != null) { 405 mStackView.onVerticalOffsetChanged(bounds.top); 406 } 407 }); 408 } 409 410 @Override 411 public void onStopFinished(Rect bounds) { 412 mMainExecutor.execute(() -> { 413 if (mStackView != null) { 414 mStackView.onVerticalOffsetChanged(bounds.top); 415 } 416 }); 417 } 418 }); 419 } 420 onInit()421 protected void onInit() { 422 mBubbleViewCallback = isShowingAsBubbleBar() 423 ? mBubbleBarViewCallback 424 : mBubbleStackViewCallback; 425 mBubbleData.setListener(mBubbleDataListener); 426 mBubbleData.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged); 427 mDataRepository.setSuppressionChangedListener(this::onBubbleMetadataFlagChanged); 428 mBubbleData.setPendingIntentCancelledListener(bubble -> { 429 if (bubble.getPendingIntent() == null) { 430 return; 431 } 432 if (bubble.isPendingIntentActive() 433 || mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { 434 bubble.setPendingIntentCanceled(); 435 return; 436 } 437 mMainExecutor.execute(() -> removeBubble(bubble.getKey(), DISMISS_INVALID_INTENT)); 438 }); 439 440 BubblesImeListener bubblesImeListener = 441 new BubblesImeListener(mDisplayController, mContext.getDisplayId()); 442 // the insets controller is notified whenever the IME visibility changes whether the IME is 443 // requested by a bubbled task or non-bubbled task. in the latter case, we need to update 444 // the position of the stack to avoid overlapping with the IME. 445 mDisplayInsetsController.addInsetsChangedListener(mContext.getDisplayId(), 446 bubblesImeListener); 447 // the ime controller is notified when the IME is requested only by a bubbled task. 448 mDisplayImeController.addPositionProcessor(bubblesImeListener); 449 450 mBubbleData.setCurrentUserId(mCurrentUserId); 451 452 mTaskOrganizer.addLocusIdListener((taskId, locus, visible) -> 453 mBubbleData.onLocusVisibilityChanged(taskId, locus, visible)); 454 455 mLauncherApps.registerCallback(new LauncherApps.Callback() { 456 @Override 457 public void onPackageAdded(String s, UserHandle userHandle) {} 458 459 @Override 460 public void onPackageChanged(String s, UserHandle userHandle) {} 461 462 @Override 463 public void onPackageRemoved(String s, UserHandle userHandle) { 464 // Remove bubbles with this package name, since it has been uninstalled and attempts 465 // to open a bubble from an uninstalled app can cause issues. 466 mBubbleData.removeBubblesWithPackageName(s, DISMISS_PACKAGE_REMOVED); 467 } 468 469 @Override 470 public void onPackagesAvailable(String[] strings, UserHandle userHandle, boolean b) {} 471 472 @Override 473 public void onPackagesUnavailable(String[] packages, UserHandle userHandle, 474 boolean b) { 475 for (String packageName : packages) { 476 // Remove bubbles from unavailable apps. This can occur when the app is on 477 // external storage that has been removed. 478 mBubbleData.removeBubblesWithPackageName(packageName, DISMISS_PACKAGE_REMOVED); 479 } 480 } 481 482 @Override 483 public void onShortcutsChanged(String packageName, List<ShortcutInfo> validShortcuts, 484 UserHandle user) { 485 super.onShortcutsChanged(packageName, validShortcuts, user); 486 487 // Remove bubbles whose shortcuts aren't in the latest list of valid shortcuts. 488 mBubbleData.removeBubblesWithInvalidShortcuts( 489 packageName, validShortcuts, DISMISS_SHORTCUT_REMOVED); 490 } 491 }, mMainHandler); 492 493 mTransitions.registerObserver(new BubblesTransitionObserver(this, mBubbleData)); 494 495 mTaskStackListener.addListener(new TaskStackListenerCallback() { 496 @Override 497 public void onActivityRestartAttempt(ActivityManager.RunningTaskInfo task, 498 boolean homeTaskVisible, boolean clearedTask, boolean wasVisible) { 499 final int taskId = task.taskId; 500 Bubble bubble = mBubbleData.getBubbleInStackWithTaskId(taskId); 501 if (bubble != null) { 502 ProtoLog.d(WM_SHELL_BUBBLES, 503 "onActivityRestartAttempt - taskId=%d selecting matching bubble=%s", 504 taskId, bubble.getKey()); 505 mBubbleData.setSelectedBubbleAndExpandStack(bubble); 506 return; 507 } 508 509 bubble = mBubbleData.getOverflowBubbleWithTaskId(taskId); 510 if (bubble != null) { 511 ProtoLog.d(WM_SHELL_BUBBLES, "onActivityRestartAttempt - taskId=%d " 512 + "selecting matching overflow bubble=%s", 513 taskId, bubble.getKey()); 514 promoteBubbleFromOverflow(bubble); 515 mBubbleData.setExpanded(true); 516 } 517 } 518 }); 519 520 mDisplayController.addDisplayChangingController( 521 (displayId, fromRotation, toRotation, newDisplayAreaInfo, t) -> { 522 Rect newScreenBounds = new Rect(); 523 if (newDisplayAreaInfo != null) { 524 newScreenBounds = 525 newDisplayAreaInfo.configuration.windowConfiguration.getBounds(); 526 } 527 // This is triggered right before the rotation or new screen size is applied 528 if (fromRotation != toRotation || !newScreenBounds.equals(mScreenBounds)) { 529 if (mStackView != null) { 530 // Layout listener set on stackView will update the positioner 531 // once the rotation or screen change is applied 532 mStackView.onOrientationChanged(); 533 } 534 } 535 }); 536 537 mOneHandedOptional.ifPresent(this::registerOneHandedState); 538 mDragAndDropController.addListener(new DragAndDropController.DragAndDropListener() { 539 @Override 540 public void onDragStarted() { 541 collapseStack(); 542 } 543 }); 544 545 // Clear out any persisted bubbles on disk that no longer have a valid user. 546 List<UserInfo> users = mUserManager.getAliveUsers(); 547 mDataRepository.sanitizeBubbles(users); 548 549 // Init profiles 550 SparseArray<UserInfo> userProfiles = new SparseArray<>(); 551 for (UserInfo user : mUserManager.getProfiles(mCurrentUserId)) { 552 userProfiles.put(user.id, user); 553 } 554 mCurrentProfiles = userProfiles; 555 556 if (Flags.enableRetrievableBubbles()) { 557 registerShortcutBroadcastReceiver(); 558 } 559 560 mShellController.addConfigurationChangeListener(this); 561 mShellController.addExternalInterface(IBubbles.DESCRIPTOR, 562 this::createExternalInterface, this); 563 mShellCommandHandler.addDumpCallback(this::dump, this); 564 } 565 createExternalInterface()566 private ExternalInterfaceBinder createExternalInterface() { 567 return new IBubblesImpl(this); 568 } 569 570 @VisibleForTesting asBubbles()571 public Bubbles asBubbles() { 572 return mImpl; 573 } 574 575 @VisibleForTesting getImplCachedState()576 public BubblesImpl.CachedState getImplCachedState() { 577 return mImpl.mCachedState; 578 } 579 getMainExecutor()580 public ShellExecutor getMainExecutor() { 581 return mMainExecutor; 582 } 583 584 @Override getContext()585 public Context getContext() { 586 return mContext; 587 } 588 589 @Override getRemoteCallExecutor()590 public ShellExecutor getRemoteCallExecutor() { 591 return mMainExecutor; 592 } 593 594 /** 595 * Sets a listener to be notified of bubble updates. This is used by launcher so that 596 * it may render bubbles in itself. Only one listener is supported. 597 * 598 * <p>If bubble bar is supported, bubble views will be updated to switch to bar mode. 599 */ registerBubbleStateListener(Bubbles.BubbleStateListener listener)600 public void registerBubbleStateListener(Bubbles.BubbleStateListener listener) { 601 final boolean bubbleBarAllowed = Flags.enableBubbleBar() 602 && (mBubblePositioner.isLargeScreen() || Flags.enableBubbleBarOnPhones()) 603 && listener != null; 604 if (bubbleBarAllowed) { 605 // Only set the listener if we can show the bubble bar. 606 mBubbleStateListener = listener; 607 setUpBubbleViewsForMode(); 608 sendInitialListenerUpdate(); 609 } else { 610 mBubbleStateListener = null; 611 } 612 } 613 614 /** 615 * Unregisters the {@link Bubbles.BubbleStateListener}. 616 * 617 * <p>If there's an existing listener, then we're switching back to stack mode and bubble views 618 * will be updated accordingly. 619 */ unregisterBubbleStateListener()620 public void unregisterBubbleStateListener() { 621 if (mBubbleStateListener != null) { 622 mBubbleStateListener = null; 623 setUpBubbleViewsForMode(); 624 } 625 } 626 627 /** 628 * If a {@link Bubbles.BubbleStateListener} is present, this will send the current bubble 629 * state to it. 630 */ sendInitialListenerUpdate()631 private void sendInitialListenerUpdate() { 632 if (mBubbleStateListener != null) { 633 boolean isCurrentNavModeGestures = ContextUtils.isGestureNavigationMode(mContext); 634 if (mIsPrevNavModeGestures && !isCurrentNavModeGestures) { 635 BubbleBarLocation bubbleBarLocation = ContextUtils.isRtl(mContext) 636 ? BubbleBarLocation.RIGHT : BubbleBarLocation.LEFT; 637 mBubblePositioner.setBubbleBarLocation(bubbleBarLocation); 638 } 639 mIsPrevNavModeGestures = isCurrentNavModeGestures; 640 BubbleBarUpdate update = mBubbleData.getInitialStateForBubbleBar(); 641 mBubbleStateListener.onBubbleStateChange(update); 642 } 643 } 644 645 /** 646 * Hides the current input method, wherever it may be focused, via InputMethodManagerInternal. 647 */ hideCurrentInputMethod(@ullable Runnable onImeHidden)648 void hideCurrentInputMethod(@Nullable Runnable onImeHidden) { 649 mOnImeHidden = onImeHidden; 650 mBubblePositioner.setImeVisible(false /* visible */, 0 /* height */); 651 int displayId = mWindowManager.getDefaultDisplay().getDisplayId(); 652 // if the device is locked we can't use the status bar service to hide the IME because 653 // the IME state is frozen and it will lead to internal IME state going out of sync. This 654 // will make the IME visible when the device is unlocked. Instead we use 655 // DisplayImeController directly to make sure the state is correct when the device unlocks. 656 if (isDeviceLocked()) { 657 mDisplayImeController.hideImeForBubblesWhenLocked(displayId); 658 return; 659 } 660 try { 661 mBarService.hideCurrentInputMethodForBubbles(displayId); 662 } catch (RemoteException e) { 663 Log.e(TAG, "Failed to hide IME", e); 664 } 665 } 666 667 /** 668 * Called when the status bar has become visible or invisible (either permanently or 669 * temporarily). 670 */ onStatusBarVisibilityChanged(boolean visible)671 private void onStatusBarVisibilityChanged(boolean visible) { 672 if (mStackView != null) { 673 // Hide the stack temporarily if the status bar has been made invisible, and the stack 674 // is collapsed. An expanded stack should remain visible until collapsed. 675 mStackView.setTemporarilyInvisible(!visible && !isStackExpanded()); 676 ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarVisibilityChanged=%b stackExpanded=%b", 677 visible, isStackExpanded()); 678 } 679 } 680 onZenStateChanged()681 private void onZenStateChanged() { 682 if (hasBubbles()) { 683 ProtoLog.d(WM_SHELL_BUBBLES, "onZenStateChanged"); 684 } 685 for (Bubble b : mBubbleData.getBubbles()) { 686 b.setShowDot(b.showInShade()); 687 } 688 } 689 690 @VisibleForTesting onStatusBarStateChanged(boolean isShade)691 public void onStatusBarStateChanged(boolean isShade) { 692 boolean didChange = mIsStatusBarShade != isShade; 693 ProtoLog.d(WM_SHELL_BUBBLES, "onStatusBarStateChanged " 694 + "isShade=%b didChange=%b mNotifEntryToExpandOnShadeUnlock=%s", 695 isShade, didChange, (mNotifEntryToExpandOnShadeUnlock != null 696 ? mNotifEntryToExpandOnShadeUnlock.getKey() : "null")); 697 mIsStatusBarShade = isShade; 698 if (!mIsStatusBarShade && didChange) { 699 if (mBubbleData.isExpanded()) { 700 // If the IME is visible, hide it first and then collapse. 701 if (mBubblePositioner.isImeVisible()) { 702 hideCurrentInputMethod(this::collapseStack); 703 } else { 704 collapseStack(); 705 } 706 } else if (mOnImeHidden != null) { 707 // a request to collapse started before we're notified that the device is locking. 708 // we're currently waiting for the IME to collapse, before mOnImeHidden can be 709 // executed, which may not happen since the screen may already be off. hide the IME 710 // immediately now that we're locked and pass the same runnable so it can complete. 711 hideCurrentInputMethod(mOnImeHidden); 712 } 713 } 714 715 if (mNotifEntryToExpandOnShadeUnlock != null) { 716 expandStackAndSelectBubble(mNotifEntryToExpandOnShadeUnlock); 717 } 718 719 updateBubbleViews(); 720 } 721 722 @VisibleForTesting onBubbleMetadataFlagChanged(Bubble bubble)723 public void onBubbleMetadataFlagChanged(Bubble bubble) { 724 ProtoLog.d(WM_SHELL_BUBBLES, "onBubbleMetadataFlagChanged=%s flags=%d", 725 bubble.getKey(), bubble.getFlags()); 726 // Make sure NoMan knows suppression state so that anyone querying it can tell. 727 try { 728 mBarService.onBubbleMetadataFlagChanged(bubble.getKey(), bubble.getFlags()); 729 } catch (RemoteException e) { 730 // Bad things have happened 731 } 732 mImpl.mCachedState.updateBubbleSuppressedState(bubble); 733 } 734 735 /** Called when the current user changes. */ 736 @VisibleForTesting onUserChanged(int newUserId)737 public void onUserChanged(int newUserId) { 738 ProtoLog.d(WM_SHELL_BUBBLES, "onUserChanged currentUser=%d newUser=%d", 739 mCurrentUserId, newUserId); 740 saveBubbles(mCurrentUserId); 741 mCurrentUserId = newUserId; 742 743 mBubbleData.dismissAll(DISMISS_USER_CHANGED); 744 mBubbleData.clearOverflow(); 745 mOverflowDataLoadNeeded = true; 746 747 restoreBubbles(newUserId); 748 mBubbleData.setCurrentUserId(newUserId); 749 } 750 751 /** Called when the profiles for the current user change. **/ onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles)752 public void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) { 753 mCurrentProfiles = currentProfiles; 754 } 755 756 /** Called when a user is removed from the device, including work profiles. */ onUserRemoved(int removedUserId)757 public void onUserRemoved(int removedUserId) { 758 UserInfo parent = mUserManager.getProfileParent(removedUserId); 759 int parentUserId = parent != null ? parent.getUserHandle().getIdentifier() : -1; 760 mBubbleData.removeBubblesForUser(removedUserId); 761 // Typically calls from BubbleData would remove bubbles from the DataRepository as well, 762 // however, this gets complicated when users are removed (mCurrentUserId won't necessarily 763 // be correct for this) so we update the repo directly. 764 mDataRepository.removeBubblesForUser(removedUserId, parentUserId); 765 } 766 767 /** Called when sensitive notification state has changed */ onSensitiveNotificationProtectionStateChanged( boolean sensitiveNotificationProtectionActive)768 public void onSensitiveNotificationProtectionStateChanged( 769 boolean sensitiveNotificationProtectionActive) { 770 if (mStackView != null) { 771 mStackView.onSensitiveNotificationProtectionStateChanged( 772 sensitiveNotificationProtectionActive); 773 ProtoLog.d(WM_SHELL_BUBBLES, "onSensitiveNotificationProtectionStateChanged=%b", 774 sensitiveNotificationProtectionActive); 775 } 776 } 777 778 /** Whether bubbles would be shown with the bubble bar UI. */ isShowingAsBubbleBar()779 public boolean isShowingAsBubbleBar() { 780 return Flags.enableBubbleBar() 781 && (mBubblePositioner.isLargeScreen() || Flags.enableBubbleBarOnPhones()) 782 && mBubbleStateListener != null; 783 } 784 785 /** 786 * Returns current {@link BubbleBarLocation} if bubble bar is being used. 787 * Otherwise returns <code>null</code> 788 */ 789 @Nullable getBubbleBarLocation()790 public BubbleBarLocation getBubbleBarLocation() { 791 if (isShowingAsBubbleBar()) { 792 return mBubblePositioner.getBubbleBarLocation(); 793 } 794 return null; 795 } 796 797 /** 798 * Update bubble bar location and trigger and update to listeners 799 */ setBubbleBarLocation(BubbleBarLocation bubbleBarLocation, @UpdateSource int source)800 public void setBubbleBarLocation(BubbleBarLocation bubbleBarLocation, 801 @UpdateSource int source) { 802 if (isShowingAsBubbleBar()) { 803 updateExpandedViewForBubbleBarLocation(bubbleBarLocation, source); 804 BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate(); 805 bubbleBarUpdate.bubbleBarLocation = bubbleBarLocation; 806 mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate); 807 } 808 } 809 updateExpandedViewForBubbleBarLocation(BubbleBarLocation bubbleBarLocation, @UpdateSource int source)810 private void updateExpandedViewForBubbleBarLocation(BubbleBarLocation bubbleBarLocation, 811 @UpdateSource int source) { 812 if (isShowingAsBubbleBar()) { 813 BubbleBarLocation previousLocation = mBubblePositioner.getBubbleBarLocation(); 814 mBubblePositioner.setBubbleBarLocation(bubbleBarLocation); 815 if (mLayerView != null && !mLayerView.isExpandedViewDragged()) { 816 mLayerView.updateExpandedView(); 817 } 818 logBubbleBarLocationIfChanged(bubbleBarLocation, previousLocation, source); 819 } 820 } 821 logBubbleBarLocationIfChanged(BubbleBarLocation location, BubbleBarLocation previous, @UpdateSource int source)822 private void logBubbleBarLocationIfChanged(BubbleBarLocation location, 823 BubbleBarLocation previous, 824 @UpdateSource int source) { 825 if (mLayerView == null) { 826 return; 827 } 828 boolean isRtl = mLayerView.isLayoutRtl(); 829 boolean wasLeft = previous.isOnLeft(isRtl); 830 boolean onLeft = location.isOnLeft(isRtl); 831 if (wasLeft == onLeft) { 832 // No changes, skip logging 833 return; 834 } 835 switch (source) { 836 case UpdateSource.DRAG_BAR: 837 case UpdateSource.A11Y_ACTION_BAR: 838 mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BAR 839 : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BAR); 840 break; 841 case UpdateSource.DRAG_BUBBLE: 842 case UpdateSource.A11Y_ACTION_BUBBLE: 843 mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_BUBBLE 844 : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_BUBBLE); 845 break; 846 case UpdateSource.DRAG_EXP_VIEW: 847 case UpdateSource.A11Y_ACTION_EXP_VIEW: 848 // TODO(b/349845968): move logging from BubbleBarLayerView to here 849 break; 850 case UpdateSource.APP_ICON_DRAG: 851 mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_APP_ICON_DROP 852 : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_APP_ICON_DROP); 853 break; 854 case UpdateSource.DRAG_TASK: 855 mLogger.log(onLeft ? BubbleLogger.Event.BUBBLE_BAR_MOVED_LEFT_DRAG_TASK 856 : BubbleLogger.Event.BUBBLE_BAR_MOVED_RIGHT_DRAG_TASK); 857 break; 858 } 859 } 860 861 /** 862 * Animate bubble bar to the given location. The location change is transient. It does not 863 * update the state of the bubble bar. 864 * To update bubble bar pinned location, use 865 * {@link #setBubbleBarLocation(BubbleBarLocation, int)}. 866 */ animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation)867 public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { 868 if (isShowingAsBubbleBar()) { 869 mBubbleStateListener.animateBubbleBarLocation(bubbleBarLocation); 870 } 871 } 872 873 @Override onDragItemOverBubbleBarDragZone(@ullable BubbleBarLocation bubbleBarLocation)874 public void onDragItemOverBubbleBarDragZone(@Nullable BubbleBarLocation bubbleBarLocation) { 875 if (bubbleBarLocation == null) return; 876 if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) { 877 mBubbleStateListener.onDragItemOverBubbleBarDragZone(bubbleBarLocation); 878 showBubbleBarExpandedViewDropTarget(bubbleBarLocation); 879 } 880 } 881 882 @Override onItemDraggedOutsideBubbleBarDropZone()883 public void onItemDraggedOutsideBubbleBarDropZone() { 884 if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) { 885 mBubbleStateListener.onItemDraggedOutsideBubbleBarDropZone(); 886 hideBubbleBarExpandedViewDropTarget(); 887 } 888 } 889 890 @Override onItemDroppedOverBubbleBarDragZone(@onNull BubbleBarLocation location, Intent itemIntent)891 public void onItemDroppedOverBubbleBarDragZone(@NonNull BubbleBarLocation location, 892 Intent itemIntent) { 893 hideBubbleBarExpandedViewDropTarget(); 894 ShortcutInfo shortcutInfo = (ShortcutInfo) itemIntent 895 .getExtra(DragAndDropConstants.EXTRA_SHORTCUT_INFO); 896 if (shortcutInfo != null) { 897 expandStackAndSelectBubble(shortcutInfo, location); 898 return; 899 } 900 UserHandle user = (UserHandle) itemIntent.getExtra(Intent.EXTRA_USER); 901 PendingIntent pendingIntent = (PendingIntent) itemIntent 902 .getExtra(ClipDescription.EXTRA_PENDING_INTENT); 903 if (pendingIntent != null && user != null) { 904 expandStackAndSelectBubble(pendingIntent, user, location); 905 } 906 } 907 908 @Override getBubbleBarDropZones(int l, int t, int r, int b)909 public Map<BubbleBarLocation, Rect> getBubbleBarDropZones(int l, int t, int r, int b) { 910 Map<BubbleBarLocation, Rect> result = new HashMap<>(); 911 if (isShowingAsBubbleBar() && BubbleAnythingFlagHelper.enableCreateAnyBubble()) { 912 // TODO(b/393172431) : Utilise DragZoneFactory once it is ready 913 final int bubbleBarDropZoneSideSize = getContext().getResources().getDimensionPixelSize( 914 R.dimen.bubble_bar_drop_zone_side_size); 915 int top = b - bubbleBarDropZoneSideSize; 916 result.put(BubbleBarLocation.LEFT, 917 new Rect(l, top, l + bubbleBarDropZoneSideSize, b)); 918 result.put(BubbleBarLocation.RIGHT, 919 new Rect(r - bubbleBarDropZoneSideSize, top, r, b)); 920 } 921 return result; 922 } 923 showBubbleBarExpandedViewDropTarget(BubbleBarLocation bubbleBarLocation)924 private void showBubbleBarExpandedViewDropTarget(BubbleBarLocation bubbleBarLocation) { 925 ensureBubbleViewsAndWindowCreated(); 926 if (mLayerView != null) { 927 mLayerView.showBubbleBarExtendedViewDropTarget(bubbleBarLocation); 928 } 929 } 930 hideBubbleBarExpandedViewDropTarget()931 private void hideBubbleBarExpandedViewDropTarget() { 932 if (mLayerView != null) { 933 mLayerView.hideBubbleBarExpandedViewDropTarget(); 934 } 935 } 936 937 /** Whether this userId belongs to the current user. */ isCurrentProfile(int userId)938 private boolean isCurrentProfile(int userId) { 939 return userId == UserHandle.USER_ALL 940 || (mCurrentProfiles != null && mCurrentProfiles.get(userId) != null); 941 } 942 943 /** 944 * Sets whether to perform inflation on the same thread as the caller. This method should only 945 * be used in tests, not in production. 946 */ 947 @VisibleForTesting setInflateSynchronously(boolean inflateSynchronously)948 public void setInflateSynchronously(boolean inflateSynchronously) { 949 mInflateSynchronously = inflateSynchronously; 950 } 951 952 /** Set a listener to be notified of when overflow view update. */ setOverflowListener(BubbleData.Listener listener)953 public void setOverflowListener(BubbleData.Listener listener) { 954 mOverflowListener = listener; 955 } 956 957 /** 958 * @return Bubbles for updating overflow. 959 */ getOverflowBubbles()960 List<Bubble> getOverflowBubbles() { 961 return mBubbleData.getOverflowBubbles(); 962 } 963 964 /** The task listener for events in bubble tasks. */ getTaskOrganizer()965 public ShellTaskOrganizer getTaskOrganizer() { 966 return mTaskOrganizer; 967 } 968 969 /** Contains information to help position things on the screen. */ 970 @VisibleForTesting getPositioner()971 public BubblePositioner getPositioner() { 972 return mBubblePositioner; 973 } 974 975 /** Provides bounds for drag zone drop targets */ getBubbleDropTargetBoundsProvider()976 public BubbleDropTargetBoundsProvider getBubbleDropTargetBoundsProvider() { 977 return mBubblePositioner; 978 } 979 getIconFactory()980 BubbleIconFactory getIconFactory() { 981 return mBubbleIconFactory; 982 } 983 984 @Override getSysuiProxy()985 public Bubbles.SysuiProxy getSysuiProxy() { 986 return mSysuiProxy; 987 } 988 989 /** 990 * The view and window for bubbles is lazily created by this method the first time a Bubble 991 * is added. Depending on the device state, this method will: 992 * - initialize a {@link BubbleStackView} and add it to window manager OR 993 * - initialize a {@link com.android.wm.shell.bubbles.bar.BubbleBarLayerView} and adds 994 * it to window manager. 995 */ ensureBubbleViewsAndWindowCreated()996 private void ensureBubbleViewsAndWindowCreated() { 997 mBubblePositioner.setShowingInBubbleBar(isShowingAsBubbleBar()); 998 if (isShowingAsBubbleBar()) { 999 // When we're showing in launcher / bubble bar is enabled, we don't have bubble stack 1000 // view, instead we just show the expanded bubble view as necessary. We still need a 1001 // window to show this in, but we use a separate code path. 1002 // TODO(b/273312602): consider foldables where we do need a stack view when folded 1003 if (mLayerView == null) { 1004 mLayerView = new BubbleBarLayerView(mContext, this, mBubbleData, mLogger); 1005 mLayerView.setUnBubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); 1006 } 1007 } else { 1008 if (mStackView == null) { 1009 BubbleStackViewManager bubbleStackViewManager = 1010 BubbleStackViewManager.fromBubbleController(this); 1011 mStackView = new BubbleStackView( 1012 mContext, bubbleStackViewManager, mBubblePositioner, mBubbleData, 1013 mSurfaceSynchronizer, mFloatingContentCoordinator, this, mMainExecutor); 1014 mStackView.onOrientationChanged(); 1015 if (mExpandListener != null) { 1016 mStackView.setExpandListener(mExpandListener); 1017 } 1018 mStackView.setUnbubbleConversationCallback(mSysuiProxy::onUnbubbleConversation); 1019 } 1020 } 1021 addToWindowManagerMaybe(); 1022 } 1023 1024 /** Adds the appropriate view to WindowManager if it's not already there. */ addToWindowManagerMaybe()1025 private void addToWindowManagerMaybe() { 1026 // If already added, don't add it. 1027 if (mAddedToWindowManager) { 1028 return; 1029 } 1030 // If the appropriate view is null, don't add it. 1031 if (isShowingAsBubbleBar() && mLayerView == null) { 1032 return; 1033 } else if (!isShowingAsBubbleBar() && mStackView == null) { 1034 return; 1035 } 1036 1037 mWmLayoutParams = new WindowManager.LayoutParams( 1038 // Fill the screen so we can use translation animations to position the bubble 1039 // views. We'll use touchable regions to ignore touches that are not on the bubbles 1040 // themselves. 1041 ViewGroup.LayoutParams.MATCH_PARENT, 1042 ViewGroup.LayoutParams.MATCH_PARENT, 1043 WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, 1044 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 1045 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL 1046 | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, 1047 PixelFormat.TRANSLUCENT); 1048 1049 mWmLayoutParams.setTrustedOverlay(); 1050 mWmLayoutParams.setFitInsetsTypes(0); 1051 mWmLayoutParams.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 1052 mWmLayoutParams.token = new Binder(); 1053 mWmLayoutParams.setTitle("Bubbles!"); 1054 mWmLayoutParams.packageName = mContext.getPackageName(); 1055 mWmLayoutParams.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS; 1056 mWmLayoutParams.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; 1057 1058 try { 1059 mAddedToWindowManager = true; 1060 registerBroadcastReceiver(); 1061 if (isShowingAsBubbleBar()) { 1062 mBubbleData.getOverflow().initializeForBubbleBar( 1063 mExpandedViewManager, mBubblePositioner); 1064 } else { 1065 mBubbleData.getOverflow().initialize( 1066 mExpandedViewManager, mStackView, mBubblePositioner); 1067 } 1068 // (TODO: b/273314541) some duplication in the inset listener 1069 if (isShowingAsBubbleBar()) { 1070 mWindowManager.addView(mLayerView, mWmLayoutParams); 1071 mLayerView.setOnApplyWindowInsetsListener((view, windowInsets) -> { 1072 if (!windowInsets.equals(mWindowInsets) && mLayerView != null) { 1073 mWindowInsets = windowInsets; 1074 mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager)); 1075 mLayerView.onDisplaySizeChanged(); 1076 } 1077 return windowInsets; 1078 }); 1079 } else { 1080 mWindowManager.addView(mStackView, mWmLayoutParams); 1081 mStackView.setOnApplyWindowInsetsListener((view, windowInsets) -> { 1082 if (!windowInsets.equals(mWindowInsets) && mStackView != null) { 1083 mWindowInsets = windowInsets; 1084 mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager)); 1085 mStackView.onDisplaySizeChanged(); 1086 } 1087 return windowInsets; 1088 }); 1089 } 1090 } catch (IllegalStateException e) { 1091 // This means the view has already been added. This shouldn't happen... 1092 e.printStackTrace(); 1093 } 1094 } 1095 1096 /** 1097 * In some situations bubble's should be able to receive key events for back: 1098 * - when the bubble overflow is showing 1099 * - when the user education for the stack is showing. 1100 * 1101 * @param interceptBack whether back should be intercepted or not. 1102 */ updateWindowFlagsForBackpress(boolean interceptBack)1103 void updateWindowFlagsForBackpress(boolean interceptBack) { 1104 if (mAddedToWindowManager) { 1105 ProtoLog.d(WM_SHELL_BUBBLES, "updateFlagsForBackPress interceptBack=%b", interceptBack); 1106 mWmLayoutParams.flags = interceptBack 1107 ? 0 1108 : WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE 1109 | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; 1110 mWmLayoutParams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 1111 if (mStackView != null) { 1112 mWindowManager.updateViewLayout(mStackView, mWmLayoutParams); 1113 } else if (mLayerView != null) { 1114 mWindowManager.updateViewLayout(mLayerView, mWmLayoutParams); 1115 } 1116 } 1117 } 1118 1119 /** Removes any bubble views from the WindowManager that exist. */ removeFromWindowManagerMaybe()1120 private void removeFromWindowManagerMaybe() { 1121 if (!mAddedToWindowManager) { 1122 return; 1123 } 1124 1125 mAddedToWindowManager = false; 1126 // Put on background for this binder call, was causing jank 1127 mBackgroundExecutor.execute(() -> { 1128 try { 1129 mContext.unregisterReceiver(mBroadcastReceiver); 1130 } catch (IllegalArgumentException e) { 1131 // Not sure if this happens in production, but was happening in tests 1132 // (b/253647225) 1133 e.printStackTrace(); 1134 } 1135 }); 1136 try { 1137 if (mStackView != null) { 1138 mWindowManager.removeView(mStackView); 1139 mBubbleData.getOverflow().cleanUpExpandedState(); 1140 } 1141 if (mLayerView != null) { 1142 mWindowManager.removeView(mLayerView); 1143 mBubbleData.getOverflow().cleanUpExpandedState(); 1144 } 1145 } catch (IllegalArgumentException e) { 1146 // This means the stack has already been removed - it shouldn't happen, but ignore if it 1147 // does, since we wanted it removed anyway. 1148 e.printStackTrace(); 1149 } 1150 } 1151 registerBroadcastReceiver()1152 private void registerBroadcastReceiver() { 1153 IntentFilter filter = new IntentFilter(); 1154 filter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 1155 filter.addAction(Intent.ACTION_SCREEN_OFF); 1156 mContext.registerReceiver(mBroadcastReceiver, filter, Context.RECEIVER_EXPORTED); 1157 } 1158 1159 private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { 1160 @Override 1161 public void onReceive(Context context, Intent intent) { 1162 if (!isStackExpanded()) return; // Nothing to do 1163 1164 String action = intent.getAction(); 1165 String reason = intent.getStringExtra(SYSTEM_DIALOG_REASON_KEY); 1166 boolean validReasonToCollapse = SYSTEM_DIALOG_REASON_RECENT_APPS.equals(reason) 1167 || SYSTEM_DIALOG_REASON_HOME_KEY.equals(reason) 1168 || SYSTEM_DIALOG_REASON_GESTURE_NAV.equals(reason); 1169 if ((Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action) && validReasonToCollapse) 1170 || Intent.ACTION_SCREEN_OFF.equals(action)) { 1171 mMainExecutor.execute(() -> collapseStack()); 1172 } 1173 } 1174 }; 1175 registerShortcutBroadcastReceiver()1176 private void registerShortcutBroadcastReceiver() { 1177 IntentFilter shortcutFilter = new IntentFilter(); 1178 shortcutFilter.addAction(BubbleShortcutHelper.ACTION_SHOW_BUBBLES); 1179 ProtoLog.d(WM_SHELL_BUBBLES, "register broadcast receive for bubbles shortcut"); 1180 mContext.registerReceiver(mShortcutBroadcastReceiver, shortcutFilter, 1181 Context.RECEIVER_NOT_EXPORTED); 1182 } 1183 1184 private final BroadcastReceiver mShortcutBroadcastReceiver = new BroadcastReceiver() { 1185 @Override 1186 public void onReceive(Context context, Intent intent) { 1187 ProtoLog.v(WM_SHELL_BUBBLES, "receive broadcast to show bubbles %s", 1188 intent.getAction()); 1189 if (BubbleShortcutHelper.ACTION_SHOW_BUBBLES.equals(intent.getAction())) { 1190 mMainExecutor.execute(() -> showBubblesFromShortcut()); 1191 } 1192 } 1193 }; 1194 1195 /** 1196 * Called by the BubbleStackView and whenever all bubbles have animated out, and none have been 1197 * added in the meantime. 1198 */ onAllBubblesAnimatedOut()1199 public void onAllBubblesAnimatedOut() { 1200 if (mStackView != null) { 1201 mStackView.setVisibility(INVISIBLE); 1202 removeFromWindowManagerMaybe(); 1203 } else if (mLayerView != null) { 1204 mLayerView.setVisibility(INVISIBLE); 1205 removeFromWindowManagerMaybe(); 1206 } 1207 } 1208 1209 /** 1210 * Records the notification key for any active bubbles. These are used to restore active 1211 * bubbles when the user returns to the foreground. 1212 * 1213 * @param userId the id of the user 1214 */ saveBubbles(@serIdInt int userId)1215 private void saveBubbles(@UserIdInt int userId) { 1216 // First clear any existing keys that might be stored. 1217 mSavedUserBubbleData.remove(userId); 1218 UserBubbleData userBubbleData = new UserBubbleData(); 1219 // Add in all active bubbles for the current user. 1220 for (Bubble bubble : mBubbleData.getBubbles()) { 1221 userBubbleData.add(bubble.getKey(), bubble.showInShade()); 1222 } 1223 mSavedUserBubbleData.put(userId, userBubbleData); 1224 } 1225 1226 /** 1227 * Promotes existing notifications to Bubbles if they were previously bubbles. 1228 * 1229 * @param userId the id of the user 1230 */ restoreBubbles(@serIdInt int userId)1231 private void restoreBubbles(@UserIdInt int userId) { 1232 UserBubbleData savedBubbleData = mSavedUserBubbleData.get(userId); 1233 if (savedBubbleData == null) { 1234 // There were no bubbles saved for this used. 1235 return; 1236 } 1237 mSysuiProxy.getShouldRestoredEntries(savedBubbleData.getKeys(), (entries) -> { 1238 mMainExecutor.execute(() -> { 1239 for (BubbleEntry e : entries) { 1240 if (canLaunchInTaskView(mContext, e)) { 1241 boolean showInShade = savedBubbleData.isShownInShade(e.getKey()); 1242 updateBubble(e, true /* suppressFlyout */, showInShade); 1243 } 1244 } 1245 }); 1246 }); 1247 // Finally, remove the entries for this user now that bubbles are restored. 1248 mSavedUserBubbleData.remove(userId); 1249 } 1250 1251 @Override onThemeChanged()1252 public void onThemeChanged() { 1253 if (mStackView != null) { 1254 mStackView.onThemeChanged(); 1255 } 1256 mBubbleIconFactory = new BubbleIconFactory(mContext, 1257 mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size), 1258 mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size), 1259 mContext.getResources().getColor( 1260 com.android.launcher3.icons.R.color.important_conversation), 1261 mContext.getResources().getDimensionPixelSize( 1262 com.android.internal.R.dimen.importance_ring_stroke_width)); 1263 1264 // Reload each bubble 1265 for (Bubble b : mBubbleData.getBubbles()) { 1266 b.inflate(null /* callback */, 1267 mContext, 1268 mExpandedViewManager, 1269 mBubbleTaskViewFactory, 1270 mBubblePositioner, 1271 mStackView, 1272 mLayerView, 1273 mBubbleIconFactory, 1274 false /* skipInflation */); 1275 } 1276 for (Bubble b : mBubbleData.getOverflowBubbles()) { 1277 b.inflate(null /* callback */, 1278 mContext, 1279 mExpandedViewManager, 1280 mBubbleTaskViewFactory, 1281 mBubblePositioner, 1282 mStackView, 1283 mLayerView, 1284 mBubbleIconFactory, 1285 false /* skipInflation */); 1286 } 1287 } 1288 1289 @Override onConfigurationChanged(Configuration newConfig)1290 public void onConfigurationChanged(Configuration newConfig) { 1291 if (mBubblePositioner != null) { 1292 mBubblePositioner.update(DeviceConfig.create(mContext, mWindowManager)); 1293 } 1294 if (mStackView != null && newConfig != null) { 1295 if (newConfig.densityDpi != mDensityDpi 1296 || !newConfig.windowConfiguration.getBounds().equals(mScreenBounds)) { 1297 mDensityDpi = newConfig.densityDpi; 1298 mScreenBounds.set(newConfig.windowConfiguration.getBounds()); 1299 mBubbleData.onMaxBubblesChanged(); 1300 mBubbleIconFactory = new BubbleIconFactory(mContext, 1301 mContext.getResources().getDimensionPixelSize(R.dimen.bubble_size), 1302 mContext.getResources().getDimensionPixelSize(R.dimen.bubble_badge_size), 1303 mContext.getResources().getColor( 1304 com.android.launcher3.icons.R.color.important_conversation), 1305 mContext.getResources().getDimensionPixelSize( 1306 com.android.internal.R.dimen.importance_ring_stroke_width)); 1307 mStackView.onDisplaySizeChanged(); 1308 } 1309 if (newConfig.fontScale != mFontScale) { 1310 mFontScale = newConfig.fontScale; 1311 mStackView.updateFontScale(); 1312 } 1313 if (newConfig.getLayoutDirection() != mLayoutDirection) { 1314 mLayoutDirection = newConfig.getLayoutDirection(); 1315 mStackView.onLayoutDirectionChanged(mLayoutDirection); 1316 } 1317 Locale newLocale = newConfig.locale; 1318 if (newLocale != null && !newLocale.equals(mLocale)) { 1319 mLocale = newLocale; 1320 mStackView.updateLocale(); 1321 } 1322 } 1323 } 1324 onNotificationPanelExpandedChanged(boolean expanded)1325 private void onNotificationPanelExpandedChanged(boolean expanded) { 1326 if (mStackView != null && mStackView.isExpanded()) { 1327 ProtoLog.d(WM_SHELL_BUBBLES, 1328 "onNotificationPanelExpandedChanged expanded=%b", expanded); 1329 if (expanded) { 1330 mStackView.stopMonitoringSwipeUpGesture(); 1331 } else { 1332 mStackView.startMonitoringSwipeUpGesture(); 1333 } 1334 } 1335 } 1336 setSysuiProxy(Bubbles.SysuiProxy proxy)1337 private void setSysuiProxy(Bubbles.SysuiProxy proxy) { 1338 mSysuiProxy = proxy; 1339 } 1340 1341 @VisibleForTesting setExpandListener(Bubbles.BubbleExpandListener listener)1342 public void setExpandListener(Bubbles.BubbleExpandListener listener) { 1343 mExpandListener = ((isExpanding, key) -> { 1344 if (listener != null) { 1345 listener.onBubbleExpandChanged(isExpanding, key); 1346 } 1347 }); 1348 if (mStackView != null) { 1349 mStackView.setExpandListener(mExpandListener); 1350 } 1351 } 1352 1353 /** 1354 * Whether or not there are bubbles present, regardless of them being visible on the 1355 * screen (e.g. if on AOD). 1356 */ 1357 @VisibleForTesting hasBubbles()1358 public boolean hasBubbles() { 1359 if (mStackView == null && mLayerView == null) { 1360 return false; 1361 } 1362 return mBubbleData.hasBubbles() || mBubbleData.isShowingOverflow(); 1363 } 1364 isStackExpanded()1365 public boolean isStackExpanded() { 1366 return mBubbleData.isExpanded(); 1367 } 1368 collapseStack()1369 public void collapseStack() { 1370 mBubbleData.setExpanded(false /* expanded */); 1371 } 1372 1373 /** 1374 * A bubble is being dragged in Launcher. 1375 * Will be called only when bubble bar is expanded. 1376 * 1377 * @param bubbleKey key of the bubble being dragged 1378 */ startBubbleDrag(String bubbleKey)1379 public void startBubbleDrag(String bubbleKey) { 1380 if (mBubbleData.getSelectedBubble() != null) { 1381 collapseExpandedViewForBubbleBar(); 1382 } 1383 if (mBubbleStateListener != null) { 1384 boolean overflow = BubbleOverflow.KEY.equals(bubbleKey); 1385 Rect rect = new Rect(); 1386 mBubblePositioner.getBubbleBarExpandedViewBounds(mBubblePositioner.isBubbleBarOnLeft(), 1387 overflow, rect); 1388 BubbleBarUpdate update = new BubbleBarUpdate(); 1389 update.expandedViewDropTargetSize = new Point(rect.width(), rect.height()); 1390 mBubbleStateListener.onBubbleStateChange(update); 1391 } 1392 } 1393 1394 /** 1395 * A bubble is no longer being dragged in Launcher. And was released in given location. 1396 * Will be called only when bubble bar is expanded. 1397 * 1398 * @param location location where bubble was released 1399 * @param topOnScreen top coordinate of the bubble bar on the screen after release 1400 */ stopBubbleDrag(BubbleBarLocation location, int topOnScreen)1401 public void stopBubbleDrag(BubbleBarLocation location, int topOnScreen) { 1402 mBubblePositioner.setBubbleBarLocation(location); 1403 mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen); 1404 if (mBubbleData.getSelectedBubble() != null) { 1405 showExpandedViewForBubbleBar(); 1406 } 1407 } 1408 1409 /** 1410 * A bubble was dragged and is released in dismiss target in Launcher. 1411 * 1412 * @param bubbleKey key of the bubble being dragged to dismiss target 1413 * @param timestamp the timestamp of the removal 1414 */ dragBubbleToDismiss(String bubbleKey, long timestamp)1415 public void dragBubbleToDismiss(String bubbleKey, long timestamp) { 1416 final String selectedBubbleKey = mBubbleData.getSelectedBubbleKey(); 1417 final Bubble bubbleToDismiss = mBubbleData.getAnyBubbleWithKey(bubbleKey); 1418 if (bubbleToDismiss != null) { 1419 mBubbleData.dismissBubbleWithKey( 1420 bubbleKey, Bubbles.DISMISS_USER_GESTURE_FROM_LAUNCHER, timestamp); 1421 mLogger.log(bubbleToDismiss, 1422 BubbleLogger.Event.BUBBLE_BAR_BUBBLE_DISMISSED_DRAG_BUBBLE); 1423 } 1424 if (mBubbleData.hasBubbles()) { 1425 // We still have bubbles, if we dragged an individual bubble to dismiss we were expanded 1426 // so re-expand to whatever is selected. 1427 showExpandedViewForBubbleBar(); 1428 if (bubbleKey.equals(selectedBubbleKey)) { 1429 // We dragged the selected bubble to dismiss, log switch event 1430 if (mBubbleData.getSelectedBubble() instanceof Bubble) { 1431 // Log only bubbles as overflow can't be dragged 1432 mLogger.log((Bubble) mBubbleData.getSelectedBubble(), 1433 BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED); 1434 } 1435 } 1436 } 1437 } 1438 1439 /** 1440 * Show bubble bar user education relative to the reference position. 1441 * @param position the reference position in Screen coordinates. 1442 */ showUserEducation(Point position)1443 public void showUserEducation(Point position) { 1444 if (mLayerView == null) return; 1445 mLayerView.showUserEducation(position); 1446 } 1447 1448 @VisibleForTesting isBubbleNotificationSuppressedFromShade(String key, String groupKey)1449 public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) { 1450 final boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key) 1451 && !mBubbleData.getAnyBubbleWithKey(key).showInShade()); 1452 1453 final boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey); 1454 final boolean isSummary = key.equals(mBubbleData.getSummaryKey(groupKey)); 1455 return (isSummary && isSuppressedSummary) || isSuppressedBubble; 1456 } 1457 1458 /** Promote the provided bubble from the overflow view. */ promoteBubbleFromOverflow(Bubble bubble)1459 public void promoteBubbleFromOverflow(Bubble bubble) { 1460 if (isShowingAsBubbleBar()) { 1461 mLogger.log(bubble, BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_REMOVE_BACK_TO_BAR); 1462 } else { 1463 mLogger.log(bubble, BubbleLogger.Event.BUBBLE_OVERFLOW_REMOVE_BACK_TO_STACK); 1464 } 1465 ProtoLog.d(WM_SHELL_BUBBLES, "promoteBubbleFromOverflow=%s", bubble.getKey()); 1466 bubble.setInflateSynchronously(mInflateSynchronously); 1467 bubble.setShouldAutoExpand(true); 1468 bubble.markAsAccessedAt(System.currentTimeMillis()); 1469 setIsBubble(bubble, true /* isBubble */); 1470 } 1471 1472 /** 1473 * Expands and selects the provided bubble as long as it already exists in the stack or the 1474 * overflow. 1475 * 1476 * <p>This is used by external callers (launcher). 1477 */ 1478 @VisibleForTesting expandStackAndSelectBubbleFromLauncher(String key, int topOnScreen)1479 public void expandStackAndSelectBubbleFromLauncher(String key, int topOnScreen) { 1480 mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen); 1481 1482 if (BubbleOverflow.KEY.equals(key)) { 1483 mBubbleData.setSelectedBubbleFromLauncher(mBubbleData.getOverflow()); 1484 mLayerView.showExpandedView(mBubbleData.getOverflow()); 1485 mLogger.log(BubbleLogger.Event.BUBBLE_BAR_OVERFLOW_SELECTED); 1486 return; 1487 } 1488 1489 final Bubble b = mBubbleData.getAnyBubbleWithKey(key); 1490 if (b == null) { 1491 return; 1492 } 1493 final boolean wasExpanded = (mLayerView != null && mLayerView.isExpanded()); 1494 if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) { 1495 // already in the stack 1496 mBubbleData.setSelectedBubbleFromLauncher(b); 1497 mLayerView.showExpandedView(b); 1498 if (wasExpanded) { 1499 mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED); 1500 } else { 1501 mLogger.log(b, BubbleLogger.Event.BUBBLE_BAR_EXPANDED); 1502 } 1503 } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) { 1504 // TODO: (b/271468319) handle overflow 1505 } else { 1506 Log.w(TAG, "didn't add bubble from launcher: " + key); 1507 } 1508 } 1509 1510 /** 1511 * Expands the stack if the selected bubble is present. This is currently used when user 1512 * education view is clicked to expand the selected bubble. 1513 */ expandStackWithSelectedBubble()1514 public void expandStackWithSelectedBubble() { 1515 if (mBubbleData.getSelectedBubble() != null) { 1516 mBubbleData.setExpanded(true); 1517 } 1518 } 1519 1520 /** 1521 * Expands and selects the provided bubble as long as it already exists in the stack or the 1522 * overflow. This is currently used when opening a bubble via clicking on a conversation widget. 1523 */ expandStackAndSelectBubble(Bubble b)1524 public void expandStackAndSelectBubble(Bubble b) { 1525 if (b == null) { 1526 return; 1527 } 1528 if (mBubbleData.hasBubbleInStackWithKey(b.getKey())) { 1529 // already in the stack 1530 mBubbleData.setSelectedBubbleAndExpandStack(b); 1531 } else if (mBubbleData.hasOverflowBubbleWithKey(b.getKey())) { 1532 // promote it out of the overflow 1533 promoteBubbleFromOverflow(b); 1534 } 1535 } 1536 1537 /** 1538 * Expands and selects a bubble created or found via the provided shortcut info. 1539 * 1540 * @param info the shortcut info for the bubble. 1541 * @param bubbleBarLocation optional location in case bubble bar should be repositioned. 1542 */ expandStackAndSelectBubble(ShortcutInfo info, @Nullable BubbleBarLocation bubbleBarLocation)1543 public void expandStackAndSelectBubble(ShortcutInfo info, 1544 @Nullable BubbleBarLocation bubbleBarLocation) { 1545 if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return; 1546 Bubble b = mBubbleData.getOrCreateBubble(info); // Removes from overflow 1547 ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - shortcut=%s", info); 1548 expandStackAndSelectAppBubble(b, bubbleBarLocation, UpdateSource.APP_ICON_DRAG); 1549 } 1550 1551 /** 1552 * Expands and selects a bubble created or found for this app. 1553 * 1554 * @param intent the intent for the bubble. 1555 */ expandStackAndSelectBubble(Intent intent, UserHandle user, @Nullable BubbleBarLocation bubbleBarLocation)1556 public void expandStackAndSelectBubble(Intent intent, UserHandle user, 1557 @Nullable BubbleBarLocation bubbleBarLocation) { 1558 if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return; 1559 Bubble b = mBubbleData.getOrCreateBubble(intent, user); // Removes from overflow 1560 ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - intent=%s", intent); 1561 expandStackAndSelectAppBubble(b, bubbleBarLocation, UpdateSource.APP_ICON_DRAG); 1562 } 1563 1564 /** 1565 * Expands and selects a bubble created or found for this app. 1566 * 1567 * @param pendingIntent the intent for the bubble. 1568 * @param bubbleBarLocation optional location in case bubble bar should be repositioned. 1569 */ expandStackAndSelectBubble(PendingIntent pendingIntent, UserHandle user, @Nullable BubbleBarLocation bubbleBarLocation)1570 public void expandStackAndSelectBubble(PendingIntent pendingIntent, UserHandle user, 1571 @Nullable BubbleBarLocation bubbleBarLocation) { 1572 if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return; 1573 Bubble b = mBubbleData.getOrCreateBubble(pendingIntent, user); // Removes from overflow 1574 ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - pendingIntent=%s", 1575 pendingIntent); 1576 expandStackAndSelectAppBubble(b, bubbleBarLocation, UpdateSource.APP_ICON_DRAG); 1577 } 1578 expandStackAndSelectAppBubble(Bubble b, @Nullable BubbleBarLocation bubbleBarLocation, @UpdateSource int source)1579 private void expandStackAndSelectAppBubble(Bubble b, 1580 @Nullable BubbleBarLocation bubbleBarLocation, @UpdateSource int source) { 1581 if (!BubbleAnythingFlagHelper.enableCreateAnyBubble()) return; 1582 BubbleBarLocation updateLocation = isShowingAsBubbleBar() ? bubbleBarLocation : null; 1583 if (updateLocation != null) { 1584 updateExpandedViewForBubbleBarLocation(updateLocation, source); 1585 } 1586 if (b.isInflated()) { 1587 mBubbleData.setSelectedBubbleAndExpandStack(b, updateLocation); 1588 } else { 1589 b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); 1590 inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false, updateLocation); 1591 } 1592 } 1593 1594 /** 1595 * Expands and selects a bubble created from a running task in a different mode. 1596 * 1597 * @param taskInfo the task. 1598 * @param dragData optional information about the task when it is being dragged into a bubble 1599 */ expandStackAndSelectBubble(ActivityManager.RunningTaskInfo taskInfo, @Nullable BubbleTransitions.DragData dragData)1600 public void expandStackAndSelectBubble(ActivityManager.RunningTaskInfo taskInfo, 1601 @Nullable BubbleTransitions.DragData dragData) { 1602 if (!BubbleAnythingFlagHelper.enableBubbleToFullscreen()) return; 1603 Bubble b = mBubbleData.getOrCreateBubble(taskInfo); // Removes from overflow 1604 ProtoLog.v(WM_SHELL_BUBBLES, "expandStackAndSelectBubble - taskId=%s", taskInfo.taskId); 1605 BubbleBarLocation location = null; 1606 if (dragData != null) { 1607 location = 1608 dragData.isReleasedOnLeft() ? BubbleBarLocation.LEFT : BubbleBarLocation.RIGHT; 1609 } 1610 if (b.isInflated()) { 1611 mBubbleData.setSelectedBubbleAndExpandStack(b, location); 1612 if (dragData != null && dragData.getPendingWct() != null) { 1613 mTransitions.startTransition(TRANSIT_CHANGE, 1614 dragData.getPendingWct(), /* handler= */ null); 1615 } 1616 } else { 1617 if (location != null) { 1618 setBubbleBarLocation(location, UpdateSource.DRAG_TASK); 1619 } 1620 b.enable(Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE); 1621 // Lazy init stack view when a bubble is created 1622 ensureBubbleViewsAndWindowCreated(); 1623 mBubbleTransitions.startConvertToBubble(b, taskInfo, mExpandedViewManager, 1624 mBubbleTaskViewFactory, mBubblePositioner, mStackView, mLayerView, 1625 mBubbleIconFactory, dragData, mInflateSynchronously); 1626 } 1627 } 1628 1629 /** 1630 * Expands and selects a bubble based on the provided {@link BubbleEntry}. If no bubble 1631 * exists for this entry, and it is able to bubble, a new bubble will be created. 1632 * 1633 * <p>This is the method to use when opening a bubble via a notification or in a state where 1634 * the device might not be unlocked. 1635 * 1636 * @param entry the entry to use for the bubble. 1637 */ expandStackAndSelectBubble(BubbleEntry entry)1638 public void expandStackAndSelectBubble(BubbleEntry entry) { 1639 ProtoLog.d(WM_SHELL_BUBBLES, "opening bubble from notification key=%s mIsStatusBarShade=%b", 1640 entry.getKey(), mIsStatusBarShade); 1641 if (mIsStatusBarShade) { 1642 mNotifEntryToExpandOnShadeUnlock = null; 1643 1644 String key = entry.getKey(); 1645 Bubble bubble = mBubbleData.getBubbleInStackWithKey(key); 1646 if (bubble != null) { 1647 mBubbleData.setSelectedBubbleAndExpandStack(bubble); 1648 } else { 1649 bubble = mBubbleData.getOverflowBubbleWithKey(key); 1650 if (bubble != null) { 1651 promoteBubbleFromOverflow(bubble); 1652 } else if (entry.canBubble()) { 1653 // It can bubble but it's not -- it got aged out of the overflow before it 1654 // was dismissed or opened, make it a bubble again. 1655 setIsBubble(entry, true /* isBubble */, true /* autoExpand */); 1656 } 1657 } 1658 } else { 1659 // Wait until we're unlocked to expand, so that the user can see the expand animation 1660 // and also to work around bugs with expansion animation + shade unlock happening at the 1661 // same time. 1662 mNotifEntryToExpandOnShadeUnlock = entry; 1663 } 1664 } 1665 1666 /** 1667 * Adds or updates a bubble associated with the provided notification entry. 1668 * 1669 * @param notif the notification associated with this bubble. 1670 */ 1671 @VisibleForTesting updateBubble(BubbleEntry notif)1672 public void updateBubble(BubbleEntry notif) { 1673 int bubbleUserId = notif.getStatusBarNotification().getUserId(); 1674 if (isCurrentProfile(bubbleUserId)) { 1675 updateBubble(notif, false /* suppressFlyout */, true /* showInShade */); 1676 } else { 1677 // Skip update, but store it in user bubbles so it gets restored after user switch 1678 mSavedUserBubbleData.get(bubbleUserId, new UserBubbleData()).add(notif.getKey(), 1679 true /* shownInShade */); 1680 Log.w(TAG, "updateBubble, ignore update for non-active user=" + bubbleUserId 1681 + " currentUser=" + mCurrentUserId); 1682 } 1683 } 1684 1685 /** 1686 * This method has different behavior depending on: 1687 * - if a notes bubble exists 1688 * - if a notes bubble is expanded 1689 * 1690 * If no notes bubble exists, this will add and expand a bubble with the provided intent. The 1691 * intent must be explicit (i.e. include a package name or fully qualified component class name) 1692 * and the activity for it should be resizable. 1693 * 1694 * If a notes bubble exists, this will toggle the visibility of it, i.e. if the notes bubble is 1695 * expanded, calling this method will collapse it. If the notes bubble is not expanded, calling 1696 * this method will expand it. 1697 * 1698 * These bubbles are <b>not</b> backed by a notification and remain until the user dismisses 1699 * the bubble or bubble stack. 1700 * 1701 * Some details: 1702 * - Calling this method with a different intent than the existing bubble will do nothing 1703 * 1704 * @param intent the intent to display in the bubble expanded view. 1705 * @param user the {@link UserHandle} of the user to start this activity for. 1706 * @param icon the {@link Icon} to use for the bubble view. 1707 */ showOrHideNotesBubble(Intent intent, UserHandle user, @Nullable Icon icon)1708 public void showOrHideNotesBubble(Intent intent, UserHandle user, @Nullable Icon icon) { 1709 if (intent == null || intent.getPackage() == null) { 1710 Log.w(TAG, "Notes bubble failed to show, invalid intent: " + intent 1711 + ((intent != null) ? " with package: " + intent.getPackage() : " ")); 1712 return; 1713 } 1714 1715 String noteBubbleKey = Bubble.getNoteBubbleKeyForApp(intent.getPackage(), user); 1716 PackageManager packageManager = getPackageManagerForUser(mContext, user.getIdentifier()); 1717 if (!mResizabilityChecker.isResizableActivity(intent, packageManager, noteBubbleKey)) { 1718 // resize check logs any errors 1719 return; 1720 } 1721 1722 Bubble existingNotebubble = mBubbleData.getBubbleInStackWithKey(noteBubbleKey); 1723 ProtoLog.d(WM_SHELL_BUBBLES, 1724 "showOrHideNotesBubble, key=%s existingAppBubble=%s stackVisibility=%s " 1725 + "statusBarShade=%s", 1726 noteBubbleKey, existingNotebubble, 1727 (mStackView != null ? mStackView.getVisibility() : "null"), 1728 mIsStatusBarShade); 1729 1730 if (existingNotebubble != null) { 1731 BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble(); 1732 if (isStackExpanded()) { 1733 if (selectedBubble != null && noteBubbleKey.equals(selectedBubble.getKey())) { 1734 ProtoLog.d(WM_SHELL_BUBBLES, "collapseStack for %s", noteBubbleKey); 1735 // Notes bubble is expanded, lets collapse 1736 collapseStack(); 1737 } else { 1738 ProtoLog.d(WM_SHELL_BUBBLES, "setSelected for %s", noteBubbleKey); 1739 // Notes bubble is not selected, select it 1740 mBubbleData.setSelectedBubble(existingNotebubble); 1741 } 1742 } else { 1743 ProtoLog.d(WM_SHELL_BUBBLES, "setSelectedBubbleAndExpandStack %s", noteBubbleKey); 1744 // Notes bubble is not selected, select it & expand 1745 mBubbleData.setSelectedBubbleAndExpandStack(existingNotebubble); 1746 } 1747 } else { 1748 // Check if it exists in the overflow 1749 Bubble b = mBubbleData.getOverflowBubbleWithKey(noteBubbleKey); 1750 if (b != null) { 1751 // It's in the overflow, so remove it & reinflate 1752 mBubbleData.dismissBubbleWithKey(noteBubbleKey, Bubbles.DISMISS_NOTIF_CANCEL); 1753 // Update the bubble entry in the overflow with the latest intent. 1754 b.setIntent(intent); 1755 } else { 1756 // Notes bubble does not exist, lets add and expand it 1757 b = Bubble.createNotesBubble(intent, user, icon, mMainExecutor, 1758 mBackgroundExecutor); 1759 } 1760 ProtoLog.d(WM_SHELL_BUBBLES, "inflateAndAdd %s", noteBubbleKey); 1761 b.setShouldAutoExpand(true); 1762 inflateAndAdd(b, /* suppressFlyout= */ true, /* showInShade= */ false); 1763 } 1764 } 1765 1766 /** 1767 * Dismiss bubble if it exists and remove it from the stack 1768 */ dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason)1769 public void dismissBubble(Bubble bubble, @Bubbles.DismissReason int reason) { 1770 dismissBubble(bubble.getKey(), reason); 1771 } 1772 1773 /** 1774 * Dismiss bubble with given key if it exists and remove it from the stack 1775 */ dismissBubble(String key, @Bubbles.DismissReason int reason)1776 public void dismissBubble(String key, @Bubbles.DismissReason int reason) { 1777 mBubbleData.dismissBubbleWithKey(key, reason); 1778 } 1779 1780 /** 1781 * Performs a screenshot that may exclude the bubble layer, if one is present. The screenshot 1782 * can be access via the supplied {@link SynchronousScreenCaptureListener#getBuffer()} 1783 * asynchronously. 1784 */ getScreenshotExcludingBubble(int displayId, SynchronousScreenCaptureListener screenCaptureListener)1785 public void getScreenshotExcludingBubble(int displayId, 1786 SynchronousScreenCaptureListener screenCaptureListener) { 1787 try { 1788 ScreenCapture.CaptureArgs args = null; 1789 View viewToUse = mStackView != null ? mStackView : mLayerView; 1790 if (viewToUse != null) { 1791 ViewRootImpl viewRoot = viewToUse.getViewRootImpl(); 1792 if (viewRoot != null) { 1793 SurfaceControl bubbleLayer = viewRoot.getSurfaceControl(); 1794 if (bubbleLayer != null) { 1795 args = new ScreenCapture.CaptureArgs.Builder<>() 1796 .setExcludeLayers(new SurfaceControl[] {bubbleLayer}) 1797 .build(); 1798 } 1799 } 1800 } 1801 1802 mWmService.captureDisplay(displayId, args, screenCaptureListener); 1803 } catch (RemoteException e) { 1804 Log.e(TAG, "Failed to capture screenshot"); 1805 } 1806 } 1807 1808 /** Sets the note bubble's taskId which is cached for SysUI. */ setNoteBubbleTaskId(String key, int taskId)1809 public void setNoteBubbleTaskId(String key, int taskId) { 1810 mImpl.mCachedState.setNoteBubbleTaskId(key, taskId); 1811 } 1812 1813 /** 1814 * Fills the overflow bubbles by loading them from disk. 1815 */ loadOverflowBubblesFromDisk()1816 void loadOverflowBubblesFromDisk() { 1817 if (!mOverflowDataLoadNeeded) { 1818 return; 1819 } 1820 mOverflowDataLoadNeeded = false; 1821 List<UserInfo> users = mUserManager.getAliveUsers(); 1822 List<Integer> userIds = users.stream().map(userInfo -> userInfo.id).toList(); 1823 mDataRepository.loadBubbles(mCurrentUserId, userIds, (bubbles) -> { 1824 bubbles.forEach(bubble -> { 1825 if (mBubbleData.hasAnyBubbleWithKey(bubble.getKey())) { 1826 // if the bubble is already active, there's no need to push it to overflow 1827 return; 1828 } 1829 bubble.inflate( 1830 (b) -> mBubbleData.overflowBubble(Bubbles.DISMISS_RELOAD_FROM_DISK, bubble), 1831 mContext, 1832 mExpandedViewManager, 1833 mBubbleTaskViewFactory, 1834 mBubblePositioner, 1835 mStackView, 1836 mLayerView, 1837 mBubbleIconFactory, 1838 true /* skipInflation */); 1839 }); 1840 return null; 1841 }); 1842 } 1843 setUpBubbleViewsForMode()1844 void setUpBubbleViewsForMode() { 1845 mBubbleViewCallback = isShowingAsBubbleBar() 1846 ? mBubbleBarViewCallback 1847 : mBubbleStackViewCallback; 1848 1849 // reset the overflow so that it can be re-added later if needed. 1850 if (mStackView != null) { 1851 mStackView.resetOverflowView(); 1852 mStackView.removeAllViews(); 1853 } 1854 // cleanup existing bubble views so they can be recreated later if needed, but retain 1855 // TaskView. 1856 mBubbleData.getBubbles().forEach(b -> b.cleanupViews(/* cleanupTaskView= */ false)); 1857 1858 // remove the current bubble container from window manager, null it out, and create a new 1859 // container based on the current mode. 1860 removeFromWindowManagerMaybe(); 1861 mLayerView = null; 1862 mStackView = null; 1863 1864 if (!mBubbleData.hasBubbles()) { 1865 // if there are no bubbles, don't create the stack or layer views. they will be created 1866 // later when the first bubble is added. 1867 return; 1868 } 1869 1870 ensureBubbleViewsAndWindowCreated(); 1871 1872 // inflate bubble views 1873 BubbleViewInfoTask.Callback callback = null; 1874 if (!isShowingAsBubbleBar()) { 1875 callback = b -> { 1876 if (mStackView != null) { 1877 b.setSuppressFlyout(true); 1878 mStackView.addBubble(b); 1879 mStackView.setSelectedBubble(b); 1880 } else { 1881 Log.w(TAG, "Tried to add a bubble to the stack but the stack is null"); 1882 } 1883 }; 1884 } else if (mBubbleData.isExpanded() && mBubbleData.getSelectedBubble() != null) { 1885 callback = b -> { 1886 if (b.getKey().equals(mBubbleData.getSelectedBubbleKey())) { 1887 mLayerView.showExpandedView(b); 1888 } 1889 }; 1890 } 1891 for (int i = mBubbleData.getBubbles().size() - 1; i >= 0; i--) { 1892 Bubble bubble = mBubbleData.getBubbles().get(i); 1893 bubble.inflate(callback, 1894 mContext, 1895 mExpandedViewManager, 1896 mBubbleTaskViewFactory, 1897 mBubblePositioner, 1898 mStackView, 1899 mLayerView, 1900 mBubbleIconFactory, 1901 false /* skipInflation */); 1902 } 1903 } 1904 1905 /** 1906 * Adds or updates a bubble associated with the provided notification entry. 1907 * 1908 * @param notif the notification associated with this bubble. 1909 * @param suppressFlyout this bubble suppress flyout or not. 1910 * @param showInShade this bubble show in shade or not. 1911 */ 1912 @VisibleForTesting updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade)1913 public void updateBubble(BubbleEntry notif, boolean suppressFlyout, boolean showInShade) { 1914 // If this is an interruptive notif, mark that it's interrupted 1915 mSysuiProxy.setNotificationInterruption(notif.getKey()); 1916 final boolean isNonInterruptiveNotExpanding = !notif.getRanking().isTextChanged() 1917 && (notif.getBubbleMetadata() != null 1918 && !notif.getBubbleMetadata().getAutoExpandBubble()); 1919 final Bubble bubble; 1920 if (isNonInterruptiveNotExpanding 1921 && mBubbleData.hasOverflowBubbleWithKey(notif.getKey())) { 1922 // Update the bubble but don't promote it out of overflow 1923 bubble = mBubbleData.getOverflowBubbleWithKey(notif.getKey()); 1924 if (notif.isBubble()) { 1925 notif.setFlagBubble(false); 1926 } 1927 updateNotNotifyingEntry(bubble, notif, showInShade); 1928 } else if (mBubbleData.hasAnyBubbleWithKey(notif.getKey()) 1929 && isNonInterruptiveNotExpanding) { 1930 bubble = mBubbleData.getAnyBubbleWithKey(notif.getKey()); 1931 if (bubble != null) { 1932 updateNotNotifyingEntry(bubble, notif, showInShade); 1933 } 1934 } else if (mBubbleData.isSuppressedWithLocusId(notif.getLocusId())) { 1935 // Update the bubble but don't promote it out of overflow 1936 bubble = mBubbleData.getSuppressedBubbleWithKey(notif.getKey()); 1937 if (bubble != null) { 1938 updateNotNotifyingEntry(bubble, notif, showInShade); 1939 } 1940 } else { 1941 bubble = mBubbleData.getOrCreateBubble(notif, null /* persistedBubble */); 1942 if (notif.shouldSuppressNotificationList()) { 1943 // If we're suppressing notifs for DND, we don't want the bubbles to randomly 1944 // expand when DND turns off so flip the flag. 1945 if (bubble.shouldAutoExpand()) { 1946 bubble.setShouldAutoExpand(false); 1947 } 1948 mImpl.mCachedState.updateBubbleSuppressedState(bubble); 1949 } else { 1950 inflateAndAdd(bubble, suppressFlyout, showInShade); 1951 } 1952 } 1953 } 1954 updateNotNotifyingEntry(Bubble b, BubbleEntry entry, boolean showInShade)1955 void updateNotNotifyingEntry(Bubble b, BubbleEntry entry, boolean showInShade) { 1956 boolean showInShadeBefore = b.showInShade(); 1957 boolean isBubbleSelected = Objects.equals(b, mBubbleData.getSelectedBubble()); 1958 boolean isBubbleExpandedAndSelected = isStackExpanded() && isBubbleSelected; 1959 b.setEntry(entry); 1960 boolean suppress = isBubbleExpandedAndSelected || !showInShade || !b.showInShade(); 1961 b.setSuppressNotification(suppress); 1962 b.setShowDot(!isBubbleExpandedAndSelected); 1963 if (showInShadeBefore != b.showInShade()) { 1964 mImpl.mCachedState.updateBubbleSuppressedState(b); 1965 } 1966 } 1967 1968 @VisibleForTesting inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade)1969 public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade) { 1970 inflateAndAdd(bubble, suppressFlyout, showInShade, /* bubbleBarLocation= */ null); 1971 } 1972 1973 /** 1974 * Inflates and adds a bubble. Updates Bubble Bar location if bubbles 1975 * are shown in the Bubble Bar and the location is not null. 1976 */ 1977 @VisibleForTesting inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade, @Nullable BubbleBarLocation bubbleBarLocation)1978 public void inflateAndAdd(Bubble bubble, boolean suppressFlyout, boolean showInShade, 1979 @Nullable BubbleBarLocation bubbleBarLocation) { 1980 // Lazy init stack view when a bubble is created 1981 ensureBubbleViewsAndWindowCreated(); 1982 bubble.setInflateSynchronously(mInflateSynchronously); 1983 bubble.inflate( 1984 b -> mBubbleData.notificationEntryUpdated(b, suppressFlyout, showInShade, 1985 bubbleBarLocation), 1986 mContext, 1987 mExpandedViewManager, 1988 mBubbleTaskViewFactory, 1989 mBubblePositioner, 1990 mStackView, 1991 mLayerView, 1992 mBubbleIconFactory, 1993 false /* skipInflation */); 1994 } 1995 1996 /** 1997 * Removes the bubble with the given key. 1998 * <p> 1999 * Must be called from the main thread. 2000 */ 2001 @MainThread removeBubble(String key, int reason)2002 public void removeBubble(String key, int reason) { 2003 if (mBubbleData.hasAnyBubbleWithKey(key)) { 2004 mBubbleData.dismissBubbleWithKey(key, reason); 2005 } 2006 } 2007 2008 /** 2009 * Removes all the bubbles. 2010 * <p> 2011 * Must be called from the main thread. 2012 */ 2013 @VisibleForTesting 2014 @MainThread removeAllBubbles(@ubbles.DismissReason int reason)2015 public void removeAllBubbles(@Bubbles.DismissReason int reason) { 2016 mBubbleData.dismissAll(reason); 2017 if (reason == Bubbles.DISMISS_USER_GESTURE) { 2018 mLogger.log(BubbleLogger.Event.BUBBLE_BAR_DISMISSED_DRAG_BAR); 2019 } 2020 } 2021 onEntryAdded(BubbleEntry entry)2022 private void onEntryAdded(BubbleEntry entry) { 2023 if (canLaunchInTaskView(mContext, entry)) { 2024 updateBubble(entry); 2025 } 2026 } 2027 2028 @VisibleForTesting onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem)2029 public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem) { 2030 if (!fromSystem) { 2031 return; 2032 } 2033 // shouldBubbleUp checks canBubble & for bubble metadata 2034 boolean shouldBubble = shouldBubbleUp && canLaunchInTaskView(mContext, entry); 2035 if (!shouldBubble && mBubbleData.hasAnyBubbleWithKey(entry.getKey())) { 2036 // It was previously a bubble but no longer a bubble -- lets remove it 2037 removeBubble(entry.getKey(), DISMISS_NO_LONGER_BUBBLE); 2038 } else if (shouldBubble && entry.isBubble()) { 2039 updateBubble(entry); 2040 } 2041 } 2042 onEntryRemoved(BubbleEntry entry)2043 private void onEntryRemoved(BubbleEntry entry) { 2044 if (isSummaryOfBubbles(entry)) { 2045 final String groupKey = entry.getStatusBarNotification().getGroupKey(); 2046 mBubbleData.removeSuppressedSummary(groupKey); 2047 2048 // Remove any associated bubble children with the summary 2049 final List<Bubble> bubbleChildren = getBubblesInGroup(groupKey); 2050 for (int i = 0; i < bubbleChildren.size(); i++) { 2051 removeBubble(bubbleChildren.get(i).getKey(), DISMISS_GROUP_CANCELLED); 2052 } 2053 } else { 2054 removeBubble(entry.getKey(), DISMISS_NOTIF_CANCEL); 2055 } 2056 } 2057 2058 @VisibleForTesting onRankingUpdated(RankingMap rankingMap, HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey)2059 public void onRankingUpdated(RankingMap rankingMap, 2060 HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) { 2061 if (mTmpRanking == null) { 2062 mTmpRanking = new NotificationListenerService.Ranking(); 2063 } 2064 String[] orderedKeys = rankingMap.getOrderedKeys(); 2065 for (int i = 0; i < orderedKeys.length; i++) { 2066 String key = orderedKeys[i]; 2067 Pair<BubbleEntry, Boolean> entryData = entryDataByKey.get(key); 2068 BubbleEntry entry = entryData.first; 2069 boolean shouldBubbleUp = entryData.second; 2070 if (entry != null && !isCurrentProfile( 2071 entry.getStatusBarNotification().getUser().getIdentifier())) { 2072 return; 2073 } 2074 if (entry != null && (entry.shouldSuppressNotificationList() 2075 || entry.getRanking().isSuspended())) { 2076 shouldBubbleUp = false; 2077 } 2078 rankingMap.getRanking(key, mTmpRanking); 2079 boolean isActiveOrInOverflow = mBubbleData.hasAnyBubbleWithKey(key); 2080 boolean isActive = mBubbleData.hasBubbleInStackWithKey(key); 2081 if (isActiveOrInOverflow && !mTmpRanking.canBubble()) { 2082 // If this entry is no longer allowed to bubble, dismiss with the BLOCKED reason. 2083 // This means that the app or channel's ability to bubble has been revoked. 2084 mBubbleData.dismissBubbleWithKey(key, DISMISS_BLOCKED); 2085 } else if (isActiveOrInOverflow && !shouldBubbleUp) { 2086 // If this entry is allowed to bubble, but cannot currently bubble up or is 2087 // suspended, dismiss it. This happens when DND is enabled and configured to hide 2088 // bubbles, or focus mode is enabled and the app is designated as distracting. 2089 // Dismissing with the reason DISMISS_NO_BUBBLE_UP will retain the underlying 2090 // notification, so that the bubble will be re-created if shouldBubbleUp returns 2091 // true. 2092 mBubbleData.dismissBubbleWithKey(key, DISMISS_NO_BUBBLE_UP); 2093 } else if (entry != null && mTmpRanking.isBubble() && !isActiveOrInOverflow) { 2094 entry.setFlagBubble(true); 2095 onEntryUpdated(entry, shouldBubbleUp, /* fromSystem= */ true); 2096 } 2097 } 2098 } 2099 2100 @VisibleForTesting onNotificationChannelModified(String pkg, UserHandle user, NotificationChannel channel, int modificationType)2101 public void onNotificationChannelModified(String pkg, UserHandle user, 2102 NotificationChannel channel, int modificationType) { 2103 // Only query overflow bubbles here because active bubbles will have an active notification 2104 // and channel changes we care about would result in a ranking update. 2105 List<Bubble> overflowBubbles = new ArrayList<>(mBubbleData.getOverflowBubbles()); 2106 for (int i = 0; i < overflowBubbles.size(); i++) { 2107 Bubble b = overflowBubbles.get(i); 2108 if (Objects.equals(b.getShortcutId(), channel.getConversationId()) 2109 && b.getPackageName().equals(pkg) 2110 && b.getUser().getIdentifier() == user.getIdentifier()) { 2111 if (!channel.canBubble() || channel.isDeleted()) { 2112 mBubbleData.dismissBubbleWithKey(b.getKey(), DISMISS_NO_LONGER_BUBBLE); 2113 } 2114 } 2115 } 2116 } 2117 2118 /** 2119 * Retrieves any bubbles that are part of the notification group represented by the provided 2120 * group key. 2121 */ getBubblesInGroup(@ullable String groupKey)2122 private ArrayList<Bubble> getBubblesInGroup(@Nullable String groupKey) { 2123 ArrayList<Bubble> bubbleChildren = new ArrayList<>(); 2124 if (groupKey == null) { 2125 return bubbleChildren; 2126 } 2127 for (Bubble bubble : mBubbleData.getBubbles()) { 2128 if (bubble.getGroupKey() != null && groupKey.equals(bubble.getGroupKey())) { 2129 bubbleChildren.add(bubble); 2130 } 2131 } 2132 return bubbleChildren; 2133 } 2134 setIsBubble(@onNull final BubbleEntry entry, final boolean isBubble, final boolean autoExpand)2135 private void setIsBubble(@NonNull final BubbleEntry entry, final boolean isBubble, 2136 final boolean autoExpand) { 2137 Objects.requireNonNull(entry); 2138 entry.setFlagBubble(isBubble); 2139 try { 2140 int flags = 0; 2141 if (autoExpand) { 2142 flags = Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; 2143 flags |= Notification.BubbleMetadata.FLAG_AUTO_EXPAND_BUBBLE; 2144 } 2145 mBarService.onNotificationBubbleChanged(entry.getKey(), isBubble, flags); 2146 } catch (RemoteException e) { 2147 // Bad things have happened 2148 } 2149 } 2150 setIsBubble(@onNull final Bubble b, final boolean isBubble)2151 private void setIsBubble(@NonNull final Bubble b, final boolean isBubble) { 2152 Objects.requireNonNull(b); 2153 b.setIsBubble(isBubble); 2154 mSysuiProxy.getPendingOrActiveEntry(b.getKey(), (entry) -> { 2155 mMainExecutor.execute(() -> { 2156 if (entry != null) { 2157 // Updating the entry to be a bubble will trigger our normal update flow 2158 setIsBubble(entry, isBubble, b.shouldAutoExpand()); 2159 } else if (isBubble) { 2160 // If bubble doesn't exist, it's a persisted bubble so we need to add it to the 2161 // stack ourselves 2162 Bubble bubble = mBubbleData.getOrCreateBubble(null, b /* persistedBubble */); 2163 inflateAndAdd(bubble, bubble.shouldAutoExpand() /* suppressFlyout */, 2164 !bubble.shouldAutoExpand() /* showInShade */); 2165 } 2166 }); 2167 }); 2168 } 2169 2170 @Override mergeTaskWithUnfold(@onNull ActivityManager.RunningTaskInfo taskInfo, @NonNull TransitionInfo.Change change, @NonNull SurfaceControl.Transaction startT, @NonNull SurfaceControl.Transaction finishT)2171 public boolean mergeTaskWithUnfold(@NonNull ActivityManager.RunningTaskInfo taskInfo, 2172 @NonNull TransitionInfo.Change change, 2173 @NonNull SurfaceControl.Transaction startT, 2174 @NonNull SurfaceControl.Transaction finishT) { 2175 if (!mBubbleTransitions.mTaskViewTransitions.isTaskViewTask(taskInfo)) { 2176 // if this task isn't managed by bubble transitions just bail. 2177 return false; 2178 } 2179 if (isShowingAsBubbleBar()) { 2180 // if bubble bar is enabled, the task view will switch to a new surface on unfold, so we 2181 // should not merge the transition. 2182 return false; 2183 } 2184 2185 boolean merged = mBubbleTransitions.mTaskViewTransitions.updateBoundsForUnfold( 2186 change.getEndAbsBounds(), startT, finishT, change.getTaskInfo(), change.getLeash()); 2187 if (merged) { 2188 BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble(); 2189 if (selectedBubble != null && selectedBubble.getExpandedView() != null) { 2190 selectedBubble.getExpandedView().onContainerClipUpdate(); 2191 } 2192 } 2193 return merged; 2194 } 2195 2196 /** When bubbles are floating, this will be used to notify the floating views. */ 2197 private final BubbleViewCallback mBubbleStackViewCallback = new BubbleViewCallback() { 2198 @Override 2199 public void removeBubble(Bubble removedBubble) { 2200 if (mStackView != null) { 2201 mStackView.removeBubble(removedBubble); 2202 } 2203 } 2204 2205 @Override 2206 public void addBubble(Bubble addedBubble) { 2207 if (mStackView != null) { 2208 mStackView.addBubble(addedBubble); 2209 } 2210 } 2211 2212 @Override 2213 public void updateBubble(Bubble updatedBubble) { 2214 if (mStackView != null) { 2215 mStackView.updateBubble(updatedBubble); 2216 } 2217 } 2218 2219 @Override 2220 public void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer) { 2221 if (mStackView != null) { 2222 mStackView.updateBubbleOrder(bubbleOrder, updatePointer); 2223 } 2224 } 2225 2226 @Override 2227 public void suppressionChanged(Bubble bubble, boolean isSuppressed) { 2228 if (mStackView != null) { 2229 mStackView.setBubbleSuppressed(bubble, isSuppressed); 2230 } 2231 } 2232 2233 @Override 2234 public void expansionChanged(boolean isExpanded) { 2235 if (mStackView != null) { 2236 mStackView.setExpanded(isExpanded); 2237 } 2238 } 2239 2240 @Override 2241 public void selectionChanged(BubbleViewProvider selectedBubble) { 2242 if (mStackView != null) { 2243 mStackView.setSelectedBubble(selectedBubble); 2244 } 2245 2246 } 2247 2248 @Override 2249 public void bubbleOverflowChanged(boolean hasBubbles) { 2250 if (Flags.enableOptionalBubbleOverflow()) { 2251 if (mStackView != null) { 2252 mStackView.showOverflow(hasBubbles); 2253 } 2254 } 2255 } 2256 }; 2257 2258 /** When bubbles are in the bubble bar, this will be used to notify bubble bar views. */ 2259 private final BubbleViewCallback mBubbleBarViewCallback = new BubbleViewCallback() { 2260 @Override 2261 public void removeBubble(Bubble removedBubble) { 2262 if (mLayerView != null) { 2263 final BubbleTransitions.BubbleTransition bubbleTransit = 2264 removedBubble.getPreparingTransition(); 2265 mLayerView.removeBubble(removedBubble, () -> { 2266 if (bubbleTransit != null) { 2267 bubbleTransit.continueCollapse(); 2268 } 2269 if (!mBubbleData.hasBubbles() && !isStackExpanded()) { 2270 mLayerView.setVisibility(INVISIBLE); 2271 removeFromWindowManagerMaybe(); 2272 } 2273 }); 2274 } 2275 } 2276 2277 @Override 2278 public void addBubble(Bubble addedBubble) { 2279 // Only log metrics event 2280 mLogger.log(addedBubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_POSTED); 2281 // Nothing to do for adds, these are handled by launcher / in the bubble bar. 2282 } 2283 2284 @Override 2285 public void updateBubble(Bubble updatedBubble) { 2286 // Only log metrics event 2287 mLogger.log(updatedBubble, BubbleLogger.Event.BUBBLE_BAR_BUBBLE_UPDATED); 2288 // Nothing to do for updates, these are handled by launcher / in the bubble bar. 2289 } 2290 2291 @Override 2292 public void bubbleOrderChanged(List<Bubble> bubbleOrder, boolean updatePointer) { 2293 // Nothing to do for order changes, these are handled by launcher / in the bubble bar. 2294 } 2295 2296 @Override 2297 public void bubbleOverflowChanged(boolean hasBubbles) { 2298 // Nothing to do for our views, handled by launcher / in the bubble bar. 2299 } 2300 2301 @Override 2302 public void suppressionChanged(Bubble bubble, boolean isSuppressed) { 2303 // Nothing to do for our views, handled by launcher / in the bubble bar. 2304 } 2305 2306 @Override 2307 public void expansionChanged(boolean isExpanded) { 2308 // in bubble bar mode, let the request to show the expanded view come from launcher. 2309 // only collapse here if we're collapsing. 2310 if (!isExpanded) { 2311 collapseExpandedViewForBubbleBar(); 2312 } 2313 2314 BubbleLogger.Event event = isExpanded ? BubbleLogger.Event.BUBBLE_BAR_EXPANDED 2315 : BubbleLogger.Event.BUBBLE_BAR_COLLAPSED; 2316 BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble(); 2317 if (selectedBubble instanceof Bubble) { 2318 mLogger.log((Bubble) selectedBubble, event); 2319 } else { 2320 mLogger.log(event); 2321 } 2322 } 2323 2324 @Override 2325 public void selectionChanged(BubbleViewProvider selectedBubble) { 2326 // Only need to update the layer view if we're currently expanded for selection changes. 2327 if (mLayerView != null && mLayerView.isExpanded()) { 2328 mLayerView.showExpandedView(selectedBubble); 2329 if (selectedBubble instanceof Bubble) { 2330 mLogger.log((Bubble) selectedBubble, 2331 BubbleLogger.Event.BUBBLE_BAR_BUBBLE_SWITCHED); 2332 } 2333 } 2334 } 2335 }; 2336 2337 @SuppressWarnings("FieldCanBeLocal") 2338 private final BubbleData.Listener mBubbleDataListener = new BubbleData.Listener() { 2339 2340 @Override 2341 public void applyUpdate(BubbleData.Update update) { 2342 ProtoLog.d(WM_SHELL_BUBBLES, "mBubbleDataListener#applyUpdate:" 2343 + " added=%s removed=%b updated=%s orderChanged=%b expansionChanged=%b" 2344 + " expanded=%b selectionChanged=%b selected=%s" 2345 + " suppressed=%s unsupressed=%s shouldShowEducation=%b showOverflowChanged=%b" 2346 + " bubbleBarLocation=%s", 2347 update.addedBubble != null ? update.addedBubble.getKey() : "null", 2348 !update.removedBubbles.isEmpty(), 2349 update.updatedBubble != null ? update.updatedBubble.getKey() : "null", 2350 update.orderChanged, update.expandedChanged, update.expanded, 2351 update.selectionChanged, 2352 update.selectedBubble != null ? update.selectedBubble.getKey() : "null", 2353 update.suppressedBubble != null ? update.suppressedBubble.getKey() : "null", 2354 update.unsuppressedBubble != null ? update.unsuppressedBubble.getKey() : "null", 2355 update.shouldShowEducation, update.showOverflowChanged, 2356 update.mBubbleBarLocation != null ? update.mBubbleBarLocation.toString() 2357 : "null"); 2358 2359 ensureBubbleViewsAndWindowCreated(); 2360 2361 // Lazy load overflow bubbles from disk 2362 loadOverflowBubblesFromDisk(); 2363 2364 if (update.showOverflowChanged) { 2365 mBubbleViewCallback.bubbleOverflowChanged(!update.overflowBubbles.isEmpty()); 2366 } 2367 2368 // If bubbles in the overflow have a dot, make sure the overflow shows a dot 2369 updateOverflowButtonDot(); 2370 2371 // Update bubbles in overflow. 2372 if (mOverflowListener != null) { 2373 mOverflowListener.applyUpdate(update); 2374 } 2375 2376 // Do removals, if any. 2377 ArrayList<Pair<Bubble, Integer>> removedBubbles = 2378 new ArrayList<>(update.removedBubbles); 2379 ArrayList<Bubble> bubblesToBeRemovedFromRepository = new ArrayList<>(); 2380 for (Pair<Bubble, Integer> removed : removedBubbles) { 2381 final Bubble bubble = removed.first; 2382 @Bubbles.DismissReason final int reason = removed.second; 2383 2384 mBubbleViewCallback.removeBubble(bubble); 2385 2386 // Leave the notification in place if we're dismissing due to user switching, or 2387 // because DND is suppressing the bubble. In both of those cases, we need to be able 2388 // to restore the bubble from the notification later. 2389 if (reason == DISMISS_USER_CHANGED || reason == DISMISS_NO_BUBBLE_UP) { 2390 continue; 2391 } 2392 if (reason == DISMISS_NOTIF_CANCEL 2393 || reason == DISMISS_SHORTCUT_REMOVED) { 2394 bubblesToBeRemovedFromRepository.add(bubble); 2395 } 2396 if (!mBubbleData.hasBubbleInStackWithKey(bubble.getKey())) { 2397 if (!mBubbleData.hasOverflowBubbleWithKey(bubble.getKey()) 2398 && (!bubble.showInShade() 2399 || reason == DISMISS_NOTIF_CANCEL 2400 || reason == DISMISS_GROUP_CANCELLED)) { 2401 // The bubble is now gone & the notification is hidden from the shade, so 2402 // time to actually remove it 2403 mSysuiProxy.notifyRemoveNotification(bubble.getKey(), REASON_CANCEL); 2404 } else { 2405 if (bubble.isBubble()) { 2406 setIsBubble(bubble, false /* isBubble */); 2407 } 2408 mSysuiProxy.updateNotificationBubbleButton(bubble.getKey()); 2409 } 2410 } 2411 } 2412 mDataRepository.removeBubbles(mCurrentUserId, bubblesToBeRemovedFromRepository); 2413 2414 if (update.addedBubble != null) { 2415 mDataRepository.addBubble(mCurrentUserId, update.addedBubble); 2416 mBubbleViewCallback.addBubble(update.addedBubble); 2417 } 2418 2419 if (update.updatedBubble != null) { 2420 mBubbleViewCallback.updateBubble(update.updatedBubble); 2421 } 2422 2423 if (update.suppressedBubble != null) { 2424 mBubbleViewCallback.suppressionChanged(update.suppressedBubble, true); 2425 } 2426 2427 if (update.unsuppressedBubble != null) { 2428 mBubbleViewCallback.suppressionChanged(update.unsuppressedBubble, false); 2429 } 2430 2431 boolean collapseStack = update.expandedChanged && !update.expanded; 2432 2433 // At this point, the correct bubbles are inflated in the stack. 2434 // Make sure the order in bubble data is reflected in bubble row. 2435 if (update.orderChanged) { 2436 mDataRepository.addBubbles(mCurrentUserId, update.bubbles); 2437 // if the stack is going to be collapsed, do not update pointer position 2438 // after reordering 2439 mBubbleViewCallback.bubbleOrderChanged(update.bubbles, !collapseStack); 2440 } 2441 2442 if (collapseStack) { 2443 mBubbleViewCallback.expansionChanged(/* expanded= */ false); 2444 mSysuiProxy.requestNotificationShadeTopUi(false, TAG); 2445 } 2446 2447 if (update.selectionChanged) { 2448 mBubbleViewCallback.selectionChanged(update.selectedBubble); 2449 } 2450 2451 // Expanding? Apply this last. 2452 if (update.expandedChanged && update.expanded) { 2453 mBubbleViewCallback.expansionChanged(/* expanded= */ true); 2454 mSysuiProxy.requestNotificationShadeTopUi(true, TAG); 2455 } 2456 2457 mSysuiProxy.notifyInvalidateNotifications("BubbleData.Listener.applyUpdate"); 2458 updateBubbleViews(); 2459 2460 // Update the cached state for queries from SysUI 2461 mImpl.mCachedState.update(update); 2462 2463 if (isShowingAsBubbleBar()) { 2464 BubbleBarUpdate bubbleBarUpdate = update.toBubbleBarUpdate(); 2465 // Some updates aren't relevant to the bubble bar so check first. 2466 if (bubbleBarUpdate.anythingChanged()) { 2467 mBubbleStateListener.onBubbleStateChange(bubbleBarUpdate); 2468 } 2469 } 2470 } 2471 }; 2472 showExpandedViewForBubbleBar()2473 private void showExpandedViewForBubbleBar() { 2474 BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble(); 2475 if (selectedBubble == null) return; 2476 if (selectedBubble instanceof Bubble) { 2477 final Bubble bubble = (Bubble) selectedBubble; 2478 if (bubble.getPreparingTransition() != null) { 2479 bubble.getPreparingTransition().continueExpand(); 2480 return; 2481 } 2482 } 2483 if (mLayerView == null) return; 2484 mLayerView.showExpandedView(selectedBubble); 2485 } 2486 collapseExpandedViewForBubbleBar()2487 private void collapseExpandedViewForBubbleBar() { 2488 if (mLayerView != null && mLayerView.isExpanded()) { 2489 if (mBubblePositioner.isImeVisible()) { 2490 // If we're collapsing, hide the IME 2491 hideCurrentInputMethod(null); 2492 } 2493 mLayerView.collapse(); 2494 } 2495 } 2496 updateOverflowButtonDot()2497 private void updateOverflowButtonDot() { 2498 BubbleOverflow overflow = mBubbleData.getOverflow(); 2499 if (overflow == null) return; 2500 2501 for (Bubble b : mBubbleData.getOverflowBubbles()) { 2502 if (b.showDot()) { 2503 overflow.setShowDot(true); 2504 return; 2505 } 2506 } 2507 overflow.setShowDot(false); 2508 } 2509 handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, IntConsumer removeCallback)2510 private boolean handleDismissalInterception(BubbleEntry entry, 2511 @Nullable List<BubbleEntry> children, IntConsumer removeCallback) { 2512 if (isSummaryOfBubbles(entry)) { 2513 handleSummaryDismissalInterception(entry, children, removeCallback); 2514 } else { 2515 Bubble bubble = mBubbleData.getBubbleInStackWithKey(entry.getKey()); 2516 if (bubble == null || !entry.isBubble()) { 2517 bubble = mBubbleData.getOverflowBubbleWithKey(entry.getKey()); 2518 } 2519 if (bubble == null) { 2520 return false; 2521 } 2522 bubble.setSuppressNotification(true); 2523 bubble.setShowDot(false /* show */); 2524 } 2525 // Update the shade 2526 mSysuiProxy.notifyInvalidateNotifications("BubbleController.handleDismissalInterception"); 2527 return true; 2528 } 2529 isSummaryOfBubbles(BubbleEntry entry)2530 private boolean isSummaryOfBubbles(BubbleEntry entry) { 2531 String groupKey = entry.getStatusBarNotification().getGroupKey(); 2532 ArrayList<Bubble> bubbleChildren = getBubblesInGroup(groupKey); 2533 boolean isSuppressedSummary = mBubbleData.isSummarySuppressed(groupKey) 2534 && mBubbleData.getSummaryKey(groupKey).equals(entry.getKey()); 2535 boolean isSummary = entry.getStatusBarNotification().getNotification().isGroupSummary(); 2536 return (isSuppressedSummary || isSummary) && !bubbleChildren.isEmpty(); 2537 } 2538 handleSummaryDismissalInterception( BubbleEntry summary, @Nullable List<BubbleEntry> children, IntConsumer removeCallback)2539 private void handleSummaryDismissalInterception( 2540 BubbleEntry summary, @Nullable List<BubbleEntry> children, IntConsumer removeCallback) { 2541 if (children != null) { 2542 for (int i = 0; i < children.size(); i++) { 2543 final BubbleEntry child = children.get(i); 2544 if (mBubbleData.hasAnyBubbleWithKey(child.getKey())) { 2545 // Suppress the bubbled child 2546 // As far as group manager is concerned, once a child is no longer shown 2547 // in the shade, it is essentially removed. 2548 final Bubble bubbleChild = mBubbleData.getAnyBubbleWithKey(child.getKey()); 2549 if (bubbleChild != null) { 2550 bubbleChild.setSuppressNotification(true); 2551 bubbleChild.setShowDot(false /* show */); 2552 } 2553 } else { 2554 // non-bubbled children can be removed 2555 removeCallback.accept(i); 2556 } 2557 } 2558 } 2559 2560 // And since all children are removed, remove the summary. 2561 removeCallback.accept(-1); 2562 2563 mBubbleData.addSummaryToSuppress(summary.getStatusBarNotification().getGroupKey(), 2564 summary.getKey()); 2565 } 2566 2567 /** 2568 * Updates the visibility of the bubbles based on current state. 2569 * Does not un-bubble, just hides or un-hides the views themselves. 2570 * 2571 * Updates view description for TalkBack focus. 2572 * Updates bubbles' icon views clickable states (when floating). 2573 */ updateBubbleViews()2574 public void updateBubbleViews() { 2575 if (mStackView == null && mLayerView == null) { 2576 return; 2577 } 2578 ProtoLog.v(WM_SHELL_BUBBLES, "updateBubbleViews mIsStatusBarShade=%s hasBubbles=%s", 2579 mIsStatusBarShade, hasBubbles()); 2580 if (!mIsStatusBarShade) { 2581 // Bubbles don't appear when the device is locked. 2582 if (mStackView != null) { 2583 mStackView.setVisibility(INVISIBLE); 2584 } 2585 if (mLayerView != null) { 2586 mLayerView.setVisibility(INVISIBLE); 2587 } 2588 } else if (hasBubbles()) { 2589 // If we're unlocked, show the stack if we have bubbles. If we don't have bubbles, the 2590 // stack will be set to INVISIBLE in onAllBubblesAnimatedOut after the bubbles animate 2591 // out. 2592 if (mStackView != null) { 2593 mStackView.setVisibility(VISIBLE); 2594 } 2595 if (mLayerView != null) { 2596 mLayerView.setVisibility(VISIBLE); 2597 } 2598 } 2599 2600 if (mStackView != null) { 2601 mStackView.updateContentDescription(); 2602 mStackView.updateBubblesAcessibillityStates(); 2603 } else if (mLayerView != null) { 2604 // TODO(b/273313561): handle a11y for BubbleBarLayerView 2605 } 2606 } 2607 2608 /** 2609 * Returns whether the stack is animating or not. 2610 */ isStackAnimating()2611 public boolean isStackAnimating() { 2612 return mStackView != null 2613 && (mStackView.isExpansionAnimating() 2614 || mStackView.isSwitchAnimating()); 2615 } 2616 2617 @VisibleForTesting 2618 @Nullable getStackView()2619 public BubbleStackView getStackView() { 2620 return mStackView; 2621 } 2622 2623 @VisibleForTesting 2624 @Nullable getLayerView()2625 public BubbleBarLayerView getLayerView() { 2626 return mLayerView; 2627 } 2628 2629 /** 2630 * Check if notification panel is in an expanded state. 2631 * Makes a call to System UI process and delivers the result via {@code callback} on the 2632 * WM Shell main thread. 2633 * 2634 * @param callback callback that has the result of notification panel expanded state 2635 */ isNotificationPanelExpanded(Consumer<Boolean> callback)2636 public void isNotificationPanelExpanded(Consumer<Boolean> callback) { 2637 mSysuiProxy.isNotificationPanelExpand(expanded -> 2638 mMainExecutor.execute(() -> callback.accept(expanded))); 2639 } 2640 2641 /** 2642 * Show bubbles UI when triggered via shortcut. 2643 * 2644 * <p>When there are bubbles visible, expands the top-most bubble. When there are no bubbles 2645 * visible, opens the bubbles overflow UI. 2646 */ showBubblesFromShortcut()2647 public void showBubblesFromShortcut() { 2648 if (isStackExpanded()) { 2649 ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: stack visible, skip"); 2650 return; 2651 } 2652 if (mBubbleData.getSelectedBubble() != null) { 2653 ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: open selected bubble"); 2654 expandStackWithSelectedBubble(); 2655 return; 2656 } 2657 BubbleViewProvider bubbleToSelect = CollectionUtils.firstOrNull(mBubbleData.getBubbles()); 2658 if (bubbleToSelect == null) { 2659 ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: no bubbles"); 2660 // make sure overflow bubbles are loaded 2661 loadOverflowBubblesFromDisk(); 2662 bubbleToSelect = mBubbleData.getOverflow(); 2663 } 2664 ProtoLog.v(WM_SHELL_BUBBLES, "showBubblesFromShortcut: select and open %s", 2665 bubbleToSelect.getKey()); 2666 mBubbleData.setSelectedBubbleAndExpandStack(bubbleToSelect); 2667 } 2668 moveDraggedBubbleToFullscreen(String key, Point dropLocation)2669 private void moveDraggedBubbleToFullscreen(String key, Point dropLocation) { 2670 Bubble b = mBubbleData.getBubbleInStackWithKey(key); 2671 mBubbleTransitions.startDraggedBubbleIconToFullscreen(b, dropLocation); 2672 } 2673 isDeviceLocked()2674 private boolean isDeviceLocked() { 2675 return !mIsStatusBarShade; 2676 } 2677 2678 /** 2679 * Description of current bubble state. 2680 */ dump(PrintWriter pw, String prefix)2681 private void dump(PrintWriter pw, String prefix) { 2682 pw.print(prefix); pw.println("BubbleController state:"); 2683 pw.print(prefix); pw.println(" currentUserId= " + mCurrentUserId); 2684 pw.print(prefix); pw.println(" isStatusBarShade= " + mIsStatusBarShade); 2685 pw.print(prefix); pw.println(" isShowingAsBubbleBar= " + isShowingAsBubbleBar()); 2686 pw.print(prefix); pw.println(" isImeVisible= " + mBubblePositioner.isImeVisible()); 2687 pw.println(); 2688 2689 mBubbleData.dump(pw); 2690 pw.println(); 2691 2692 if (mStackView != null) { 2693 mStackView.dump(pw); 2694 } 2695 pw.println(); 2696 2697 mImpl.mCachedState.dump(pw); 2698 } 2699 2700 /** 2701 * Whether an intent is properly configured to display in a 2702 * {@link TaskView}. 2703 * 2704 * Keep checks in sync with BubbleExtractor#canLaunchInTaskView. Typically 2705 * that should filter out any invalid bubbles, but should protect SysUI side just in case. 2706 * 2707 * @param context the context to use. 2708 * @param entry the entry to bubble. 2709 */ canLaunchInTaskView(Context context, BubbleEntry entry)2710 boolean canLaunchInTaskView(Context context, BubbleEntry entry) { 2711 if (BubbleAnythingFlagHelper.enableCreateAnyBubble()) return true; 2712 PendingIntent intent = entry.getBubbleMetadata() != null 2713 ? entry.getBubbleMetadata().getIntent() 2714 : null; 2715 if (entry.getBubbleMetadata() != null 2716 && entry.getBubbleMetadata().getShortcutId() != null) { 2717 return true; 2718 } 2719 if (intent == null) { 2720 Log.w(TAG, "Unable to create bubble -- no intent: " + entry.getKey()); 2721 return false; 2722 } 2723 PackageManager packageManager = getPackageManagerForUser( 2724 context, entry.getStatusBarNotification().getUser().getIdentifier()); 2725 return mResizabilityChecker.isResizableActivity(intent.getIntent(), packageManager, 2726 entry.getKey()); 2727 } 2728 getPackageManagerForUser(Context context, int userId)2729 static PackageManager getPackageManagerForUser(Context context, int userId) { 2730 Context contextForUser = context; 2731 // UserHandle defines special userId as negative values, e.g. USER_ALL 2732 if (userId >= 0) { 2733 try { 2734 // Create a context for the correct user so if a package isn't installed 2735 // for user 0 we can still load information about the package. 2736 contextForUser = 2737 context.createPackageContextAsUser(context.getPackageName(), 2738 Context.CONTEXT_RESTRICTED, 2739 new UserHandle(userId)); 2740 } catch (PackageManager.NameNotFoundException e) { 2741 // Shouldn't fail to find the package name for system ui. 2742 } 2743 } 2744 return contextForUser.getPackageManager(); 2745 } 2746 2747 /** {@link ImeListener} that dispatches IME visibility updates to the stack. */ 2748 private class BubblesImeListener extends ImeListener implements 2749 DisplayImeController.ImePositionProcessor { 2750 BubblesImeListener(DisplayController displayController, int displayId)2751 BubblesImeListener(DisplayController displayController, int displayId) { 2752 super(displayController, displayId); 2753 } 2754 2755 @Override onImeVisibilityChanged(boolean imeVisible, int imeHeight)2756 protected void onImeVisibilityChanged(boolean imeVisible, int imeHeight) { 2757 if (getDisplayId() != mContext.getDisplayId()) { 2758 return; 2759 } 2760 // the imeHeight here is actually the ime inset; it only includes the part of the ime 2761 // that overlaps with the Bubbles window. adjust it to include the bottom screen inset, 2762 // so we have the total height of the ime. 2763 int totalImeHeight = imeHeight + mBubblePositioner.getInsets().bottom; 2764 mBubblePositioner.setImeVisible(imeVisible, totalImeHeight); 2765 if (mStackView != null) { 2766 mStackView.setImeVisible(imeVisible); 2767 if (!imeVisible && mOnImeHidden != null) { 2768 mOnImeHidden.run(); 2769 mOnImeHidden = null; 2770 } 2771 } 2772 } 2773 2774 @Override onImeStartPositioning(int displayId, int hiddenTop, int shownTop, boolean showing, boolean isFloating, SurfaceControl.Transaction t)2775 public int onImeStartPositioning(int displayId, int hiddenTop, int shownTop, 2776 boolean showing, boolean isFloating, SurfaceControl.Transaction t) { 2777 if (mContext.getDisplayId() != displayId) { 2778 return IME_ANIMATION_DEFAULT; 2779 } 2780 2781 if (showing) { 2782 mBubblePositioner.setImeVisible(true, hiddenTop - shownTop); 2783 } else { 2784 mBubblePositioner.setImeVisible(false, 0); 2785 } 2786 if (mStackView != null) { 2787 mStackView.setImeVisible(showing); 2788 } 2789 2790 return IME_ANIMATION_DEFAULT; 2791 } 2792 2793 @Override onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t)2794 public void onImePositionChanged(int displayId, int imeTop, SurfaceControl.Transaction t) { 2795 if (mContext.getDisplayId() != displayId) { 2796 return; 2797 } 2798 if (mLayerView != null) { 2799 mLayerView.onImeTopChanged(imeTop); 2800 } 2801 } 2802 } 2803 2804 /** 2805 * The interface for calls from outside the host process. 2806 */ 2807 @BinderThread 2808 private class IBubblesImpl extends IBubbles.Stub implements ExternalInterfaceBinder { 2809 private BubbleController mController; 2810 private final SingleInstanceRemoteListener<BubbleController, IBubblesListener> mListener; 2811 private final Bubbles.BubbleStateListener mBubbleListener = 2812 new Bubbles.BubbleStateListener() { 2813 @Override 2814 public void onBubbleStateChange(BubbleBarUpdate update) { 2815 Bundle b = new Bundle(); 2816 b.setClassLoader(BubbleBarUpdate.class.getClassLoader()); 2817 b.putParcelable(BubbleBarUpdate.BUNDLE_KEY, update); 2818 mListener.call(l -> l.onBubbleStateChange(b)); 2819 } 2820 2821 @Override 2822 public void animateBubbleBarLocation(BubbleBarLocation location) { 2823 mListener.call(l -> l.animateBubbleBarLocation(location)); 2824 } 2825 2826 @Override 2827 public void onDragItemOverBubbleBarDragZone( 2828 @NonNull BubbleBarLocation location) { 2829 mListener.call(l -> l.onDragItemOverBubbleBarDragZone(location)); 2830 } 2831 2832 @Override 2833 public void onItemDraggedOutsideBubbleBarDropZone() { 2834 mListener.call(IBubblesListener::onItemDraggedOutsideBubbleBarDropZone); 2835 } 2836 }; 2837 IBubblesImpl(BubbleController controller)2838 IBubblesImpl(BubbleController controller) { 2839 mController = controller; 2840 mListener = new SingleInstanceRemoteListener<>(mController, 2841 c -> c.registerBubbleStateListener(mBubbleListener), 2842 c -> c.unregisterBubbleStateListener()); 2843 } 2844 2845 /** 2846 * Invalidates this instance, preventing future calls from updating the controller. 2847 */ 2848 @Override invalidate()2849 public void invalidate() { 2850 mController = null; 2851 // Unregister the listeners to ensure any binder death recipients are unlinked 2852 mListener.unregister(); 2853 } 2854 2855 @Override registerBubbleListener(IBubblesListener listener)2856 public void registerBubbleListener(IBubblesListener listener) { 2857 mMainExecutor.execute(() -> mListener.register(listener)); 2858 } 2859 2860 @Override unregisterBubbleListener(IBubblesListener listener)2861 public void unregisterBubbleListener(IBubblesListener listener) { 2862 mMainExecutor.execute(mListener::unregister); 2863 } 2864 2865 @Override showShortcutBubble(ShortcutInfo info, @Nullable BubbleBarLocation location)2866 public void showShortcutBubble(ShortcutInfo info, @Nullable BubbleBarLocation location) { 2867 mMainExecutor.execute(() -> mController 2868 .expandStackAndSelectBubble(info, location)); 2869 } 2870 2871 @Override showAppBubble(Intent intent, UserHandle user, @Nullable BubbleBarLocation location)2872 public void showAppBubble(Intent intent, UserHandle user, 2873 @Nullable BubbleBarLocation location) { 2874 mMainExecutor.execute( 2875 () -> mController.expandStackAndSelectBubble(intent, user, location)); 2876 } 2877 2878 @Override showBubble(String key, int topOnScreen)2879 public void showBubble(String key, int topOnScreen) { 2880 mMainExecutor.execute( 2881 () -> mController.expandStackAndSelectBubbleFromLauncher(key, topOnScreen)); 2882 } 2883 2884 @Override removeAllBubbles()2885 public void removeAllBubbles() { 2886 mMainExecutor.execute(() -> mController.removeAllBubbles(Bubbles.DISMISS_USER_GESTURE)); 2887 } 2888 2889 @Override collapseBubbles()2890 public void collapseBubbles() { 2891 mMainExecutor.execute(() -> { 2892 if (mBubbleData.getSelectedBubble() instanceof Bubble) { 2893 if (((Bubble) mBubbleData.getSelectedBubble()).getPreparingTransition() 2894 != null) { 2895 // Currently preparing a transition which will, itself, collapse the bubble. 2896 // For transition preparation, the timing of bubble-collapse must be in 2897 // sync with the rest of the set-up. 2898 return; 2899 } 2900 } 2901 mController.collapseStack(); 2902 }); 2903 } 2904 2905 @Override startBubbleDrag(String bubbleKey)2906 public void startBubbleDrag(String bubbleKey) { 2907 mMainExecutor.execute(() -> mController.startBubbleDrag(bubbleKey)); 2908 } 2909 2910 @Override stopBubbleDrag(BubbleBarLocation location, int topOnScreen)2911 public void stopBubbleDrag(BubbleBarLocation location, int topOnScreen) { 2912 mMainExecutor.execute(() -> mController.stopBubbleDrag(location, topOnScreen)); 2913 } 2914 2915 @Override dragBubbleToDismiss(String key, long timestamp)2916 public void dragBubbleToDismiss(String key, long timestamp) { 2917 mMainExecutor.execute(() -> mController.dragBubbleToDismiss(key, timestamp)); 2918 } 2919 2920 @Override showUserEducation(int positionX, int positionY)2921 public void showUserEducation(int positionX, int positionY) { 2922 mMainExecutor.execute(() -> 2923 mController.showUserEducation(new Point(positionX, positionY))); 2924 } 2925 2926 @Override setBubbleBarLocation(BubbleBarLocation location, @UpdateSource int source)2927 public void setBubbleBarLocation(BubbleBarLocation location, 2928 @UpdateSource int source) { 2929 mMainExecutor.execute(() -> 2930 mController.setBubbleBarLocation(location, source)); 2931 } 2932 2933 @Override updateBubbleBarTopOnScreen(int topOnScreen)2934 public void updateBubbleBarTopOnScreen(int topOnScreen) { 2935 mMainExecutor.execute(() -> { 2936 mBubblePositioner.setBubbleBarTopOnScreen(topOnScreen); 2937 if (mLayerView != null) mLayerView.updateExpandedView(); 2938 }); 2939 } 2940 2941 @Override showExpandedView()2942 public void showExpandedView() { 2943 mMainExecutor.execute(() -> { 2944 if (mLayerView != null) { 2945 showExpandedViewForBubbleBar(); 2946 } 2947 }); 2948 } 2949 2950 @Override showDropTarget(boolean show, BubbleBarLocation location)2951 public void showDropTarget(boolean show, BubbleBarLocation location) { 2952 mMainExecutor.execute(() -> { 2953 if (show) { 2954 showBubbleBarExpandedViewDropTarget(location); 2955 } else { 2956 hideBubbleBarExpandedViewDropTarget(); 2957 } 2958 }); 2959 } 2960 2961 @Override moveDraggedBubbleToFullscreen(String key, Point dropLocation)2962 public void moveDraggedBubbleToFullscreen(String key, Point dropLocation) { 2963 mMainExecutor.execute( 2964 () -> mController.moveDraggedBubbleToFullscreen(key, dropLocation)); 2965 } 2966 } 2967 2968 private class BubblesImpl implements Bubbles { 2969 // Up-to-date cached state of bubbles data for SysUI to query from the calling thread 2970 @VisibleForTesting 2971 public class CachedState { 2972 private boolean mIsStackExpanded; 2973 private String mSelectedBubbleKey; 2974 private HashSet<String> mSuppressedBubbleKeys = new HashSet<>(); 2975 private HashMap<String, String> mSuppressedGroupToNotifKeys = new HashMap<>(); 2976 private HashMap<String, Bubble> mShortcutIdToBubble = new HashMap<>(); 2977 2978 private HashMap<String, Integer> mNoteBubbleTaskIds = new HashMap(); 2979 2980 private ArrayList<Bubble> mTmpBubbles = new ArrayList<>(); 2981 2982 /** 2983 * Updates the cached state based on the last full BubbleData change. 2984 */ update(BubbleData.Update update)2985 synchronized void update(BubbleData.Update update) { 2986 if (update.selectionChanged) { 2987 mSelectedBubbleKey = update.selectedBubble != null 2988 ? update.selectedBubble.getKey() 2989 : null; 2990 } 2991 if (update.expandedChanged) { 2992 mIsStackExpanded = update.expanded; 2993 } 2994 if (update.suppressedSummaryChanged) { 2995 String summaryKey = 2996 mBubbleData.getSummaryKey(update.suppressedSummaryGroup); 2997 if (summaryKey != null) { 2998 mSuppressedGroupToNotifKeys.put(update.suppressedSummaryGroup, summaryKey); 2999 } else { 3000 mSuppressedGroupToNotifKeys.remove(update.suppressedSummaryGroup); 3001 } 3002 } 3003 3004 mTmpBubbles.clear(); 3005 mTmpBubbles.addAll(update.bubbles); 3006 mTmpBubbles.addAll(update.overflowBubbles); 3007 3008 mSuppressedBubbleKeys.clear(); 3009 mShortcutIdToBubble.clear(); 3010 mNoteBubbleTaskIds.clear(); 3011 for (Bubble b : mTmpBubbles) { 3012 mShortcutIdToBubble.put(b.getShortcutId(), b); 3013 updateBubbleSuppressedState(b); 3014 3015 if (b.isNote()) { 3016 mNoteBubbleTaskIds.put(b.getKey(), b.getTaskId()); 3017 } 3018 } 3019 } 3020 3021 /** Sets the note bubble's taskId which is cached for SysUI. */ setNoteBubbleTaskId(String key, int taskId)3022 synchronized void setNoteBubbleTaskId(String key, int taskId) { 3023 mNoteBubbleTaskIds.put(key, taskId); 3024 } 3025 3026 /** 3027 * Updates a specific bubble suppressed state. This is used mainly because notification 3028 * suppression changes don't go through the same BubbleData update mechanism. 3029 */ updateBubbleSuppressedState(Bubble b)3030 synchronized void updateBubbleSuppressedState(Bubble b) { 3031 if (!b.showInShade()) { 3032 mSuppressedBubbleKeys.add(b.getKey()); 3033 } else { 3034 mSuppressedBubbleKeys.remove(b.getKey()); 3035 } 3036 } 3037 isStackExpanded()3038 public synchronized boolean isStackExpanded() { 3039 return mIsStackExpanded; 3040 } 3041 isBubbleExpanded(String key)3042 public synchronized boolean isBubbleExpanded(String key) { 3043 return mIsStackExpanded && key.equals(mSelectedBubbleKey); 3044 } 3045 isBubbleNotificationSuppressedFromShade(String key, String groupKey)3046 public synchronized boolean isBubbleNotificationSuppressedFromShade(String key, 3047 String groupKey) { 3048 return mSuppressedBubbleKeys.contains(key) 3049 || (mSuppressedGroupToNotifKeys.containsKey(groupKey) 3050 && key.equals(mSuppressedGroupToNotifKeys.get(groupKey))); 3051 } 3052 3053 @Nullable getBubbleWithShortcutId(String id)3054 public synchronized Bubble getBubbleWithShortcutId(String id) { 3055 return mShortcutIdToBubble.get(id); 3056 } 3057 dump(PrintWriter pw)3058 synchronized void dump(PrintWriter pw) { 3059 pw.println("BubbleImpl.CachedState state:"); 3060 3061 pw.println("mIsStackExpanded: " + mIsStackExpanded); 3062 pw.println("mSelectedBubbleKey: " + mSelectedBubbleKey); 3063 3064 pw.println("mSuppressedBubbleKeys: " + mSuppressedBubbleKeys.size()); 3065 for (String key : mSuppressedBubbleKeys) { 3066 pw.println(" suppressing: " + key); 3067 } 3068 3069 pw.print("mSuppressedGroupToNotifKeys: "); 3070 pw.println(mSuppressedGroupToNotifKeys.size()); 3071 for (String key : mSuppressedGroupToNotifKeys.keySet()) { 3072 pw.println(" suppressing: " + key); 3073 } 3074 3075 pw.println("mNoteBubbleTaskIds: " + mNoteBubbleTaskIds.values()); 3076 } 3077 } 3078 3079 private CachedState mCachedState = new CachedState(); 3080 3081 @Override isBubbleNotificationSuppressedFromShade(String key, String groupKey)3082 public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) { 3083 return mCachedState.isBubbleNotificationSuppressedFromShade(key, groupKey); 3084 } 3085 3086 @Override isBubbleExpanded(String key)3087 public boolean isBubbleExpanded(String key) { 3088 return mCachedState.isBubbleExpanded(key); 3089 } 3090 3091 @Override 3092 @Nullable getBubbleWithShortcutId(String shortcutId)3093 public Bubble getBubbleWithShortcutId(String shortcutId) { 3094 return mCachedState.getBubbleWithShortcutId(shortcutId); 3095 } 3096 3097 @Override collapseStack()3098 public void collapseStack() { 3099 mMainExecutor.execute(() -> { 3100 BubbleController.this.collapseStack(); 3101 }); 3102 } 3103 3104 @Override expandStackAndSelectBubble(BubbleEntry entry)3105 public void expandStackAndSelectBubble(BubbleEntry entry) { 3106 mMainExecutor.execute(() -> { 3107 BubbleController.this.expandStackAndSelectBubble(entry); 3108 }); 3109 } 3110 3111 @Override expandStackAndSelectBubble(ShortcutInfo info)3112 public void expandStackAndSelectBubble(ShortcutInfo info) { 3113 mMainExecutor.execute(() -> 3114 BubbleController.this 3115 .expandStackAndSelectBubble(info, /* bubbleBarLocation = */ null) 3116 ); 3117 } 3118 3119 @Override expandStackAndSelectBubble(Bubble bubble)3120 public void expandStackAndSelectBubble(Bubble bubble) { 3121 mMainExecutor.execute(() -> { 3122 BubbleController.this.expandStackAndSelectBubble(bubble); 3123 }); 3124 } 3125 3126 @Override showOrHideNoteBubble(Intent intent, UserHandle user, @Nullable Icon icon)3127 public void showOrHideNoteBubble(Intent intent, UserHandle user, @Nullable Icon icon) { 3128 mMainExecutor.execute( 3129 () -> BubbleController.this.showOrHideNotesBubble(intent, user, icon)); 3130 } 3131 3132 @Override isNoteBubbleTaskId(int taskId)3133 public boolean isNoteBubbleTaskId(int taskId) { 3134 return mCachedState.mNoteBubbleTaskIds.values().contains(taskId); 3135 } 3136 3137 @Override 3138 @Nullable getScreenshotExcludingBubble(int displayId)3139 public SynchronousScreenCaptureListener getScreenshotExcludingBubble(int displayId) { 3140 SynchronousScreenCaptureListener screenCaptureListener = 3141 ScreenCapture.createSyncCaptureListener(); 3142 3143 mMainExecutor.execute( 3144 () -> BubbleController.this.getScreenshotExcludingBubble(displayId, 3145 screenCaptureListener)); 3146 3147 return screenCaptureListener; 3148 } 3149 3150 @Override handleDismissalInterception(BubbleEntry entry, @Nullable List<BubbleEntry> children, IntConsumer removeCallback, Executor callbackExecutor)3151 public boolean handleDismissalInterception(BubbleEntry entry, 3152 @Nullable List<BubbleEntry> children, IntConsumer removeCallback, 3153 Executor callbackExecutor) { 3154 IntConsumer cb = removeCallback != null 3155 ? (index) -> callbackExecutor.execute(() -> removeCallback.accept(index)) 3156 : null; 3157 return mMainExecutor.executeBlockingForResult(() -> { 3158 return BubbleController.this.handleDismissalInterception(entry, children, cb); 3159 }, Boolean.class); 3160 } 3161 3162 @Override setSysuiProxy(SysuiProxy proxy)3163 public void setSysuiProxy(SysuiProxy proxy) { 3164 mMainExecutor.execute(() -> { 3165 BubbleController.this.setSysuiProxy(proxy); 3166 }); 3167 } 3168 3169 @Override setExpandListener(BubbleExpandListener listener)3170 public void setExpandListener(BubbleExpandListener listener) { 3171 mMainExecutor.execute(() -> { 3172 BubbleController.this.setExpandListener(listener); 3173 }); 3174 } 3175 3176 @Override onEntryAdded(BubbleEntry entry)3177 public void onEntryAdded(BubbleEntry entry) { 3178 mMainExecutor.execute(() -> { 3179 BubbleController.this.onEntryAdded(entry); 3180 }); 3181 } 3182 3183 @Override onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem)3184 public void onEntryUpdated(BubbleEntry entry, boolean shouldBubbleUp, boolean fromSystem) { 3185 mMainExecutor.execute(() -> { 3186 BubbleController.this.onEntryUpdated(entry, shouldBubbleUp, fromSystem); 3187 }); 3188 } 3189 3190 @Override onEntryRemoved(BubbleEntry entry)3191 public void onEntryRemoved(BubbleEntry entry) { 3192 mMainExecutor.execute(() -> { 3193 BubbleController.this.onEntryRemoved(entry); 3194 }); 3195 } 3196 3197 @Override onRankingUpdated(RankingMap rankingMap, HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey)3198 public void onRankingUpdated(RankingMap rankingMap, 3199 HashMap<String, Pair<BubbleEntry, Boolean>> entryDataByKey) { 3200 mMainExecutor.execute(() -> { 3201 BubbleController.this.onRankingUpdated(rankingMap, entryDataByKey); 3202 }); 3203 } 3204 3205 @Override onNotificationChannelModified(String pkg, UserHandle user, NotificationChannel channel, int modificationType)3206 public void onNotificationChannelModified(String pkg, 3207 UserHandle user, NotificationChannel channel, int modificationType) { 3208 // Bubbles only cares about updates or deletions. 3209 if (modificationType == NOTIFICATION_CHANNEL_OR_GROUP_UPDATED 3210 || modificationType == NOTIFICATION_CHANNEL_OR_GROUP_DELETED) { 3211 mMainExecutor.execute(() -> { 3212 BubbleController.this.onNotificationChannelModified(pkg, user, channel, 3213 modificationType); 3214 }); 3215 } 3216 } 3217 3218 @Override onStatusBarVisibilityChanged(boolean visible)3219 public void onStatusBarVisibilityChanged(boolean visible) { 3220 mMainExecutor.execute(() -> { 3221 BubbleController.this.onStatusBarVisibilityChanged(visible); 3222 }); 3223 } 3224 3225 @Override onZenStateChanged()3226 public void onZenStateChanged() { 3227 mMainExecutor.execute(() -> { 3228 BubbleController.this.onZenStateChanged(); 3229 }); 3230 } 3231 3232 @Override onStatusBarStateChanged(boolean isShade)3233 public void onStatusBarStateChanged(boolean isShade) { 3234 mMainExecutor.execute(() -> { 3235 BubbleController.this.onStatusBarStateChanged(isShade); 3236 }); 3237 } 3238 3239 @Override onUserChanged(int newUserId)3240 public void onUserChanged(int newUserId) { 3241 mMainExecutor.execute(() -> { 3242 BubbleController.this.onUserChanged(newUserId); 3243 }); 3244 } 3245 3246 @Override onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles)3247 public void onCurrentProfilesChanged(SparseArray<UserInfo> currentProfiles) { 3248 mMainExecutor.execute(() -> { 3249 BubbleController.this.onCurrentProfilesChanged(currentProfiles); 3250 }); 3251 } 3252 3253 @Override onUserRemoved(int removedUserId)3254 public void onUserRemoved(int removedUserId) { 3255 mMainExecutor.execute(() -> { 3256 BubbleController.this.onUserRemoved(removedUserId); 3257 }); 3258 } 3259 3260 @Override onNotificationPanelExpandedChanged(boolean expanded)3261 public void onNotificationPanelExpandedChanged(boolean expanded) { 3262 mMainExecutor.execute( 3263 () -> BubbleController.this.onNotificationPanelExpandedChanged(expanded)); 3264 } 3265 3266 @Override onSensitiveNotificationProtectionStateChanged( boolean sensitiveNotificationProtectionActive)3267 public void onSensitiveNotificationProtectionStateChanged( 3268 boolean sensitiveNotificationProtectionActive) { 3269 mMainExecutor.execute( 3270 () -> BubbleController.this.onSensitiveNotificationProtectionStateChanged( 3271 sensitiveNotificationProtectionActive)); 3272 } 3273 3274 @Override canShowBubbleNotification()3275 public boolean canShowBubbleNotification() { 3276 // in bubble bar mode, when the IME is visible we can't animate new bubbles. 3277 if (BubbleController.this.isShowingAsBubbleBar()) { 3278 return !BubbleController.this.mBubblePositioner.isImeVisible(); 3279 } 3280 return true; 3281 } 3282 } 3283 3284 /** 3285 * Bubble data that is stored per user. 3286 * Used to store and restore active bubbles during user switching. 3287 */ 3288 private static class UserBubbleData { 3289 private final Map<String, Boolean> mKeyToShownInShadeMap = new HashMap<>(); 3290 3291 /** 3292 * Add bubble key and whether it should be shown in notification shade 3293 */ add(String key, boolean shownInShade)3294 void add(String key, boolean shownInShade) { 3295 mKeyToShownInShadeMap.put(key, shownInShade); 3296 } 3297 3298 /** 3299 * Get all bubble keys stored for this user 3300 */ getKeys()3301 Set<String> getKeys() { 3302 return mKeyToShownInShadeMap.keySet(); 3303 } 3304 3305 /** 3306 * Check if this bubble with the given key should be shown in the notification shade 3307 */ isShownInShade(String key)3308 boolean isShownInShade(String key) { 3309 return mKeyToShownInShadeMap.get(key); 3310 } 3311 } 3312 3313 private class BubbleTaskViewController implements TaskViewController { 3314 private final TaskViewTransitions mBaseTransitions; 3315 BubbleTaskViewController(TaskViewTransitions baseTransitions)3316 BubbleTaskViewController(TaskViewTransitions baseTransitions) { 3317 mBaseTransitions = baseTransitions; 3318 } 3319 3320 @Override registerTaskView(TaskViewTaskController tv)3321 public void registerTaskView(TaskViewTaskController tv) { 3322 mBaseTransitions.registerTaskView(tv); 3323 } 3324 3325 @Override unregisterTaskView(TaskViewTaskController tv)3326 public void unregisterTaskView(TaskViewTaskController tv) { 3327 mBaseTransitions.unregisterTaskView(tv); 3328 } 3329 3330 @Override startShortcutActivity(@onNull TaskViewTaskController destination, @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options, @Nullable Rect launchBounds)3331 public void startShortcutActivity(@NonNull TaskViewTaskController destination, 3332 @NonNull ShortcutInfo shortcut, @NonNull ActivityOptions options, 3333 @Nullable Rect launchBounds) { 3334 mBaseTransitions.startShortcutActivity(destination, shortcut, options, launchBounds); 3335 } 3336 3337 @Override startActivity(@onNull TaskViewTaskController destination, @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, @NonNull ActivityOptions options, @Nullable Rect launchBounds)3338 public void startActivity(@NonNull TaskViewTaskController destination, 3339 @NonNull PendingIntent pendingIntent, @Nullable Intent fillInIntent, 3340 @NonNull ActivityOptions options, @Nullable Rect launchBounds) { 3341 mBaseTransitions.startActivity(destination, pendingIntent, fillInIntent, 3342 options, launchBounds); 3343 } 3344 3345 @Override startRootTask(@onNull TaskViewTaskController destination, ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, @Nullable WindowContainerTransaction wct)3346 public void startRootTask(@NonNull TaskViewTaskController destination, 3347 ActivityManager.RunningTaskInfo taskInfo, SurfaceControl leash, 3348 @Nullable WindowContainerTransaction wct) { 3349 mBaseTransitions.startRootTask(destination, taskInfo, leash, wct); 3350 } 3351 3352 @Override removeTaskView(@onNull TaskViewTaskController taskView, @Nullable WindowContainerToken taskToken)3353 public void removeTaskView(@NonNull TaskViewTaskController taskView, 3354 @Nullable WindowContainerToken taskToken) { 3355 mBaseTransitions.removeTaskView(taskView, taskToken); 3356 } 3357 3358 @Override moveTaskViewToFullscreen(@onNull TaskViewTaskController taskView)3359 public void moveTaskViewToFullscreen(@NonNull TaskViewTaskController taskView) { 3360 final TaskInfo tinfo = taskView.getTaskInfo(); 3361 if (tinfo == null) { 3362 return; 3363 } 3364 Bubble bub = null; 3365 for (Bubble b : mBubbleData.getBubbles()) { 3366 if (b.getTaskId() == tinfo.taskId) { 3367 bub = b; 3368 break; 3369 } 3370 } 3371 if (bub == null) { 3372 return; 3373 } 3374 mBubbleTransitions.startConvertFromBubble(bub, tinfo); 3375 } 3376 3377 @Override setTaskViewVisible(TaskViewTaskController taskView, boolean visible)3378 public void setTaskViewVisible(TaskViewTaskController taskView, boolean visible) { 3379 mBaseTransitions.setTaskViewVisible(taskView, visible); 3380 } 3381 3382 @Override setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen)3383 public void setTaskBounds(TaskViewTaskController taskView, Rect boundsOnScreen) { 3384 mBaseTransitions.setTaskBounds(taskView, boundsOnScreen); 3385 } 3386 3387 @Override isUsingShellTransitions()3388 public boolean isUsingShellTransitions() { 3389 return mBaseTransitions.isUsingShellTransitions(); 3390 } 3391 } 3392 } 3393