• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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