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