diff --git a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java index e4f726257d..2cb37ff845 100644 --- a/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java +++ b/quickstep/src/com/android/launcher3/taskbar/TaskbarActivityContext.java @@ -108,6 +108,7 @@ import com.android.launcher3.taskbar.bubbles.BubbleBarPinController; import com.android.launcher3.taskbar.bubbles.BubbleBarView; import com.android.launcher3.taskbar.bubbles.BubbleBarViewController; import com.android.launcher3.taskbar.bubbles.BubbleControllers; +import com.android.launcher3.taskbar.bubbles.BubbleCreator; import com.android.launcher3.taskbar.bubbles.BubbleDismissController; import com.android.launcher3.taskbar.bubbles.BubbleDragController; import com.android.launcher3.taskbar.bubbles.BubblePinController; @@ -296,7 +297,8 @@ public class TaskbarActivityContext extends BaseTaskbarContext { new BubbleBarPinController(this, mDragLayer, () -> DisplayController.INSTANCE.get(this).getInfo().currentSize), new BubblePinController(this, mDragLayer, - () -> DisplayController.INSTANCE.get(this).getInfo().currentSize) + () -> DisplayController.INSTANCE.get(this).getInfo().currentSize), + new BubbleCreator(this) )); } diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java index 58cd042fc1..fbee0802e2 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarController.java @@ -15,13 +15,8 @@ */ package com.android.launcher3.taskbar.bubbles; -import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_PERSONS_DATA; -import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED; -import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; -import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER; import static android.os.Process.THREAD_PRIORITY_BACKGROUND; -import static com.android.launcher3.icons.FastBitmapDrawable.WHITE_SCRIM_ALPHA; import static com.android.launcher3.util.Executors.MAIN_EXECUTOR; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_BOUNCER_SHOWING; import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING; @@ -34,35 +29,12 @@ import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_S import android.annotation.BinderThread; import android.annotation.Nullable; import android.content.Context; -import android.content.pm.ApplicationInfo; -import android.content.pm.LauncherApps; -import android.content.pm.PackageManager; -import android.content.pm.ShortcutInfo; -import android.content.res.TypedArray; -import android.graphics.Bitmap; -import android.graphics.Color; -import android.graphics.Matrix; -import android.graphics.Path; import android.graphics.Point; -import android.graphics.drawable.AdaptiveIconDrawable; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.graphics.drawable.InsetDrawable; import android.os.Bundle; import android.os.SystemProperties; -import android.os.UserHandle; import android.util.ArrayMap; import android.util.Log; -import android.util.PathParser; -import android.view.LayoutInflater; -import androidx.appcompat.content.res.AppCompatResources; - -import com.android.internal.graphics.ColorUtils; -import com.android.launcher3.R; -import com.android.launcher3.icons.BitmapInfo; -import com.android.launcher3.icons.BubbleIconFactory; -import com.android.launcher3.shortcuts.ShortcutRequest; import com.android.launcher3.taskbar.bubbles.stashing.BubbleStashController; import com.android.launcher3.util.Executors.SimpleThreadFactory; import com.android.quickstep.SystemUiProxy; @@ -136,18 +108,16 @@ public class BubbleBarController extends IBubblesListener.Stub { private static final Executor BUBBLE_STATE_EXECUTOR = Executors.newSingleThreadExecutor( new SimpleThreadFactory("BubbleStateUpdates-", THREAD_PRIORITY_BACKGROUND)); - private final LauncherApps mLauncherApps; - private final BubbleIconFactory mIconFactory; private final SystemUiProxy mSystemUiProxy; private BubbleBarItem mSelectedBubble; - private BubbleBarOverflow mOverflowBubble; private ImeVisibilityChecker mImeVisibilityChecker; private BubbleBarViewController mBubbleBarViewController; private BubbleStashController mBubbleStashController; private Optional mBubbleStashedHandleViewController; private BubblePinController mBubblePinController; + private BubbleCreator mBubbleCreator; // Cache last sent top coordinate to avoid sending duplicate updates to shell private int mLastSentBubbleBarTop; @@ -198,13 +168,6 @@ public class BubbleBarController extends IBubblesListener.Stub { if (sBubbleBarEnabled) { mSystemUiProxy.setBubblesListener(this); } - mLauncherApps = context.getSystemService(LauncherApps.class); - mIconFactory = new BubbleIconFactory(context, - context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size), - context.getResources().getDimensionPixelSize(R.dimen.bubblebar_badge_size), - context.getResources().getColor(R.color.important_conversation), - context.getResources().getDimensionPixelSize( - com.android.internal.R.dimen.importance_ring_stroke_width)); } public void onDestroy() { @@ -219,6 +182,7 @@ public class BubbleBarController extends IBubblesListener.Stub { mBubbleStashController = bubbleControllers.bubbleStashController; mBubbleStashedHandleViewController = bubbleControllers.bubbleStashedHandleViewController; mBubblePinController = bubbleControllers.bubblePinController; + mBubbleCreator = bubbleControllers.bubbleCreator; bubbleControllers.runAfterInit(() -> { mBubbleBarViewController.setHiddenForBubbles( @@ -232,27 +196,6 @@ public class BubbleBarController extends IBubblesListener.Stub { }); } - /** - * Creates and adds the overflow bubble to the bubble bar if it hasn't been created yet. - * - *

This should be called on the {@link #BUBBLE_STATE_EXECUTOR} executor to avoid inflating - * the overflow multiple times. - */ - private void createAndAddOverflowIfNeeded() { - if (mOverflowBubble == null) { - BubbleBarOverflow overflow = createOverflow(mContext); - MAIN_EXECUTOR.execute(() -> { - // we're on the main executor now, so check that the overflow hasn't been created - // again to avoid races. - if (mOverflowBubble == null) { - mBubbleBarViewController.addBubble( - overflow, /* isExpanding= */ false, /* suppressAnimation= */ true); - mOverflowBubble = overflow; - } - }); - } - } - /** * Updates the bubble bar, handle bar, and stash controllers based on sysui state flags. */ @@ -283,23 +226,25 @@ public class BubbleBarController extends IBubblesListener.Stub { || !update.currentBubbleList.isEmpty()) { // We have bubbles to load BUBBLE_STATE_EXECUTOR.execute(() -> { - createAndAddOverflowIfNeeded(); if (update.addedBubble != null) { - viewUpdate.addedBubble = populateBubble(mContext, update.addedBubble, mBarView, + viewUpdate.addedBubble = mBubbleCreator.populateBubble(mContext, + update.addedBubble, + mBarView, null /* existingBubble */); } if (update.updatedBubble != null) { BubbleBarBubble existingBubble = mBubbles.get(update.updatedBubble.getKey()); viewUpdate.updatedBubble = - populateBubble(mContext, update.updatedBubble, mBarView, + mBubbleCreator.populateBubble(mContext, update.updatedBubble, + mBarView, existingBubble); } if (update.currentBubbleList != null && !update.currentBubbleList.isEmpty()) { List currentBubbles = new ArrayList<>(); for (int i = 0; i < update.currentBubbleList.size(); i++) { - BubbleBarBubble b = - populateBubble(mContext, update.currentBubbleList.get(i), mBarView, - null /* existingBubble */); + BubbleBarBubble b = mBubbleCreator.populateBubble(mContext, + update.currentBubbleList.get(i), mBarView, + null /* existingBubble */); currentBubbles.add(b); } viewUpdate.currentBubbles = currentBubbles; @@ -530,133 +475,6 @@ public class BubbleBarController extends IBubblesListener.Stub { // Loading data for the bubbles // - @Nullable - private BubbleBarBubble populateBubble(Context context, BubbleInfo b, BubbleBarView bbv, - @Nullable BubbleBarBubble existingBubble) { - String appName; - Bitmap badgeBitmap; - Bitmap bubbleBitmap; - Path dotPath; - int dotColor; - - boolean isImportantConvo = b.isImportantConversation(); - - ShortcutRequest.QueryResult result = new ShortcutRequest(context, - new UserHandle(b.getUserId())) - .forPackage(b.getPackageName(), b.getShortcutId()) - .query(FLAG_MATCH_DYNAMIC - | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER - | FLAG_MATCH_CACHED - | FLAG_GET_PERSONS_DATA); - - ShortcutInfo shortcutInfo = result.size() > 0 ? result.get(0) : null; - if (shortcutInfo == null) { - Log.w(TAG, "No shortcutInfo found for bubble: " + b.getKey() - + " with shortcutId: " + b.getShortcutId()); - } - - ApplicationInfo appInfo; - try { - appInfo = mLauncherApps.getApplicationInfo( - b.getPackageName(), - 0, - new UserHandle(b.getUserId())); - } catch (PackageManager.NameNotFoundException e) { - // If we can't find package... don't think we should show the bubble. - Log.w(TAG, "Unable to find packageName: " + b.getPackageName()); - return null; - } - if (appInfo == null) { - Log.w(TAG, "Unable to find appInfo: " + b.getPackageName()); - return null; - } - PackageManager pm = context.getPackageManager(); - appName = String.valueOf(appInfo.loadLabel(pm)); - Drawable appIcon = appInfo.loadUnbadgedIcon(pm); - Drawable badgedIcon = pm.getUserBadgedIcon(appIcon, new UserHandle(b.getUserId())); - - // Badged bubble image - Drawable bubbleDrawable = mIconFactory.getBubbleDrawable(context, shortcutInfo, - b.getIcon()); - if (bubbleDrawable == null) { - // Default to app icon - bubbleDrawable = appIcon; - } - - BitmapInfo badgeBitmapInfo = mIconFactory.getBadgeBitmap(badgedIcon, isImportantConvo); - badgeBitmap = badgeBitmapInfo.icon; - - float[] bubbleBitmapScale = new float[1]; - bubbleBitmap = mIconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale); - - // Dot color & placement - Path iconPath = PathParser.createPathFromPathData( - context.getResources().getString( - com.android.internal.R.string.config_icon_mask)); - Matrix matrix = new Matrix(); - float scale = bubbleBitmapScale[0]; - float radius = BubbleView.DEFAULT_PATH_SIZE / 2f; - matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, - radius /* pivot y */); - iconPath.transform(matrix); - dotPath = iconPath; - dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, - Color.WHITE, WHITE_SCRIM_ALPHA / 255f); - - if (existingBubble == null) { - LayoutInflater inflater = LayoutInflater.from(context); - BubbleView bubbleView = (BubbleView) inflater.inflate( - R.layout.bubblebar_item_view, bbv, false /* attachToRoot */); - - BubbleBarBubble bubble = new BubbleBarBubble(b, bubbleView, - badgeBitmap, bubbleBitmap, dotColor, dotPath, appName); - bubbleView.setBubble(bubble); - return bubble; - } else { - // If we already have a bubble (so it already has an inflated view), update it. - existingBubble.setInfo(b); - existingBubble.setBadge(badgeBitmap); - existingBubble.setIcon(bubbleBitmap); - existingBubble.setDotColor(dotColor); - existingBubble.setDotPath(dotPath); - existingBubble.setAppName(appName); - return existingBubble; - } - } - - private BubbleBarOverflow createOverflow(Context context) { - Bitmap bitmap = createOverflowBitmap(context); - LayoutInflater inflater = LayoutInflater.from(context); - BubbleView bubbleView = (BubbleView) inflater.inflate( - R.layout.bubble_bar_overflow_button, mBarView, false /* attachToRoot */); - BubbleBarOverflow overflow = new BubbleBarOverflow(bubbleView); - bubbleView.setOverflow(overflow, bitmap); - return overflow; - } - - private Bitmap createOverflowBitmap(Context context) { - Drawable iconDrawable = AppCompatResources.getDrawable(mContext, - R.drawable.bubble_ic_overflow_button); - - final TypedArray ta = mContext.obtainStyledAttributes( - new int[]{ - R.attr.materialColorOnPrimaryFixed, - R.attr.materialColorPrimaryFixed - }); - int overflowIconColor = ta.getColor(0, Color.WHITE); - int overflowBackgroundColor = ta.getColor(1, Color.BLACK); - ta.recycle(); - - iconDrawable.setTint(overflowIconColor); - - int inset = context.getResources().getDimensionPixelSize(R.dimen.bubblebar_overflow_inset); - Drawable foreground = new InsetDrawable(iconDrawable, inset); - Drawable drawable = new AdaptiveIconDrawable(new ColorDrawable(overflowBackgroundColor), - foreground); - - return mIconFactory.createBadgedIconBitmap(drawable).icon; - } - private void onBubbleBarBoundsChanged() { int newTop = mBarView.getRestingTopPositionOnScreen(); if (newTop != mLastSentBubbleBarTop) { diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java index 916b7b1a84..590916edd0 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleBarViewController.java @@ -75,6 +75,7 @@ public class BubbleBarViewController { private View.OnClickListener mBubbleClickListener; private View.OnClickListener mBubbleBarClickListener; private BubbleView.Controller mBubbleViewController; + private BubbleBarOverflow mOverflowBubble; // These are exposed to {@link BubbleStashController} to animate for stashing/un-stashing private final MultiValueAlpha mBubbleBarAlpha; @@ -121,6 +122,8 @@ public class BubbleBarViewController { mBubbleClickListener = v -> onBubbleClicked((BubbleView) v); mBubbleBarClickListener = v -> expandBubbleBar(); mBubbleDragController.setupBubbleBarView(mBarView); + mOverflowBubble = bubbleControllers.bubbleCreator.createOverflow(mBarView); + addOverflow(); mBarView.setOnClickListener(mBubbleBarClickListener); mBarView.addOnLayoutChangeListener( (v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom) -> { @@ -491,6 +494,15 @@ public class BubbleBarViewController { } } + /** + * Adds the overflow view to the bubble bar. + */ + public void addOverflow() { + mBarView.addBubble(mOverflowBubble.getView()); + mOverflowBubble.getView().setOnClickListener(mBubbleClickListener); + mOverflowBubble.getView().setController(mBubbleViewController); + } + /** * Adds the provided bubble to the bubble bar. */ @@ -501,10 +513,6 @@ public class BubbleBarViewController { mBubbleDragController.setupBubbleView(b.getView()); b.getView().setController(mBubbleViewController); - if (b instanceof BubbleBarOverflow) { - return; - } - if (suppressAnimation || !(b instanceof BubbleBarBubble bubble)) { // the bubble bar and handle are initialized as part of the first bubble animation. // if the animation is suppressed, immediately stash or show the bubble bar to diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java index a5243fafc1..8478dc2171 100644 --- a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleControllers.java @@ -33,6 +33,7 @@ public class BubbleControllers { public final BubbleDismissController bubbleDismissController; public final BubbleBarPinController bubbleBarPinController; public final BubblePinController bubblePinController; + public final BubbleCreator bubbleCreator; private final RunnableList mPostInitRunnables = new RunnableList(); @@ -49,7 +50,8 @@ public class BubbleControllers { BubbleDragController bubbleDragController, BubbleDismissController bubbleDismissController, BubbleBarPinController bubbleBarPinController, - BubblePinController bubblePinController) { + BubblePinController bubblePinController, + BubbleCreator bubbleCreator) { this.bubbleBarController = bubbleBarController; this.bubbleBarViewController = bubbleBarViewController; this.bubbleStashController = bubbleStashController; @@ -58,6 +60,7 @@ public class BubbleControllers { this.bubbleDismissController = bubbleDismissController; this.bubbleBarPinController = bubbleBarPinController; this.bubblePinController = bubblePinController; + this.bubbleCreator = bubbleCreator; } /** diff --git a/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java new file mode 100644 index 0000000000..8e9a2f67a0 --- /dev/null +++ b/quickstep/src/com/android/launcher3/taskbar/bubbles/BubbleCreator.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3.taskbar.bubbles; + +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_PERSONS_DATA; +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED; +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; +import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER; + +import static com.android.launcher3.icons.FastBitmapDrawable.WHITE_SCRIM_ALPHA; + +import android.annotation.Nullable; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.LauncherApps; +import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Path; +import android.graphics.drawable.AdaptiveIconDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.InsetDrawable; +import android.os.UserHandle; +import android.util.Log; +import android.util.PathParser; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.appcompat.content.res.AppCompatResources; + +import com.android.internal.graphics.ColorUtils; +import com.android.launcher3.R; +import com.android.launcher3.icons.BitmapInfo; +import com.android.launcher3.icons.BubbleIconFactory; +import com.android.launcher3.shortcuts.ShortcutRequest; +import com.android.wm.shell.common.bubbles.BubbleInfo; + +/** + * Loads the necessary info to populate / present a bubble (name, icon, shortcut). + */ +public class BubbleCreator { + + private static final String TAG = BubbleCreator.class.getSimpleName(); + + private final Context mContext; + private final LauncherApps mLauncherApps; + private final BubbleIconFactory mIconFactory; + + public BubbleCreator(Context context) { + mContext = context; + mLauncherApps = mContext.getSystemService(LauncherApps.class); + mIconFactory = new BubbleIconFactory(context, + context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size), + context.getResources().getDimensionPixelSize(R.dimen.bubblebar_badge_size), + context.getResources().getColor(R.color.important_conversation), + context.getResources().getDimensionPixelSize( + com.android.internal.R.dimen.importance_ring_stroke_width)); + } + + /** + * Creates a BubbleBarBubble object, including the view if needed, and populates it with + * the info needed for presentation. + * + * @param context the context to use for inflation. + * @param info the info to use to populate the bubble. + * @param barView the parent view for the bubble (bubble is not added to the view). + * @param existingBubble if a bubble exists already, this object gets updated with the new + * info & returned (& any existing views are reused instead of inflating + * new ones. + */ + @Nullable + public BubbleBarBubble populateBubble(Context context, BubbleInfo info, ViewGroup barView, + @Nullable BubbleBarBubble existingBubble) { + String appName; + Bitmap badgeBitmap; + Bitmap bubbleBitmap; + Path dotPath; + int dotColor; + + boolean isImportantConvo = info.isImportantConversation(); + + ShortcutRequest.QueryResult result = new ShortcutRequest(context, + new UserHandle(info.getUserId())) + .forPackage(info.getPackageName(), info.getShortcutId()) + .query(FLAG_MATCH_DYNAMIC + | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER + | FLAG_MATCH_CACHED + | FLAG_GET_PERSONS_DATA); + + ShortcutInfo shortcutInfo = result.size() > 0 ? result.get(0) : null; + if (shortcutInfo == null) { + Log.w(TAG, "No shortcutInfo found for bubble: " + info.getKey() + + " with shortcutId: " + info.getShortcutId()); + } + + ApplicationInfo appInfo; + try { + appInfo = mLauncherApps.getApplicationInfo( + info.getPackageName(), + 0, + new UserHandle(info.getUserId())); + } catch (PackageManager.NameNotFoundException e) { + // If we can't find package... don't think we should show the bubble. + Log.w(TAG, "Unable to find packageName: " + info.getPackageName()); + return null; + } + if (appInfo == null) { + Log.w(TAG, "Unable to find appInfo: " + info.getPackageName()); + return null; + } + PackageManager pm = context.getPackageManager(); + appName = String.valueOf(appInfo.loadLabel(pm)); + Drawable appIcon = appInfo.loadUnbadgedIcon(pm); + Drawable badgedIcon = pm.getUserBadgedIcon(appIcon, new UserHandle(info.getUserId())); + + // Badged bubble image + Drawable bubbleDrawable = mIconFactory.getBubbleDrawable(context, shortcutInfo, + info.getIcon()); + if (bubbleDrawable == null) { + // Default to app icon + bubbleDrawable = appIcon; + } + + BitmapInfo badgeBitmapInfo = mIconFactory.getBadgeBitmap(badgedIcon, isImportantConvo); + badgeBitmap = badgeBitmapInfo.icon; + + float[] bubbleBitmapScale = new float[1]; + bubbleBitmap = mIconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale); + + // Dot color & placement + Path iconPath = PathParser.createPathFromPathData( + context.getResources().getString( + com.android.internal.R.string.config_icon_mask)); + Matrix matrix = new Matrix(); + float scale = bubbleBitmapScale[0]; + float radius = BubbleView.DEFAULT_PATH_SIZE / 2f; + matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, + radius /* pivot y */); + iconPath.transform(matrix); + dotPath = iconPath; + dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, + Color.WHITE, WHITE_SCRIM_ALPHA / 255f); + + if (existingBubble == null) { + LayoutInflater inflater = LayoutInflater.from(context); + BubbleView bubbleView = (BubbleView) inflater.inflate( + R.layout.bubblebar_item_view, barView, false /* attachToRoot */); + + BubbleBarBubble bubble = new BubbleBarBubble(info, bubbleView, + badgeBitmap, bubbleBitmap, dotColor, dotPath, appName); + bubbleView.setBubble(bubble); + return bubble; + } else { + // If we already have a bubble (so it already has an inflated view), update it. + existingBubble.setInfo(info); + existingBubble.setBadge(badgeBitmap); + existingBubble.setIcon(bubbleBitmap); + existingBubble.setDotColor(dotColor); + existingBubble.setDotPath(dotPath); + existingBubble.setAppName(appName); + return existingBubble; + } + } + + /** + * Creates the overflow view shown in the bubble bar. + * + * @param barView the parent view for the bubble (bubble is not added to the view). + */ + public BubbleBarOverflow createOverflow(ViewGroup barView) { + Bitmap bitmap = createOverflowBitmap(); + LayoutInflater inflater = LayoutInflater.from(mContext); + BubbleView bubbleView = (BubbleView) inflater.inflate( + R.layout.bubble_bar_overflow_button, barView, false /* attachToRoot */); + BubbleBarOverflow overflow = new BubbleBarOverflow(bubbleView); + bubbleView.setOverflow(overflow, bitmap); + return overflow; + } + + private Bitmap createOverflowBitmap() { + Drawable iconDrawable = AppCompatResources.getDrawable(mContext, + R.drawable.bubble_ic_overflow_button); + + final TypedArray ta = mContext.obtainStyledAttributes( + new int[]{ + R.attr.materialColorOnPrimaryFixed, + R.attr.materialColorPrimaryFixed + }); + int overflowIconColor = ta.getColor(0, Color.WHITE); + int overflowBackgroundColor = ta.getColor(1, Color.BLACK); + ta.recycle(); + + iconDrawable.setTint(overflowIconColor); + + int inset = mContext.getResources().getDimensionPixelSize(R.dimen.bubblebar_overflow_inset); + Drawable foreground = new InsetDrawable(iconDrawable, inset); + Drawable drawable = new AdaptiveIconDrawable(new ColorDrawable(overflowBackgroundColor), + foreground); + + return mIconFactory.createBadgedIconBitmap(drawable).icon; + } + +}