blob: ffb650d620648886bed9f2ca65705ffbb6cd0753 [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
Mady Mellor7f234902019-10-20 12:06:29 -070073 /**
74 * When true, if a notification has the information necessary to bubble (i.e. valid
75 * contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata}
76 * object will be created by the system and added to the notification.
Mady Mellorb547e5e2019-12-02 10:15:56 -080077 * <p>
78 * This does not produce a bubble, only adds the metadata based on the notification info.
Mady Mellor7f234902019-10-20 12:06:29 -070079 */
80 static boolean allowAnyNotifToBubble(Context context) {
81 return Settings.Secure.getInt(context.getContentResolver(),
82 ALLOW_ANY_NOTIF_TO_BUBBLE,
83 ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
84 }
85
86 /**
87 * Same as {@link #allowAnyNotifToBubble(Context)} except it filters for notifications that
88 * are using {@link Notification.MessagingStyle} and have remote input.
89 */
90 static boolean allowMessageNotifsToBubble(Context context) {
91 return Settings.Secure.getInt(context.getContentResolver(),
92 ALLOW_MESSAGE_NOTIFS_TO_BUBBLE,
93 ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
94 }
95
96 /**
Mady Mellorb547e5e2019-12-02 10:15:56 -080097 * When true, if the notification is able to bubble via {@link #allowAnyNotifToBubble(Context)}
98 * or {@link #allowMessageNotifsToBubble(Context)} or via normal BubbleMetadata, then a new
99 * BubbleMetadata object is constructed based on the shortcut info.
100 * <p>
101 * This does not produce a bubble, only adds the metadata based on shortcut info.
102 */
103 static boolean useShortcutInfoToBubble(Context context) {
104 return Settings.Secure.getInt(context.getContentResolver(),
105 ALLOW_SHORTCUTS_TO_BUBBLE,
106 ALLOW_SHORTCUT_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
107 }
108
109 /**
Mady Mellorea13b232019-12-05 15:55:46 -0800110 * Returns whether the provided package is whitelisted to bubble.
111 */
112 static boolean isPackageWhitelistedToAutoBubble(Context context, String packageName) {
113 String unsplitList = Settings.Secure.getString(context.getContentResolver(),
114 WHITELISTED_AUTO_BUBBLE_APPS);
115 if (unsplitList != null) {
116 // We expect the list to be separated by commas and no white space (but we trim in case)
117 String[] packageList = unsplitList.split(",");
118 for (int i = 0; i < packageList.length; i++) {
119 if (packageList[i].trim().equals(packageName)) {
120 return true;
121 }
122 }
123 }
124 return false;
125 }
126
127 /**
Mady Mellor7f234902019-10-20 12:06:29 -0700128 * If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds
129 * {@link android.app.Notification.BubbleMetadata} to the notification entry as long as
130 * the notification has necessary info for BubbleMetadata.
Mady Mellor30672942019-12-04 15:43:19 -0800131 *
132 * @return whether an adjustment was made.
Mady Mellor7f234902019-10-20 12:06:29 -0700133 */
Mady Mellor30672942019-12-04 15:43:19 -0800134 static boolean adjustForExperiments(Context context, NotificationEntry entry,
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800135 boolean previouslyUserCreated, boolean userBlocked) {
Mady Mellorb547e5e2019-12-02 10:15:56 -0800136 Notification.BubbleMetadata metadata = null;
137 boolean addedMetadata = false;
Mady Mellorea13b232019-12-05 15:55:46 -0800138 boolean whiteListedToAutoBubble =
139 isPackageWhitelistedToAutoBubble(context, entry.getSbn().getPackageName());
Mady Mellor7f234902019-10-20 12:06:29 -0700140
141 Notification notification = entry.getSbn().getNotification();
142 boolean isMessage = Notification.MessagingStyle.class.equals(
143 notification.getNotificationStyle());
144 boolean bubbleNotifForExperiment = (isMessage && allowMessageNotifsToBubble(context))
145 || allowAnyNotifToBubble(context);
146
Mady Mellorb547e5e2019-12-02 10:15:56 -0800147 boolean useShortcutInfo = useShortcutInfoToBubble(context);
148 String shortcutId = entry.getSbn().getNotification().getShortcutId();
149
Mady Mellorff076eb2019-11-13 10:12:06 -0800150 boolean hasMetadata = entry.getBubbleMetadata() != null;
151 if ((!hasMetadata && (previouslyUserCreated || bubbleNotifForExperiment))
152 || useShortcutInfo) {
153 if (DEBUG_EXPERIMENTS) {
154 Log.d(TAG, "Adjusting " + entry.getKey() + " for bubble experiment."
155 + " allowMessages=" + allowMessageNotifsToBubble(context)
156 + " isMessage=" + isMessage
157 + " allowNotifs=" + allowAnyNotifToBubble(context)
158 + " useShortcutInfo=" + useShortcutInfo
159 + " previouslyUserCreated=" + previouslyUserCreated);
160 }
161 }
162
Mady Mellorb547e5e2019-12-02 10:15:56 -0800163 if (useShortcutInfo && shortcutId != null) {
164 // We don't actually get anything useful from ShortcutInfo so just check existence
165 ShortcutInfo info = getShortcutInfo(context, entry.getSbn().getPackageName(),
166 entry.getSbn().getUser(), shortcutId);
167 if (info != null) {
Mady Mellor2ac2d3a2020-01-08 17:18:54 -0800168 metadata = createForShortcut(shortcutId);
Mady Mellorb547e5e2019-12-02 10:15:56 -0800169 }
170
171 // Replace existing metadata with shortcut, or we're bubbling for experiment
Mady Mellorff076eb2019-11-13 10:12:06 -0800172 boolean shouldBubble = entry.getBubbleMetadata() != null
173 || bubbleNotifForExperiment
174 || previouslyUserCreated;
Mady Mellorb547e5e2019-12-02 10:15:56 -0800175 if (shouldBubble && metadata != null) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800176 if (DEBUG_EXPERIMENTS) {
177 Log.d(TAG, "Adding experimental shortcut bubble for: " + entry.getKey());
178 }
Mady Mellorb547e5e2019-12-02 10:15:56 -0800179 entry.setBubbleMetadata(metadata);
180 addedMetadata = true;
181 }
Mady Mellor7f234902019-10-20 12:06:29 -0700182 }
183
Mady Mellorb547e5e2019-12-02 10:15:56 -0800184 // Didn't get metadata from a shortcut & we're bubbling for experiment
Mady Mellorff076eb2019-11-13 10:12:06 -0800185 if (entry.getBubbleMetadata() == null
186 && (bubbleNotifForExperiment || previouslyUserCreated)) {
Mady Mellorb547e5e2019-12-02 10:15:56 -0800187 metadata = createFromNotif(context, entry);
188 if (metadata != null) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800189 if (DEBUG_EXPERIMENTS) {
190 Log.d(TAG, "Adding experimental notification bubble for: " + entry.getKey());
191 }
Mady Mellorb547e5e2019-12-02 10:15:56 -0800192 entry.setBubbleMetadata(metadata);
193 addedMetadata = true;
194 }
195 }
196
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800197 boolean bubbleForWhitelist = !userBlocked
198 && whiteListedToAutoBubble
199 && (addedMetadata || hasMetadata);
Mady Mellorea13b232019-12-05 15:55:46 -0800200 if ((previouslyUserCreated && addedMetadata) || bubbleForWhitelist) {
201 // Update to a previous bubble (or new autobubble), set its flag now.
Mady Mellorff076eb2019-11-13 10:12:06 -0800202 if (DEBUG_EXPERIMENTS) {
203 Log.d(TAG, "Setting FLAG_BUBBLE for: " + entry.getKey());
204 }
Mady Mellor7f234902019-10-20 12:06:29 -0700205 entry.setFlagBubble(true);
Mady Mellor30672942019-12-04 15:43:19 -0800206 return true;
Mady Mellor7f234902019-10-20 12:06:29 -0700207 }
Mady Mellor30672942019-12-04 15:43:19 -0800208 return addedMetadata;
Mady Mellor7f234902019-10-20 12:06:29 -0700209 }
Mady Mellorb547e5e2019-12-02 10:15:56 -0800210
211 static Notification.BubbleMetadata createFromNotif(Context context, NotificationEntry entry) {
212 Notification notification = entry.getSbn().getNotification();
213 final PendingIntent intent = notification.contentIntent;
Mady Mellor89eb3482019-11-13 17:32:01 -0800214 Icon icon = null;
215 // Use the icon of the person if available
216 List<Person> personList = getPeopleFromNotification(entry);
217 if (personList.size() > 0) {
Joshua Tsuji22f90a02019-12-11 14:41:10 -0500218 final Person person = personList.get(0);
Joshua Tsuji22f90a02019-12-11 14:41:10 -0500219 if (person != null) {
220 icon = person.getIcon();
Mady Mellor58dc5192019-12-16 13:49:56 -0800221 if (icon == null) {
222 // Lets try and grab the icon constructed by the layout
223 Drawable d = PeopleHubNotificationListenerKt.extractAvatarFromRow(entry);
224 if (d instanceof BitmapDrawable) {
225 icon = Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
226 }
227 }
Joshua Tsuji22f90a02019-12-11 14:41:10 -0500228 }
Mady Mellor89eb3482019-11-13 17:32:01 -0800229 }
230 if (icon == null) {
Mady Mellor58dc5192019-12-16 13:49:56 -0800231 boolean shouldTint = notification.getLargeIcon() == null;
232 icon = shouldTint
233 ? notification.getSmallIcon()
234 : notification.getLargeIcon();
235 if (shouldTint) {
236 int notifColor = entry.getSbn().getNotification().color;
237 notifColor = ColorUtils.setAlphaComponent(notifColor, 255);
238 notifColor = ContrastColorUtil.findContrastColor(notifColor, Color.WHITE,
239 true /* findFg */, 3f);
240 icon.setTint(notifColor);
241 }
Mady Mellor89eb3482019-11-13 17:32:01 -0800242 }
Mady Mellor30672942019-12-04 15:43:19 -0800243 if (intent != null) {
Mady Melloraa9ce172020-03-17 10:34:20 -0700244 return new Notification.BubbleMetadata.Builder(intent, icon)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800245 .setDesiredHeight(BUBBLE_HEIGHT)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800246 .build();
247 }
248 return null;
249 }
250
Mady Mellor2ac2d3a2020-01-08 17:18:54 -0800251 static Notification.BubbleMetadata createForShortcut(String shortcutId) {
Mady Melloraa9ce172020-03-17 10:34:20 -0700252 return new Notification.BubbleMetadata.Builder(shortcutId)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800253 .setDesiredHeight(BUBBLE_HEIGHT)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800254 .build();
255 }
256
257 static ShortcutInfo getShortcutInfo(Context context, String packageName, UserHandle user,
258 String shortcutId) {
259 LauncherApps launcherAppService =
260 (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
261 LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
262 if (packageName != null) {
263 query.setPackage(packageName);
264 }
265 if (shortcutId != null) {
266 query.setShortcutIds(Arrays.asList(shortcutId));
267 }
268 query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST);
269 List<ShortcutInfo> shortcuts = launcherAppService.getShortcuts(query, user);
270 return shortcuts != null && shortcuts.size() > 0
271 ? shortcuts.get(0)
272 : null;
273 }
274
Mady Mellor89eb3482019-11-13 17:32:01 -0800275 static List<Person> getPeopleFromNotification(NotificationEntry entry) {
276 Bundle extras = entry.getSbn().getNotification().extras;
277 ArrayList<Person> personList = new ArrayList<>();
278 if (extras == null) {
279 return personList;
280 }
281
282 List<Person> p = extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST);
283
284 if (p != null) {
285 personList.addAll(p);
286 }
287
288 if (Notification.MessagingStyle.class.equals(
289 entry.getSbn().getNotification().getNotificationStyle())) {
290 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
291 if (!ArrayUtils.isEmpty(messages)) {
292 for (Notification.MessagingStyle.Message message :
293 Notification.MessagingStyle.Message
294 .getMessagesFromBundleArray(messages)) {
295 personList.add(message.getSenderPerson());
296 }
297 }
298 }
299 return personList;
300 }
Mady Mellor7f234902019-10-20 12:06:29 -0700301}