• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 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 android.app.notification.current.cts;
18 
19 import static android.app.Notification.CATEGORY_CALL;
20 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
21 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALL;
22 
23 import android.app.ActivityManager;
24 import android.app.Instrumentation;
25 import android.app.Notification;
26 import android.app.NotificationChannel;
27 import android.app.NotificationChannelGroup;
28 import android.app.NotificationManager;
29 import android.app.PendingIntent;
30 import android.app.Person;
31 import android.app.role.RoleManager;
32 import android.app.stubs.BubbledActivity;
33 import android.app.stubs.R;
34 import android.app.stubs.shared.NotificationHelper;
35 import android.app.stubs.shared.NotificationHelper.SEARCH_TYPE;
36 import android.app.stubs.shared.TestNotificationAssistant;
37 import android.app.stubs.shared.TestNotificationListener;
38 import android.content.ComponentName;
39 import android.content.Intent;
40 import android.content.pm.PackageManager;
41 import android.content.pm.ShortcutInfo;
42 import android.content.pm.ShortcutManager;
43 import android.graphics.drawable.Icon;
44 import android.media.AudioManager;
45 import android.net.Uri;
46 import android.os.Bundle;
47 import android.os.SystemClock;
48 import android.provider.Telephony;
49 import android.test.AndroidTestCase;
50 import android.util.ArraySet;
51 
52 import androidx.test.platform.app.InstrumentationRegistry;
53 
54 import com.android.compatibility.common.util.AmUtils;
55 
56 import java.io.IOException;
57 import java.util.ArrayList;
58 import java.util.Arrays;
59 import java.util.Collections;
60 import java.util.List;
61 import java.util.Set;
62 
63 /* Base class for NotificationManager tests. Handles some of the common set up logic for tests. */
64 public abstract class BaseNotificationManagerTest extends AndroidTestCase {
65 
66     static final String STUB_PACKAGE_NAME = "android.app.stubs";
67     protected static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
68     protected static final String SHARE_SHORTCUT_CATEGORY =
69             "android.app.stubs.SHARE_SHORTCUT_CATEGORY";
70     protected static final String SHARE_SHORTCUT_ID = "shareShortcut";
71     // Constants for GetResultActivity and return codes from MatchesCallFilterTestActivity
72     // the permitted/not permitted values need to stay the same as in the test activity.
73     protected static final int REQUEST_CODE = 42;
74     protected static final String TEST_APP = "com.android.test.notificationapp";
75 
76     private static final String TAG = BaseNotificationManagerTest.class.getSimpleName();
77 
78     protected PackageManager mPackageManager;
79     protected AudioManager mAudioManager;
80     protected RoleManager mRoleManager;
81     protected NotificationManager mNotificationManager;
82     protected ActivityManager mActivityManager;
83     protected TestNotificationAssistant mAssistant;
84     protected TestNotificationListener mListener;
85     protected List<String> mRuleIds;
86     protected Instrumentation mInstrumentation;
87     protected NotificationHelper mNotificationHelper;
88 
89     @Override
setUp()90     protected void setUp() throws Exception {
91         super.setUp();
92         mNotificationManager = mContext.getSystemService(NotificationManager.class);
93         mNotificationHelper = new NotificationHelper(mContext);
94         // clear the deck so that our getActiveNotifications results are predictable
95         mNotificationManager.cancelAll();
96 
97         assertEquals("Previous test left system in a bad state ",
98                 0, mNotificationManager.getActiveNotifications().length);
99 
100         mNotificationManager.createNotificationChannel(new NotificationChannel(
101                 NOTIFICATION_CHANNEL_ID, "name", IMPORTANCE_DEFAULT));
102         mActivityManager = mContext.getSystemService(ActivityManager.class);
103         mPackageManager = mContext.getPackageManager();
104         mAudioManager = mContext.getSystemService(AudioManager.class);
105         mRoleManager = mContext.getSystemService(RoleManager.class);
106         mRuleIds = new ArrayList<>();
107 
108         // ensure listener access isn't allowed before test runs (other tests could put
109         // TestListener in an unexpected state)
110         mNotificationHelper.disableListener(STUB_PACKAGE_NAME);
111         mNotificationHelper.disableAssistant(STUB_PACKAGE_NAME);
112         mInstrumentation = InstrumentationRegistry.getInstrumentation();
113         toggleNotificationPolicyAccess(mContext.getPackageName(), mInstrumentation, true);
114         mNotificationManager.setInterruptionFilter(INTERRUPTION_FILTER_ALL);
115         toggleNotificationPolicyAccess(mContext.getPackageName(), mInstrumentation, false);
116 
117         // Ensure that the tests are exempt from global service-related rate limits
118         setEnableServiceNotificationRateLimit(false);
119     }
120 
121     @Override
tearDown()122     protected void tearDown() throws Exception {
123         super.tearDown();
124 
125         setEnableServiceNotificationRateLimit(true);
126 
127         mNotificationManager.cancelAll();
128         for (String id : mRuleIds) {
129             mNotificationManager.removeAutomaticZenRule(id);
130         }
131 
132         assertExpectedDndState(INTERRUPTION_FILTER_ALL);
133 
134         List<NotificationChannel> channels = mNotificationManager.getNotificationChannels();
135         // Delete all channels.
136         for (NotificationChannel nc : channels) {
137             if (NotificationChannel.DEFAULT_CHANNEL_ID.equals(nc.getId())) {
138                 continue;
139             }
140             mNotificationManager.deleteNotificationChannel(nc.getId());
141         }
142 
143         // Unsuspend package if it was suspended in the test
144         suspendPackage(mContext.getPackageName(), mInstrumentation, false);
145 
146         mNotificationHelper.disableListener(STUB_PACKAGE_NAME);
147         mNotificationHelper.disableAssistant(STUB_PACKAGE_NAME);
148         toggleNotificationPolicyAccess(mContext.getPackageName(), mInstrumentation, false);
149 
150         List<NotificationChannelGroup> groups = mNotificationManager.getNotificationChannelGroups();
151         // Delete all groups.
152         for (NotificationChannelGroup ncg : groups) {
153             mNotificationManager.deleteNotificationChannelGroup(ncg.getId());
154         }
155     }
156 
setUpNotifListener()157     protected void setUpNotifListener() {
158         try {
159             mListener = mNotificationHelper.enableListener(STUB_PACKAGE_NAME);
160             assertNotNull(mListener);
161             mListener.resetData();
162         } catch (Exception e) {
163         }
164     }
165 
toggleExternalListenerAccess(ComponentName listenerComponent, boolean on)166     protected void toggleExternalListenerAccess(ComponentName listenerComponent, boolean on)
167             throws IOException {
168         String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
169                 + listenerComponent.flattenToString();
170         mNotificationHelper.runCommand(command, InstrumentationRegistry.getInstrumentation());
171     }
172 
assertExpectedDndState(int expectedState)173     protected void assertExpectedDndState(int expectedState) {
174         int tries = 3;
175         for (int i = tries; i >= 0; i--) {
176             if (expectedState
177                     == mNotificationManager.getCurrentInterruptionFilter()) {
178                 break;
179             }
180             try {
181                 Thread.sleep(100);
182             } catch (InterruptedException e) {
183                 e.printStackTrace();
184             }
185         }
186 
187         assertEquals(expectedState, mNotificationManager.getCurrentInterruptionFilter());
188     }
189 
190     /** Creates a dynamic, longlived, sharing shortcut. Call {@link #deleteShortcuts()} after. */
createDynamicShortcut()191     protected void createDynamicShortcut() {
192         Person person = new Person.Builder()
193                 .setBot(false)
194                 .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
195                 .setName("BubbleBot")
196                 .setImportant(true)
197                 .build();
198 
199         Set<String> categorySet = new ArraySet<>();
200         categorySet.add(SHARE_SHORTCUT_CATEGORY);
201         Intent shortcutIntent = new Intent(mContext, BubbledActivity.class);
202         shortcutIntent.setAction(Intent.ACTION_VIEW);
203 
204         ShortcutInfo shortcut = new ShortcutInfo.Builder(mContext, SHARE_SHORTCUT_ID)
205                 .setShortLabel(SHARE_SHORTCUT_ID)
206                 .setIcon(Icon.createWithResource(mContext, R.drawable.icon_black))
207                 .setIntent(shortcutIntent)
208                 .setPerson(person)
209                 .setCategories(categorySet)
210                 .setLongLived(true)
211                 .build();
212 
213         ShortcutManager scManager = mContext.getSystemService(ShortcutManager.class);
214         scManager.addDynamicShortcuts(Arrays.asList(shortcut));
215     }
216 
deleteShortcuts()217     protected void deleteShortcuts() {
218         ShortcutManager scManager = mContext.getSystemService(ShortcutManager.class);
219         scManager.removeAllDynamicShortcuts();
220         scManager.removeLongLivedShortcuts(Collections.singletonList(SHARE_SHORTCUT_ID));
221     }
222 
223     /**
224      * Notification fulfilling conversation policy; for the shortcut to be valid
225      * call {@link #createDynamicShortcut()}
226      */
getConversationNotification()227     protected Notification.Builder getConversationNotification() {
228         Person person = new Person.Builder()
229                 .setName("bubblebot")
230                 .build();
231         return new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
232                 .setContentTitle("foo")
233                 .setShortcutId(SHARE_SHORTCUT_ID)
234                 .setStyle(new Notification.MessagingStyle(person)
235                         .setConversationTitle("Bubble Chat")
236                         .addMessage("Hello?",
237                                 SystemClock.currentThreadTimeMillis() - 300000, person)
238                         .addMessage("Is it me you're looking for?",
239                                 SystemClock.currentThreadTimeMillis(), person)
240                 )
241                 .setSmallIcon(android.R.drawable.sym_def_app_icon);
242     }
243 
cancelAndPoll(int id)244     protected void cancelAndPoll(int id) {
245         mNotificationManager.cancel(id);
246 
247         try {
248             Thread.sleep(500);
249         } catch (InterruptedException ex) {
250             // pass
251         }
252         assertTrue(mNotificationHelper.isNotificationGone(id, SEARCH_TYPE.APP));
253     }
254 
sendNotification(final int id, final int icon)255     protected void sendNotification(final int id,
256             final int icon) throws Exception {
257         sendNotification(id, null, icon);
258     }
259 
sendNotification(final int id, String groupKey, final int icon)260     protected void sendNotification(final int id,
261             String groupKey, final int icon) {
262         sendNotification(id, groupKey, icon, false, null);
263     }
264 
sendNotification(final int id, String groupKey, final int icon, boolean isCall, Uri phoneNumber)265     protected void sendNotification(final int id,
266             String groupKey, final int icon,
267             boolean isCall, Uri phoneNumber) {
268         final Intent intent = new Intent(Intent.ACTION_MAIN, Telephony.Threads.CONTENT_URI);
269 
270         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP
271                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
272         intent.setAction(Intent.ACTION_MAIN);
273         intent.setPackage(mContext.getPackageName());
274 
275         final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent,
276                 PendingIntent.FLAG_MUTABLE);
277         Notification.Builder nb = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
278                 .setSmallIcon(icon)
279                 .setWhen(System.currentTimeMillis())
280                 .setContentTitle("notify#" + id)
281                 .setContentText("This is #" + id + "notification  ")
282                 .setContentIntent(pendingIntent)
283                 .setGroup(groupKey);
284 
285         if (isCall) {
286             nb.setCategory(CATEGORY_CALL);
287             if (phoneNumber != null) {
288                 Bundle extras = new Bundle();
289                 ArrayList<Person> pList = new ArrayList<>();
290                 pList.add(new Person.Builder().setUri(phoneNumber.toString()).build());
291                 extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, pList);
292                 nb.setExtras(extras);
293             }
294         }
295 
296         final Notification notification = nb.build();
297         mNotificationManager.notify(id, notification);
298 
299         assertNotNull(mNotificationHelper.findPostedNotification(null, id, SEARCH_TYPE.APP));
300     }
301 
setEnableServiceNotificationRateLimit(boolean enable)302     protected void setEnableServiceNotificationRateLimit(boolean enable) throws IOException {
303         String command = "cmd activity fgs-notification-rate-limit "
304                 + (enable ? "enable" : "disable");
305 
306         mNotificationHelper.runCommand(command, InstrumentationRegistry.getInstrumentation());
307     }
308 
suspendPackage(String packageName, Instrumentation instrumentation, boolean suspend)309     protected void suspendPackage(String packageName,
310             Instrumentation instrumentation, boolean suspend) throws IOException {
311         int userId = mContext.getUserId();
312         String command = " cmd package " + (suspend ? "suspend " : "unsuspend ")
313                 + "--user " + userId + " " + packageName;
314 
315         mNotificationHelper.runCommand(command, instrumentation);
316         AmUtils.waitForBroadcastBarrier();
317     }
318 
toggleNotificationPolicyAccess(String packageName, Instrumentation instrumentation, boolean on)319     protected void toggleNotificationPolicyAccess(String packageName,
320             Instrumentation instrumentation, boolean on) throws IOException {
321 
322         String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName;
323 
324         mNotificationHelper.runCommand(command, instrumentation);
325         AmUtils.waitForBroadcastBarrier();
326 
327         NotificationManager nm = mContext.getSystemService(NotificationManager.class);
328         assertEquals("Notification Policy Access Grant is "
329                 + nm.isNotificationPolicyAccessGranted() + " not " + on + " for "
330                 + packageName, on, nm.isNotificationPolicyAccessGranted());
331     }
332 }
333