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