1 /* 2 * Copyright (C) 2024 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.launcher3.taskbar.bubbles; 18 19 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_GET_PERSONS_DATA; 20 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_CACHED; 21 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC; 22 import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED_BY_ANY_LAUNCHER; 23 24 import static com.android.launcher3.icons.FastBitmapDrawable.WHITE_SCRIM_ALPHA; 25 import static com.android.wm.shell.shared.bubbles.FlyoutDrawableLoader.loadFlyoutDrawable; 26 27 import android.annotation.Nullable; 28 import android.content.Context; 29 import android.content.pm.ApplicationInfo; 30 import android.content.pm.LauncherApps; 31 import android.content.pm.PackageManager; 32 import android.content.pm.ShortcutInfo; 33 import android.graphics.Bitmap; 34 import android.graphics.Color; 35 import android.graphics.Matrix; 36 import android.graphics.Path; 37 import android.graphics.drawable.AdaptiveIconDrawable; 38 import android.graphics.drawable.ColorDrawable; 39 import android.graphics.drawable.Drawable; 40 import android.graphics.drawable.InsetDrawable; 41 import android.os.UserHandle; 42 import android.util.Log; 43 import android.util.PathParser; 44 import android.view.LayoutInflater; 45 import android.view.ViewGroup; 46 47 import com.android.internal.graphics.ColorUtils; 48 import com.android.launcher3.R; 49 import com.android.launcher3.icons.BitmapInfo; 50 import com.android.launcher3.icons.BubbleIconFactory; 51 import com.android.launcher3.shortcuts.ShortcutRequest; 52 import com.android.launcher3.taskbar.bubbles.flyout.BubbleBarFlyoutMessage; 53 import com.android.wm.shell.shared.bubbles.BubbleInfo; 54 import com.android.wm.shell.shared.bubbles.ParcelableFlyoutMessage; 55 56 /** 57 * Loads the necessary info to populate / present a bubble (name, icon, shortcut). 58 */ 59 public class BubbleCreator { 60 61 private static final String TAG = BubbleCreator.class.getSimpleName(); 62 63 private final Context mContext; 64 private final LauncherApps mLauncherApps; 65 private final BubbleIconFactory mIconFactory; 66 BubbleCreator(Context context)67 public BubbleCreator(Context context) { 68 mContext = context; 69 mLauncherApps = mContext.getSystemService(LauncherApps.class); 70 mIconFactory = new BubbleIconFactory(context, 71 context.getResources().getDimensionPixelSize(R.dimen.bubblebar_icon_size), 72 context.getResources().getDimensionPixelSize(R.dimen.bubblebar_badge_size), 73 context.getResources().getColor(R.color.important_conversation), 74 context.getResources().getDimensionPixelSize( 75 com.android.internal.R.dimen.importance_ring_stroke_width)); 76 } 77 78 /** 79 * Creates a BubbleBarBubble object, including the view if needed, and populates it with 80 * the info needed for presentation. 81 * 82 * @param context the context to use for inflation. 83 * @param info the info to use to populate the bubble. 84 * @param barView the parent view for the bubble (bubble is not added to the view). 85 * @param existingBubble if a bubble exists already, this object gets updated with the new 86 * info & returned (& any existing views are reused instead of inflating 87 * new ones. 88 */ 89 @Nullable populateBubble(Context context, BubbleInfo info, ViewGroup barView, @Nullable BubbleBarBubble existingBubble)90 public BubbleBarBubble populateBubble(Context context, BubbleInfo info, ViewGroup barView, 91 @Nullable BubbleBarBubble existingBubble) { 92 String appName; 93 Bitmap badgeBitmap; 94 Bitmap bubbleBitmap; 95 Path dotPath; 96 int dotColor; 97 98 boolean isImportantConvo = info.isImportantConversation(); 99 100 ShortcutRequest.QueryResult result = new ShortcutRequest(context, 101 new UserHandle(info.getUserId())) 102 .forPackage(info.getPackageName(), info.getShortcutId()) 103 .query(FLAG_MATCH_DYNAMIC 104 | FLAG_MATCH_PINNED_BY_ANY_LAUNCHER 105 | FLAG_MATCH_CACHED 106 | FLAG_GET_PERSONS_DATA); 107 108 ShortcutInfo shortcutInfo = result.size() > 0 ? result.get(0) : null; 109 if (shortcutInfo == null) { 110 Log.w(TAG, "No shortcutInfo found for bubble: " + info.getKey() 111 + " with shortcutId: " + info.getShortcutId()); 112 } 113 114 ApplicationInfo appInfo; 115 try { 116 appInfo = mLauncherApps.getApplicationInfo( 117 info.getPackageName(), 118 0, 119 new UserHandle(info.getUserId())); 120 } catch (PackageManager.NameNotFoundException e) { 121 // If we can't find package... don't think we should show the bubble. 122 Log.w(TAG, "Unable to find packageName: " + info.getPackageName()); 123 return null; 124 } 125 if (appInfo == null) { 126 Log.w(TAG, "Unable to find appInfo: " + info.getPackageName()); 127 return null; 128 } 129 PackageManager pm = context.getPackageManager(); 130 appName = String.valueOf(appInfo.loadLabel(pm)); 131 Drawable appIcon = appInfo.loadUnbadgedIcon(pm); 132 Drawable badgedIcon = pm.getUserBadgedIcon(appIcon, new UserHandle(info.getUserId())); 133 134 // Badged bubble image 135 Drawable bubbleDrawable = mIconFactory.getBubbleDrawable(context, shortcutInfo, 136 info.getIcon()); 137 if (bubbleDrawable == null) { 138 // Default to app icon 139 bubbleDrawable = appIcon; 140 } 141 142 BitmapInfo badgeBitmapInfo = mIconFactory.getBadgeBitmap(badgedIcon, isImportantConvo); 143 badgeBitmap = badgeBitmapInfo.icon; 144 145 float[] bubbleBitmapScale = new float[1]; 146 bubbleBitmap = mIconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale); 147 148 // Dot color & placement 149 Path iconPath = PathParser.createPathFromPathData( 150 context.getResources().getString( 151 com.android.internal.R.string.config_icon_mask)); 152 Matrix matrix = new Matrix(); 153 float scale = bubbleBitmapScale[0]; 154 float radius = BubbleView.DEFAULT_PATH_SIZE / 2f; 155 matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, 156 radius /* pivot y */); 157 iconPath.transform(matrix); 158 dotPath = iconPath; 159 dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, 160 Color.WHITE, WHITE_SCRIM_ALPHA / 255f); 161 162 final BubbleBarFlyoutMessage flyoutMessage = 163 getFlyoutMessage(info.getParcelableFlyoutMessage()); 164 165 if (existingBubble == null) { 166 LayoutInflater inflater = LayoutInflater.from(context); 167 BubbleView bubbleView = (BubbleView) inflater.inflate( 168 R.layout.bubblebar_item_view, barView, false /* attachToRoot */); 169 170 BubbleBarBubble bubble = new BubbleBarBubble(info, bubbleView, 171 badgeBitmap, bubbleBitmap, dotColor, dotPath, appName, flyoutMessage); 172 bubbleView.setBubble(bubble); 173 return bubble; 174 } else { 175 // If we already have a bubble (so it already has an inflated view), update it. 176 existingBubble.setInfo(info); 177 existingBubble.setBadge(badgeBitmap); 178 existingBubble.setIcon(bubbleBitmap); 179 existingBubble.setDotColor(dotColor); 180 existingBubble.setDotPath(dotPath); 181 existingBubble.setAppName(appName); 182 existingBubble.setFlyoutMessage(flyoutMessage); 183 return existingBubble; 184 } 185 } 186 187 @Nullable getFlyoutMessage( @ullable ParcelableFlyoutMessage parcelableFlyoutMessage)188 private BubbleBarFlyoutMessage getFlyoutMessage( 189 @Nullable ParcelableFlyoutMessage parcelableFlyoutMessage) { 190 if (parcelableFlyoutMessage == null) { 191 return null; 192 } 193 String title = parcelableFlyoutMessage.getTitle(); 194 String message = parcelableFlyoutMessage.getMessage(); 195 return new BubbleBarFlyoutMessage( 196 loadFlyoutDrawable(parcelableFlyoutMessage.getIcon(), mContext), 197 title == null ? "" : title, 198 message == null ? "" : message); 199 } 200 201 /** 202 * Creates the overflow view shown in the bubble bar. 203 * 204 * @param barView the parent view for the bubble (bubble is not added to the view). 205 */ createOverflow(ViewGroup barView)206 public BubbleBarOverflow createOverflow(ViewGroup barView) { 207 Bitmap bitmap = createOverflowBitmap(); 208 LayoutInflater inflater = LayoutInflater.from(mContext); 209 BubbleView bubbleView = (BubbleView) inflater.inflate( 210 R.layout.bubble_bar_overflow_button, barView, false /* attachToRoot */); 211 BubbleBarOverflow overflow = new BubbleBarOverflow(bubbleView); 212 bubbleView.setOverflow(overflow, bitmap); 213 return overflow; 214 } 215 createOverflowBitmap()216 private Bitmap createOverflowBitmap() { 217 Drawable iconDrawable = mContext.getDrawable(R.drawable.bubble_ic_overflow_button); 218 219 int overflowIconColor = mContext.getColor(R.color.materialColorOnPrimaryFixed); 220 int overflowBackgroundColor = mContext.getColor(R.color.materialColorPrimaryFixed); 221 222 iconDrawable.setTint(overflowIconColor); 223 224 int inset = mContext.getResources().getDimensionPixelSize(R.dimen.bubblebar_overflow_inset); 225 Drawable foreground = new InsetDrawable(iconDrawable, inset); 226 Drawable drawable = new AdaptiveIconDrawable(new ColorDrawable(overflowBackgroundColor), 227 foreground); 228 229 return mIconFactory.createBadgedIconBitmap(drawable).icon; 230 } 231 232 } 233