Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 1 | /* |
| 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 | |
| 17 | package com.android.systemui.bubbles; |
| 18 | |
| 19 | import static com.android.systemui.bubbles.BadgedImageView.DEFAULT_PATH_SIZE; |
| 20 | import static com.android.systemui.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA; |
| 21 | import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES; |
| 22 | import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME; |
| 23 | |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 24 | import android.annotation.NonNull; |
| 25 | import android.app.Notification; |
| 26 | import android.app.Person; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 27 | import android.content.Context; |
Lyn Han | e566b36 | 2020-03-13 17:32:19 -0700 | [diff] [blame] | 28 | import android.content.Intent; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 29 | import android.content.pm.ApplicationInfo; |
| 30 | import android.content.pm.PackageManager; |
| 31 | import android.content.pm.ShortcutInfo; |
| 32 | import android.graphics.Bitmap; |
| 33 | import android.graphics.Color; |
| 34 | import android.graphics.Matrix; |
| 35 | import android.graphics.Path; |
| 36 | import android.graphics.drawable.Drawable; |
Lyn Han | e566b36 | 2020-03-13 17:32:19 -0700 | [diff] [blame] | 37 | import android.graphics.drawable.Icon; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 38 | import android.os.AsyncTask; |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 39 | import android.os.Parcelable; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 40 | import android.service.notification.StatusBarNotification; |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 41 | import android.text.TextUtils; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 42 | import android.util.Log; |
| 43 | import android.util.PathParser; |
| 44 | import android.view.LayoutInflater; |
| 45 | |
| 46 | import androidx.annotation.Nullable; |
| 47 | |
| 48 | import com.android.internal.graphics.ColorUtils; |
| 49 | import com.android.launcher3.icons.BitmapInfo; |
| 50 | import com.android.systemui.R; |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 51 | import com.android.systemui.statusbar.notification.collection.NotificationEntry; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 52 | |
| 53 | import java.lang.ref.WeakReference; |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 54 | import java.util.List; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 55 | |
| 56 | /** |
| 57 | * Simple task to inflate views & load necessary info to display a bubble. |
| 58 | */ |
| 59 | public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask.BubbleViewInfo> { |
| 60 | private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleViewInfoTask" : TAG_BUBBLES; |
| 61 | |
| 62 | |
| 63 | /** |
| 64 | * Callback to find out when the bubble has been inflated & necessary data loaded. |
| 65 | */ |
| 66 | public interface Callback { |
| 67 | /** |
| 68 | * Called when data has been loaded for the bubble. |
| 69 | */ |
| 70 | void onBubbleViewsReady(Bubble bubble); |
| 71 | } |
| 72 | |
| 73 | private Bubble mBubble; |
| 74 | private WeakReference<Context> mContext; |
| 75 | private WeakReference<BubbleStackView> mStackView; |
| 76 | private BubbleIconFactory mIconFactory; |
| 77 | private Callback mCallback; |
| 78 | |
| 79 | /** |
| 80 | * Creates a task to load information for the provided {@link Bubble}. Once all info |
| 81 | * is loaded, {@link Callback} is notified. |
| 82 | */ |
| 83 | BubbleViewInfoTask(Bubble b, |
| 84 | Context context, |
| 85 | BubbleStackView stackView, |
| 86 | BubbleIconFactory factory, |
| 87 | Callback c) { |
| 88 | mBubble = b; |
| 89 | mContext = new WeakReference<>(context); |
| 90 | mStackView = new WeakReference<>(stackView); |
| 91 | mIconFactory = factory; |
| 92 | mCallback = c; |
| 93 | } |
| 94 | |
| 95 | @Override |
| 96 | protected BubbleViewInfo doInBackground(Void... voids) { |
| 97 | return BubbleViewInfo.populate(mContext.get(), mStackView.get(), mIconFactory, mBubble); |
| 98 | } |
| 99 | |
| 100 | @Override |
| 101 | protected void onPostExecute(BubbleViewInfo viewInfo) { |
| 102 | if (viewInfo != null) { |
| 103 | mBubble.setViewInfo(viewInfo); |
| 104 | if (mCallback != null && !isCancelled()) { |
| 105 | mCallback.onBubbleViewsReady(mBubble); |
| 106 | } |
| 107 | } |
| 108 | } |
| 109 | |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 110 | /** |
| 111 | * Info necessary to render a bubble. |
| 112 | */ |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 113 | static class BubbleViewInfo { |
| 114 | BadgedImageView imageView; |
| 115 | BubbleExpandedView expandedView; |
| 116 | ShortcutInfo shortcutInfo; |
| 117 | String appName; |
| 118 | Bitmap badgedBubbleImage; |
Joshua Tsuji | 6855cab | 2020-04-16 01:05:39 -0400 | [diff] [blame] | 119 | Drawable badgedAppIcon; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 120 | int dotColor; |
| 121 | Path dotPath; |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 122 | Bubble.FlyoutMessage flyoutMessage; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 123 | |
| 124 | @Nullable |
| 125 | static BubbleViewInfo populate(Context c, BubbleStackView stackView, |
| 126 | BubbleIconFactory iconFactory, Bubble b) { |
| 127 | BubbleViewInfo info = new BubbleViewInfo(); |
| 128 | |
| 129 | // View inflation: only should do this once per bubble |
| 130 | if (!b.isInflated()) { |
| 131 | LayoutInflater inflater = LayoutInflater.from(c); |
| 132 | info.imageView = (BadgedImageView) inflater.inflate( |
| 133 | R.layout.bubble_view, stackView, false /* attachToRoot */); |
| 134 | |
| 135 | info.expandedView = (BubbleExpandedView) inflater.inflate( |
| 136 | R.layout.bubble_expanded_view, stackView, false /* attachToRoot */); |
| 137 | info.expandedView.setStackView(stackView); |
| 138 | } |
| 139 | |
| 140 | StatusBarNotification sbn = b.getEntry().getSbn(); |
| 141 | String packageName = sbn.getPackageName(); |
| 142 | |
Mady Mellor | 2ac2d3a | 2020-01-08 17:18:54 -0800 | [diff] [blame] | 143 | String bubbleShortcutId = b.getEntry().getBubbleMetadata().getShortcutId(); |
| 144 | if (bubbleShortcutId != null) { |
Mady Mellor | dd6fe61 | 2020-04-15 11:47:55 -0700 | [diff] [blame] | 145 | info.shortcutInfo = b.getEntry().getRanking().getShortcutInfo(); |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 146 | } |
| 147 | |
| 148 | // App name & app icon |
| 149 | PackageManager pm = c.getPackageManager(); |
| 150 | ApplicationInfo appInfo; |
| 151 | Drawable badgedIcon; |
Mady Mellor | 2dae34b | 2020-01-07 13:52:58 -0800 | [diff] [blame] | 152 | Drawable appIcon; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 153 | try { |
| 154 | appInfo = pm.getApplicationInfo( |
| 155 | packageName, |
| 156 | PackageManager.MATCH_UNINSTALLED_PACKAGES |
| 157 | | PackageManager.MATCH_DISABLED_COMPONENTS |
| 158 | | PackageManager.MATCH_DIRECT_BOOT_UNAWARE |
| 159 | | PackageManager.MATCH_DIRECT_BOOT_AWARE); |
| 160 | if (appInfo != null) { |
| 161 | info.appName = String.valueOf(pm.getApplicationLabel(appInfo)); |
| 162 | } |
Mady Mellor | 2dae34b | 2020-01-07 13:52:58 -0800 | [diff] [blame] | 163 | appIcon = pm.getApplicationIcon(packageName); |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 164 | badgedIcon = pm.getUserBadgedIcon(appIcon, sbn.getUser()); |
| 165 | } catch (PackageManager.NameNotFoundException exception) { |
| 166 | // If we can't find package... don't think we should show the bubble. |
| 167 | Log.w(TAG, "Unable to find package: " + packageName); |
| 168 | return null; |
| 169 | } |
| 170 | |
| 171 | // Badged bubble image |
Mady Mellor | 2ac2d3a | 2020-01-08 17:18:54 -0800 | [diff] [blame] | 172 | Drawable bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo, |
| 173 | b.getEntry().getBubbleMetadata()); |
Mady Mellor | 2dae34b | 2020-01-07 13:52:58 -0800 | [diff] [blame] | 174 | if (bubbleDrawable == null) { |
| 175 | // Default to app icon |
| 176 | bubbleDrawable = appIcon; |
| 177 | } |
| 178 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 179 | BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon); |
Joshua Tsuji | 6855cab | 2020-04-16 01:05:39 -0400 | [diff] [blame] | 180 | info.badgedAppIcon = badgedIcon; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 181 | info.badgedBubbleImage = iconFactory.getBubbleBitmap(bubbleDrawable, |
| 182 | badgeBitmapInfo).icon; |
| 183 | |
| 184 | // Dot color & placement |
| 185 | Path iconPath = PathParser.createPathFromPathData( |
| 186 | c.getResources().getString(com.android.internal.R.string.config_icon_mask)); |
| 187 | Matrix matrix = new Matrix(); |
| 188 | float scale = iconFactory.getNormalizer().getScale(bubbleDrawable, |
| 189 | null /* outBounds */, null /* path */, null /* outMaskShape */); |
| 190 | float radius = DEFAULT_PATH_SIZE / 2f; |
| 191 | matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */, |
| 192 | radius /* pivot y */); |
| 193 | iconPath.transform(matrix); |
| 194 | info.dotPath = iconPath; |
| 195 | info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color, |
| 196 | Color.WHITE, WHITE_SCRIM_ALPHA); |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 197 | |
| 198 | // Flyout |
| 199 | info.flyoutMessage = extractFlyoutMessage(c, b.getEntry()); |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 200 | return info; |
| 201 | } |
| 202 | } |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 203 | |
| 204 | |
| 205 | /** |
| 206 | * Returns our best guess for the most relevant text summary of the latest update to this |
| 207 | * notification, based on its type. Returns null if there should not be an update message. |
| 208 | */ |
| 209 | @NonNull |
| 210 | static Bubble.FlyoutMessage extractFlyoutMessage(Context context, |
| 211 | NotificationEntry entry) { |
| 212 | final Notification underlyingNotif = entry.getSbn().getNotification(); |
| 213 | final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle(); |
| 214 | |
| 215 | Bubble.FlyoutMessage bubbleMessage = new Bubble.FlyoutMessage(); |
| 216 | bubbleMessage.isGroupChat = underlyingNotif.extras.getBoolean( |
| 217 | Notification.EXTRA_IS_GROUP_CONVERSATION); |
| 218 | try { |
| 219 | if (Notification.BigTextStyle.class.equals(style)) { |
| 220 | // Return the big text, it is big so probably important. If it's not there use the |
| 221 | // normal text. |
| 222 | CharSequence bigText = |
| 223 | underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT); |
| 224 | bubbleMessage.message = !TextUtils.isEmpty(bigText) |
| 225 | ? bigText |
| 226 | : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT); |
| 227 | return bubbleMessage; |
| 228 | } else if (Notification.MessagingStyle.class.equals(style)) { |
| 229 | final List<Notification.MessagingStyle.Message> messages = |
| 230 | Notification.MessagingStyle.Message.getMessagesFromBundleArray( |
| 231 | (Parcelable[]) underlyingNotif.extras.get( |
| 232 | Notification.EXTRA_MESSAGES)); |
| 233 | |
| 234 | final Notification.MessagingStyle.Message latestMessage = |
| 235 | Notification.MessagingStyle.findLatestIncomingMessage(messages); |
| 236 | if (latestMessage != null) { |
| 237 | bubbleMessage.message = latestMessage.getText(); |
| 238 | Person sender = latestMessage.getSenderPerson(); |
| 239 | bubbleMessage.senderName = sender != null |
| 240 | ? sender.getName() |
| 241 | : null; |
Lyn Han | e566b36 | 2020-03-13 17:32:19 -0700 | [diff] [blame] | 242 | |
| 243 | bubbleMessage.senderAvatar = null; |
| 244 | if (sender != null && sender.getIcon() != null) { |
| 245 | if (sender.getIcon().getType() == Icon.TYPE_URI |
| 246 | || sender.getIcon().getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) { |
| 247 | context.grantUriPermission(context.getPackageName(), |
| 248 | sender.getIcon().getUri(), |
| 249 | Intent.FLAG_GRANT_READ_URI_PERMISSION); |
| 250 | } |
| 251 | bubbleMessage.senderAvatar = sender.getIcon().loadDrawable(context); |
| 252 | } |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 253 | return bubbleMessage; |
| 254 | } |
| 255 | } else if (Notification.InboxStyle.class.equals(style)) { |
| 256 | CharSequence[] lines = |
| 257 | underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES); |
| 258 | |
| 259 | // Return the last line since it should be the most recent. |
| 260 | if (lines != null && lines.length > 0) { |
| 261 | bubbleMessage.message = lines[lines.length - 1]; |
| 262 | return bubbleMessage; |
| 263 | } |
| 264 | } else if (Notification.MediaStyle.class.equals(style)) { |
| 265 | // Return nothing, media updates aren't typically useful as a text update. |
| 266 | return bubbleMessage; |
| 267 | } else { |
| 268 | // Default to text extra. |
| 269 | bubbleMessage.message = |
| 270 | underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT); |
| 271 | return bubbleMessage; |
| 272 | } |
| 273 | } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) { |
| 274 | // No use crashing, we'll just return null and the caller will assume there's no update |
| 275 | // message. |
| 276 | e.printStackTrace(); |
| 277 | } |
| 278 | |
| 279 | return bubbleMessage; |
| 280 | } |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 281 | } |