blob: 2060391d38a3e77363edc09579f9d69f3b74f2e8 [file] [log] [blame]
Mady Mellor7f234902019-10-20 12:06:29 -07001/*
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
Mady Mellor89eb3482019-11-13 17:32:01 -080019import static android.app.Notification.EXTRA_MESSAGES;
Mady Mellorb547e5e2019-12-02 10:15:56 -080020import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC;
21import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST;
22import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED;
23
Mady Mellorff076eb2019-11-13 10:12:06 -080024import static com.android.systemui.bubbles.BubbleDebugConfig.DEBUG_EXPERIMENTS;
25import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_BUBBLES;
26import static com.android.systemui.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
Mady Mellorb547e5e2019-12-02 10:15:56 -080027
Mady Mellor7f234902019-10-20 12:06:29 -070028import android.app.Notification;
29import android.app.PendingIntent;
Mady Mellor89eb3482019-11-13 17:32:01 -080030import android.app.Person;
Mady Mellor7f234902019-10-20 12:06:29 -070031import android.content.Context;
Mady Mellorb547e5e2019-12-02 10:15:56 -080032import android.content.pm.LauncherApps;
33import android.content.pm.ShortcutInfo;
Mady Mellor58dc5192019-12-16 13:49:56 -080034import android.graphics.Color;
35import android.graphics.drawable.BitmapDrawable;
36import android.graphics.drawable.Drawable;
Mady Mellor7f234902019-10-20 12:06:29 -070037import android.graphics.drawable.Icon;
Mady Mellor89eb3482019-11-13 17:32:01 -080038import android.os.Bundle;
39import android.os.Parcelable;
Mady Mellorb547e5e2019-12-02 10:15:56 -080040import android.os.UserHandle;
Mady Mellor7f234902019-10-20 12:06:29 -070041import android.provider.Settings;
Mady Mellorff076eb2019-11-13 10:12:06 -080042import android.util.Log;
Mady Mellor7f234902019-10-20 12:06:29 -070043
Mady Mellor58dc5192019-12-16 13:49:56 -080044import com.android.internal.graphics.ColorUtils;
Mady Mellor89eb3482019-11-13 17:32:01 -080045import com.android.internal.util.ArrayUtils;
Mady Mellor58dc5192019-12-16 13:49:56 -080046import com.android.internal.util.ContrastColorUtil;
Mady Mellor7f234902019-10-20 12:06:29 -070047import com.android.systemui.statusbar.notification.collection.NotificationEntry;
Mady Mellor58dc5192019-12-16 13:49:56 -080048import com.android.systemui.statusbar.notification.people.PeopleHubNotificationListenerKt;
Mady Mellor7f234902019-10-20 12:06:29 -070049
Mady Mellor89eb3482019-11-13 17:32:01 -080050import java.util.ArrayList;
Mady Mellorb547e5e2019-12-02 10:15:56 -080051import java.util.Arrays;
52import java.util.List;
53
Mady Mellor7f234902019-10-20 12:06:29 -070054/**
55 * Common class for experiments controlled via secure settings.
56 */
57public class BubbleExperimentConfig {
Mady Mellorff076eb2019-11-13 10:12:06 -080058 private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleController" : TAG_BUBBLES;
Mady Mellor7f234902019-10-20 12:06:29 -070059
Mady Mellorb547e5e2019-12-02 10:15:56 -080060 private static final int BUBBLE_HEIGHT = 10000;
61
Mady Mellor7f234902019-10-20 12:06:29 -070062 private static final String ALLOW_ANY_NOTIF_TO_BUBBLE = "allow_any_notif_to_bubble";
63 private static final boolean ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT = false;
64
65 private static final String ALLOW_MESSAGE_NOTIFS_TO_BUBBLE = "allow_message_notifs_to_bubble";
Mady Mellordd6fe612020-04-15 11:47:55 -070066 private static final boolean ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT = false;
Mady Mellor7f234902019-10-20 12:06:29 -070067
Mady Mellorb547e5e2019-12-02 10:15:56 -080068 private static final String ALLOW_SHORTCUTS_TO_BUBBLE = "allow_shortcuts_to_bubble";
69 private static final boolean ALLOW_SHORTCUT_TO_BUBBLE_DEFAULT = false;
70
Mady Mellorea13b232019-12-05 15:55:46 -080071 private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps";
72
Lyn Han8cc4bf82020-03-05 16:34:37 -080073 private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow";
74 private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = false;
75
Mady Mellor7f234902019-10-20 12:06:29 -070076 /**
77 * When true, if a notification has the information necessary to bubble (i.e. valid
78 * contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata}
79 * object will be created by the system and added to the notification.
Mady Mellorb547e5e2019-12-02 10:15:56 -080080 * <p>
81 * This does not produce a bubble, only adds the metadata based on the notification info.
Mady Mellor7f234902019-10-20 12:06:29 -070082 */
83 static boolean allowAnyNotifToBubble(Context context) {
84 return Settings.Secure.getInt(context.getContentResolver(),
85 ALLOW_ANY_NOTIF_TO_BUBBLE,
86 ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
87 }
88
89 /**
Lyn Han8cc4bf82020-03-05 16:34:37 -080090 * When true, show a menu with dismissed and aged-out bubbles.
91 */
92 static boolean allowBubbleOverflow(Context context) {
93 return Settings.Secure.getInt(context.getContentResolver(),
94 ALLOW_BUBBLE_OVERFLOW,
95 ALLOW_BUBBLE_OVERFLOW_DEFAULT ? 1 : 0) != 0;
96 }
97
98 /**
Mady Mellor7f234902019-10-20 12:06:29 -070099 * Same as {@link #allowAnyNotifToBubble(Context)} except it filters for notifications that
100 * are using {@link Notification.MessagingStyle} and have remote input.
101 */
102 static boolean allowMessageNotifsToBubble(Context context) {
103 return Settings.Secure.getInt(context.getContentResolver(),
104 ALLOW_MESSAGE_NOTIFS_TO_BUBBLE,
105 ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
106 }
107
108 /**
Mady Mellorb547e5e2019-12-02 10:15:56 -0800109 * When true, if the notification is able to bubble via {@link #allowAnyNotifToBubble(Context)}
110 * or {@link #allowMessageNotifsToBubble(Context)} or via normal BubbleMetadata, then a new
111 * BubbleMetadata object is constructed based on the shortcut info.
112 * <p>
113 * This does not produce a bubble, only adds the metadata based on shortcut info.
114 */
115 static boolean useShortcutInfoToBubble(Context context) {
116 return Settings.Secure.getInt(context.getContentResolver(),
117 ALLOW_SHORTCUTS_TO_BUBBLE,
118 ALLOW_SHORTCUT_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
119 }
120
121 /**
Mady Mellorea13b232019-12-05 15:55:46 -0800122 * Returns whether the provided package is whitelisted to bubble.
123 */
124 static boolean isPackageWhitelistedToAutoBubble(Context context, String packageName) {
125 String unsplitList = Settings.Secure.getString(context.getContentResolver(),
126 WHITELISTED_AUTO_BUBBLE_APPS);
127 if (unsplitList != null) {
128 // We expect the list to be separated by commas and no white space (but we trim in case)
129 String[] packageList = unsplitList.split(",");
130 for (int i = 0; i < packageList.length; i++) {
131 if (packageList[i].trim().equals(packageName)) {
132 return true;
133 }
134 }
135 }
136 return false;
137 }
138
139 /**
Mady Mellor7f234902019-10-20 12:06:29 -0700140 * If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds
141 * {@link android.app.Notification.BubbleMetadata} to the notification entry as long as
142 * the notification has necessary info for BubbleMetadata.
Mady Mellor30672942019-12-04 15:43:19 -0800143 *
144 * @return whether an adjustment was made.
Mady Mellor7f234902019-10-20 12:06:29 -0700145 */
Mady Mellor30672942019-12-04 15:43:19 -0800146 static boolean adjustForExperiments(Context context, NotificationEntry entry,
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800147 boolean previouslyUserCreated, boolean userBlocked) {
Mady Mellorb547e5e2019-12-02 10:15:56 -0800148 Notification.BubbleMetadata metadata = null;
149 boolean addedMetadata = false;
Mady Mellorea13b232019-12-05 15:55:46 -0800150 boolean whiteListedToAutoBubble =
151 isPackageWhitelistedToAutoBubble(context, entry.getSbn().getPackageName());
Mady Mellor7f234902019-10-20 12:06:29 -0700152
153 Notification notification = entry.getSbn().getNotification();
154 boolean isMessage = Notification.MessagingStyle.class.equals(
155 notification.getNotificationStyle());
156 boolean bubbleNotifForExperiment = (isMessage && allowMessageNotifsToBubble(context))
157 || allowAnyNotifToBubble(context);
158
Mady Mellorb547e5e2019-12-02 10:15:56 -0800159 boolean useShortcutInfo = useShortcutInfoToBubble(context);
160 String shortcutId = entry.getSbn().getNotification().getShortcutId();
161
Mady Mellorff076eb2019-11-13 10:12:06 -0800162 boolean hasMetadata = entry.getBubbleMetadata() != null;
163 if ((!hasMetadata && (previouslyUserCreated || bubbleNotifForExperiment))
164 || useShortcutInfo) {
165 if (DEBUG_EXPERIMENTS) {
166 Log.d(TAG, "Adjusting " + entry.getKey() + " for bubble experiment."
167 + " allowMessages=" + allowMessageNotifsToBubble(context)
168 + " isMessage=" + isMessage
169 + " allowNotifs=" + allowAnyNotifToBubble(context)
170 + " useShortcutInfo=" + useShortcutInfo
171 + " previouslyUserCreated=" + previouslyUserCreated);
172 }
173 }
174
Mady Mellorb547e5e2019-12-02 10:15:56 -0800175 if (useShortcutInfo && shortcutId != null) {
176 // We don't actually get anything useful from ShortcutInfo so just check existence
177 ShortcutInfo info = getShortcutInfo(context, entry.getSbn().getPackageName(),
178 entry.getSbn().getUser(), shortcutId);
179 if (info != null) {
Mady Mellor2ac2d3a2020-01-08 17:18:54 -0800180 metadata = createForShortcut(shortcutId);
Mady Mellorb547e5e2019-12-02 10:15:56 -0800181 }
182
183 // Replace existing metadata with shortcut, or we're bubbling for experiment
Mady Mellorff076eb2019-11-13 10:12:06 -0800184 boolean shouldBubble = entry.getBubbleMetadata() != null
185 || bubbleNotifForExperiment
186 || previouslyUserCreated;
Mady Mellorb547e5e2019-12-02 10:15:56 -0800187 if (shouldBubble && metadata != null) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800188 if (DEBUG_EXPERIMENTS) {
189 Log.d(TAG, "Adding experimental shortcut bubble for: " + entry.getKey());
190 }
Mady Mellorb547e5e2019-12-02 10:15:56 -0800191 entry.setBubbleMetadata(metadata);
192 addedMetadata = true;
193 }
Mady Mellor7f234902019-10-20 12:06:29 -0700194 }
195
Mady Mellorb547e5e2019-12-02 10:15:56 -0800196 // Didn't get metadata from a shortcut & we're bubbling for experiment
Mady Mellorff076eb2019-11-13 10:12:06 -0800197 if (entry.getBubbleMetadata() == null
198 && (bubbleNotifForExperiment || previouslyUserCreated)) {
Mady Mellorb547e5e2019-12-02 10:15:56 -0800199 metadata = createFromNotif(context, entry);
200 if (metadata != null) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800201 if (DEBUG_EXPERIMENTS) {
202 Log.d(TAG, "Adding experimental notification bubble for: " + entry.getKey());
203 }
Mady Mellorb547e5e2019-12-02 10:15:56 -0800204 entry.setBubbleMetadata(metadata);
205 addedMetadata = true;
206 }
207 }
208
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800209 boolean bubbleForWhitelist = !userBlocked
210 && whiteListedToAutoBubble
211 && (addedMetadata || hasMetadata);
Mady Mellorea13b232019-12-05 15:55:46 -0800212 if ((previouslyUserCreated && addedMetadata) || bubbleForWhitelist) {
213 // Update to a previous bubble (or new autobubble), set its flag now.
Mady Mellorff076eb2019-11-13 10:12:06 -0800214 if (DEBUG_EXPERIMENTS) {
215 Log.d(TAG, "Setting FLAG_BUBBLE for: " + entry.getKey());
216 }
Mady Mellor7f234902019-10-20 12:06:29 -0700217 entry.setFlagBubble(true);
Mady Mellor30672942019-12-04 15:43:19 -0800218 return true;
Mady Mellor7f234902019-10-20 12:06:29 -0700219 }
Mady Mellor30672942019-12-04 15:43:19 -0800220 return addedMetadata;
Mady Mellor7f234902019-10-20 12:06:29 -0700221 }
Mady Mellorb547e5e2019-12-02 10:15:56 -0800222
223 static Notification.BubbleMetadata createFromNotif(Context context, NotificationEntry entry) {
224 Notification notification = entry.getSbn().getNotification();
225 final PendingIntent intent = notification.contentIntent;
Mady Mellor89eb3482019-11-13 17:32:01 -0800226 Icon icon = null;
227 // Use the icon of the person if available
228 List<Person> personList = getPeopleFromNotification(entry);
229 if (personList.size() > 0) {
Joshua Tsuji22f90a02019-12-11 14:41:10 -0500230 final Person person = personList.get(0);
Joshua Tsuji22f90a02019-12-11 14:41:10 -0500231 if (person != null) {
232 icon = person.getIcon();
Mady Mellor58dc5192019-12-16 13:49:56 -0800233 if (icon == null) {
234 // Lets try and grab the icon constructed by the layout
235 Drawable d = PeopleHubNotificationListenerKt.extractAvatarFromRow(entry);
236 if (d instanceof BitmapDrawable) {
237 icon = Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
238 }
239 }
Joshua Tsuji22f90a02019-12-11 14:41:10 -0500240 }
Mady Mellor89eb3482019-11-13 17:32:01 -0800241 }
242 if (icon == null) {
Mady Mellor58dc5192019-12-16 13:49:56 -0800243 boolean shouldTint = notification.getLargeIcon() == null;
244 icon = shouldTint
245 ? notification.getSmallIcon()
246 : notification.getLargeIcon();
247 if (shouldTint) {
248 int notifColor = entry.getSbn().getNotification().color;
249 notifColor = ColorUtils.setAlphaComponent(notifColor, 255);
250 notifColor = ContrastColorUtil.findContrastColor(notifColor, Color.WHITE,
251 true /* findFg */, 3f);
252 icon.setTint(notifColor);
253 }
Mady Mellor89eb3482019-11-13 17:32:01 -0800254 }
Mady Mellor30672942019-12-04 15:43:19 -0800255 if (intent != null) {
Mady Melloraa9ce172020-03-17 10:34:20 -0700256 return new Notification.BubbleMetadata.Builder(intent, icon)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800257 .setDesiredHeight(BUBBLE_HEIGHT)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800258 .build();
259 }
260 return null;
261 }
262
Mady Mellor2ac2d3a2020-01-08 17:18:54 -0800263 static Notification.BubbleMetadata createForShortcut(String shortcutId) {
Mady Melloraa9ce172020-03-17 10:34:20 -0700264 return new Notification.BubbleMetadata.Builder(shortcutId)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800265 .setDesiredHeight(BUBBLE_HEIGHT)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800266 .build();
267 }
268
269 static ShortcutInfo getShortcutInfo(Context context, String packageName, UserHandle user,
270 String shortcutId) {
271 LauncherApps launcherAppService =
272 (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
273 LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
274 if (packageName != null) {
275 query.setPackage(packageName);
276 }
277 if (shortcutId != null) {
278 query.setShortcutIds(Arrays.asList(shortcutId));
279 }
280 query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST);
281 List<ShortcutInfo> shortcuts = launcherAppService.getShortcuts(query, user);
282 return shortcuts != null && shortcuts.size() > 0
283 ? shortcuts.get(0)
284 : null;
285 }
286
Mady Mellor89eb3482019-11-13 17:32:01 -0800287 static List<Person> getPeopleFromNotification(NotificationEntry entry) {
288 Bundle extras = entry.getSbn().getNotification().extras;
289 ArrayList<Person> personList = new ArrayList<>();
290 if (extras == null) {
291 return personList;
292 }
293
294 List<Person> p = extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST);
295
296 if (p != null) {
297 personList.addAll(p);
298 }
299
300 if (Notification.MessagingStyle.class.equals(
301 entry.getSbn().getNotification().getNotificationStyle())) {
302 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
303 if (!ArrayUtils.isEmpty(messages)) {
304 for (Notification.MessagingStyle.Message message :
305 Notification.MessagingStyle.Message
306 .getMessagesFromBundleArray(messages)) {
307 personList.add(message.getSenderPerson());
308 }
309 }
310 }
311 return personList;
312 }
Mady Mellor7f234902019-10-20 12:06:29 -0700313}