1 /* 2 * Copyright (C) 2017 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 17 package com.android.systemui.statusbar; 18 19 import static android.app.Notification.FLAG_BUBBLE; 20 import static android.app.NotificationManager.IMPORTANCE_DEFAULT; 21 import static android.app.NotificationManager.IMPORTANCE_HIGH; 22 23 import android.annotation.Nullable; 24 import android.app.ActivityManager; 25 import android.app.Instrumentation; 26 import android.app.Notification; 27 import android.app.Notification.BubbleMetadata; 28 import android.app.NotificationChannel; 29 import android.app.PendingIntent; 30 import android.content.Context; 31 import android.content.Intent; 32 import android.graphics.drawable.Icon; 33 import android.os.UserHandle; 34 import android.service.notification.StatusBarNotification; 35 import android.text.TextUtils; 36 import android.view.LayoutInflater; 37 import android.widget.RemoteViews; 38 39 import androidx.test.InstrumentationRegistry; 40 41 import com.android.systemui.R; 42 import com.android.systemui.bubbles.BubblesTestActivity; 43 import com.android.systemui.statusbar.notification.collection.NotificationEntry; 44 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow; 45 import com.android.systemui.statusbar.notification.row.NotificationContentInflater.InflationFlag; 46 import com.android.systemui.statusbar.notification.row.NotificationContentInflaterTest; 47 import com.android.systemui.statusbar.phone.HeadsUpManagerPhone; 48 import com.android.systemui.statusbar.phone.NotificationGroupManager; 49 import com.android.systemui.statusbar.policy.HeadsUpManager; 50 51 /** 52 * A helper class to create {@link ExpandableNotificationRow} (for both individual and group 53 * notifications). 54 */ 55 public class NotificationTestHelper { 56 57 /** Package name for testing purposes. */ 58 public static final String PKG = "com.android.systemui"; 59 /** System UI id for testing purposes. */ 60 public static final int UID = 1000; 61 /** Current {@link UserHandle} of the system. */ 62 public static final UserHandle USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser()); 63 64 private static final String GROUP_KEY = "gruKey"; 65 66 private final Context mContext; 67 private final Instrumentation mInstrumentation; 68 private int mId; 69 private final NotificationGroupManager mGroupManager = new NotificationGroupManager(); 70 private ExpandableNotificationRow mRow; 71 private HeadsUpManager mHeadsUpManager; 72 NotificationTestHelper(Context context)73 public NotificationTestHelper(Context context) { 74 mContext = context; 75 mInstrumentation = InstrumentationRegistry.getInstrumentation(); 76 mHeadsUpManager = new HeadsUpManagerPhone(mContext, null, mGroupManager, null, null); 77 mGroupManager.setHeadsUpManager(mHeadsUpManager); 78 } 79 80 /** 81 * Creates a generic row. 82 * 83 * @return a generic row with no special properties. 84 * @throws Exception 85 */ createRow()86 public ExpandableNotificationRow createRow() throws Exception { 87 return createRow(PKG, UID, USER_HANDLE); 88 } 89 90 /** 91 * Create a row with the package and user id specified. 92 * 93 * @param pkg package 94 * @param uid user id 95 * @return a row with a notification using the package and user id 96 * @throws Exception 97 */ createRow(String pkg, int uid, UserHandle userHandle)98 public ExpandableNotificationRow createRow(String pkg, int uid, UserHandle userHandle) 99 throws Exception { 100 return createRow(pkg, uid, userHandle, false /* isGroupSummary */, null /* groupKey */); 101 } 102 103 /** 104 * Creates a row based off the notification given. 105 * 106 * @param notification the notification 107 * @return a row built off the notification 108 * @throws Exception 109 */ createRow(Notification notification)110 public ExpandableNotificationRow createRow(Notification notification) throws Exception { 111 return generateRow(notification, PKG, UID, USER_HANDLE, 0 /* extraInflationFlags */); 112 } 113 114 /** 115 * Create a row with the specified content views inflated in addition to the default. 116 * 117 * @param extraInflationFlags the flags corresponding to the additional content views that 118 * should be inflated 119 * @return a row with the specified content views inflated in addition to the default 120 * @throws Exception 121 */ createRow(@nflationFlag int extraInflationFlags)122 public ExpandableNotificationRow createRow(@InflationFlag int extraInflationFlags) 123 throws Exception { 124 return generateRow(createNotification(), PKG, UID, USER_HANDLE, extraInflationFlags); 125 } 126 127 /** 128 * Returns an {@link ExpandableNotificationRow} group with the given number of child 129 * notifications. 130 */ createGroup(int numChildren)131 public ExpandableNotificationRow createGroup(int numChildren) throws Exception { 132 ExpandableNotificationRow row = createGroupSummary(GROUP_KEY); 133 for (int i = 0; i < numChildren; i++) { 134 ExpandableNotificationRow childRow = createGroupChild(GROUP_KEY); 135 row.addChildNotification(childRow); 136 } 137 return row; 138 } 139 140 /** Returns a group notification with 2 child notifications. */ createGroup()141 public ExpandableNotificationRow createGroup() throws Exception { 142 return createGroup(2); 143 } 144 createGroupSummary(String groupkey)145 private ExpandableNotificationRow createGroupSummary(String groupkey) throws Exception { 146 return createRow(PKG, UID, USER_HANDLE, true /* isGroupSummary */, groupkey); 147 } 148 createGroupChild(String groupkey)149 private ExpandableNotificationRow createGroupChild(String groupkey) throws Exception { 150 return createRow(PKG, UID, USER_HANDLE, false /* isGroupSummary */, groupkey); 151 } 152 153 /** 154 * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. 155 */ createBubble()156 public ExpandableNotificationRow createBubble() 157 throws Exception { 158 return createBubble(makeBubbleMetadata(null), PKG); 159 } 160 161 /** 162 * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. 163 * 164 * @param deleteIntent the intent to assign to {@link BubbleMetadata#deleteIntent} 165 */ createBubble(@ullable PendingIntent deleteIntent)166 public ExpandableNotificationRow createBubble(@Nullable PendingIntent deleteIntent) 167 throws Exception { 168 return createBubble(makeBubbleMetadata(deleteIntent), PKG); 169 } 170 171 /** 172 * Returns an {@link ExpandableNotificationRow} that should be shown as a bubble. 173 * 174 * @param bubbleMetadata the {@link BubbleMetadata} to use 175 */ createBubble(BubbleMetadata bubbleMetadata, String pkg)176 public ExpandableNotificationRow createBubble(BubbleMetadata bubbleMetadata, String pkg) 177 throws Exception { 178 Notification n = createNotification(false /* isGroupSummary */, 179 null /* groupKey */, bubbleMetadata); 180 n.flags |= FLAG_BUBBLE; 181 ExpandableNotificationRow row = generateRow(n, pkg, UID, USER_HANDLE, 182 0 /* extraInflationFlags */, IMPORTANCE_HIGH); 183 row.getEntry().canBubble = true; 184 return row; 185 } 186 187 /** 188 * Creates a notification row with the given details. 189 * 190 * @param pkg package used for creating a {@link StatusBarNotification} 191 * @param uid uid used for creating a {@link StatusBarNotification} 192 * @param isGroupSummary whether the notification row is a group summary 193 * @param groupKey the group key for the notification group used across notifications 194 * @return a row with that's either a standalone notification or a group notification if the 195 * groupKey is non-null 196 * @throws Exception 197 */ createRow( String pkg, int uid, UserHandle userHandle, boolean isGroupSummary, @Nullable String groupKey)198 private ExpandableNotificationRow createRow( 199 String pkg, 200 int uid, 201 UserHandle userHandle, 202 boolean isGroupSummary, 203 @Nullable String groupKey) 204 throws Exception { 205 Notification notif = createNotification(isGroupSummary, groupKey); 206 return generateRow(notif, pkg, uid, userHandle, 0 /* inflationFlags */); 207 } 208 209 /** 210 * Creates a generic notification. 211 * 212 * @return a notification with no special properties 213 */ createNotification()214 public Notification createNotification() { 215 return createNotification(false /* isGroupSummary */, null /* groupKey */); 216 } 217 218 /** 219 * Creates a notification with the given parameters. 220 * 221 * @param isGroupSummary whether the notification is a group summary 222 * @param groupKey the group key for the notification group used across notifications 223 * @return a notification that is in the group specified or standalone if unspecified 224 */ createNotification(boolean isGroupSummary, @Nullable String groupKey)225 private Notification createNotification(boolean isGroupSummary, @Nullable String groupKey) { 226 return createNotification(isGroupSummary, groupKey, null /* bubble metadata */); 227 } 228 229 /** 230 * Creates a notification with the given parameters. 231 * 232 * @param isGroupSummary whether the notification is a group summary 233 * @param groupKey the group key for the notification group used across notifications 234 * @param bubbleMetadata the bubble metadata to use for this notification if it exists. 235 * @return a notification that is in the group specified or standalone if unspecified 236 */ createNotification(boolean isGroupSummary, @Nullable String groupKey, @Nullable BubbleMetadata bubbleMetadata)237 public Notification createNotification(boolean isGroupSummary, 238 @Nullable String groupKey, @Nullable BubbleMetadata bubbleMetadata) { 239 Notification publicVersion = new Notification.Builder(mContext).setSmallIcon( 240 R.drawable.ic_person) 241 .setCustomContentView(new RemoteViews(mContext.getPackageName(), 242 R.layout.custom_view_dark)) 243 .build(); 244 Notification.Builder notificationBuilder = new Notification.Builder(mContext, "channelId") 245 .setSmallIcon(R.drawable.ic_person) 246 .setContentTitle("Title") 247 .setContentText("Text") 248 .setPublicVersion(publicVersion) 249 .setStyle(new Notification.BigTextStyle().bigText("Big Text")); 250 if (isGroupSummary) { 251 notificationBuilder.setGroupSummary(true); 252 } 253 if (!TextUtils.isEmpty(groupKey)) { 254 notificationBuilder.setGroup(groupKey); 255 } 256 if (bubbleMetadata != null) { 257 notificationBuilder.setBubbleMetadata(bubbleMetadata); 258 } 259 return notificationBuilder.build(); 260 } 261 generateRow( Notification notification, String pkg, int uid, UserHandle userHandle, @InflationFlag int extraInflationFlags)262 private ExpandableNotificationRow generateRow( 263 Notification notification, 264 String pkg, 265 int uid, 266 UserHandle userHandle, 267 @InflationFlag int extraInflationFlags) 268 throws Exception { 269 return generateRow(notification, pkg, uid, userHandle, extraInflationFlags, 270 IMPORTANCE_DEFAULT); 271 } 272 generateRow( Notification notification, String pkg, int uid, UserHandle userHandle, @InflationFlag int extraInflationFlags, int importance)273 private ExpandableNotificationRow generateRow( 274 Notification notification, 275 String pkg, 276 int uid, 277 UserHandle userHandle, 278 @InflationFlag int extraInflationFlags, 279 int importance) 280 throws Exception { 281 LayoutInflater inflater = (LayoutInflater) mContext.getSystemService( 282 mContext.LAYOUT_INFLATER_SERVICE); 283 mRow = (ExpandableNotificationRow) inflater.inflate( 284 R.layout.status_bar_notification_row, 285 null /* root */, 286 false /* attachToRoot */); 287 ExpandableNotificationRow row = mRow; 288 row.setGroupManager(mGroupManager); 289 row.setHeadsUpManager(mHeadsUpManager); 290 row.setAboveShelfChangedListener(aboveShelf -> {}); 291 StatusBarNotification sbn = new StatusBarNotification( 292 pkg, 293 pkg, 294 mId++, 295 null /* tag */, 296 uid, 297 2000 /* initialPid */, 298 notification, 299 userHandle, 300 null /* overrideGroupKey */, 301 System.currentTimeMillis()); 302 NotificationEntry entry = new NotificationEntry(sbn); 303 entry.setRow(row); 304 entry.createIcons(mContext, sbn); 305 entry.channel = new NotificationChannel( 306 notification.getChannelId(), notification.getChannelId(), importance); 307 entry.channel.setBlockableSystem(true); 308 row.setEntry(entry); 309 row.getNotificationInflater().addInflationFlags(extraInflationFlags); 310 NotificationContentInflaterTest.runThenWaitForInflation( 311 () -> row.inflateViews(), 312 row.getNotificationInflater()); 313 314 // This would be done as part of onAsyncInflationFinished, but we skip large amounts of 315 // the callback chain, so we need to make up for not adding it to the group manager 316 // here. 317 mGroupManager.onEntryAdded(entry); 318 return row; 319 } 320 makeBubbleMetadata(PendingIntent deleteIntent)321 private BubbleMetadata makeBubbleMetadata(PendingIntent deleteIntent) { 322 Intent target = new Intent(mContext, BubblesTestActivity.class); 323 PendingIntent bubbleIntent = PendingIntent.getActivity(mContext, 0, target, 0); 324 325 return new BubbleMetadata.Builder() 326 .setIntent(bubbleIntent) 327 .setDeleteIntent(deleteIntent) 328 .setIcon(Icon.createWithResource(mContext, R.drawable.android)) 329 .setDesiredHeight(314) 330 .build(); 331 } 332 } 333