Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -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 | package com.android.systemui.bubbles; |
| 17 | |
| 18 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 19 | import static android.os.AsyncTask.Status.FINISHED; |
Issei Suzuki | cac2a50 | 2019-04-16 16:52:50 +0200 | [diff] [blame] | 20 | import static android.view.Display.INVALID_DISPLAY; |
| 21 | |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 22 | import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; |
| 23 | |
Mady Mellor | 70cba7bb | 2019-07-02 15:06:07 -0700 | [diff] [blame] | 24 | import android.annotation.NonNull; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 25 | import android.annotation.Nullable; |
| 26 | import android.app.Notification; |
| 27 | import android.app.PendingIntent; |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 28 | import android.content.Context; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 29 | import android.content.Intent; |
Mady Mellor | b547e5e | 2019-12-02 10:15:56 -0800 | [diff] [blame] | 30 | import android.content.pm.LauncherApps; |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 31 | import android.content.pm.PackageManager; |
Mady Mellor | b547e5e | 2019-12-02 10:15:56 -0800 | [diff] [blame] | 32 | import android.content.pm.ShortcutInfo; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 33 | import android.content.res.Resources; |
Lyn Han | b58c756 | 2020-01-07 14:29:20 -0800 | [diff] [blame] | 34 | import android.graphics.Bitmap; |
| 35 | import android.graphics.Path; |
Mady Mellor | b547e5e | 2019-12-02 10:15:56 -0800 | [diff] [blame] | 36 | import android.graphics.Rect; |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 37 | import android.graphics.drawable.Drawable; |
Mady Mellor | b547e5e | 2019-12-02 10:15:56 -0800 | [diff] [blame] | 38 | import android.os.Bundle; |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 39 | import android.os.UserHandle; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 40 | import android.provider.Settings; |
Lyn Han | 3cd75d7 | 2020-02-15 19:10:12 -0800 | [diff] [blame] | 41 | import android.service.notification.StatusBarNotification; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 42 | import android.util.Log; |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 43 | |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 44 | import com.android.internal.annotations.VisibleForTesting; |
Lyn Han | 3cd75d7 | 2020-02-15 19:10:12 -0800 | [diff] [blame] | 45 | import com.android.systemui.shared.system.SysUiStatsLog; |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 46 | import com.android.systemui.statusbar.notification.collection.NotificationEntry; |
| 47 | |
Mady Mellor | 70cba7bb | 2019-07-02 15:06:07 -0700 | [diff] [blame] | 48 | import java.io.FileDescriptor; |
| 49 | import java.io.PrintWriter; |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 50 | import java.util.Objects; |
| 51 | |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 52 | /** |
| 53 | * Encapsulates the data and UI elements of a bubble. |
| 54 | */ |
Lyn Han | 3cd75d7 | 2020-02-15 19:10:12 -0800 | [diff] [blame] | 55 | class Bubble implements BubbleViewProvider { |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 56 | private static final String TAG = "Bubble"; |
| 57 | |
| 58 | private NotificationEntry mEntry; |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 59 | private final String mKey; |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 60 | private final String mGroupId; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 61 | |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 62 | private long mLastUpdated; |
| 63 | private long mLastAccessed; |
Mady Mellor | 7f23490 | 2019-10-20 12:06:29 -0700 | [diff] [blame] | 64 | |
Mady Mellor | f44b683 | 2020-01-14 13:26:14 -0800 | [diff] [blame] | 65 | private BubbleController.NotificationSuppressionChangedListener mSuppressionListener; |
| 66 | |
| 67 | /** Whether the bubble should show a dot for the notification indicating updated content. */ |
| 68 | private boolean mShowBubbleUpdateDot = true; |
| 69 | |
| 70 | /** Whether flyout text should be suppressed, regardless of any other flags or state. */ |
| 71 | private boolean mSuppressFlyout; |
| 72 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 73 | // Items that are typically loaded later |
| 74 | private String mAppName; |
| 75 | private ShortcutInfo mShortcutInfo; |
| 76 | private BadgedImageView mIconView; |
| 77 | private BubbleExpandedView mExpandedView; |
| 78 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 79 | private BubbleViewInfoTask mInflationTask; |
| 80 | private boolean mInflateSynchronously; |
Lyn Han | 6cb4e5f | 2020-04-27 15:11:18 -0700 | [diff] [blame] | 81 | private boolean mPendingIntentCanceled; |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 82 | |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 83 | /** |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 84 | * Presentational info about the flyout. |
| 85 | */ |
| 86 | public static class FlyoutMessage { |
| 87 | @Nullable public Drawable senderAvatar; |
| 88 | @Nullable public CharSequence senderName; |
| 89 | @Nullable public CharSequence message; |
| 90 | @Nullable public boolean isGroupChat; |
| 91 | } |
| 92 | |
| 93 | private FlyoutMessage mFlyoutMessage; |
Joshua Tsuji | 6855cab | 2020-04-16 01:05:39 -0400 | [diff] [blame] | 94 | private Drawable mBadgedAppIcon; |
Lyn Han | b58c756 | 2020-01-07 14:29:20 -0800 | [diff] [blame] | 95 | private Bitmap mBadgedImage; |
| 96 | private int mDotColor; |
| 97 | private Path mDotPath; |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 98 | |
Lyn Han | cd4f87e | 2020-02-19 20:33:45 -0800 | [diff] [blame] | 99 | |
Mark Renouf | ba5ab51 | 2019-05-02 15:21:01 -0400 | [diff] [blame] | 100 | public static String groupId(NotificationEntry entry) { |
Ned Burns | 00b4b2d | 2019-10-17 22:09:27 -0400 | [diff] [blame] | 101 | UserHandle user = entry.getSbn().getUser(); |
| 102 | return user.getIdentifier() + "|" + entry.getSbn().getPackageName(); |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 103 | } |
| 104 | |
Pinyao Ting | d4c1ba9 | 2020-05-04 20:29:56 -0700 | [diff] [blame^] | 105 | // TODO: Decouple Bubble from NotificationEntry and transform ShortcutInfo into Bubble |
| 106 | Bubble(ShortcutInfo shortcutInfo) { |
| 107 | mShortcutInfo = shortcutInfo; |
| 108 | mKey = shortcutInfo.getId(); |
| 109 | mGroupId = shortcutInfo.getId(); |
| 110 | } |
| 111 | |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 112 | /** Used in tests when no UI is required. */ |
| 113 | @VisibleForTesting(visibility = PRIVATE) |
Mady Mellor | f44b683 | 2020-01-14 13:26:14 -0800 | [diff] [blame] | 114 | Bubble(NotificationEntry e, |
| 115 | BubbleController.NotificationSuppressionChangedListener listener) { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 116 | mEntry = e; |
Ned Burns | 00b4b2d | 2019-10-17 22:09:27 -0400 | [diff] [blame] | 117 | mKey = e.getKey(); |
| 118 | mLastUpdated = e.getSbn().getPostTime(); |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 119 | mGroupId = groupId(e); |
Mady Mellor | f44b683 | 2020-01-14 13:26:14 -0800 | [diff] [blame] | 120 | mSuppressionListener = listener; |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 121 | } |
| 122 | |
Lyn Han | cd4f87e | 2020-02-19 20:33:45 -0800 | [diff] [blame] | 123 | @Override |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 124 | public String getKey() { |
| 125 | return mKey; |
| 126 | } |
| 127 | |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 128 | public NotificationEntry getEntry() { |
| 129 | return mEntry; |
| 130 | } |
| 131 | |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 132 | public String getGroupId() { |
| 133 | return mGroupId; |
| 134 | } |
| 135 | |
| 136 | public String getPackageName() { |
Ned Burns | 00b4b2d | 2019-10-17 22:09:27 -0400 | [diff] [blame] | 137 | return mEntry.getSbn().getPackageName(); |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 138 | } |
| 139 | |
Lyn Han | cd4f87e | 2020-02-19 20:33:45 -0800 | [diff] [blame] | 140 | @Override |
Lyn Han | b58c756 | 2020-01-07 14:29:20 -0800 | [diff] [blame] | 141 | public Bitmap getBadgedImage() { |
| 142 | return mBadgedImage; |
| 143 | } |
| 144 | |
Joshua Tsuji | 6855cab | 2020-04-16 01:05:39 -0400 | [diff] [blame] | 145 | public Drawable getBadgedAppIcon() { |
| 146 | return mBadgedAppIcon; |
| 147 | } |
| 148 | |
Lyn Han | cd4f87e | 2020-02-19 20:33:45 -0800 | [diff] [blame] | 149 | @Override |
Lyn Han | b58c756 | 2020-01-07 14:29:20 -0800 | [diff] [blame] | 150 | public int getDotColor() { |
| 151 | return mDotColor; |
| 152 | } |
| 153 | |
Lyn Han | cd4f87e | 2020-02-19 20:33:45 -0800 | [diff] [blame] | 154 | @Override |
Lyn Han | b58c756 | 2020-01-07 14:29:20 -0800 | [diff] [blame] | 155 | public Path getDotPath() { |
| 156 | return mDotPath; |
| 157 | } |
| 158 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 159 | @Nullable |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 160 | public String getAppName() { |
| 161 | return mAppName; |
| 162 | } |
| 163 | |
Mady Mellor | b547e5e | 2019-12-02 10:15:56 -0800 | [diff] [blame] | 164 | @Nullable |
| 165 | public ShortcutInfo getShortcutInfo() { |
| 166 | return mShortcutInfo; |
| 167 | } |
| 168 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 169 | @Nullable |
Lyn Han | cd4f87e | 2020-02-19 20:33:45 -0800 | [diff] [blame] | 170 | @Override |
Lyn Han | 3cd75d7 | 2020-02-15 19:10:12 -0800 | [diff] [blame] | 171 | public BadgedImageView getIconView() { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 172 | return mIconView; |
| 173 | } |
| 174 | |
Lyn Han | cd4f87e | 2020-02-19 20:33:45 -0800 | [diff] [blame] | 175 | @Override |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 176 | @Nullable |
Lyn Han | 3cd75d7 | 2020-02-15 19:10:12 -0800 | [diff] [blame] | 177 | public BubbleExpandedView getExpandedView() { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 178 | return mExpandedView; |
| 179 | } |
| 180 | |
Mady Mellor | e967e96 | 2020-03-26 17:36:44 -0700 | [diff] [blame] | 181 | /** |
| 182 | * Call when the views should be removed, ensure this is called to clean up ActivityView |
| 183 | * content. |
| 184 | */ |
| 185 | void cleanupViews() { |
Mark Renouf | c19b473 | 2019-06-26 12:08:33 -0400 | [diff] [blame] | 186 | if (mExpandedView != null) { |
| 187 | mExpandedView.cleanUpExpandedState(); |
Mady Mellor | e967e96 | 2020-03-26 17:36:44 -0700 | [diff] [blame] | 188 | mExpandedView = null; |
Mark Renouf | c19b473 | 2019-06-26 12:08:33 -0400 | [diff] [blame] | 189 | } |
Mady Mellor | e967e96 | 2020-03-26 17:36:44 -0700 | [diff] [blame] | 190 | mIconView = null; |
Mark Renouf | c19b473 | 2019-06-26 12:08:33 -0400 | [diff] [blame] | 191 | } |
| 192 | |
Lyn Han | 6cb4e5f | 2020-04-27 15:11:18 -0700 | [diff] [blame] | 193 | void setPendingIntentCanceled() { |
| 194 | mPendingIntentCanceled = true; |
| 195 | } |
| 196 | |
| 197 | boolean getPendingIntentCanceled() { |
| 198 | return mPendingIntentCanceled; |
| 199 | } |
| 200 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 201 | /** |
| 202 | * Sets whether to perform inflation on the same thread as the caller. This method should only |
| 203 | * be used in tests, not in production. |
| 204 | */ |
| 205 | @VisibleForTesting |
| 206 | void setInflateSynchronously(boolean inflateSynchronously) { |
| 207 | mInflateSynchronously = inflateSynchronously; |
| 208 | } |
| 209 | |
| 210 | /** |
| 211 | * Starts a task to inflate & load any necessary information to display a bubble. |
| 212 | * |
| 213 | * @param callback the callback to notify one the bubble is ready to be displayed. |
| 214 | * @param context the context for the bubble. |
| 215 | * @param stackView the stackView the bubble is eventually added to. |
| 216 | * @param iconFactory the iconfactory use to create badged images for the bubble. |
| 217 | */ |
| 218 | void inflate(BubbleViewInfoTask.Callback callback, |
| 219 | Context context, |
| 220 | BubbleStackView stackView, |
| 221 | BubbleIconFactory iconFactory) { |
| 222 | if (isBubbleLoading()) { |
| 223 | mInflationTask.cancel(true /* mayInterruptIfRunning */); |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 224 | } |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 225 | mInflationTask = new BubbleViewInfoTask(this, |
| 226 | context, |
| 227 | stackView, |
| 228 | iconFactory, |
| 229 | callback); |
| 230 | if (mInflateSynchronously) { |
| 231 | mInflationTask.onPostExecute(mInflationTask.doInBackground()); |
| 232 | } else { |
| 233 | mInflationTask.execute(); |
| 234 | } |
| 235 | } |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 236 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 237 | private boolean isBubbleLoading() { |
| 238 | return mInflationTask != null && mInflationTask.getStatus() != FINISHED; |
| 239 | } |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 240 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 241 | boolean isInflated() { |
Mady Mellor | e967e96 | 2020-03-26 17:36:44 -0700 | [diff] [blame] | 242 | return mIconView != null && mExpandedView != null; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 243 | } |
| 244 | |
Lyn Han | 18cdc1c | 2020-03-18 19:12:42 -0700 | [diff] [blame] | 245 | void stopInflation() { |
| 246 | if (mInflationTask == null) { |
| 247 | return; |
| 248 | } |
Mady Mellor | e967e96 | 2020-03-26 17:36:44 -0700 | [diff] [blame] | 249 | mInflationTask.cancel(true /* mayInterruptIfRunning */); |
| 250 | cleanupViews(); |
Lyn Han | 18cdc1c | 2020-03-18 19:12:42 -0700 | [diff] [blame] | 251 | } |
| 252 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 253 | void setViewInfo(BubbleViewInfoTask.BubbleViewInfo info) { |
| 254 | if (!isInflated()) { |
| 255 | mIconView = info.imageView; |
| 256 | mExpandedView = info.expandedView; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 257 | } |
| 258 | |
| 259 | mShortcutInfo = info.shortcutInfo; |
| 260 | mAppName = info.appName; |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 261 | mFlyoutMessage = info.flyoutMessage; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 262 | |
Joshua Tsuji | 6855cab | 2020-04-16 01:05:39 -0400 | [diff] [blame] | 263 | mBadgedAppIcon = info.badgedAppIcon; |
Lyn Han | b58c756 | 2020-01-07 14:29:20 -0800 | [diff] [blame] | 264 | mBadgedImage = info.badgedBubbleImage; |
| 265 | mDotColor = info.dotColor; |
| 266 | mDotPath = info.dotPath; |
| 267 | |
Lyn Han | 18cdc1c | 2020-03-18 19:12:42 -0700 | [diff] [blame] | 268 | if (mExpandedView != null) { |
| 269 | mExpandedView.update(/* bubble */ this); |
| 270 | } |
| 271 | if (mIconView != null) { |
Joshua Tsuji | 2ed260e | 2020-03-26 14:26:01 -0400 | [diff] [blame] | 272 | mIconView.setRenderedBubble(/* bubble */ this); |
Lyn Han | 18cdc1c | 2020-03-18 19:12:42 -0700 | [diff] [blame] | 273 | } |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 274 | } |
| 275 | |
Issei Suzuki | cac2a50 | 2019-04-16 16:52:50 +0200 | [diff] [blame] | 276 | /** |
| 277 | * Set visibility of bubble in the expanded state. |
| 278 | * |
| 279 | * @param visibility {@code true} if the expanded bubble should be visible on the screen. |
| 280 | * |
| 281 | * Note that this contents visibility doesn't affect visibility at {@link android.view.View}, |
| 282 | * and setting {@code false} actually means rendering the expanded view in transparent. |
| 283 | */ |
Lyn Han | cd4f87e | 2020-02-19 20:33:45 -0800 | [diff] [blame] | 284 | @Override |
Lyn Han | 3cd75d7 | 2020-02-15 19:10:12 -0800 | [diff] [blame] | 285 | public void setContentVisibility(boolean visibility) { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 286 | if (mExpandedView != null) { |
| 287 | mExpandedView.setContentVisibility(visibility); |
Issei Suzuki | cac2a50 | 2019-04-16 16:52:50 +0200 | [diff] [blame] | 288 | } |
| 289 | } |
| 290 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 291 | /** |
| 292 | * Sets the entry associated with this bubble. |
| 293 | */ |
| 294 | void setEntry(NotificationEntry entry) { |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 295 | mEntry = entry; |
Ned Burns | 00b4b2d | 2019-10-17 22:09:27 -0400 | [diff] [blame] | 296 | mLastUpdated = entry.getSbn().getPostTime(); |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 297 | } |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 298 | |
Mark Renouf | ba5ab51 | 2019-05-02 15:21:01 -0400 | [diff] [blame] | 299 | /** |
| 300 | * @return the newer of {@link #getLastUpdateTime()} and {@link #getLastAccessTime()} |
| 301 | */ |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 302 | long getLastActivity() { |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 303 | return Math.max(mLastUpdated, mLastAccessed); |
| 304 | } |
| 305 | |
| 306 | /** |
Mark Renouf | ba5ab51 | 2019-05-02 15:21:01 -0400 | [diff] [blame] | 307 | * @return the timestamp in milliseconds of the most recent notification entry for this bubble |
| 308 | */ |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 309 | long getLastUpdateTime() { |
Mark Renouf | ba5ab51 | 2019-05-02 15:21:01 -0400 | [diff] [blame] | 310 | return mLastUpdated; |
| 311 | } |
| 312 | |
| 313 | /** |
Lyn Han | 6cb4e5f | 2020-04-27 15:11:18 -0700 | [diff] [blame] | 314 | * @return if the bubble was ever expanded |
| 315 | */ |
| 316 | boolean getWasAccessed() { |
| 317 | return mLastAccessed != 0L; |
| 318 | } |
| 319 | |
| 320 | /** |
Issei Suzuki | cac2a50 | 2019-04-16 16:52:50 +0200 | [diff] [blame] | 321 | * @return the display id of the virtual display on which bubble contents is drawn. |
| 322 | */ |
Lyn Han | 9f66c3b | 2020-03-05 23:59:29 -0800 | [diff] [blame] | 323 | @Override |
| 324 | public int getDisplayId() { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 325 | return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY; |
Issei Suzuki | cac2a50 | 2019-04-16 16:52:50 +0200 | [diff] [blame] | 326 | } |
| 327 | |
| 328 | /** |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 329 | * Should be invoked whenever a Bubble is accessed (selected while expanded). |
| 330 | */ |
| 331 | void markAsAccessedAt(long lastAccessedMillis) { |
| 332 | mLastAccessed = lastAccessedMillis; |
Mady Mellor | f44b683 | 2020-01-14 13:26:14 -0800 | [diff] [blame] | 333 | setSuppressNotification(true); |
Joshua Tsuji | 2ed260e | 2020-03-26 14:26:01 -0400 | [diff] [blame] | 334 | setShowDot(false /* show */); |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 335 | } |
| 336 | |
| 337 | /** |
Lyn Han | b58c756 | 2020-01-07 14:29:20 -0800 | [diff] [blame] | 338 | * Should be invoked whenever a Bubble is promoted from overflow. |
| 339 | */ |
| 340 | void markUpdatedAt(long lastAccessedMillis) { |
| 341 | mLastUpdated = lastAccessedMillis; |
| 342 | } |
| 343 | |
| 344 | /** |
Mady Mellor | f44b683 | 2020-01-14 13:26:14 -0800 | [diff] [blame] | 345 | * Whether this notification should be shown in the shade. |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 346 | */ |
Mady Mellor | b8aaf97 | 2019-11-26 10:28:00 -0800 | [diff] [blame] | 347 | boolean showInShade() { |
Mady Mellor | f44b683 | 2020-01-14 13:26:14 -0800 | [diff] [blame] | 348 | return !shouldSuppressNotification() || !mEntry.isClearable(); |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 349 | } |
| 350 | |
| 351 | /** |
Mady Mellor | f44b683 | 2020-01-14 13:26:14 -0800 | [diff] [blame] | 352 | * Sets whether this notification should be suppressed in the shade. |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 353 | */ |
Mady Mellor | f44b683 | 2020-01-14 13:26:14 -0800 | [diff] [blame] | 354 | void setSuppressNotification(boolean suppressNotification) { |
| 355 | boolean prevShowInShade = showInShade(); |
| 356 | |
| 357 | Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); |
| 358 | int flags = data.getFlags(); |
| 359 | if (suppressNotification) { |
| 360 | flags |= Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; |
| 361 | } else { |
| 362 | flags &= ~Notification.BubbleMetadata.FLAG_SUPPRESS_NOTIFICATION; |
| 363 | } |
| 364 | data.setFlags(flags); |
| 365 | |
| 366 | if (showInShade() != prevShowInShade && mSuppressionListener != null) { |
| 367 | mSuppressionListener.onBubbleNotificationSuppressionChange(this); |
| 368 | } |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 369 | } |
| 370 | |
| 371 | /** |
Mady Mellor | df48d0a | 2019-06-25 18:26:46 -0700 | [diff] [blame] | 372 | * Sets whether the bubble for this notification should show a dot indicating updated content. |
| 373 | */ |
Joshua Tsuji | 2ed260e | 2020-03-26 14:26:01 -0400 | [diff] [blame] | 374 | void setShowDot(boolean showDot) { |
Mady Mellor | df48d0a | 2019-06-25 18:26:46 -0700 | [diff] [blame] | 375 | mShowBubbleUpdateDot = showDot; |
Joshua Tsuji | 2ed260e | 2020-03-26 14:26:01 -0400 | [diff] [blame] | 376 | |
| 377 | if (mIconView != null) { |
| 378 | mIconView.updateDotVisibility(true /* animate */); |
Mady Mellor | b8aaf97 | 2019-11-26 10:28:00 -0800 | [diff] [blame] | 379 | } |
Mady Mellor | df48d0a | 2019-06-25 18:26:46 -0700 | [diff] [blame] | 380 | } |
| 381 | |
| 382 | /** |
| 383 | * Whether the bubble for this notification should show a dot indicating updated content. |
| 384 | */ |
Lyn Han | cd4f87e | 2020-02-19 20:33:45 -0800 | [diff] [blame] | 385 | @Override |
| 386 | public boolean showDot() { |
Mady Mellor | b25f906 | 2019-12-09 12:12:57 -0800 | [diff] [blame] | 387 | return mShowBubbleUpdateDot |
| 388 | && !mEntry.shouldSuppressNotificationDot() |
| 389 | && !shouldSuppressNotification(); |
Mady Mellor | df48d0a | 2019-06-25 18:26:46 -0700 | [diff] [blame] | 390 | } |
| 391 | |
| 392 | /** |
| 393 | * Whether the flyout for the bubble should be shown. |
| 394 | */ |
Mady Mellor | b8aaf97 | 2019-11-26 10:28:00 -0800 | [diff] [blame] | 395 | boolean showFlyout() { |
Mark Renouf | c19b473 | 2019-06-26 12:08:33 -0400 | [diff] [blame] | 396 | return !mSuppressFlyout && !mEntry.shouldSuppressPeek() |
Mady Mellor | b25f906 | 2019-12-09 12:12:57 -0800 | [diff] [blame] | 397 | && !shouldSuppressNotification() |
Mark Renouf | c19b473 | 2019-06-26 12:08:33 -0400 | [diff] [blame] | 398 | && !mEntry.shouldSuppressNotificationList(); |
| 399 | } |
| 400 | |
| 401 | /** |
| 402 | * Set whether the flyout text for the bubble should be shown when an update is received. |
| 403 | * |
| 404 | * @param suppressFlyout whether the flyout text is shown |
| 405 | */ |
| 406 | void setSuppressFlyout(boolean suppressFlyout) { |
| 407 | mSuppressFlyout = suppressFlyout; |
Mady Mellor | df48d0a | 2019-06-25 18:26:46 -0700 | [diff] [blame] | 408 | } |
| 409 | |
Mady Mellor | df898fd | 2020-01-09 09:26:36 -0800 | [diff] [blame] | 410 | FlyoutMessage getFlyoutMessage() { |
| 411 | return mFlyoutMessage; |
| 412 | } |
| 413 | |
Mady Mellor | df48d0a | 2019-06-25 18:26:46 -0700 | [diff] [blame] | 414 | /** |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 415 | * Returns whether the notification for this bubble is a foreground service. It shows that this |
| 416 | * is an ongoing bubble. |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 417 | */ |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 418 | boolean isOngoing() { |
Ned Burns | 00b4b2d | 2019-10-17 22:09:27 -0400 | [diff] [blame] | 419 | int flags = mEntry.getSbn().getNotification().flags; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 420 | return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; |
| 421 | } |
| 422 | |
| 423 | float getDesiredHeight(Context context) { |
| 424 | Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); |
| 425 | boolean useRes = data.getDesiredHeightResId() != 0; |
| 426 | if (useRes) { |
| 427 | return getDimenForPackageUser(context, data.getDesiredHeightResId(), |
Ned Burns | 00b4b2d | 2019-10-17 22:09:27 -0400 | [diff] [blame] | 428 | mEntry.getSbn().getPackageName(), |
| 429 | mEntry.getSbn().getUser().getIdentifier()); |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 430 | } else { |
| 431 | return data.getDesiredHeight() |
| 432 | * context.getResources().getDisplayMetrics().density; |
| 433 | } |
| 434 | } |
| 435 | |
Mady Mellor | 70cba7bb | 2019-07-02 15:06:07 -0700 | [diff] [blame] | 436 | String getDesiredHeightString() { |
| 437 | Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); |
| 438 | boolean useRes = data.getDesiredHeightResId() != 0; |
| 439 | if (useRes) { |
| 440 | return String.valueOf(data.getDesiredHeightResId()); |
| 441 | } else { |
| 442 | return String.valueOf(data.getDesiredHeight()); |
| 443 | } |
| 444 | } |
| 445 | |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 446 | /** |
| 447 | * Whether shortcut information should be used to populate the bubble. |
| 448 | * <p> |
| 449 | * To populate the activity use {@link LauncherApps#startShortcut(ShortcutInfo, Rect, Bundle)}. |
| 450 | * To populate the icon use {@link LauncherApps#getShortcutIconDrawable(ShortcutInfo, int)}. |
| 451 | */ |
| 452 | boolean usingShortcutInfo() { |
Mady Mellor | 2ac2d3a | 2020-01-08 17:18:54 -0800 | [diff] [blame] | 453 | return mEntry.getBubbleMetadata().getShortcutId() != null; |
Mady Mellor | 3df7ab0 | 2019-12-09 15:07:10 -0800 | [diff] [blame] | 454 | } |
| 455 | |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 456 | @Nullable |
Mady Mellor | b547e5e | 2019-12-02 10:15:56 -0800 | [diff] [blame] | 457 | PendingIntent getBubbleIntent() { |
Mady Mellor | 7f23490 | 2019-10-20 12:06:29 -0700 | [diff] [blame] | 458 | Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); |
| 459 | if (data != null) { |
Mady Mellor | aa9ce17 | 2020-03-17 10:34:20 -0700 | [diff] [blame] | 460 | return data.getIntent(); |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 461 | } |
| 462 | return null; |
| 463 | } |
| 464 | |
| 465 | Intent getSettingsIntent() { |
| 466 | final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS); |
| 467 | intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); |
Ned Burns | 00b4b2d | 2019-10-17 22:09:27 -0400 | [diff] [blame] | 468 | intent.putExtra(Settings.EXTRA_APP_UID, mEntry.getSbn().getUid()); |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 469 | intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); |
| 470 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 471 | intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); |
| 472 | return intent; |
| 473 | } |
| 474 | |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 475 | private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) { |
| 476 | PackageManager pm = context.getPackageManager(); |
| 477 | Resources r; |
| 478 | if (pkg != null) { |
| 479 | try { |
| 480 | if (userId == UserHandle.USER_ALL) { |
| 481 | userId = UserHandle.USER_SYSTEM; |
| 482 | } |
| 483 | r = pm.getResourcesForApplicationAsUser(pkg, userId); |
| 484 | return r.getDimensionPixelSize(resId); |
| 485 | } catch (PackageManager.NameNotFoundException ex) { |
| 486 | // Uninstalled, don't care |
| 487 | } catch (Resources.NotFoundException e) { |
| 488 | // Invalid res id, return 0 and user our default |
| 489 | Log.e(TAG, "Couldn't find desired height res id", e); |
| 490 | } |
| 491 | } |
| 492 | return 0; |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 493 | } |
| 494 | |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 495 | private boolean shouldSuppressNotification() { |
| 496 | return mEntry.getBubbleMetadata() != null |
| 497 | && mEntry.getBubbleMetadata().isNotificationSuppressed(); |
| 498 | } |
| 499 | |
Mady Mellor | 70cba7bb | 2019-07-02 15:06:07 -0700 | [diff] [blame] | 500 | boolean shouldAutoExpand() { |
| 501 | Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata(); |
| 502 | return metadata != null && metadata.getAutoExpandBubble(); |
| 503 | } |
| 504 | |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 505 | @Override |
| 506 | public String toString() { |
| 507 | return "Bubble{" + mKey + '}'; |
| 508 | } |
| 509 | |
Mady Mellor | 70cba7bb | 2019-07-02 15:06:07 -0700 | [diff] [blame] | 510 | /** |
| 511 | * Description of current bubble state. |
| 512 | */ |
| 513 | public void dump( |
| 514 | @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { |
| 515 | pw.print("key: "); pw.println(mKey); |
Mady Mellor | b8aaf97 | 2019-11-26 10:28:00 -0800 | [diff] [blame] | 516 | pw.print(" showInShade: "); pw.println(showInShade()); |
| 517 | pw.print(" showDot: "); pw.println(showDot()); |
| 518 | pw.print(" showFlyout: "); pw.println(showFlyout()); |
Mady Mellor | 70cba7bb | 2019-07-02 15:06:07 -0700 | [diff] [blame] | 519 | pw.print(" desiredHeight: "); pw.println(getDesiredHeightString()); |
| 520 | pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification()); |
| 521 | pw.print(" autoExpand: "); pw.println(shouldAutoExpand()); |
| 522 | } |
| 523 | |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 524 | @Override |
| 525 | public boolean equals(Object o) { |
| 526 | if (this == o) return true; |
| 527 | if (!(o instanceof Bubble)) return false; |
| 528 | Bubble bubble = (Bubble) o; |
| 529 | return Objects.equals(mKey, bubble.mKey); |
| 530 | } |
| 531 | |
| 532 | @Override |
| 533 | public int hashCode() { |
| 534 | return Objects.hash(mKey); |
| 535 | } |
Lyn Han | 3cd75d7 | 2020-02-15 19:10:12 -0800 | [diff] [blame] | 536 | |
Lyn Han | cd4f87e | 2020-02-19 20:33:45 -0800 | [diff] [blame] | 537 | @Override |
Lyn Han | 3cd75d7 | 2020-02-15 19:10:12 -0800 | [diff] [blame] | 538 | public void logUIEvent(int bubbleCount, int action, float normalX, float normalY, int index) { |
| 539 | if (this.getEntry() == null |
| 540 | || this.getEntry().getSbn() == null) { |
| 541 | SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, |
| 542 | null /* package name */, |
| 543 | null /* notification channel */, |
| 544 | 0 /* notification ID */, |
| 545 | 0 /* bubble position */, |
| 546 | bubbleCount, |
| 547 | action, |
| 548 | normalX, |
| 549 | normalY, |
| 550 | false /* unread bubble */, |
| 551 | false /* on-going bubble */, |
| 552 | false /* isAppForeground (unused) */); |
| 553 | } else { |
| 554 | StatusBarNotification notification = this.getEntry().getSbn(); |
| 555 | SysUiStatsLog.write(SysUiStatsLog.BUBBLE_UI_CHANGED, |
| 556 | notification.getPackageName(), |
| 557 | notification.getNotification().getChannelId(), |
| 558 | notification.getId(), |
| 559 | index, |
| 560 | bubbleCount, |
| 561 | action, |
| 562 | normalX, |
| 563 | normalY, |
| 564 | this.showInShade(), |
| 565 | this.isOngoing(), |
| 566 | false /* isAppForeground (unused) */); |
| 567 | } |
| 568 | } |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 569 | } |