1 /* 2 * Copyright (C) 2023 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 package com.android.launcher3.taskbar.bubbles; 17 18 import static android.os.Process.THREAD_PRIORITY_BACKGROUND; 19 20 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 21 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; 22 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_VISIBLE; 23 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; 24 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; 25 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; 26 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; 27 28 import android.annotation.BinderThread; 29 import android.annotation.Nullable; 30 import android.content.Context; 31 import android.graphics.Point; 32 import android.os.Bundle; 33 import android.os.SystemProperties; 34 import android.util.ArrayMap; 35 import android.util.Log; 36 37 import androidx.annotation.NonNull; 38 39 import com.android.launcher3.taskbar.TaskbarSharedState; 40 import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController; 41 import com.android.launcher3.util.Executors.SimpleThreadFactory; 42 import com.android.quickstep.SystemUiProxy; 43 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; 44 import com.android.wm.shell.Flags; 45 import com.android.wm.shell.bubbles.IBubblesListener; 46 import com.android.wm.shell.shared.bubbles.BubbleBarLocation; 47 import com.android.wm.shell.shared.bubbles.BubbleBarUpdate; 48 import com.android.wm.shell.shared.bubbles.BubbleInfo; 49 import com.android.wm.shell.shared.bubbles.RemovedBubble; 50 51 import java.util.ArrayList; 52 import java.util.Arrays; 53 import java.util.List; 54 import java.util.Objects; 55 import java.util.Optional; 56 import java.util.concurrent.Executor; 57 import java.util.concurrent.Executors; 58 59 /** 60 * This registers a listener with SysUIProxy to get information about changes to the bubble 61 * stack state from WMShell (SysUI). The controller is also responsible for loading the necessary 62 * information to render each of the bubbles & dispatches changes to 63 * {@link BubbleBarViewController} which will then update {@link BubbleBarView} as needed. 64 * 65 * <p>For details around the behavior of the bubble bar, see {@link BubbleBarView}. 66 */ 67 public class BubbleBarController extends IBubblesListener.Stub { 68 69 private static final String TAG = "BubbleBarController"; 70 private static final boolean DEBUG = false; 71 72 /** 73 * Determines whether bubbles can be shown in the bubble bar. This value updates when the 74 * taskbar is recreated. 75 * 76 * @see #onTaskbarRecreated() 77 */ 78 private static boolean sBubbleBarEnabled = Flags.enableBubbleBar() 79 || SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); 80 81 /** Whether showing bubbles in the launcher bubble bar is enabled. */ isBubbleBarEnabled()82 public static boolean isBubbleBarEnabled() { 83 return sBubbleBarEnabled; 84 } 85 86 /** Re-reads the value of the flag from SystemProperties when taskbar is recreated. */ onTaskbarRecreated()87 public static void onTaskbarRecreated() { 88 sBubbleBarEnabled = Flags.enableBubbleBar() 89 || SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); 90 } 91 92 private static final long MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING 93 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING 94 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED 95 | SYSUI_STATE_IME_VISIBLE 96 | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED 97 | SYSUI_STATE_QUICK_SETTINGS_EXPANDED; 98 99 private static final long MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING 100 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING 101 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; 102 103 private static final long MASK_SYSUI_LOCKED = SYSUI_STATE_BOUNCER_SHOWING 104 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING 105 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; 106 107 private final Context mContext; 108 private final BubbleBarView mBarView; 109 private final ArrayMap<String, BubbleBarBubble> mBubbles = new ArrayMap<>(); 110 private final ArrayMap<String, BubbleBarBubble> mSuppressedBubbles = new ArrayMap<>(); 111 112 private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor( 113 new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND)); 114 private final SystemUiProxy mSystemUiProxy; 115 116 private BubbleBarItem mSelectedBubble; 117 118 private TaskbarSharedState mSharedState; 119 private BubbleBarViewController mBubbleBarViewController; 120 private BubbleStashController mBubbleStashController; 121 private Optional<BubbleStashedHandleViewController> mBubbleStashedHandleViewController; 122 private BubblePinController mBubblePinController; 123 private BubbleCreator mBubbleCreator; 124 private BubbleBarLocationListener mBubbleBarLocationListener; 125 126 // Cache last sent top coordinate to avoid sending duplicate updates to shell 127 private int mLastSentBubbleBarTop; 128 129 private boolean mIsImeVisible = false; 130 131 /** 132 * Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it uses 133 * {@link BubbleBarBubble}s so that it can be used to update the views. 134 */ 135 private static class BubbleBarViewUpdate { 136 final boolean initialState; 137 boolean expandedChanged; 138 boolean expanded; 139 boolean shouldShowEducation; 140 String selectedBubbleKey; 141 String suppressedBubbleKey; 142 String unsuppressedBubbleKey; 143 BubbleBarLocation bubbleBarLocation; 144 List<RemovedBubble> removedBubbles; 145 List<String> bubbleKeysInOrder; 146 Point expandedViewDropTargetSize; 147 boolean showOverflow; 148 boolean showOverflowChanged; 149 150 // These need to be loaded in the background 151 BubbleBarBubble addedBubble; 152 BubbleBarBubble updatedBubble; 153 List<BubbleBarBubble> currentBubbles; 154 BubbleBarViewUpdate(BubbleBarUpdate update)155 BubbleBarViewUpdate(BubbleBarUpdate update) { 156 initialState = update.initialState; 157 expandedChanged = update.expandedChanged; 158 expanded = update.expanded; 159 shouldShowEducation = update.shouldShowEducation; 160 selectedBubbleKey = update.selectedBubbleKey; 161 suppressedBubbleKey = update.suppressedBubbleKey; 162 unsuppressedBubbleKey = update.unsupressedBubbleKey; 163 bubbleBarLocation = update.bubbleBarLocation; 164 removedBubbles = update.removedBubbles; 165 bubbleKeysInOrder = update.bubbleKeysInOrder; 166 expandedViewDropTargetSize = update.expandedViewDropTargetSize; 167 showOverflow = update.showOverflow; 168 showOverflowChanged = update.showOverflowChanged; 169 } 170 } 171 BubbleBarController(Context context, BubbleBarView bubbleView)172 public BubbleBarController(Context context, BubbleBarView bubbleView) { 173 mContext = context; 174 mBarView = bubbleView; // Need the view for inflating bubble views. 175 176 mSystemUiProxy = SystemUiProxy.INSTANCE.get(context); 177 } 178 onDestroy()179 public void onDestroy() { 180 mSystemUiProxy.setBubblesListener(null); 181 // Saves bubble bar state 182 BubbleInfo[] bubbleInfoItems = new BubbleInfo[mBubbles.size()]; 183 mBubbles.values().forEach(bubbleBarBubble -> { 184 int index = mBubbleBarViewController.bubbleViewIndex(bubbleBarBubble.getView()); 185 if (index < 0 || index >= bubbleInfoItems.length) { 186 Log.e(TAG, "Found improper index: " + index + " for " + bubbleBarBubble); 187 } else { 188 bubbleInfoItems[index] = bubbleBarBubble.getInfo(); 189 } 190 }); 191 mSharedState.bubbleInfoItems = Arrays.asList(bubbleInfoItems); 192 mSharedState.suppressedBubbleInfoItems = new ArrayList<>(mSuppressedBubbles.size()); 193 for (int i = 0; i < mSuppressedBubbles.size(); i++) { 194 mSharedState.suppressedBubbleInfoItems.add(mSuppressedBubbles.valueAt(i).getInfo()); 195 } 196 } 197 198 /** Initializes controllers. */ init(BubbleControllers bubbleControllers, BubbleBarLocationListener bubbleBarLocationListener, TaskbarSharedState sharedState)199 public void init(BubbleControllers bubbleControllers, 200 BubbleBarLocationListener bubbleBarLocationListener, 201 TaskbarSharedState sharedState) { 202 mSharedState = sharedState; 203 mBubbleBarViewController = bubbleControllers.bubbleBarViewController; 204 mBubbleStashController = bubbleControllers.bubbleStashController; 205 mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController; 206 mBubblePinController = bubbleControllers.bubblePinController; 207 mBubbleCreator = bubbleControllers.bubbleCreator; 208 mBubbleBarLocationListener = bubbleBarLocationListener; 209 210 bubbleControllers.runAfterInit(() -> { 211 restoreSavedState(sharedState); 212 mBubbleBarViewController.setHiddenForBubbles( 213 !sBubbleBarEnabled || mBubbles.isEmpty()); 214 mBubbleStashedHandleViewController.ifPresent( 215 controller -> controller.setHiddenForBubbles( 216 !sBubbleBarEnabled || mBubbles.isEmpty())); 217 mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse( 218 key -> setSelectedBubbleInternal(mBubbles.get(key))); 219 mBubbleBarViewController.setBoundsChangeListener(this::onBubbleBarBoundsChanged); 220 mBubbleBarLocationListener.onBubbleBarLocationUpdated( 221 mBubbleBarViewController.getBubbleBarLocation()); 222 if (sBubbleBarEnabled) { 223 mSystemUiProxy.setBubblesListener(this); 224 } 225 }); 226 } 227 228 /** 229 * Updates the bubble bar, handle bar, and stash controllers based on sysui state flags. 230 */ updateStateForSysuiFlags(@ystemUiStateFlags long flags)231 public void updateStateForSysuiFlags(@SystemUiStateFlags long flags) { 232 boolean hideBubbleBar = (flags & MASK_HIDE_BUBBLE_BAR) != 0; 233 mBubbleBarViewController.setHiddenForSysui(hideBubbleBar); 234 235 boolean hideHandleView = (flags & MASK_HIDE_HANDLE_VIEW) != 0; 236 mBubbleStashedHandleViewController.ifPresent( 237 controller -> controller.setHiddenForSysui(hideHandleView)); 238 239 boolean sysuiLocked = (flags & MASK_SYSUI_LOCKED) != 0; 240 mBubbleStashController.setSysuiLocked(sysuiLocked); 241 mIsImeVisible = (flags & SYSUI_STATE_IME_VISIBLE) != 0; 242 if (mIsImeVisible) { 243 mBubbleBarViewController.onImeVisible(); 244 } 245 } 246 247 // 248 // Bubble data changes 249 // 250 251 @BinderThread 252 @Override onBubbleStateChange(Bundle bundle)253 public void onBubbleStateChange(Bundle bundle) { 254 bundle.setClassLoader(BubbleBarUpdate.class.getClassLoader()); 255 BubbleBarUpdate update = bundle.getParcelable("update", BubbleBarUpdate.class); 256 BubbleBarViewUpdate viewUpdate = new BubbleBarViewUpdate(update); 257 if (update.addedBubble != null 258 || update.updatedBubble != null 259 || !update.currentBubbleList.isEmpty()) { 260 // We have bubbles to load 261 BUBBLE_STATE_EXECUTOR.execute(() -> { 262 if (update.addedBubble != null) { 263 viewUpdate.addedBubble = mBubbleCreator.populateBubble(mContext, 264 update.addedBubble, 265 mBarView, 266 null /* existingBubble */); 267 } 268 if (update.updatedBubble != null) { 269 BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey()); 270 viewUpdate.updatedBubble = 271 mBubbleCreator.populateBubble(mContext, update.updatedBubble, 272 mBarView, 273 existingBubble); 274 } 275 if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) { 276 List<BubbleBarBubble> currentBubbles = new ArrayList<>(); 277 for (int i = 0; i < update.currentBubbleList.size(); i++) { 278 BubbleBarBubble b = mBubbleCreator.populateBubble(mContext, 279 update.currentBubbleList.get(i), mBarView, 280 null /* existingBubble */); 281 currentBubbles.add(b); 282 } 283 viewUpdate.currentBubbles = currentBubbles; 284 } 285 MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate)); 286 }); 287 } else { 288 // No bubbles to load, immediately apply the changes. 289 BUBBLE_STATE_EXECUTOR.execute( 290 () -> MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate))); 291 } 292 } 293 restoreSavedState(TaskbarSharedState sharedState)294 private void restoreSavedState(TaskbarSharedState sharedState) { 295 if (sharedState.bubbleBarLocation != null) { 296 updateBubbleBarLocationInternal(sharedState.bubbleBarLocation); 297 } 298 restoreSavedBubbles(sharedState.bubbleInfoItems); 299 restoreSuppressed(sharedState.suppressedBubbleInfoItems); 300 } 301 restoreSavedBubbles(List<BubbleInfo> bubbleInfos)302 private void restoreSavedBubbles(List<BubbleInfo> bubbleInfos) { 303 if (bubbleInfos == null || bubbleInfos.isEmpty()) return; 304 // Iterate in reverse because new bubbles are added in front and the list is in order. 305 for (int i = bubbleInfos.size() - 1; i >= 0; i--) { 306 BubbleBarBubble bubble = mBubbleCreator.populateBubble(mContext, 307 bubbleInfos.get(i), mBarView, /* existingBubble = */ null); 308 if (bubble == null) { 309 Log.e(TAG, "Could not instantiate BubbleBarBubble for " + bubbleInfos.get(i)); 310 continue; 311 } 312 addBubbleInternally(bubble, /* isExpanding= */ false, /* suppressAnimation= */ true); 313 } 314 } 315 restoreSuppressed(List<BubbleInfo> bubbleInfos)316 private void restoreSuppressed(List<BubbleInfo> bubbleInfos) { 317 if (bubbleInfos == null || bubbleInfos.isEmpty()) return; 318 for (BubbleInfo bubbleInfo : bubbleInfos.reversed()) { 319 BubbleBarBubble bb = mBubbleCreator.populateBubble(mContext, bubbleInfo, 320 mBarView, /* existingBubble= */ 321 null); 322 if (bb != null) { 323 mSuppressedBubbles.put(bb.getKey(), bb); 324 } 325 } 326 } 327 applyViewChanges(BubbleBarViewUpdate update)328 private void applyViewChanges(BubbleBarViewUpdate update) { 329 final boolean isCollapsed = (update.expandedChanged && !update.expanded) 330 || (!update.expandedChanged && !mBubbleBarViewController.isExpanded()); 331 final boolean isExpanding = update.expandedChanged && update.expanded; 332 // don't animate bubbles if this is the initial state because we may be unfolding or 333 // enabling gesture nav. also suppress animation if the bubble bar is hidden for sysui e.g. 334 // the shade is open, or we're locked. 335 final boolean suppressAnimation = 336 update.initialState || mBubbleBarViewController.isHiddenForSysui() || mIsImeVisible; 337 338 if (update.initialState && mSharedState.hasSavedBubbles()) { 339 // clear restored state 340 mBubbleBarViewController.removeAllBubbles(); 341 mBubbles.clear(); 342 mBubbleBarViewController.showOverflow(update.showOverflow); 343 } 344 345 if (update.addedBubble != null) { 346 mBubbles.put(update.addedBubble.getKey(), update.addedBubble); 347 } 348 BubbleBarBubble bubbleToSelect = null; 349 if (update.selectedBubbleKey != null) { 350 if (mSelectedBubble == null 351 || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) { 352 BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey); 353 if (newlySelected != null) { 354 bubbleToSelect = newlySelected; 355 } else { 356 Log.w(TAG, "trying to select bubble that doesn't exist:" 357 + update.selectedBubbleKey); 358 } 359 } 360 } 361 if (Flags.enableOptionalBubbleOverflow() 362 && update.showOverflowChanged && !update.showOverflow && update.addedBubble != null 363 && update.removedBubbles.isEmpty() 364 && !mBubbles.isEmpty()) { 365 // A bubble was added from the overflow (& now it's empty / not showing) 366 mBubbleBarViewController.removeOverflowAndAddBubble(update.addedBubble, bubbleToSelect); 367 } else if (update.addedBubble != null && update.removedBubbles.size() == 1) { 368 // we're adding and removing a bubble at the same time. handle this as a single update. 369 RemovedBubble removedBubble = update.removedBubbles.get(0); 370 BubbleBarBubble bubbleToRemove = mBubbles.remove(removedBubble.getKey()); 371 boolean showOverflow = update.showOverflowChanged && update.showOverflow; 372 if (bubbleToRemove != null) { 373 mBubbleBarViewController.addBubbleAndRemoveBubble(update.addedBubble, 374 bubbleToRemove, bubbleToSelect, isExpanding, suppressAnimation, 375 showOverflow); 376 } else { 377 mBubbleBarViewController.addBubble(update.addedBubble, isExpanding, 378 suppressAnimation, bubbleToSelect); 379 Log.w(TAG, "trying to remove bubble that doesn't exist: " + removedBubble.getKey()); 380 } 381 } else { 382 boolean overflowNeedsToBeAdded = Flags.enableOptionalBubbleOverflow() 383 && update.showOverflowChanged && update.showOverflow; 384 if (!update.removedBubbles.isEmpty()) { 385 for (int i = 0; i < update.removedBubbles.size(); i++) { 386 RemovedBubble removedBubble = update.removedBubbles.get(i); 387 BubbleBarBubble bubble = mBubbles.remove(removedBubble.getKey()); 388 if (bubble != null && overflowNeedsToBeAdded) { 389 // First removal, show the overflow 390 overflowNeedsToBeAdded = false; 391 mBubbleBarViewController.addOverflowAndRemoveBubble(bubble, bubbleToSelect); 392 } else if (bubble != null) { 393 mBubbleBarViewController.removeBubble(bubble); 394 } else { 395 Log.w(TAG, "trying to remove bubble that doesn't exist: " 396 + removedBubble.getKey()); 397 } 398 } 399 } 400 if (update.addedBubble != null) { 401 mBubbleBarViewController.addBubble(update.addedBubble, isExpanding, 402 suppressAnimation, bubbleToSelect); 403 } 404 if (Flags.enableOptionalBubbleOverflow() 405 && update.showOverflowChanged 406 && update.showOverflow != mBubbleBarViewController.isOverflowAdded()) { 407 mBubbleBarViewController.showOverflow(update.showOverflow); 408 } 409 } 410 411 // if a bubble was updated upstream, but removed before the update was received, add it back 412 if (update.updatedBubble != null && !mBubbles.containsKey(update.updatedBubble.getKey())) { 413 addBubbleInternally(update.updatedBubble, isExpanding, suppressAnimation); 414 } 415 416 if (update.addedBubble != null && isCollapsed && bubbleToSelect == null) { 417 // If we're collapsed, the most recently added bubble will be selected. 418 bubbleToSelect = update.addedBubble; 419 } 420 421 if (update.currentBubbles != null && !update.currentBubbles.isEmpty()) { 422 // Iterate in reverse because new bubbles are added in front and the list is in order. 423 for (int i = update.currentBubbles.size() - 1; i >= 0; i--) { 424 BubbleBarBubble bubble = update.currentBubbles.get(i); 425 if (bubble != null) { 426 addBubbleInternally(bubble, isExpanding, suppressAnimation); 427 if (isCollapsed && bubbleToSelect == null) { 428 // If we're collapsed, the most recently added bubble will be selected. 429 bubbleToSelect = bubble; 430 } 431 } else { 432 Log.w(TAG, "trying to add bubble but null after loading! " 433 + update.addedBubble.getKey()); 434 } 435 } 436 } 437 if (Flags.enableOptionalBubbleOverflow() && update.initialState && update.showOverflow) { 438 mBubbleBarViewController.showOverflow(true); 439 } 440 441 if (update.suppressedBubbleKey != null) { 442 BubbleBarBubble bb = mBubbles.remove(update.suppressedBubbleKey); 443 if (bb != null) { 444 mSuppressedBubbles.put(update.suppressedBubbleKey, bb); 445 mBubbleBarViewController.removeBubble(bb); 446 } 447 } 448 if (update.unsuppressedBubbleKey != null) { 449 BubbleBarBubble bb = mSuppressedBubbles.remove(update.unsuppressedBubbleKey); 450 if (bb != null) { 451 // Unsuppressing an existing bubble should not cause the bar to expand or animate 452 addBubbleInternally(bb, /* isExpanding= */ false, /* suppressAnimation= */ true); 453 if (mBubbleBarViewController.isHiddenForNoBubbles()) { 454 mBubbleBarViewController.setHiddenForBubbles(false); 455 } 456 } 457 } 458 459 // Update the visibility if this is the initial state, if there are no bubbles, or if the 460 // animation is suppressed. 461 // If this is the initial bubble, the bubble bar will become visible as part of the 462 // animation. 463 if (update.initialState || mBubbles.isEmpty() || suppressAnimation) { 464 mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty()); 465 } 466 mBubbleStashedHandleViewController.ifPresent( 467 controller -> controller.setHiddenForBubbles(mBubbles.isEmpty())); 468 469 if (mBubbles.isEmpty()) { 470 // all bubbles were removed. clear the selected bubble 471 mSelectedBubble = null; 472 } 473 474 if (update.updatedBubble != null) { 475 // Updates mean the dot state may have changed; any other changes were updated in 476 // the populateBubble step. 477 BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey()); 478 if (suppressAnimation) { 479 // since we're not animating this update, we should update the dot visibility here. 480 bb.getView().updateDotVisibility(/* animate= */ false); 481 } else { 482 mBubbleBarViewController.animateBubbleNotification( 483 bb, /* isExpanding= */ false, /* isUpdate= */ true); 484 } 485 } 486 if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) { 487 // Create the new list 488 List<BubbleBarBubble> newOrder = update.bubbleKeysInOrder.stream() 489 .map(mBubbles::get).filter(Objects::nonNull).toList(); 490 if (!newOrder.isEmpty()) { 491 mBubbleBarViewController.reorderBubbles(newOrder); 492 } 493 } 494 if (bubbleToSelect != null) { 495 setSelectedBubbleInternal(bubbleToSelect); 496 } 497 if (update.shouldShowEducation) { 498 mBubbleBarViewController.prepareToShowEducation(); 499 } 500 if (update.expandedChanged) { 501 if (update.expanded != mBubbleBarViewController.isExpanded()) { 502 mBubbleBarViewController.setExpandedFromSysui(update.expanded); 503 } else { 504 Log.w(TAG, "expansion was changed but is the same"); 505 } 506 } 507 if (update.bubbleBarLocation != null) { 508 mSharedState.bubbleBarLocation = update.bubbleBarLocation; 509 if (update.bubbleBarLocation != mBubbleBarViewController.getBubbleBarLocation()) { 510 updateBubbleBarLocationInternal(update.bubbleBarLocation); 511 } 512 } 513 if (update.expandedViewDropTargetSize != null) { 514 mBubblePinController.setDropTargetSize(update.expandedViewDropTargetSize); 515 } 516 } 517 518 /** 519 * Removes the given bubble from the backing list of bubbles after it was dismissed by the user. 520 */ onBubbleDismissed(BubbleView bubble)521 public void onBubbleDismissed(BubbleView bubble) { 522 mBubbles.remove(bubble.getBubble().getKey()); 523 } 524 525 /** Tells WMShell to show the currently selected bubble. */ showSelectedBubble()526 public void showSelectedBubble() { 527 if (getSelectedBubbleKey() != null) { 528 mLastSentBubbleBarTop = mBarView.getRestingTopPositionOnScreen(); 529 mSystemUiProxy.showBubble(getSelectedBubbleKey(), mLastSentBubbleBarTop); 530 } else { 531 Log.w(TAG, "Trying to show the selected bubble but it's null"); 532 } 533 } 534 535 /** Updates the currently selected bubble for launcher views and tells WMShell to show it. */ showAndSelectBubble(BubbleBarItem b)536 public void showAndSelectBubble(BubbleBarItem b) { 537 if (DEBUG) Log.w(TAG, "showingSelectedBubble: " + b.getKey()); 538 setSelectedBubbleInternal(b); 539 showSelectedBubble(); 540 } 541 542 /** 543 * Sets the bubble that should be selected. This notifies the views, it does not notify 544 * WMShell that the selection has changed, that should go through either 545 * {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}. 546 */ setSelectedBubbleInternal(BubbleBarItem b)547 private void setSelectedBubbleInternal(BubbleBarItem b) { 548 if (!Objects.equals(b, mSelectedBubble)) { 549 if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey()); 550 mSelectedBubble = b; 551 mBubbleBarViewController.updateSelectedBubble(mSelectedBubble); 552 } 553 } 554 555 /** 556 * Returns the selected bubble or null if no bubble is selected. 557 */ 558 @Nullable getSelectedBubbleKey()559 public String getSelectedBubbleKey() { 560 if (mSelectedBubble != null) { 561 return mSelectedBubble.getKey(); 562 } 563 return null; 564 } 565 566 /** 567 * Set a new bubble bar location. 568 * <p> 569 * Updates the value locally in Launcher and in WMShell. 570 */ updateBubbleBarLocation(BubbleBarLocation location, @BubbleBarLocation.UpdateSource int source)571 public void updateBubbleBarLocation(BubbleBarLocation location, 572 @BubbleBarLocation.UpdateSource int source) { 573 updateBubbleBarLocationInternal(location); 574 mSystemUiProxy.setBubbleBarLocation(location, source); 575 } 576 updateBubbleBarLocationInternal(BubbleBarLocation location)577 private void updateBubbleBarLocationInternal(BubbleBarLocation location) { 578 mBubbleBarViewController.setBubbleBarLocation(location); 579 mBubbleStashController.setBubbleBarLocation(location); 580 mBubbleBarLocationListener.onBubbleBarLocationUpdated(location); 581 } 582 583 @Override animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation)584 public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { 585 MAIN_EXECUTOR.execute( 586 () -> { 587 mBubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation); 588 mBubbleBarLocationListener.onBubbleBarLocationAnimated(bubbleBarLocation); 589 }); 590 } 591 592 @Override onDragItemOverBubbleBarDragZone(@onNull BubbleBarLocation bubbleBarLocation)593 public void onDragItemOverBubbleBarDragZone(@NonNull BubbleBarLocation bubbleBarLocation) { 594 MAIN_EXECUTOR.execute(() -> { 595 mBubbleBarViewController.onDragItemOverBubbleBarDragZone(bubbleBarLocation); 596 if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) { 597 mBubbleBarLocationListener.onBubbleBarLocationAnimated(bubbleBarLocation); 598 } 599 }); 600 } 601 602 @Override onItemDraggedOutsideBubbleBarDropZone()603 public void onItemDraggedOutsideBubbleBarDropZone() { 604 MAIN_EXECUTOR.execute(() -> { 605 if (mBubbleBarViewController.isLocationUpdatedForDropTarget()) { 606 BubbleBarLocation original = mBubbleBarViewController.getBubbleBarLocation(); 607 mBubbleBarLocationListener.onBubbleBarLocationAnimated(original); 608 } 609 mBubbleBarViewController.onItemDraggedOutsideBubbleBarDropZone(); 610 }); 611 } 612 613 /** Notifies WMShell to show the expanded view. */ showExpandedView()614 void showExpandedView() { 615 mSystemUiProxy.showExpandedView(); 616 } 617 618 // 619 // Loading data for the bubbles 620 // 621 onBubbleBarBoundsChanged()622 private void onBubbleBarBoundsChanged() { 623 int newTop = mBarView.getRestingTopPositionOnScreen(); 624 if (newTop != mLastSentBubbleBarTop) { 625 mLastSentBubbleBarTop = newTop; 626 mSystemUiProxy.updateBubbleBarTopOnScreen(newTop); 627 } 628 } 629 addBubbleInternally(BubbleBarBubble bubble, boolean isExpanding, boolean suppressAnimation)630 private void addBubbleInternally(BubbleBarBubble bubble, boolean isExpanding, 631 boolean suppressAnimation) { 632 mBubbles.put(bubble.getKey(), bubble); 633 mBubbleBarViewController.addBubble(bubble, isExpanding, 634 suppressAnimation, /* bubbleToSelect = */ null); 635 } 636 637 /** Listener of {@link BubbleBarLocation} updates. */ 638 public interface BubbleBarLocationListener { 639 640 /** Called when {@link BubbleBarLocation} is animated, but change is not yet final. */ onBubbleBarLocationAnimated(BubbleBarLocation location)641 void onBubbleBarLocationAnimated(BubbleBarLocation location); 642 643 /** Called when {@link BubbleBarLocation} is updated permanently. */ onBubbleBarLocationUpdated(BubbleBarLocation location)644 void onBubbleBarLocationUpdated(BubbleBarLocation location); 645 } 646 } 647