blob: 41dbb489c2f63c9cc5beb07643ea97dd666ca1e1 [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 String SHORTCUT_DUMMY_INTENT = "bubble_experiment_shortcut_intent";
61 private static PendingIntent sDummyShortcutIntent;
62
63 private static final int BUBBLE_HEIGHT = 10000;
64
Mady Mellor7f234902019-10-20 12:06:29 -070065 private static final String ALLOW_ANY_NOTIF_TO_BUBBLE = "allow_any_notif_to_bubble";
66 private static final boolean ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT = false;
67
68 private static final String ALLOW_MESSAGE_NOTIFS_TO_BUBBLE = "allow_message_notifs_to_bubble";
Mady Mellor6ad86282019-12-05 14:33:42 -080069 private static final boolean ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT = true;
Mady Mellor7f234902019-10-20 12:06:29 -070070
Mady Mellorb547e5e2019-12-02 10:15:56 -080071 private static final String ALLOW_SHORTCUTS_TO_BUBBLE = "allow_shortcuts_to_bubble";
72 private static final boolean ALLOW_SHORTCUT_TO_BUBBLE_DEFAULT = false;
73
Mady Mellorea13b232019-12-05 15:55:46 -080074 private static final String WHITELISTED_AUTO_BUBBLE_APPS = "whitelisted_auto_bubble_apps";
75
Lyn Han8cc4bf82020-03-05 16:34:37 -080076 private static final String ALLOW_BUBBLE_OVERFLOW = "allow_bubble_overflow";
77 private static final boolean ALLOW_BUBBLE_OVERFLOW_DEFAULT = false;
78
Mady Mellor7f234902019-10-20 12:06:29 -070079 /**
80 * When true, if a notification has the information necessary to bubble (i.e. valid
81 * contentIntent and an icon or image), then a {@link android.app.Notification.BubbleMetadata}
82 * object will be created by the system and added to the notification.
Mady Mellorb547e5e2019-12-02 10:15:56 -080083 * <p>
84 * This does not produce a bubble, only adds the metadata based on the notification info.
Mady Mellor7f234902019-10-20 12:06:29 -070085 */
86 static boolean allowAnyNotifToBubble(Context context) {
87 return Settings.Secure.getInt(context.getContentResolver(),
88 ALLOW_ANY_NOTIF_TO_BUBBLE,
89 ALLOW_ANY_NOTIF_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
90 }
91
92 /**
Lyn Han8cc4bf82020-03-05 16:34:37 -080093 * When true, show a menu with dismissed and aged-out bubbles.
94 */
95 static boolean allowBubbleOverflow(Context context) {
96 return Settings.Secure.getInt(context.getContentResolver(),
97 ALLOW_BUBBLE_OVERFLOW,
98 ALLOW_BUBBLE_OVERFLOW_DEFAULT ? 1 : 0) != 0;
99 }
100
101 /**
Mady Mellor7f234902019-10-20 12:06:29 -0700102 * Same as {@link #allowAnyNotifToBubble(Context)} except it filters for notifications that
103 * are using {@link Notification.MessagingStyle} and have remote input.
104 */
105 static boolean allowMessageNotifsToBubble(Context context) {
106 return Settings.Secure.getInt(context.getContentResolver(),
107 ALLOW_MESSAGE_NOTIFS_TO_BUBBLE,
108 ALLOW_MESSAGE_NOTIFS_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
109 }
110
111 /**
Mady Mellorb547e5e2019-12-02 10:15:56 -0800112 * When true, if the notification is able to bubble via {@link #allowAnyNotifToBubble(Context)}
113 * or {@link #allowMessageNotifsToBubble(Context)} or via normal BubbleMetadata, then a new
114 * BubbleMetadata object is constructed based on the shortcut info.
115 * <p>
116 * This does not produce a bubble, only adds the metadata based on shortcut info.
117 */
118 static boolean useShortcutInfoToBubble(Context context) {
119 return Settings.Secure.getInt(context.getContentResolver(),
120 ALLOW_SHORTCUTS_TO_BUBBLE,
121 ALLOW_SHORTCUT_TO_BUBBLE_DEFAULT ? 1 : 0) != 0;
122 }
123
124 /**
Mady Mellorea13b232019-12-05 15:55:46 -0800125 * Returns whether the provided package is whitelisted to bubble.
126 */
127 static boolean isPackageWhitelistedToAutoBubble(Context context, String packageName) {
128 String unsplitList = Settings.Secure.getString(context.getContentResolver(),
129 WHITELISTED_AUTO_BUBBLE_APPS);
130 if (unsplitList != null) {
131 // We expect the list to be separated by commas and no white space (but we trim in case)
132 String[] packageList = unsplitList.split(",");
133 for (int i = 0; i < packageList.length; i++) {
134 if (packageList[i].trim().equals(packageName)) {
135 return true;
136 }
137 }
138 }
139 return false;
140 }
141
142 /**
Mady Mellor7f234902019-10-20 12:06:29 -0700143 * If {@link #allowAnyNotifToBubble(Context)} is true, this method creates and adds
144 * {@link android.app.Notification.BubbleMetadata} to the notification entry as long as
145 * the notification has necessary info for BubbleMetadata.
Mady Mellor30672942019-12-04 15:43:19 -0800146 *
147 * @return whether an adjustment was made.
Mady Mellor7f234902019-10-20 12:06:29 -0700148 */
Mady Mellor30672942019-12-04 15:43:19 -0800149 static boolean adjustForExperiments(Context context, NotificationEntry entry,
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800150 boolean previouslyUserCreated, boolean userBlocked) {
Mady Mellorb547e5e2019-12-02 10:15:56 -0800151 Notification.BubbleMetadata metadata = null;
152 boolean addedMetadata = false;
Mady Mellorea13b232019-12-05 15:55:46 -0800153 boolean whiteListedToAutoBubble =
154 isPackageWhitelistedToAutoBubble(context, entry.getSbn().getPackageName());
Mady Mellor7f234902019-10-20 12:06:29 -0700155
156 Notification notification = entry.getSbn().getNotification();
157 boolean isMessage = Notification.MessagingStyle.class.equals(
158 notification.getNotificationStyle());
159 boolean bubbleNotifForExperiment = (isMessage && allowMessageNotifsToBubble(context))
160 || allowAnyNotifToBubble(context);
161
Mady Mellorb547e5e2019-12-02 10:15:56 -0800162 boolean useShortcutInfo = useShortcutInfoToBubble(context);
163 String shortcutId = entry.getSbn().getNotification().getShortcutId();
164
Mady Mellorff076eb2019-11-13 10:12:06 -0800165 boolean hasMetadata = entry.getBubbleMetadata() != null;
166 if ((!hasMetadata && (previouslyUserCreated || bubbleNotifForExperiment))
167 || useShortcutInfo) {
168 if (DEBUG_EXPERIMENTS) {
169 Log.d(TAG, "Adjusting " + entry.getKey() + " for bubble experiment."
170 + " allowMessages=" + allowMessageNotifsToBubble(context)
171 + " isMessage=" + isMessage
172 + " allowNotifs=" + allowAnyNotifToBubble(context)
173 + " useShortcutInfo=" + useShortcutInfo
174 + " previouslyUserCreated=" + previouslyUserCreated);
175 }
176 }
177
Mady Mellorb547e5e2019-12-02 10:15:56 -0800178 if (useShortcutInfo && shortcutId != null) {
179 // We don't actually get anything useful from ShortcutInfo so just check existence
180 ShortcutInfo info = getShortcutInfo(context, entry.getSbn().getPackageName(),
181 entry.getSbn().getUser(), shortcutId);
182 if (info != null) {
Mady Mellor2ac2d3a2020-01-08 17:18:54 -0800183 metadata = createForShortcut(shortcutId);
Mady Mellorb547e5e2019-12-02 10:15:56 -0800184 }
185
186 // Replace existing metadata with shortcut, or we're bubbling for experiment
Mady Mellorff076eb2019-11-13 10:12:06 -0800187 boolean shouldBubble = entry.getBubbleMetadata() != null
188 || bubbleNotifForExperiment
189 || previouslyUserCreated;
Mady Mellorb547e5e2019-12-02 10:15:56 -0800190 if (shouldBubble && metadata != null) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800191 if (DEBUG_EXPERIMENTS) {
192 Log.d(TAG, "Adding experimental shortcut bubble for: " + entry.getKey());
193 }
Mady Mellorb547e5e2019-12-02 10:15:56 -0800194 entry.setBubbleMetadata(metadata);
195 addedMetadata = true;
196 }
Mady Mellor7f234902019-10-20 12:06:29 -0700197 }
198
Mady Mellorb547e5e2019-12-02 10:15:56 -0800199 // Didn't get metadata from a shortcut & we're bubbling for experiment
Mady Mellorff076eb2019-11-13 10:12:06 -0800200 if (entry.getBubbleMetadata() == null
201 && (bubbleNotifForExperiment || previouslyUserCreated)) {
Mady Mellorb547e5e2019-12-02 10:15:56 -0800202 metadata = createFromNotif(context, entry);
203 if (metadata != null) {
Mady Mellorff076eb2019-11-13 10:12:06 -0800204 if (DEBUG_EXPERIMENTS) {
205 Log.d(TAG, "Adding experimental notification bubble for: " + entry.getKey());
206 }
Mady Mellorb547e5e2019-12-02 10:15:56 -0800207 entry.setBubbleMetadata(metadata);
208 addedMetadata = true;
209 }
210 }
211
Mady Mellor3b86a4f2019-12-11 13:15:41 -0800212 boolean bubbleForWhitelist = !userBlocked
213 && whiteListedToAutoBubble
214 && (addedMetadata || hasMetadata);
Mady Mellorea13b232019-12-05 15:55:46 -0800215 if ((previouslyUserCreated && addedMetadata) || bubbleForWhitelist) {
216 // Update to a previous bubble (or new autobubble), set its flag now.
Mady Mellorff076eb2019-11-13 10:12:06 -0800217 if (DEBUG_EXPERIMENTS) {
218 Log.d(TAG, "Setting FLAG_BUBBLE for: " + entry.getKey());
219 }
Mady Mellor7f234902019-10-20 12:06:29 -0700220 entry.setFlagBubble(true);
Mady Mellor30672942019-12-04 15:43:19 -0800221 return true;
Mady Mellor7f234902019-10-20 12:06:29 -0700222 }
Mady Mellor30672942019-12-04 15:43:19 -0800223 return addedMetadata;
Mady Mellor7f234902019-10-20 12:06:29 -0700224 }
Mady Mellorb547e5e2019-12-02 10:15:56 -0800225
226 static Notification.BubbleMetadata createFromNotif(Context context, NotificationEntry entry) {
227 Notification notification = entry.getSbn().getNotification();
228 final PendingIntent intent = notification.contentIntent;
Mady Mellor89eb3482019-11-13 17:32:01 -0800229 Icon icon = null;
230 // Use the icon of the person if available
231 List<Person> personList = getPeopleFromNotification(entry);
232 if (personList.size() > 0) {
Joshua Tsuji22f90a02019-12-11 14:41:10 -0500233 final Person person = personList.get(0);
Joshua Tsuji22f90a02019-12-11 14:41:10 -0500234 if (person != null) {
235 icon = person.getIcon();
Mady Mellor58dc5192019-12-16 13:49:56 -0800236 if (icon == null) {
237 // Lets try and grab the icon constructed by the layout
238 Drawable d = PeopleHubNotificationListenerKt.extractAvatarFromRow(entry);
239 if (d instanceof BitmapDrawable) {
240 icon = Icon.createWithBitmap(((BitmapDrawable) d).getBitmap());
241 }
242 }
Joshua Tsuji22f90a02019-12-11 14:41:10 -0500243 }
Mady Mellor89eb3482019-11-13 17:32:01 -0800244 }
245 if (icon == null) {
Mady Mellor58dc5192019-12-16 13:49:56 -0800246 boolean shouldTint = notification.getLargeIcon() == null;
247 icon = shouldTint
248 ? notification.getSmallIcon()
249 : notification.getLargeIcon();
250 if (shouldTint) {
251 int notifColor = entry.getSbn().getNotification().color;
252 notifColor = ColorUtils.setAlphaComponent(notifColor, 255);
253 notifColor = ContrastColorUtil.findContrastColor(notifColor, Color.WHITE,
254 true /* findFg */, 3f);
255 icon.setTint(notifColor);
256 }
Mady Mellor89eb3482019-11-13 17:32:01 -0800257 }
Mady Mellor30672942019-12-04 15:43:19 -0800258 if (intent != null) {
Mady Melloraa9ce172020-03-17 10:34:20 -0700259 return new Notification.BubbleMetadata.Builder(intent, icon)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800260 .setDesiredHeight(BUBBLE_HEIGHT)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800261 .build();
262 }
263 return null;
264 }
265
Mady Mellor2ac2d3a2020-01-08 17:18:54 -0800266 static Notification.BubbleMetadata createForShortcut(String shortcutId) {
Mady Melloraa9ce172020-03-17 10:34:20 -0700267 return new Notification.BubbleMetadata.Builder(shortcutId)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800268 .setDesiredHeight(BUBBLE_HEIGHT)
Mady Mellorb547e5e2019-12-02 10:15:56 -0800269 .build();
270 }
271
272 static ShortcutInfo getShortcutInfo(Context context, String packageName, UserHandle user,
273 String shortcutId) {
274 LauncherApps launcherAppService =
275 (LauncherApps) context.getSystemService(Context.LAUNCHER_APPS_SERVICE);
276 LauncherApps.ShortcutQuery query = new LauncherApps.ShortcutQuery();
277 if (packageName != null) {
278 query.setPackage(packageName);
279 }
280 if (shortcutId != null) {
281 query.setShortcutIds(Arrays.asList(shortcutId));
282 }
283 query.setQueryFlags(FLAG_MATCH_DYNAMIC | FLAG_MATCH_PINNED | FLAG_MATCH_MANIFEST);
284 List<ShortcutInfo> shortcuts = launcherAppService.getShortcuts(query, user);
285 return shortcuts != null && shortcuts.size() > 0
286 ? shortcuts.get(0)
287 : null;
288 }
289
Mady Mellor89eb3482019-11-13 17:32:01 -0800290 static List<Person> getPeopleFromNotification(NotificationEntry entry) {
291 Bundle extras = entry.getSbn().getNotification().extras;
292 ArrayList<Person> personList = new ArrayList<>();
293 if (extras == null) {
294 return personList;
295 }
296
297 List<Person> p = extras.getParcelableArrayList(Notification.EXTRA_PEOPLE_LIST);
298
299 if (p != null) {
300 personList.addAll(p);
301 }
302
303 if (Notification.MessagingStyle.class.equals(
304 entry.getSbn().getNotification().getNotificationStyle())) {
305 final Parcelable[] messages = extras.getParcelableArray(EXTRA_MESSAGES);
306 if (!ArrayUtils.isEmpty(messages)) {
307 for (Notification.MessagingStyle.Message message :
308 Notification.MessagingStyle.Message
309 .getMessagesFromBundleArray(messages)) {
310 personList.add(message.getSenderPerson());
311 }
312 }
313 }
314 return personList;
315 }
Mady Mellor7f234902019-10-20 12:06:29 -0700316}