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 | |
Issei Suzuki | cac2a50 | 2019-04-16 16:52:50 +0200 | [diff] [blame] | 19 | import static android.view.Display.INVALID_DISPLAY; |
| 20 | |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 21 | import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; |
| 22 | |
Mady Mellor | 70cba7bb | 2019-07-02 15:06:07 -0700 | [diff] [blame] | 23 | import android.annotation.NonNull; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 24 | import android.annotation.Nullable; |
| 25 | import android.app.Notification; |
| 26 | import android.app.PendingIntent; |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 27 | import android.content.Context; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 28 | import android.content.Intent; |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 29 | import android.content.pm.ApplicationInfo; |
| 30 | import android.content.pm.PackageManager; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 31 | import android.content.res.Resources; |
Lyn Han | 4c1731f | 2019-06-19 19:14:24 -0700 | [diff] [blame] | 32 | import android.graphics.drawable.Drawable; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 33 | import android.os.Parcelable; |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 34 | import android.os.UserHandle; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 35 | import android.provider.Settings; |
| 36 | import android.text.TextUtils; |
| 37 | import android.util.Log; |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 38 | import android.view.LayoutInflater; |
| 39 | |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 40 | import com.android.internal.annotations.VisibleForTesting; |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 41 | import com.android.systemui.R; |
| 42 | import com.android.systemui.statusbar.notification.collection.NotificationEntry; |
| 43 | |
Mady Mellor | 70cba7bb | 2019-07-02 15:06:07 -0700 | [diff] [blame] | 44 | import java.io.FileDescriptor; |
| 45 | import java.io.PrintWriter; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 46 | import java.util.List; |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 47 | import java.util.Objects; |
| 48 | |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 49 | /** |
| 50 | * Encapsulates the data and UI elements of a bubble. |
| 51 | */ |
| 52 | class Bubble { |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 53 | private static final String TAG = "Bubble"; |
| 54 | |
| 55 | private NotificationEntry mEntry; |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 56 | private final String mKey; |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 57 | private final String mGroupId; |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 58 | private String mAppName; |
Lyn Han | 4c1731f | 2019-06-19 19:14:24 -0700 | [diff] [blame] | 59 | private Drawable mUserBadgedAppIcon; |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 60 | |
| 61 | private boolean mInflated; |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 62 | private BubbleView mIconView; |
| 63 | private BubbleExpandedView mExpandedView; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 64 | |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 65 | private long mLastUpdated; |
| 66 | private long mLastAccessed; |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 67 | private boolean mIsRemoved; |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 68 | |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 69 | /** |
| 70 | * Whether this notification should be shown in the shade when it is also displayed as a bubble. |
| 71 | * |
| 72 | * <p>When a notification is a bubble we don't show it in the shade once the bubble has been |
| 73 | * expanded</p> |
| 74 | */ |
| 75 | private boolean mShowInShadeWhenBubble = true; |
| 76 | |
Mady Mellor | df48d0a | 2019-06-25 18:26:46 -0700 | [diff] [blame] | 77 | /** |
| 78 | * Whether the bubble should show a dot for the notification indicating updated content. |
| 79 | */ |
| 80 | private boolean mShowBubbleUpdateDot = true; |
| 81 | |
Mark Renouf | c19b473 | 2019-06-26 12:08:33 -0400 | [diff] [blame] | 82 | /** Whether flyout text should be suppressed, regardless of any other flags or state. */ |
| 83 | private boolean mSuppressFlyout; |
| 84 | |
Mark Renouf | ba5ab51 | 2019-05-02 15:21:01 -0400 | [diff] [blame] | 85 | public static String groupId(NotificationEntry entry) { |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 86 | UserHandle user = entry.notification.getUser(); |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 87 | return user.getIdentifier() + "|" + entry.notification.getPackageName(); |
| 88 | } |
| 89 | |
| 90 | /** Used in tests when no UI is required. */ |
| 91 | @VisibleForTesting(visibility = PRIVATE) |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 92 | Bubble(Context context, NotificationEntry e) { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 93 | mEntry = e; |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 94 | mKey = e.key; |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 95 | mLastUpdated = e.notification.getPostTime(); |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 96 | mGroupId = groupId(e); |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 97 | |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 98 | PackageManager pm = context.getPackageManager(); |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 99 | ApplicationInfo info; |
| 100 | try { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 101 | info = pm.getApplicationInfo( |
| 102 | mEntry.notification.getPackageName(), |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 103 | PackageManager.MATCH_UNINSTALLED_PACKAGES |
| 104 | | PackageManager.MATCH_DISABLED_COMPONENTS |
| 105 | | PackageManager.MATCH_DIRECT_BOOT_UNAWARE |
| 106 | | PackageManager.MATCH_DIRECT_BOOT_AWARE); |
| 107 | if (info != null) { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 108 | mAppName = String.valueOf(pm.getApplicationLabel(info)); |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 109 | } |
Lyn Han | 4c1731f | 2019-06-19 19:14:24 -0700 | [diff] [blame] | 110 | Drawable appIcon = pm.getApplicationIcon(mEntry.notification.getPackageName()); |
| 111 | mUserBadgedAppIcon = pm.getUserBadgedIcon(appIcon, mEntry.notification.getUser()); |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 112 | } catch (PackageManager.NameNotFoundException unused) { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 113 | mAppName = mEntry.notification.getPackageName(); |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 114 | } |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 115 | } |
| 116 | |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 117 | public String getKey() { |
| 118 | return mKey; |
| 119 | } |
| 120 | |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 121 | public NotificationEntry getEntry() { |
| 122 | return mEntry; |
| 123 | } |
| 124 | |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 125 | public String getGroupId() { |
| 126 | return mGroupId; |
| 127 | } |
| 128 | |
| 129 | public String getPackageName() { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 130 | return mEntry.notification.getPackageName(); |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 131 | } |
| 132 | |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 133 | public String getAppName() { |
| 134 | return mAppName; |
| 135 | } |
| 136 | |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 137 | boolean isInflated() { |
| 138 | return mInflated; |
| 139 | } |
| 140 | |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 141 | void updateDotVisibility() { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 142 | if (mIconView != null) { |
| 143 | mIconView.updateDotVisibility(true /* animate */); |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 144 | } |
| 145 | } |
| 146 | |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 147 | BubbleView getIconView() { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 148 | return mIconView; |
| 149 | } |
| 150 | |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 151 | BubbleExpandedView getExpandedView() { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 152 | return mExpandedView; |
| 153 | } |
| 154 | |
Mark Renouf | c19b473 | 2019-06-26 12:08:33 -0400 | [diff] [blame] | 155 | void cleanupExpandedState() { |
| 156 | if (mExpandedView != null) { |
| 157 | mExpandedView.cleanUpExpandedState(); |
| 158 | } |
| 159 | } |
| 160 | |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 161 | void inflate(LayoutInflater inflater, BubbleStackView stackView) { |
| 162 | if (mInflated) { |
| 163 | return; |
| 164 | } |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 165 | mIconView = (BubbleView) inflater.inflate( |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 166 | R.layout.bubble_view, stackView, false /* attachToRoot */); |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 167 | mIconView.setBubble(this); |
Lyn Han | 4c1731f | 2019-06-19 19:14:24 -0700 | [diff] [blame] | 168 | mIconView.setAppIcon(mUserBadgedAppIcon); |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 169 | |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 170 | mExpandedView = (BubbleExpandedView) inflater.inflate( |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 171 | R.layout.bubble_expanded_view, stackView, false /* attachToRoot */); |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 172 | mExpandedView.setBubble(this, stackView, mAppName); |
Lyn Han | 6c40fe7 | 2019-05-08 14:06:33 -0700 | [diff] [blame] | 173 | |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 174 | mInflated = true; |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 175 | } |
| 176 | |
Issei Suzuki | cac2a50 | 2019-04-16 16:52:50 +0200 | [diff] [blame] | 177 | /** |
| 178 | * Set visibility of bubble in the expanded state. |
| 179 | * |
| 180 | * @param visibility {@code true} if the expanded bubble should be visible on the screen. |
| 181 | * |
| 182 | * Note that this contents visibility doesn't affect visibility at {@link android.view.View}, |
| 183 | * and setting {@code false} actually means rendering the expanded view in transparent. |
| 184 | */ |
| 185 | void setContentVisibility(boolean visibility) { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 186 | if (mExpandedView != null) { |
| 187 | mExpandedView.setContentVisibility(visibility); |
Issei Suzuki | cac2a50 | 2019-04-16 16:52:50 +0200 | [diff] [blame] | 188 | } |
| 189 | } |
| 190 | |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 191 | void updateEntry(NotificationEntry entry) { |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 192 | mEntry = entry; |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 193 | mLastUpdated = entry.notification.getPostTime(); |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 194 | if (mInflated) { |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 195 | mIconView.update(this); |
| 196 | mExpandedView.update(this); |
Mark Renouf | 85e0a90 | 2019-04-05 15:51:51 -0400 | [diff] [blame] | 197 | } |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 198 | } |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 199 | |
Mark Renouf | ba5ab51 | 2019-05-02 15:21:01 -0400 | [diff] [blame] | 200 | /** |
| 201 | * @return the newer of {@link #getLastUpdateTime()} and {@link #getLastAccessTime()} |
| 202 | */ |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 203 | long getLastActivity() { |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 204 | return Math.max(mLastUpdated, mLastAccessed); |
| 205 | } |
| 206 | |
| 207 | /** |
Mark Renouf | ba5ab51 | 2019-05-02 15:21:01 -0400 | [diff] [blame] | 208 | * @return the timestamp in milliseconds of the most recent notification entry for this bubble |
| 209 | */ |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 210 | long getLastUpdateTime() { |
Mark Renouf | ba5ab51 | 2019-05-02 15:21:01 -0400 | [diff] [blame] | 211 | return mLastUpdated; |
| 212 | } |
| 213 | |
| 214 | /** |
| 215 | * @return the timestamp in milliseconds when this bubble was last displayed in expanded state |
| 216 | */ |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 217 | long getLastAccessTime() { |
Mark Renouf | ba5ab51 | 2019-05-02 15:21:01 -0400 | [diff] [blame] | 218 | return mLastAccessed; |
| 219 | } |
| 220 | |
| 221 | /** |
Issei Suzuki | cac2a50 | 2019-04-16 16:52:50 +0200 | [diff] [blame] | 222 | * @return the display id of the virtual display on which bubble contents is drawn. |
| 223 | */ |
| 224 | int getDisplayId() { |
Mady Mellor | ed99c27 | 2019-06-13 15:58:30 -0700 | [diff] [blame] | 225 | return mExpandedView != null ? mExpandedView.getVirtualDisplayId() : INVALID_DISPLAY; |
Issei Suzuki | cac2a50 | 2019-04-16 16:52:50 +0200 | [diff] [blame] | 226 | } |
| 227 | |
| 228 | /** |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 229 | * Should be invoked whenever a Bubble is accessed (selected while expanded). |
| 230 | */ |
| 231 | void markAsAccessedAt(long lastAccessedMillis) { |
| 232 | mLastAccessed = lastAccessedMillis; |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 233 | setShowInShadeWhenBubble(false); |
Mady Mellor | df48d0a | 2019-06-25 18:26:46 -0700 | [diff] [blame] | 234 | setShowBubbleDot(false); |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 235 | } |
| 236 | |
| 237 | /** |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 238 | * Whether this notification should be shown in the shade when it is also displayed as a |
| 239 | * bubble. |
| 240 | */ |
| 241 | boolean showInShadeWhenBubble() { |
| 242 | return !mEntry.isRowDismissed() && !shouldSuppressNotification() |
| 243 | && (!mEntry.isClearable() || mShowInShadeWhenBubble); |
| 244 | } |
| 245 | |
| 246 | /** |
| 247 | * Sets whether this notification should be shown in the shade when it is also displayed as a |
| 248 | * bubble. |
| 249 | */ |
| 250 | void setShowInShadeWhenBubble(boolean showInShade) { |
| 251 | mShowInShadeWhenBubble = showInShade; |
| 252 | } |
| 253 | |
| 254 | /** |
Mady Mellor | df48d0a | 2019-06-25 18:26:46 -0700 | [diff] [blame] | 255 | * Sets whether the bubble for this notification should show a dot indicating updated content. |
| 256 | */ |
| 257 | void setShowBubbleDot(boolean showDot) { |
| 258 | mShowBubbleUpdateDot = showDot; |
| 259 | } |
| 260 | |
| 261 | /** |
| 262 | * Whether the bubble for this notification should show a dot indicating updated content. |
| 263 | */ |
| 264 | boolean showBubbleDot() { |
| 265 | return mShowBubbleUpdateDot && !mEntry.shouldSuppressNotificationDot(); |
| 266 | } |
| 267 | |
| 268 | /** |
| 269 | * Whether the flyout for the bubble should be shown. |
| 270 | */ |
| 271 | boolean showFlyoutForBubble() { |
Mark Renouf | c19b473 | 2019-06-26 12:08:33 -0400 | [diff] [blame] | 272 | return !mSuppressFlyout && !mEntry.shouldSuppressPeek() |
| 273 | && !mEntry.shouldSuppressNotificationList(); |
| 274 | } |
| 275 | |
| 276 | /** |
| 277 | * Set whether the flyout text for the bubble should be shown when an update is received. |
| 278 | * |
| 279 | * @param suppressFlyout whether the flyout text is shown |
| 280 | */ |
| 281 | void setSuppressFlyout(boolean suppressFlyout) { |
| 282 | mSuppressFlyout = suppressFlyout; |
Mady Mellor | df48d0a | 2019-06-25 18:26:46 -0700 | [diff] [blame] | 283 | } |
| 284 | |
| 285 | /** |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 286 | * Returns whether the notification for this bubble is a foreground service. It shows that this |
| 287 | * is an ongoing bubble. |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 288 | */ |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 289 | boolean isOngoing() { |
| 290 | int flags = mEntry.notification.getNotification().flags; |
| 291 | return (flags & Notification.FLAG_FOREGROUND_SERVICE) != 0; |
| 292 | } |
| 293 | |
| 294 | float getDesiredHeight(Context context) { |
| 295 | Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); |
| 296 | boolean useRes = data.getDesiredHeightResId() != 0; |
| 297 | if (useRes) { |
| 298 | return getDimenForPackageUser(context, data.getDesiredHeightResId(), |
| 299 | mEntry.notification.getPackageName(), |
| 300 | mEntry.notification.getUser().getIdentifier()); |
| 301 | } else { |
| 302 | return data.getDesiredHeight() |
| 303 | * context.getResources().getDisplayMetrics().density; |
| 304 | } |
| 305 | } |
| 306 | |
Mady Mellor | 70cba7bb | 2019-07-02 15:06:07 -0700 | [diff] [blame] | 307 | String getDesiredHeightString() { |
| 308 | Notification.BubbleMetadata data = mEntry.getBubbleMetadata(); |
| 309 | boolean useRes = data.getDesiredHeightResId() != 0; |
| 310 | if (useRes) { |
| 311 | return String.valueOf(data.getDesiredHeightResId()); |
| 312 | } else { |
| 313 | return String.valueOf(data.getDesiredHeight()); |
| 314 | } |
| 315 | } |
| 316 | |
Mady Mellor | 99a30260 | 2019-06-14 11:39:56 -0700 | [diff] [blame] | 317 | @Nullable |
| 318 | PendingIntent getBubbleIntent(Context context) { |
| 319 | Notification notif = mEntry.notification.getNotification(); |
| 320 | Notification.BubbleMetadata data = notif.getBubbleMetadata(); |
| 321 | if (BubbleController.canLaunchInActivityView(context, mEntry) && data != null) { |
| 322 | return data.getIntent(); |
| 323 | } |
| 324 | return null; |
| 325 | } |
| 326 | |
| 327 | Intent getSettingsIntent() { |
| 328 | final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_BUBBLE_SETTINGS); |
| 329 | intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName()); |
| 330 | intent.putExtra(Settings.EXTRA_APP_UID, mEntry.notification.getUid()); |
| 331 | intent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); |
| 332 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); |
| 333 | intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP); |
| 334 | return intent; |
| 335 | } |
| 336 | |
| 337 | /** |
| 338 | * Returns our best guess for the most relevant text summary of the latest update to this |
| 339 | * notification, based on its type. Returns null if there should not be an update message. |
| 340 | */ |
| 341 | CharSequence getUpdateMessage(Context context) { |
| 342 | final Notification underlyingNotif = mEntry.notification.getNotification(); |
| 343 | final Class<? extends Notification.Style> style = underlyingNotif.getNotificationStyle(); |
| 344 | |
| 345 | try { |
| 346 | if (Notification.BigTextStyle.class.equals(style)) { |
| 347 | // Return the big text, it is big so probably important. If it's not there use the |
| 348 | // normal text. |
| 349 | CharSequence bigText = |
| 350 | underlyingNotif.extras.getCharSequence(Notification.EXTRA_BIG_TEXT); |
| 351 | return !TextUtils.isEmpty(bigText) |
| 352 | ? bigText |
| 353 | : underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT); |
| 354 | } else if (Notification.MessagingStyle.class.equals(style)) { |
| 355 | final List<Notification.MessagingStyle.Message> messages = |
| 356 | Notification.MessagingStyle.Message.getMessagesFromBundleArray( |
| 357 | (Parcelable[]) underlyingNotif.extras.get( |
| 358 | Notification.EXTRA_MESSAGES)); |
| 359 | |
| 360 | final Notification.MessagingStyle.Message latestMessage = |
| 361 | Notification.MessagingStyle.findLatestIncomingMessage(messages); |
| 362 | |
| 363 | if (latestMessage != null) { |
| 364 | final CharSequence personName = latestMessage.getSenderPerson() != null |
| 365 | ? latestMessage.getSenderPerson().getName() |
| 366 | : null; |
| 367 | |
| 368 | // Prepend the sender name if available since group chats also use messaging |
| 369 | // style. |
| 370 | if (!TextUtils.isEmpty(personName)) { |
| 371 | return context.getResources().getString( |
| 372 | R.string.notification_summary_message_format, |
| 373 | personName, |
| 374 | latestMessage.getText()); |
| 375 | } else { |
| 376 | return latestMessage.getText(); |
| 377 | } |
| 378 | } |
| 379 | } else if (Notification.InboxStyle.class.equals(style)) { |
| 380 | CharSequence[] lines = |
| 381 | underlyingNotif.extras.getCharSequenceArray(Notification.EXTRA_TEXT_LINES); |
| 382 | |
| 383 | // Return the last line since it should be the most recent. |
| 384 | if (lines != null && lines.length > 0) { |
| 385 | return lines[lines.length - 1]; |
| 386 | } |
| 387 | } else if (Notification.MediaStyle.class.equals(style)) { |
| 388 | // Return nothing, media updates aren't typically useful as a text update. |
| 389 | return null; |
| 390 | } else { |
| 391 | // Default to text extra. |
| 392 | return underlyingNotif.extras.getCharSequence(Notification.EXTRA_TEXT); |
| 393 | } |
| 394 | } catch (ClassCastException | NullPointerException | ArrayIndexOutOfBoundsException e) { |
| 395 | // No use crashing, we'll just return null and the caller will assume there's no update |
| 396 | // message. |
| 397 | e.printStackTrace(); |
| 398 | } |
| 399 | |
| 400 | return null; |
| 401 | } |
| 402 | |
| 403 | private int getDimenForPackageUser(Context context, int resId, String pkg, int userId) { |
| 404 | PackageManager pm = context.getPackageManager(); |
| 405 | Resources r; |
| 406 | if (pkg != null) { |
| 407 | try { |
| 408 | if (userId == UserHandle.USER_ALL) { |
| 409 | userId = UserHandle.USER_SYSTEM; |
| 410 | } |
| 411 | r = pm.getResourcesForApplicationAsUser(pkg, userId); |
| 412 | return r.getDimensionPixelSize(resId); |
| 413 | } catch (PackageManager.NameNotFoundException ex) { |
| 414 | // Uninstalled, don't care |
| 415 | } catch (Resources.NotFoundException e) { |
| 416 | // Invalid res id, return 0 and user our default |
| 417 | Log.e(TAG, "Couldn't find desired height res id", e); |
| 418 | } |
| 419 | } |
| 420 | return 0; |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 421 | } |
| 422 | |
Mady Mellor | ce23c46 | 2019-06-17 17:30:07 -0700 | [diff] [blame] | 423 | private boolean shouldSuppressNotification() { |
| 424 | return mEntry.getBubbleMetadata() != null |
| 425 | && mEntry.getBubbleMetadata().isNotificationSuppressed(); |
| 426 | } |
| 427 | |
Mady Mellor | 70cba7bb | 2019-07-02 15:06:07 -0700 | [diff] [blame] | 428 | boolean shouldAutoExpand() { |
| 429 | Notification.BubbleMetadata metadata = mEntry.getBubbleMetadata(); |
| 430 | return metadata != null && metadata.getAutoExpandBubble(); |
| 431 | } |
| 432 | |
Mark Renouf | 9ba6cea | 2019-04-17 11:53:50 -0400 | [diff] [blame] | 433 | @Override |
| 434 | public String toString() { |
| 435 | return "Bubble{" + mKey + '}'; |
| 436 | } |
| 437 | |
Mady Mellor | 70cba7bb | 2019-07-02 15:06:07 -0700 | [diff] [blame] | 438 | /** |
| 439 | * Description of current bubble state. |
| 440 | */ |
| 441 | public void dump( |
| 442 | @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args) { |
| 443 | pw.print("key: "); pw.println(mKey); |
| 444 | pw.print(" showInShade: "); pw.println(showInShadeWhenBubble()); |
| 445 | pw.print(" showDot: "); pw.println(showBubbleDot()); |
| 446 | pw.print(" showFlyout: "); pw.println(showFlyoutForBubble()); |
| 447 | pw.print(" desiredHeight: "); pw.println(getDesiredHeightString()); |
| 448 | pw.print(" suppressNotif: "); pw.println(shouldSuppressNotification()); |
| 449 | pw.print(" autoExpand: "); pw.println(shouldAutoExpand()); |
| 450 | } |
| 451 | |
Mark Renouf | 71a3af6 | 2019-04-08 15:02:54 -0400 | [diff] [blame] | 452 | @Override |
| 453 | public boolean equals(Object o) { |
| 454 | if (this == o) return true; |
| 455 | if (!(o instanceof Bubble)) return false; |
| 456 | Bubble bubble = (Bubble) o; |
| 457 | return Objects.equals(mKey, bubble.mKey); |
| 458 | } |
| 459 | |
| 460 | @Override |
| 461 | public int hashCode() { |
| 462 | return Objects.hash(mKey); |
| 463 | } |
Mady Mellor | 3dff9e6 | 2019-02-05 18:12:53 -0800 | [diff] [blame] | 464 | } |