blob: 525d5b56cc8ece5f36368465739b7d641029a908 [file] [log] [blame]
Mady Mellor3df7ab02019-12-09 15:07:10 -08001/*
2 * Copyright (C) 2019 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
17package com.android.systemui.bubbles;
18
19import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE;
20import static com.android.systemui.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA;
21import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
22import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
23
Mady Mellordf898fd2020-01-09 09:26:36 -080024import android.annotation.NonNull;
25import android.app.Notification;
26import android.app.Person;
Mady Mellor3df7ab02019-12-09 15:07:10 -080027import android.content.Context;
Lyn Hane566b362020-03-13 17:32:19 -070028import android.content.Intent;
Mady Mellor3df7ab02019-12-09 15:07:10 -080029import android.content.pm.ApplicationInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.ShortcutInfo;
32import android.graphics.Bitmap;
33import android.graphics.Color;
34import android.graphics.Matrix;
35import android.graphics.Path;
36import android.graphics.drawable.Drawable;
Lyn Hane566b362020-03-13 17:32:19 -070037import android.graphics.drawable.Icon;
Mady Mellor3df7ab02019-12-09 15:07:10 -080038import android.os.AsyncTask;
Mady Mellordf898fd2020-01-09 09:26:36 -080039import android.os.Parcelable;
Pinyao Ting293b83d2020-05-06 17:10:56 -070040import android.os.UserHandle;
Mady Mellor3df7ab02019-12-09 15:07:10 -080041import android.service.notification.StatusBarNotification;
Mady Mellordf898fd2020-01-09 09:26:36 -080042import android.text.TextUtils;
Mady Mellor3df7ab02019-12-09 15:07:10 -080043import android.util.Log;
44import android.util.PathParser;
45import android.view.LayoutInflater;
46
47import androidx.annotation.Nullable;
48
49import com.android.internal.graphics.ColorUtils;
50import com.android.launcher3.icons.BitmapInfo;
51import com.android.systemui.R;
Mady Mellordf898fd2020-01-09 09:26:36 -080052import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor3df7ab02019-12-09 15:07:10 -080053
54import java.lang.ref.WeakReference;
Mady Mellordf898fd2020-01-09 09:26:36 -080055import java.util.List;
Mady Mellor3df7ab02019-12-09 15:07:10 -080056
57/**
58 * Simple task to inflate views & load necessary info to display a bubble.
59 */
60public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask.BubbleViewInfo> {
61 private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleViewInfoTask" : TAG_BUBBLES;
62
63
64 /**
65 * Callback to find out when the bubble has been inflated & necessary data loaded.
66 */
67 public interface Callback {
68 /**
69 * Called when data has been loaded for the bubble.
70 */
71 void onBubbleViewsReady(Bubble bubble);
72 }
73
74 private Bubble mBubble;
75 private WeakReference<Context> mContext;
76 private WeakReference<BubbleStackView> mStackView;
77 private BubbleIconFactory mIconFactory;
Pinyao Ting293b83d2020-05-06 17:10:56 -070078 private boolean mSkipInflation;
Mady Mellor3df7ab02019-12-09 15:07:10 -080079 private Callback mCallback;
80
81 /**
82 * Creates a task to load information for the provided {@link Bubble}. Once all info
83 * is loaded, {@link Callback} is notified.
84 */
85 BubbleViewInfoTask(Bubble b,
86 Context context,
87 BubbleStackView stackView,
88 BubbleIconFactory factory,
Pinyao Ting293b83d2020-05-06 17:10:56 -070089 boolean skipInflation,
Mady Mellor3df7ab02019-12-09 15:07:10 -080090 Callback c) {
91 mBubble = b;
92 mContext = new WeakReference<>(context);
93 mStackView = new WeakReference<>(stackView);
94 mIconFactory = factory;
Pinyao Ting293b83d2020-05-06 17:10:56 -070095 mSkipInflation = skipInflation;
Mady Mellor3df7ab02019-12-09 15:07:10 -080096 mCallback = c;
97 }
98
99 @Override
100 protected BubbleViewInfo doInBackground(Void... voids) {
Pinyao Ting293b83d2020-05-06 17:10:56 -0700101 return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble,
102 mSkipInflation);
Mady Mellor3df7ab02019-12-09 15:07:10 -0800103 }
104
105 @Override
106 protected void onPostExecute(BubbleViewInfo viewInfo) {
107 if (viewInfo != null) {
108 mBubble.setViewInfo(viewInfo);
109 if (mCallback != null && !isCancelled()) {
110 mCallback.onBubbleViewsReady(mBubble);
111 }
112 }
113 }
114
Mady Mellordf898fd2020-01-09 09:26:36 -0800115 /**
116 * Info necessary to render a bubble.
117 */
Mady Mellor3df7ab02019-12-09 15:07:10 -0800118 static class BubbleViewInfo {
119 BadgedImageView imageView;
120 BubbleExpandedView expandedView;
121 ShortcutInfo shortcutInfo;
122 String appName;
123 Bitmap badgedBubbleImage;
Joshua Tsuji6855cab2020-04-16 01:05:39 -0400124 Drawable badgedAppIcon;
Mady Mellor3df7ab02019-12-09 15:07:10 -0800125 int dotColor;
126 Path dotPath;
Mady Mellordf898fd2020-01-09 09:26:36 -0800127 Bubble.FlyoutMessage flyoutMessage;
Mady Mellor3df7ab02019-12-09 15:07:10 -0800128
129 @Nullable
130 static BubbleViewInfo populate(Context c, BubbleStackView stackView,
Pinyao Ting293b83d2020-05-06 17:10:56 -0700131 BubbleIconFactory iconFactory, Bubble b, boolean skipInflation) {
132 final NotificationEntry entry = b.getEntry();
133 if (entry == null) {
134 // populate from ShortcutInfo when NotificationEntry is not available
135 final ShortcutInfo s = b.getShortcutInfo();
136 return populate(c, stackView, iconFactory, skipInflation || b.isInflated(),
137 s.getPackage(), s.getUserHandle(), s, null);
138 }
139 final StatusBarNotification sbn = entry.getSbn();
140 final String bubbleShortcutId = entry.getBubbleMetadata().getShortcutId();
141 final ShortcutInfo si = bubbleShortcutId == null
142 ? null : entry.getRanking().getShortcutInfo();
143 return populate(
144 c, stackView, iconFactory, skipInflation || b.isInflated(),
145 sbn.getPackageName(), sbn.getUser(), si, entry);
146 }
147
148 private static BubbleViewInfo populate(
149 @NonNull final Context c,
150 @NonNull final BubbleStackView stackView,
151 @NonNull final BubbleIconFactory iconFactory,
152 final boolean isInflated,
153 @NonNull final String packageName,
154 @NonNull final UserHandle user,
155 @Nullable final ShortcutInfo shortcutInfo,
156 @Nullable final NotificationEntry entry) {
Mady Mellor3df7ab02019-12-09 15:07:10 -0800157 BubbleViewInfo info = new BubbleViewInfo();
158
159 // View inflation: only should do this once per bubble
Pinyao Ting293b83d2020-05-06 17:10:56 -0700160 if (!isInflated) {
Mady Mellor3df7ab02019-12-09 15:07:10 -0800161 LayoutInflater inflater = LayoutInflater.from(c);
162 info.imageView = (BadgedImageView) inflater.inflate(
163 R.layout.bubble_view, stackView, false /* attachToRoot */);
164
165 info.expandedView = (BubbleExpandedView) inflater.inflate(
166 R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
167 info.expandedView.setStackView(stackView);
168 }
169
Pinyao Ting293b83d2020-05-06 17:10:56 -0700170 if (shortcutInfo != null) {
171 info.shortcutInfo = shortcutInfo;
Mady Mellor3df7ab02019-12-09 15:07:10 -0800172 }
173
174 // App name & app icon
175 PackageManager pm = c.getPackageManager();
176 ApplicationInfo appInfo;
177 Drawable badgedIcon;
Mady Mellor2dae34b2020-01-07 13:52:58 -0800178 Drawable appIcon;
Mady Mellor3df7ab02019-12-09 15:07:10 -0800179 try {
180 appInfo = pm.getApplicationInfo(
181 packageName,
182 PackageManager.MATCH_UNINSTALLED_PACKAGES
183 | PackageManager.MATCH_DISABLED_COMPONENTS
184 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
185 | PackageManager.MATCH_DIRECT_BOOT_AWARE);
186 if (appInfo != null) {
187 info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
188 }
Mady Mellor2dae34b2020-01-07 13:52:58 -0800189 appIcon = pm.getApplicationIcon(packageName);
Pinyao Ting293b83d2020-05-06 17:10:56 -0700190 badgedIcon = pm.getUserBadgedIcon(appIcon, user);
Mady Mellor3df7ab02019-12-09 15:07:10 -0800191 } catch (PackageManager.NameNotFoundException exception) {
192 // If we can't find package... don't think we should show the bubble.
193 Log.w(TAG, "Unable to find package: " + packageName);
194 return null;
195 }
196
197 // Badged bubble image
Mady Mellor2ac2d3a2020-01-08 17:18:54 -0800198 Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
Pinyao Ting293b83d2020-05-06 17:10:56 -0700199 entry == null ? null : entry.getBubbleMetadata());
Mady Mellor2dae34b2020-01-07 13:52:58 -0800200 if (bubbleDrawable == null) {
201 // Default to app icon
202 bubbleDrawable = appIcon;
203 }
204
Mady Mellor3df7ab02019-12-09 15:07:10 -0800205 BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon);
Joshua Tsuji6855cab2020-04-16 01:05:39 -0400206 info.badgedAppIcon = badgedIcon;
Mady Mellor3df7ab02019-12-09 15:07:10 -0800207 info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable,
208 badgeBitmapInfo).icon;
209
210 // Dot color & placement
211 Path iconPath = PathParser.createPathFromPathData(
212 c.getResources().getString(com.android.internal.R.string.config_icon_mask));
213 Matrix matrix = new Matrix();
214 float scale = iconFactory.getNormalizer().getScale(bubbleDrawable,
215 null /* outBounds */, null /* path */, null /* outMaskShape */);
216 float radius = DEFAULT_PATH_SIZE / 2f;
217 matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
218 radius /* pivot y */);
219 iconPath.transform(matrix);
220 info.dotPath = iconPath;
221 info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
222 Color.WHITE, WHITE_SCRIM_ALPHA);
Mady Mellordf898fd2020-01-09 09:26:36 -0800223
224 // Flyout
Pinyao Ting293b83d2020-05-06 17:10:56 -0700225 if (entry != null) {
226 info.flyoutMessage = extractFlyoutMessage(c, entry);
227 }
Mady Mellor3df7ab02019-12-09 15:07:10 -0800228 return info;
229 }
230 }
Mady Mellordf898fd2020-01-09 09:26:36 -0800231
232
233 /**
234 * Returns our best guess for the most relevant text summary of the latest update to this
235 * notification, based on its type. Returns null if there should not be an update message.
236 */
237 @NonNull
238 static Bubble.FlyoutMessage extractFlyoutMessage(Context context,
239 NotificationEntry entry) {
240 final Notification underlyingNotif = entry.getSbn().getNotification();
241 final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle();
242
243 Bubble.FlyoutMessage bubbleMessage = new Bubble.FlyoutMessage();
244 bubbleMessage.isGroupChat = underlyingNotif.extras.getBoolean(
245 Notification.EXTRA_IS_GROUP_CONVERSATION);
246 try {
247 if (Notification.BigTextStyle.class.equals(style)) {
248 // Return the big text, it is big so probably important. If it's not there use the
249 // normal text.
250 CharSequence bigText =
251 underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT);
252 bubbleMessage.message = !TextUtils.isEmpty(bigText)
253 ? bigText
254 : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
255 return bubbleMessage;
256 } else if (Notification.MessagingStyle.class.equals(style)) {
257 final List<Notification.MessagingStyle.Message> messages =
258 Notification.MessagingStyle.Message.getMessagesFromBundleArray(
259 (Parcelable[]) underlyingNotif.extras.get(
260 Notification.EXTRA_MESSAGES));
261
262 final Notification.MessagingStyle.Message latestMessage =
263 Notification.MessagingStyle.findLatestIncomingMessage(messages);
264 if (latestMessage != null) {
265 bubbleMessage.message = latestMessage.getText();
266 Person sender = latestMessage.getSenderPerson();
267 bubbleMessage.senderName = sender != null
268 ? sender.getName()
269 : null;
Lyn Hane566b362020-03-13 17:32:19 -0700270
271 bubbleMessage.senderAvatar = null;
272 if (sender != null && sender.getIcon() != null) {
273 if (sender.getIcon().getType() == Icon.TYPE_URI
274 || sender.getIcon().getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
275 context.grantUriPermission(context.getPackageName(),
276 sender.getIcon().getUri(),
277 Intent.FLAG_GRANT_READ_URI_PERMISSION);
278 }
279 bubbleMessage.senderAvatar = sender.getIcon().loadDrawable(context);
280 }
Mady Mellordf898fd2020-01-09 09:26:36 -0800281 return bubbleMessage;
282 }
283 } else if (Notification.InboxStyle.class.equals(style)) {
284 CharSequence[] lines =
285 underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES);
286
287 // Return the last line since it should be the most recent.
288 if (lines != null && lines.length > 0) {
289 bubbleMessage.message = lines[lines.length - 1];
290 return bubbleMessage;
291 }
292 } else if (Notification.MediaStyle.class.equals(style)) {
293 // Return nothing, media updates aren't typically useful as a text update.
294 return bubbleMessage;
295 } else {
296 // Default to text extra.
297 bubbleMessage.message =
298 underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT);
299 return bubbleMessage;
300 }
301 } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) {
302 // No use crashing, we'll just return null and the caller will assume there's no update
303 // message.
304 e.printStackTrace();
305 }
306
307 return bubbleMessage;
308 }
Mady Mellor3df7ab02019-12-09 15:07:10 -0800309}