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.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_PERSONS_DATA; 19 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED; 20 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; 21 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER; 22 import static android.os.Process.THREAD_PRIORITY_BACKGROUND; 23 24 import static com.android.launcher3.icons.FastBitmapDrawable.WHITE_SCRIM_ALPHA; 25 import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; 26 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; 27 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; 28 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING; 29 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED; 30 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED; 31 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING; 32 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; 33 34 import android.annotation.BinderThread; 35 import android.annotation.Nullable; 36 import android.app.Notification; 37 import android.content.Context; 38 import android.content.pm.ApplicationInfo; 39 import android.content.pm.LauncherApps; 40 import android.content.pm.PackageManager; 41 import android.content.pm.ShortcutInfo; 42 import android.content.res.TypedArray; 43 import android.graphics.Bitmap; 44 import android.graphics.Color; 45 import android.graphics.Matrix; 46 import android.graphics.Path; 47 import android.graphics.Point; 48 import android.graphics.drawable.AdaptiveIconDrawable; 49 import android.graphics.drawable.ColorDrawable; 50 import android.graphics.drawable.Drawable; 51 import android.graphics.drawable.InsetDrawable; 52 import android.os.Bundle; 53 import android.os.SystemProperties; 54 import android.os.UserHandle; 55 import android.util.ArrayMap; 56 import android.util.Log; 57 import android.util.PathParser; 58 import android.view.LayoutInflater; 59 60 import androidx.appcompat.content.res.AppCompatResources; 61 62 import com.android.internal.graphics.ColorUtils; 63 import com.android.launcher3.R; 64 import com.android.launcher3.icons.BitmapInfo; 65 import com.android.launcher3.icons.BubbleIconFactory; 66 import com.android.launcher3.shortcuts.ShortcutRequest; 67 import com.android.launcher3.util.Executors.SimpleThreadFactory; 68 import com.android.quickstep.SystemUiProxy; 69 import com.android.systemui.shared.system.QuickStepContract.SystemUiStateFlags; 70 import com.android.wm.shell.Flags; 71 import com.android.wm.shell.bubbles.IBubblesListener; 72 import com.android.wm.shell.common.bubbles.BubbleBarLocation; 73 import com.android.wm.shell.common.bubbles.BubbleBarUpdate; 74 import com.android.wm.shell.common.bubbles.BubbleInfo; 75 import com.android.wm.shell.common.bubbles.RemovedBubble; 76 77 import java.util.ArrayList; 78 import java.util.List; 79 import java.util.Objects; 80 import java.util.concurrent.Executor; 81 import java.util.concurrent.Executors; 82 83 /** 84 * This registers a listener with SysUIProxy to get information about changes to the bubble 85 * stack state from WMShell (SysUI). The controller is also responsible for loading the necessary 86 * information to render each of the bubbles & dispatches changes to 87 * {@link BubbleBarViewController} which will then update {@link BubbleBarView} as needed. 88 * 89 * <p>For details around the behavior of the bubble bar, see {@link BubbleBarView}. 90 */ 91 public class BubbleBarController extends IBubblesListener.Stub { 92 93 private static final String TAG = "BubbleBarController"; 94 private static final boolean DEBUG = false; 95 96 /** 97 * Determines whether bubbles can be shown in the bubble bar. This value updates when the 98 * taskbar is recreated. 99 * 100 * @see #onTaskbarRecreated() 101 */ 102 private static boolean sBubbleBarEnabled = Flags.enableBubbleBar() 103 || SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); 104 105 /** Whether showing bubbles in the launcher bubble bar is enabled. */ isBubbleBarEnabled()106 public static boolean isBubbleBarEnabled() { 107 return sBubbleBarEnabled; 108 } 109 110 /** Re-reads the value of the flag from SystemProperties when taskbar is recreated. */ onTaskbarRecreated()111 public static void onTaskbarRecreated() { 112 sBubbleBarEnabled = Flags.enableBubbleBar() 113 || SystemProperties.getBoolean("persist.wm.debug.bubble_bar", false); 114 } 115 116 private static final long MASK_HIDE_BUBBLE_BAR = SYSUI_STATE_BOUNCER_SHOWING 117 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING 118 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED 119 | SYSUI_STATE_IME_SHOWING 120 | SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED 121 | SYSUI_STATE_QUICK_SETTINGS_EXPANDED 122 | SYSUI_STATE_IME_SWITCHER_SHOWING; 123 124 private static final long MASK_HIDE_HANDLE_VIEW = SYSUI_STATE_BOUNCER_SHOWING 125 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING 126 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; 127 128 private static final long MASK_SYSUI_LOCKED = SYSUI_STATE_BOUNCER_SHOWING 129 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING 130 | SYSUI_STATE_STATUS_BAR_KEYGUARD_SHOWING_OCCLUDED; 131 132 private final Context mContext; 133 private final BubbleBarView mBarView; 134 private final ArrayMap<String, BubbleBarBubble> mBubbles = new ArrayMap<>(); 135 136 private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor( 137 new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND)); 138 private final LauncherApps mLauncherApps; 139 private final BubbleIconFactory mIconFactory; 140 private final SystemUiProxy mSystemUiProxy; 141 142 private BubbleBarItem mSelectedBubble; 143 private BubbleBarOverflow mOverflowBubble; 144 145 private ImeVisibilityChecker mImeVisibilityChecker; 146 private BubbleBarViewController mBubbleBarViewController; 147 private BubbleStashController mBubbleStashController; 148 private BubbleStashedHandleViewController mBubbleStashedHandleViewController; 149 private BubblePinController mBubblePinController; 150 151 // Cache last sent top coordinate to avoid sending duplicate updates to shell 152 private int mLastSentBubbleBarTop; 153 154 /** 155 * Similar to {@link BubbleBarUpdate} but rather than {@link BubbleInfo}s it uses 156 * {@link BubbleBarBubble}s so that it can be used to update the views. 157 */ 158 private static class BubbleBarViewUpdate { 159 final boolean initialState; 160 boolean expandedChanged; 161 boolean expanded; 162 boolean shouldShowEducation; 163 String selectedBubbleKey; 164 String suppressedBubbleKey; 165 String unsuppressedBubbleKey; 166 BubbleBarLocation bubbleBarLocation; 167 List<RemovedBubble> removedBubbles; 168 List<String> bubbleKeysInOrder; 169 Point expandedViewDropTargetSize; 170 171 // These need to be loaded in the background 172 BubbleBarBubble addedBubble; 173 BubbleBarBubble updatedBubble; 174 List<BubbleBarBubble> currentBubbles; 175 BubbleBarViewUpdate(BubbleBarUpdate update)176 BubbleBarViewUpdate(BubbleBarUpdate update) { 177 initialState = update.initialState; 178 expandedChanged = update.expandedChanged; 179 expanded = update.expanded; 180 shouldShowEducation = update.shouldShowEducation; 181 selectedBubbleKey = update.selectedBubbleKey; 182 suppressedBubbleKey = update.suppressedBubbleKey; 183 unsuppressedBubbleKey = update.unsupressedBubbleKey; 184 bubbleBarLocation = update.bubbleBarLocation; 185 removedBubbles = update.removedBubbles; 186 bubbleKeysInOrder = update.bubbleKeysInOrder; 187 expandedViewDropTargetSize = update.expandedViewDropTargetSize; 188 } 189 } 190 BubbleBarController(Context context, BubbleBarView bubbleView)191 public BubbleBarController(Context context, BubbleBarView bubbleView) { 192 mContext = context; 193 mBarView = bubbleView; // Need the view for inflating bubble views. 194 195 mSystemUiProxy = SystemUiProxy.INSTANCE.get(context); 196 197 if (sBubbleBarEnabled) { 198 mSystemUiProxy.setBubblesListener(this); 199 } 200 mLauncherApps = context.getSystemService(LauncherApps.class); 201 mIconFactory = new BubbleIconFactory(context, 202 context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size), 203 context.getResources().getDimensionPixelSize(R.dimen.bubblebar_badge_size), 204 context.getResources().getColor(R.color.important_conversation), 205 context.getResources().getDimensionPixelSize( 206 com.android.internal.R.dimen.importance_ring_stroke_width)); 207 } 208 onDestroy()209 public void onDestroy() { 210 mSystemUiProxy.setBubblesListener(null); 211 } 212 213 /** Initializes controllers. */ init(BubbleControllers bubbleControllers, ImeVisibilityChecker imeVisibilityChecker)214 public void init(BubbleControllers bubbleControllers, 215 ImeVisibilityChecker imeVisibilityChecker) { 216 mImeVisibilityChecker = imeVisibilityChecker; 217 mBubbleBarViewController = bubbleControllers.bubbleBarViewController; 218 mBubbleStashController = bubbleControllers.bubbleStashController; 219 mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController; 220 mBubblePinController = bubbleControllers.bubblePinController; 221 222 bubbleControllers.runAfterInit(() -> { 223 mBubbleBarViewController.setHiddenForBubbles( 224 !sBubbleBarEnabled || mBubbles.isEmpty()); 225 mBubbleStashedHandleViewController.setHiddenForBubbles( 226 !sBubbleBarEnabled || mBubbles.isEmpty()); 227 mBubbleBarViewController.setUpdateSelectedBubbleAfterCollapse( 228 key -> setSelectedBubbleInternal(mBubbles.get(key))); 229 mBubbleBarViewController.setBoundsChangeListener(this::onBubbleBarBoundsChanged); 230 }); 231 } 232 233 /** 234 * Creates and adds the overflow bubble to the bubble bar if it hasn't been created yet. 235 * 236 * <p>This should be called on the {@link #BUBBLE_STATE_EXECUTOR} executor to avoid inflating 237 * the overflow multiple times. 238 */ createAndAddOverflowIfNeeded()239 private void createAndAddOverflowIfNeeded() { 240 if (mOverflowBubble == null) { 241 BubbleBarOverflow overflow = createOverflow(mContext); 242 MAIN_EXECUTOR.execute(() -> { 243 // we're on the main executor now, so check that the overflow hasn't been created 244 // again to avoid races. 245 if (mOverflowBubble == null) { 246 mBubbleBarViewController.addBubble( 247 overflow, /* isExpanding= */ false, /* suppressAnimation= */ true); 248 mOverflowBubble = overflow; 249 } 250 }); 251 } 252 } 253 254 /** 255 * Updates the bubble bar, handle bar, and stash controllers based on sysui state flags. 256 */ updateStateForSysuiFlags(@ystemUiStateFlags long flags)257 public void updateStateForSysuiFlags(@SystemUiStateFlags long flags) { 258 boolean hideBubbleBar = (flags & MASK_HIDE_BUBBLE_BAR) != 0; 259 mBubbleBarViewController.setHiddenForSysui(hideBubbleBar); 260 261 boolean hideHandleView = (flags & MASK_HIDE_HANDLE_VIEW) != 0; 262 mBubbleStashedHandleViewController.setHiddenForSysui(hideHandleView); 263 264 boolean sysuiLocked = (flags & MASK_SYSUI_LOCKED) != 0; 265 mBubbleStashController.onSysuiLockedStateChange(sysuiLocked); 266 } 267 268 // 269 // Bubble data changes 270 // 271 272 @BinderThread 273 @Override onBubbleStateChange(Bundle bundle)274 public void onBubbleStateChange(Bundle bundle) { 275 bundle.setClassLoader(BubbleBarUpdate.class.getClassLoader()); 276 BubbleBarUpdate update = bundle.getParcelable("update", BubbleBarUpdate.class); 277 BubbleBarViewUpdate viewUpdate = new BubbleBarViewUpdate(update); 278 if (update.addedBubble != null 279 || update.updatedBubble != null 280 || !update.currentBubbleList.isEmpty()) { 281 // We have bubbles to load 282 BUBBLE_STATE_EXECUTOR.execute(() -> { 283 createAndAddOverflowIfNeeded(); 284 if (update.addedBubble != null) { 285 viewUpdate.addedBubble = populateBubble(mContext, update.addedBubble, mBarView, 286 null /* existingBubble */); 287 } 288 if (update.updatedBubble != null) { 289 BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey()); 290 viewUpdate.updatedBubble = 291 populateBubble(mContext, update.updatedBubble, mBarView, 292 existingBubble); 293 } 294 if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) { 295 List<BubbleBarBubble> currentBubbles = new ArrayList<>(); 296 for (int i = 0; i < update.currentBubbleList.size(); i++) { 297 BubbleBarBubble b = 298 populateBubble(mContext, update.currentBubbleList.get(i), mBarView, 299 null /* existingBubble */); 300 currentBubbles.add(b); 301 } 302 viewUpdate.currentBubbles = currentBubbles; 303 } 304 MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate)); 305 }); 306 } else { 307 // No bubbles to load, immediately apply the changes. 308 BUBBLE_STATE_EXECUTOR.execute( 309 () -> MAIN_EXECUTOR.execute(() -> applyViewChanges(viewUpdate))); 310 } 311 } 312 applyViewChanges(BubbleBarViewUpdate update)313 private void applyViewChanges(BubbleBarViewUpdate update) { 314 final boolean isCollapsed = (update.expandedChanged && !update.expanded) 315 || (!update.expandedChanged && !mBubbleBarViewController.isExpanded()); 316 final boolean isExpanding = update.expandedChanged && update.expanded; 317 // don't animate bubbles if this is the initial state because we may be unfolding or 318 // enabling gesture nav. also suppress animation if the bubble bar is hidden for sysui e.g. 319 // the shade is open, or we're locked. 320 final boolean suppressAnimation = 321 update.initialState || mBubbleBarViewController.isHiddenForSysui() 322 || mImeVisibilityChecker.isImeVisible(); 323 324 BubbleBarBubble bubbleToSelect = null; 325 if (!update.removedBubbles.isEmpty()) { 326 for (int i = 0; i < update.removedBubbles.size(); i++) { 327 RemovedBubble removedBubble = update.removedBubbles.get(i); 328 BubbleBarBubble bubble = mBubbles.remove(removedBubble.getKey()); 329 if (bubble != null) { 330 mBubbleBarViewController.removeBubble(bubble); 331 } else { 332 Log.w(TAG, "trying to remove bubble that doesn't exist: " 333 + removedBubble.getKey()); 334 } 335 } 336 } 337 if (update.addedBubble != null) { 338 mBubbles.put(update.addedBubble.getKey(), update.addedBubble); 339 mBubbleBarViewController.addBubble(update.addedBubble, isExpanding, suppressAnimation); 340 if (isCollapsed) { 341 // If we're collapsed, the most recently added bubble will be selected. 342 bubbleToSelect = update.addedBubble; 343 } 344 345 } 346 if (update.currentBubbles != null && !update.currentBubbles.isEmpty()) { 347 // Iterate in reverse because new bubbles are added in front and the list is in order. 348 for (int i = update.currentBubbles.size() - 1; i >= 0; i--) { 349 BubbleBarBubble bubble = update.currentBubbles.get(i); 350 if (bubble != null) { 351 mBubbles.put(bubble.getKey(), bubble); 352 mBubbleBarViewController.addBubble(bubble, isExpanding, suppressAnimation); 353 if (isCollapsed) { 354 // If we're collapsed, the most recently added bubble will be selected. 355 bubbleToSelect = bubble; 356 } 357 } else { 358 Log.w(TAG, "trying to add bubble but null after loading! " 359 + update.addedBubble.getKey()); 360 } 361 } 362 } 363 364 // Adds and removals have happened, update visibility before any other visual changes. 365 mBubbleBarViewController.setHiddenForBubbles(mBubbles.isEmpty()); 366 mBubbleStashedHandleViewController.setHiddenForBubbles(mBubbles.isEmpty()); 367 368 if (mBubbles.isEmpty()) { 369 // all bubbles were removed. clear the selected bubble 370 mSelectedBubble = null; 371 } 372 373 if (update.updatedBubble != null) { 374 // Updates mean the dot state may have changed; any other changes were updated in 375 // the populateBubble step. 376 BubbleBarBubble bb = mBubbles.get(update.updatedBubble.getKey()); 377 // If we're not stashed, we're visible so animate 378 bb.getView().updateDotVisibility(!mBubbleStashController.isStashed() /* animate */); 379 mBubbleBarViewController.animateBubbleNotification(bb, /* isExpanding= */ false); 380 } 381 if (update.bubbleKeysInOrder != null && !update.bubbleKeysInOrder.isEmpty()) { 382 // Create the new list 383 List<BubbleBarBubble> newOrder = update.bubbleKeysInOrder.stream() 384 .map(mBubbles::get).filter(Objects::nonNull).toList(); 385 if (!newOrder.isEmpty()) { 386 mBubbleBarViewController.reorderBubbles(newOrder); 387 } 388 } 389 if (update.suppressedBubbleKey != null) { 390 // TODO: (b/273316505) handle suppression 391 } 392 if (update.unsuppressedBubbleKey != null) { 393 // TODO: (b/273316505) handle suppression 394 } 395 if (update.selectedBubbleKey != null) { 396 if (mSelectedBubble == null 397 || !update.selectedBubbleKey.equals(mSelectedBubble.getKey())) { 398 BubbleBarBubble newlySelected = mBubbles.get(update.selectedBubbleKey); 399 if (newlySelected != null) { 400 bubbleToSelect = newlySelected; 401 } else { 402 Log.w(TAG, "trying to select bubble that doesn't exist:" 403 + update.selectedBubbleKey); 404 } 405 } 406 } 407 if (bubbleToSelect != null) { 408 setSelectedBubbleInternal(bubbleToSelect); 409 } 410 if (update.shouldShowEducation) { 411 mBubbleBarViewController.prepareToShowEducation(); 412 } 413 if (update.expandedChanged) { 414 if (update.expanded != mBubbleBarViewController.isExpanded()) { 415 mBubbleBarViewController.setExpandedFromSysui(update.expanded); 416 } else { 417 Log.w(TAG, "expansion was changed but is the same"); 418 } 419 } 420 if (update.bubbleBarLocation != null) { 421 if (update.bubbleBarLocation != mBubbleBarViewController.getBubbleBarLocation()) { 422 updateBubbleBarLocationInternal(update.bubbleBarLocation); 423 } 424 } 425 if (update.expandedViewDropTargetSize != null) { 426 mBubblePinController.setDropTargetSize(update.expandedViewDropTargetSize); 427 } 428 } 429 430 /** Tells WMShell to show the currently selected bubble. */ showSelectedBubble()431 public void showSelectedBubble() { 432 if (getSelectedBubbleKey() != null) { 433 if (mSelectedBubble instanceof BubbleBarBubble) { 434 // Because we've visited this bubble, we should suppress the notification. 435 // This is updated on WMShell side when we show the bubble, but that update isn't 436 // passed to launcher, instead we apply it directly here. 437 BubbleInfo info = ((BubbleBarBubble) mSelectedBubble).getInfo(); 438 info.setFlags( 439 info.getFlags() | Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION); 440 mSelectedBubble.getView().updateDotVisibility(true /* animate */); 441 } 442 mLastSentBubbleBarTop = mBarView.getRestingTopPositionOnScreen(); 443 mSystemUiProxy.showBubble(getSelectedBubbleKey(), mLastSentBubbleBarTop); 444 } else { 445 Log.w(TAG, "Trying to show the selected bubble but it's null"); 446 } 447 } 448 449 /** Updates the currently selected bubble for launcher views and tells WMShell to show it. */ showAndSelectBubble(BubbleBarItem b)450 public void showAndSelectBubble(BubbleBarItem b) { 451 if (DEBUG) Log.w(TAG, "showingSelectedBubble: " + b.getKey()); 452 setSelectedBubbleInternal(b); 453 showSelectedBubble(); 454 } 455 456 /** 457 * Sets the bubble that should be selected. This notifies the views, it does not notify 458 * WMShell that the selection has changed, that should go through either 459 * {@link #showSelectedBubble()} or {@link #showAndSelectBubble(BubbleBarItem)}. 460 */ setSelectedBubbleInternal(BubbleBarItem b)461 private void setSelectedBubbleInternal(BubbleBarItem b) { 462 if (!Objects.equals(b, mSelectedBubble)) { 463 if (DEBUG) Log.w(TAG, "selectingBubble: " + b.getKey()); 464 mSelectedBubble = b; 465 mBubbleBarViewController.updateSelectedBubble(mSelectedBubble); 466 } 467 } 468 469 /** 470 * Returns the selected bubble or null if no bubble is selected. 471 */ 472 @Nullable getSelectedBubbleKey()473 public String getSelectedBubbleKey() { 474 if (mSelectedBubble != null) { 475 return mSelectedBubble.getKey(); 476 } 477 return null; 478 } 479 480 /** 481 * Set a new bubble bar location. 482 * <p> 483 * Updates the value locally in Launcher and in WMShell. 484 */ updateBubbleBarLocation(BubbleBarLocation location)485 public void updateBubbleBarLocation(BubbleBarLocation location) { 486 updateBubbleBarLocationInternal(location); 487 mSystemUiProxy.setBubbleBarLocation(location); 488 } 489 updateBubbleBarLocationInternal(BubbleBarLocation location)490 private void updateBubbleBarLocationInternal(BubbleBarLocation location) { 491 mBubbleBarViewController.setBubbleBarLocation(location); 492 mBubbleStashController.setBubbleBarLocation(location); 493 } 494 495 @Override animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation)496 public void animateBubbleBarLocation(BubbleBarLocation bubbleBarLocation) { 497 MAIN_EXECUTOR.execute( 498 () -> mBubbleBarViewController.animateBubbleBarLocation(bubbleBarLocation)); 499 } 500 501 // 502 // Loading data for the bubbles 503 // 504 505 @Nullable populateBubble(Context context, BubbleInfo b, BubbleBarView bbv, @Nullable BubbleBarBubble existingBubble)506 private BubbleBarBubble populateBubble(Context context, BubbleInfo b, BubbleBarView bbv, 507 @Nullable BubbleBarBubble existingBubble) { 508 String appName; 509 Bitmap badgeBitmap; 510 Bitmap bubbleBitmap; 511 Path dotPath; 512 int dotColor; 513 514 boolean isImportantConvo = b.isImportantConversation(); 515 516 ShortcutRequest.QueryResult result = new ShortcutRequest(context, 517 new UserHandle(b.getUserId())) 518 .forPackage(b.getPackageName(), b.getShortcutId()) 519 .query(FLAG_MATCH_DYNAMIC 520 | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER 521 | FLAG_MATCH_CACHED 522 | FLAG_GET_PERSONS_DATA); 523 524 ShortcutInfo shortcutInfo = result.size() > 0 ? result.get(0) : null; 525 if (shortcutInfo == null) { 526 Log.w(TAG, "No shortcutInfo found for bubble: " + b.getKey() 527 + " with shortcutId: " + b.getShortcutId()); 528 } 529 530 ApplicationInfo appInfo; 531 try { 532 appInfo = mLauncherApps.getApplicationInfo( 533 b.getPackageName(), 534 0, 535 new UserHandle(b.getUserId())); 536 } catch (PackageManager.NameNotFoundException e) { 537 // If we can't find package... don't think we should show the bubble. 538 Log.w(TAG, "Unable to find packageName: " + b.getPackageName()); 539 return null; 540 } 541 if (appInfo == null) { 542 Log.w(TAG, "Unable to find appInfo: " + b.getPackageName()); 543 return null; 544 } 545 PackageManager pm = context.getPackageManager(); 546 appName = String.valueOf(appInfo.loadLabel(pm)); 547 Drawable appIcon = appInfo.loadUnbadgedIcon(pm); 548 Drawable badgedIcon = pm.getUserBadgedIcon(appIcon, new UserHandle(b.getUserId())); 549 550 // Badged bubble image 551 Drawable bubbleDrawable = mIconFactory.getBubbleDrawable(context, shortcutInfo, 552 b.getIcon()); 553 if (bubbleDrawable == null) { 554 // Default to app icon 555 bubbleDrawable = appIcon; 556 } 557 558 BitmapInfo badgeBitmapInfo = mIconFactory.getBadgeBitmap(badgedIcon, isImportantConvo); 559 badgeBitmap = badgeBitmapInfo.icon; 560 561 float[] bubbleBitmapScale = new float[1]; 562 bubbleBitmap = mIconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale); 563 564 // Dot color & placement 565 Path iconPath = PathParser.createPathFromPathData( 566 context.getResources().getString( 567 com.android.internal.R.string.config_icon_mask)); 568 Matrix matrix = new Matrix(); 569 float scale = bubbleBitmapScale[0]; 570 float radius = BubbleView.DEFAULT_PATH_SIZE / 2f; 571 matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, 572 radius /* pivot y */); 573 iconPath.transform(matrix); 574 dotPath = iconPath; 575 dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, 576 Color.WHITE, WHITE_SCRIM_ALPHA / 255f); 577 578 if (existingBubble == null) { 579 LayoutInflater inflater = LayoutInflater.from(context); 580 BubbleView bubbleView = (BubbleView) inflater.inflate( 581 R.layout.bubblebar_item_view, bbv, false /* attachToRoot */); 582 583 BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView, 584 badgeBitmap, bubbleBitmap, dotColor, dotPath, appName); 585 bubbleView.setBubble(bubble); 586 return bubble; 587 } else { 588 // If we already have a bubble (so it already has an inflated view), update it. 589 existingBubble.setInfo(b); 590 existingBubble.setBadge(badgeBitmap); 591 existingBubble.setIcon(bubbleBitmap); 592 existingBubble.setDotColor(dotColor); 593 existingBubble.setDotPath(dotPath); 594 existingBubble.setAppName(appName); 595 return existingBubble; 596 } 597 } 598 createOverflow(Context context)599 private BubbleBarOverflow createOverflow(Context context) { 600 Bitmap bitmap = createOverflowBitmap(context); 601 LayoutInflater inflater = LayoutInflater.from(context); 602 BubbleView bubbleView = (BubbleView) inflater.inflate( 603 R.layout.bubblebar_item_view, mBarView, false /* attachToRoot */); 604 BubbleBarOverflow overflow = new BubbleBarOverflow(bubbleView); 605 bubbleView.setOverflow(overflow, bitmap); 606 return overflow; 607 } 608 createOverflowBitmap(Context context)609 private Bitmap createOverflowBitmap(Context context) { 610 Drawable iconDrawable = AppCompatResources.getDrawable(mContext, 611 R.drawable.bubble_ic_overflow_button); 612 613 final TypedArray ta = mContext.obtainStyledAttributes( 614 new int[]{ 615 com.android.internal.R.attr.materialColorOnPrimaryFixed, 616 com.android.internal.R.attr.materialColorPrimaryFixed 617 }); 618 int overflowIconColor = ta.getColor(0, Color.WHITE); 619 int overflowBackgroundColor = ta.getColor(1, Color.BLACK); 620 ta.recycle(); 621 622 iconDrawable.setTint(overflowIconColor); 623 624 int inset = context.getResources().getDimensionPixelSize(R.dimen.bubblebar_overflow_inset); 625 Drawable foreground = new InsetDrawable(iconDrawable, inset); 626 Drawable drawable = new AdaptiveIconDrawable(new ColorDrawable(overflowBackgroundColor), 627 foreground); 628 629 return mIconFactory.createBadgedIconBitmap(drawable).icon; 630 } 631 onBubbleBarBoundsChanged()632 private void onBubbleBarBoundsChanged() { 633 int newTop = mBarView.getRestingTopPositionOnScreen(); 634 if (newTop != mLastSentBubbleBarTop) { 635 mLastSentBubbleBarTop = newTop; 636 mSystemUiProxy.updateBubbleBarTopOnScreen(newTop); 637 } 638 } 639 640 /** Interface for checking whether the IME is visible. */ 641 public interface ImeVisibilityChecker { 642 /** Whether the IME is visible. */ isImeVisible()643 boolean isImeVisible(); 644 } 645 } 646