• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2020 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.wm.shell.bubbles;
18 
19 import static com.android.wm.shell.bubbles.BadgedImageView.DEFAULT_PATH_SIZE;
20 import static com.android.wm.shell.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA;
21 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
22 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
23 
24 import android.annotation.NonNull;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.pm.ApplicationInfo;
28 import android.content.pm.PackageManager;
29 import android.content.pm.ShortcutInfo;
30 import android.graphics.Bitmap;
31 import android.graphics.Color;
32 import android.graphics.Matrix;
33 import android.graphics.Path;
34 import android.graphics.drawable.Drawable;
35 import android.graphics.drawable.Icon;
36 import android.os.AsyncTask;
37 import android.util.Log;
38 import android.util.PathParser;
39 import android.view.LayoutInflater;
40 
41 import androidx.annotation.Nullable;
42 import androidx.annotation.VisibleForTesting;
43 
44 import com.android.internal.graphics.ColorUtils;
45 import com.android.launcher3.icons.BitmapInfo;
46 import com.android.wm.shell.R;
47 
48 import java.lang.ref.WeakReference;
49 import java.util.Objects;
50 import java.util.concurrent.Executor;
51 
52 /**
53  * Simple task to inflate views & load necessary info to display a bubble.
54  */
55 public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask.BubbleViewInfo> {
56     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleViewInfoTask" : TAG_BUBBLES;
57 
58 
59     /**
60      * Callback to find out when the bubble has been inflated & necessary data loaded.
61      */
62     public interface Callback {
63         /**
64          * Called when data has been loaded for the bubble.
65          */
onBubbleViewsReady(Bubble bubble)66         void onBubbleViewsReady(Bubble bubble);
67     }
68 
69     private Bubble mBubble;
70     private WeakReference<Context> mContext;
71     private WeakReference<BubbleController> mController;
72     private WeakReference<BubbleStackView> mStackView;
73     private BubbleIconFactory mIconFactory;
74     private boolean mSkipInflation;
75     private Callback mCallback;
76     private Executor mMainExecutor;
77 
78     /**
79      * Creates a task to load information for the provided {@link Bubble}. Once all info
80      * is loaded, {@link Callback} is notified.
81      */
BubbleViewInfoTask(Bubble b, Context context, BubbleController controller, BubbleStackView stackView, BubbleIconFactory factory, boolean skipInflation, Callback c, Executor mainExecutor)82     BubbleViewInfoTask(Bubble b,
83             Context context,
84             BubbleController controller,
85             BubbleStackView stackView,
86             BubbleIconFactory factory,
87             boolean skipInflation,
88             Callback c,
89             Executor mainExecutor) {
90         mBubble = b;
91         mContext = new WeakReference<>(context);
92         mController = new WeakReference<>(controller);
93         mStackView = new WeakReference<>(stackView);
94         mIconFactory = factory;
95         mSkipInflation = skipInflation;
96         mCallback = c;
97         mMainExecutor = mainExecutor;
98     }
99 
100     @Override
doInBackground(Void... voids)101     protected BubbleViewInfo doInBackground(Void... voids) {
102         return BubbleViewInfo.populate(mContext.get(), mController.get(), mStackView.get(),
103                 mIconFactory, mBubble, mSkipInflation);
104     }
105 
106     @Override
onPostExecute(BubbleViewInfo viewInfo)107     protected void onPostExecute(BubbleViewInfo viewInfo) {
108         if (isCancelled() || viewInfo == null) {
109             return;
110         }
111         mMainExecutor.execute(() -> {
112             mBubble.setViewInfo(viewInfo);
113             if (mCallback != null) {
114                 mCallback.onBubbleViewsReady(mBubble);
115             }
116         });
117     }
118 
119     /**
120      * Info necessary to render a bubble.
121      */
122     @VisibleForTesting
123     public static class BubbleViewInfo {
124         BadgedImageView imageView;
125         BubbleExpandedView expandedView;
126         ShortcutInfo shortcutInfo;
127         String appName;
128         Bitmap bubbleBitmap;
129         Bitmap badgeBitmap;
130         int dotColor;
131         Path dotPath;
132         Bubble.FlyoutMessage flyoutMessage;
133 
134         @VisibleForTesting
135         @Nullable
populate(Context c, BubbleController controller, BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b, boolean skipInflation)136         public static BubbleViewInfo populate(Context c, BubbleController controller,
137                 BubbleStackView stackView, BubbleIconFactory iconFactory, Bubble b,
138                 boolean skipInflation) {
139             BubbleViewInfo info = new BubbleViewInfo();
140 
141             // View inflation: only should do this once per bubble
142             if (!skipInflation && !b.isInflated()) {
143                 LayoutInflater inflater = LayoutInflater.from(c);
144                 info.imageView = (BadgedImageView) inflater.inflate(
145                         R.layout.bubble_view, stackView, false /* attachToRoot */);
146                 info.imageView.initialize(controller.getPositioner());
147 
148                 info.expandedView = (BubbleExpandedView) inflater.inflate(
149                         R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
150                 info.expandedView.initialize(controller, stackView, false /* isOverflow */);
151             }
152 
153             if (b.getShortcutInfo() != null) {
154                 info.shortcutInfo = b.getShortcutInfo();
155             }
156 
157             // App name & app icon
158             PackageManager pm = BubbleController.getPackageManagerForUser(c,
159                     b.getUser().getIdentifier());
160             ApplicationInfo appInfo;
161             Drawable badgedIcon;
162             Drawable appIcon;
163             try {
164                 appInfo = pm.getApplicationInfo(
165                         b.getPackageName(),
166                         PackageManager.MATCH_UNINSTALLED_PACKAGES
167                                 | PackageManager.MATCH_DISABLED_COMPONENTS
168                                 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
169                                 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
170                 if (appInfo != null) {
171                     info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
172                 }
173                 appIcon = pm.getApplicationIcon(b.getPackageName());
174                 badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
175             } catch (PackageManager.NameNotFoundException exception) {
176                 // If we can't find package... don't think we should show the bubble.
177                 Log.w(TAG, "Unable to find package: " + b.getPackageName());
178                 return null;
179             }
180 
181             // Badged bubble image
182             Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
183                     b.getIcon());
184             if (bubbleDrawable == null) {
185                 // Default to app icon
186                 bubbleDrawable = appIcon;
187             }
188 
189             BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
190                     b.isImportantConversation());
191             info.badgeBitmap = badgeBitmapInfo.icon;
192             info.bubbleBitmap = iconFactory.createBadgedIconBitmap(bubbleDrawable,
193                     null /* user */,
194                     true /* shrinkNonAdaptiveIcons */).icon;
195 
196             // Dot color & placement
197             Path iconPath = PathParser.createPathFromPathData(
198                     c.getResources().getString(com.android.internal.R.string.config_icon_mask));
199             Matrix matrix = new Matrix();
200             float scale = iconFactory.getNormalizer().getScale(bubbleDrawable,
201                     null /* outBounds */, null /* path */, null /* outMaskShape */);
202             float radius = DEFAULT_PATH_SIZE / 2f;
203             matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
204                     radius /* pivot y */);
205             iconPath.transform(matrix);
206             info.dotPath = iconPath;
207             info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
208                     Color.WHITE, WHITE_SCRIM_ALPHA);
209 
210             // Flyout
211             info.flyoutMessage = b.getFlyoutMessage();
212             if (info.flyoutMessage != null) {
213                 info.flyoutMessage.senderAvatar =
214                         loadSenderAvatar(c, info.flyoutMessage.senderIcon);
215             }
216             return info;
217         }
218     }
219 
220     @Nullable
loadSenderAvatar(@onNull final Context context, @Nullable final Icon icon)221     static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) {
222         Objects.requireNonNull(context);
223         if (icon == null) return null;
224         try {
225             if (icon.getType() == Icon.TYPE_URI
226                     || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
227                 context.grantUriPermission(context.getPackageName(),
228                         icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION);
229             }
230             return icon.loadDrawable(context);
231         } catch (Exception e) {
232             Log.w(TAG, "loadSenderAvatar failed: " + e.getMessage());
233             return null;
234         }
235     }
236 }
237