1 /* 2 * Copyright (C) 2018 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.legacy.cts; 18 19 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS; 20 import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE; 21 import static android.service.notification.NotificationListenerService.INTERRUPTION_FILTER_PRIORITY; 22 23 import static junit.framework.Assert.assertEquals; 24 import static junit.framework.Assert.assertNull; 25 import static junit.framework.TestCase.fail; 26 27 import static org.junit.Assume.assumeFalse; 28 29 import static java.lang.Thread.sleep; 30 31 import android.app.ActivityManager; 32 import android.app.AutomaticZenRule; 33 import android.app.Instrumentation; 34 import android.app.NotificationManager; 35 import android.app.UiAutomation; 36 import android.content.ComponentName; 37 import android.content.Context; 38 import android.content.IntentFilter; 39 import android.net.Uri; 40 import android.os.ParcelFileDescriptor; 41 import android.service.notification.Condition; 42 import android.service.notification.ZenModeConfig; 43 import android.util.ArraySet; 44 import android.util.Log; 45 46 import androidx.test.InstrumentationRegistry; 47 import androidx.test.runner.AndroidJUnit4; 48 49 import junit.framework.Assert; 50 51 import org.junit.After; 52 import org.junit.Before; 53 import org.junit.Test; 54 import org.junit.runner.RunWith; 55 56 import java.io.FileInputStream; 57 import java.io.IOException; 58 import java.io.InputStream; 59 60 @RunWith(AndroidJUnit4.class) 61 public class ConditionProviderServiceTest { 62 private static String TAG = "CpsTest"; 63 64 private NotificationManager mNm; 65 private ActivityManager mActivityManager; 66 private Context mContext; 67 private ZenModeBroadcastReceiver mModeReceiver; 68 private IntentFilter mModeFilter; 69 private ArraySet<String> ids = new ArraySet<>(); 70 71 @Before setUp()72 public void setUp() throws Exception { 73 mContext = InstrumentationRegistry.getContext(); 74 mActivityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); 75 assumeFalse(mActivityManager.isLowRamDevice()); 76 77 toggleNotificationPolicyAccess(mContext.getPackageName(), 78 InstrumentationRegistry.getInstrumentation(), true); 79 LegacyConditionProviderService.requestRebind(LegacyConditionProviderService.getId()); 80 SecondaryConditionProviderService.requestRebind(SecondaryConditionProviderService.getId()); 81 mNm = (NotificationManager) mContext.getSystemService( 82 Context.NOTIFICATION_SERVICE); 83 mNm.setInterruptionFilter(NotificationManager.INTERRUPTION_FILTER_ALL); 84 mModeReceiver = new ZenModeBroadcastReceiver(); 85 mModeFilter = new IntentFilter(); 86 mModeFilter.addAction(NotificationManager.ACTION_INTERRUPTION_FILTER_CHANGED); 87 mContext.registerReceiver(mModeReceiver, mModeFilter); 88 } 89 90 @After tearDown()91 public void tearDown() throws Exception { 92 mContext.unregisterReceiver(mModeReceiver); 93 if (mNm == null) { 94 // assumption in setUp is false, so mNm is not initialized 95 return; 96 } 97 try { 98 for (String id : ids) { 99 if (id != null) { 100 if (!mNm.removeAutomaticZenRule(id)) { 101 throw new Exception("Could not remove rule " + id); 102 } 103 sleep(100); 104 assertNull(mNm.getAutomaticZenRule(id)); 105 } 106 } 107 } finally { 108 toggleNotificationPolicyAccess(mContext.getPackageName(), 109 InstrumentationRegistry.getInstrumentation(), false); 110 pollForConnection(LegacyConditionProviderService.class, false); 111 pollForConnection(SecondaryConditionProviderService.class, false); 112 } 113 } 114 115 @Test testUnboundCPSMaintainsCondition_addsNewRule()116 public void testUnboundCPSMaintainsCondition_addsNewRule() throws Exception { 117 if (mActivityManager.isLowRamDevice()) { 118 return; 119 } 120 121 // make sure service get bound 122 pollForConnection(SecondaryConditionProviderService.class, true); 123 124 final ComponentName cn = SecondaryConditionProviderService.getId(); 125 126 // add rule 127 mModeReceiver.reset(); 128 129 addRule(cn, INTERRUPTION_FILTER_ALARMS, true); 130 pollForSubscribe(SecondaryConditionProviderService.getInstance()); 131 132 mModeReceiver.waitFor(1/*Secondary only*/, 1000/*Limit is 1 second*/); 133 assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter()); 134 135 // unbind service 136 SecondaryConditionProviderService.getInstance().requestUnbind(); 137 138 // verify that DND state doesn't change 139 assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter()); 140 141 // add a new rule 142 addRule(cn, INTERRUPTION_FILTER_NONE, true); 143 144 // verify that the unbound service maintains it's DND vote 145 assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter()); 146 } 147 148 @Test testUnboundCPSMaintainsCondition_otherConditionChanges()149 public void testUnboundCPSMaintainsCondition_otherConditionChanges() throws Exception { 150 if (mActivityManager.isLowRamDevice()) { 151 return; 152 } 153 154 // make sure both services get bound 155 pollForConnection(LegacyConditionProviderService.class, true); 156 pollForConnection(SecondaryConditionProviderService.class, true); 157 158 // add rules for both 159 mModeReceiver.reset(); 160 161 addRule(LegacyConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, true); 162 pollForSubscribe(LegacyConditionProviderService.getInstance()); 163 164 addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_ALARMS, true); 165 pollForSubscribe(SecondaryConditionProviderService.getInstance()); 166 167 mModeReceiver.waitFor(2/*Legacy and Secondary*/, 1000/*Limit is 1 second*/); 168 assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter()); 169 170 // unbind one of the services 171 SecondaryConditionProviderService.getInstance().requestUnbind(); 172 173 // verify that DND state doesn't change 174 assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter()); 175 176 // trigger a change in the bound service's condition 177 ((LegacyConditionProviderService) LegacyConditionProviderService.getInstance()) 178 .toggleDND(false); 179 sleep(500); 180 181 // verify that the unbound service maintains it's DND vote 182 assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter()); 183 } 184 185 @Test testUnboundCPSMaintainsCondition_otherProviderRuleChanges()186 public void testUnboundCPSMaintainsCondition_otherProviderRuleChanges() throws Exception { 187 if (mActivityManager.isLowRamDevice()) { 188 return; 189 } 190 191 // make sure both services get bound 192 pollForConnection(LegacyConditionProviderService.class, true); 193 pollForConnection(SecondaryConditionProviderService.class, true); 194 195 // add rules for both 196 mModeReceiver.reset(); 197 198 addRule(LegacyConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, true); 199 pollForSubscribe(LegacyConditionProviderService.getInstance()); 200 201 addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_ALARMS, true); 202 pollForSubscribe(SecondaryConditionProviderService.getInstance()); 203 204 mModeReceiver.waitFor(2/*Legacy and Secondary*/, 1000/*Limit is 1 second*/); 205 assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter()); 206 207 // unbind one of the services 208 SecondaryConditionProviderService.getInstance().requestUnbind(); 209 210 // verify that DND state doesn't change 211 assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter()); 212 213 // trigger a change in the bound service's rule 214 addRule(SecondaryConditionProviderService.getId(), INTERRUPTION_FILTER_PRIORITY, false); 215 216 // verify that the unbound service maintains it's DND vote 217 assertEquals(INTERRUPTION_FILTER_ALARMS, mNm.getCurrentInterruptionFilter()); 218 } 219 220 @Test testRequestRebindWhenLostAccess()221 public void testRequestRebindWhenLostAccess() throws Exception { 222 if (mActivityManager.isLowRamDevice()) { 223 return; 224 } 225 226 // make sure it gets bound 227 pollForConnection(LegacyConditionProviderService.class, true); 228 229 // request unbind 230 LegacyConditionProviderService.getInstance().requestUnbind(); 231 232 // make sure it unbinds 233 pollForConnection(LegacyConditionProviderService.class, false); 234 235 // lose dnd access 236 toggleNotificationPolicyAccess(mContext.getPackageName(), 237 InstrumentationRegistry.getInstrumentation(), false); 238 239 // try to rebind 240 LegacyConditionProviderService.requestRebind(LegacyConditionProviderService.getId()); 241 242 // make sure it isn't rebound 243 try { 244 pollForConnection(LegacyConditionProviderService.class, true); 245 fail("Service got rebound after permission lost"); 246 } catch (Exception e) { 247 // pass 248 } 249 } 250 251 @Test testRequestRebindWhenStillHasAccess()252 public void testRequestRebindWhenStillHasAccess() throws Exception { 253 if (mActivityManager.isLowRamDevice()) { 254 return; 255 } 256 257 // make sure it gets bound 258 pollForConnection(LegacyConditionProviderService.class, true); 259 260 // request unbind 261 LegacyConditionProviderService.getInstance().requestUnbind(); 262 263 // make sure it unbinds 264 pollForConnection(LegacyConditionProviderService.class, false); 265 266 // try to rebind 267 LegacyConditionProviderService.requestRebind(LegacyConditionProviderService.getId()); 268 269 // make sure it did rebind 270 try { 271 pollForConnection(LegacyConditionProviderService.class, true); 272 } catch (Exception e) { 273 fail("Service should've been able to rebind"); 274 } 275 } 276 277 @Test testMethodsExistAndDoNotThrow()278 public void testMethodsExistAndDoNotThrow() throws Exception { 279 // behavior is covered in cts verifier 280 281 if (mActivityManager.isLowRamDevice()) { 282 return; 283 } 284 285 // make sure it gets bound 286 pollForConnection(LegacyConditionProviderService.class, true); 287 288 // request unbind 289 LegacyConditionProviderService.getInstance().onConnected(); 290 LegacyConditionProviderService.getInstance().onRequestConditions( 291 Condition.FLAG_RELEVANT_NOW); 292 LegacyConditionProviderService.getInstance().onSubscribe(Uri.EMPTY); 293 LegacyConditionProviderService.getInstance().onUnsubscribe(Uri.EMPTY); 294 295 } 296 addRule(ComponentName cn, int filter, boolean enabled)297 private void addRule(ComponentName cn, int filter, boolean enabled) { 298 final Uri conditionId = new Uri.Builder() 299 .scheme(Condition.SCHEME) 300 .authority(ZenModeConfig.SYSTEM_AUTHORITY) 301 .appendPath(cn.toString()) 302 .build(); 303 String id = mNm.addAutomaticZenRule(new AutomaticZenRule("name", 304 cn, conditionId, filter, enabled)); 305 Log.d(TAG, "Created rule with id " + id); 306 ids.add(id); 307 } 308 toggleNotificationPolicyAccess(String packageName, Instrumentation instrumentation, boolean on)309 private void toggleNotificationPolicyAccess(String packageName, 310 Instrumentation instrumentation, boolean on) throws IOException { 311 312 String command = " cmd notification " + (on ? "allow_dnd " : "disallow_dnd ") + packageName; 313 314 runCommand(command, instrumentation); 315 316 NotificationManager nm = mContext.getSystemService(NotificationManager.class); 317 Assert.assertEquals("Notification Policy Access Grant is " + 318 nm.isNotificationPolicyAccessGranted() + " not " + on, on, 319 nm.isNotificationPolicyAccessGranted()); 320 } 321 runCommand(String command, Instrumentation instrumentation)322 private void runCommand(String command, Instrumentation instrumentation) throws IOException { 323 UiAutomation uiAutomation = instrumentation.getUiAutomation(); 324 // Execute command 325 try (ParcelFileDescriptor fd = uiAutomation.executeShellCommand(command)) { 326 Assert.assertNotNull("Failed to execute shell command: " + command, fd); 327 // Wait for the command to finish by reading until EOF 328 try (InputStream in = new FileInputStream(fd.getFileDescriptor())) { 329 byte[] buffer = new byte[4096]; 330 while (in.read(buffer) > 0) {} 331 } catch (IOException e) { 332 throw new IOException("Could not read stdout of command: " + command, e); 333 } 334 } finally { 335 uiAutomation.destroy(); 336 } 337 } 338 pollForSubscribe(PollableConditionProviderService service)339 private void pollForSubscribe(PollableConditionProviderService service) throws Exception { 340 int tries = 30; 341 int delayMs = 200; 342 343 while (tries-- > 0 && !service.subscribed) { 344 try { 345 sleep(delayMs); 346 } catch (InterruptedException e) { 347 e.printStackTrace(); 348 } 349 } 350 351 if (!service.subscribed) { 352 Log.d(TAG, "not subscribed"); 353 throw new Exception("Service never got onSubscribe()"); 354 } 355 } 356 pollForConnection(Class<? extends PollableConditionProviderService> service, boolean waitForConnection)357 private void pollForConnection(Class<? extends PollableConditionProviderService> service, 358 boolean waitForConnection) throws Exception { 359 int tries = 100; 360 int delayMs = 200; 361 362 PollableConditionProviderService instance = 363 (PollableConditionProviderService) service.getMethod("getInstance").invoke(null); 364 365 while (tries-- > 0 && (waitForConnection ? instance == null : instance != null)) { 366 try { 367 sleep(delayMs); 368 } catch (InterruptedException e) { 369 e.printStackTrace(); 370 } 371 instance = (PollableConditionProviderService) service.getMethod("getInstance") 372 .invoke(null); 373 } 374 375 if (waitForConnection && instance == null) { 376 Log.d(TAG, service.getName() + " not bound"); 377 throw new Exception("CPS never bound"); 378 } else if (!waitForConnection && instance != null) { 379 Log.d(TAG, service.getName() + " still bound"); 380 throw new Exception("CPS still bound"); 381 } else { 382 Log.d(TAG, service.getName() + " has a correct bind state"); 383 } 384 } 385 } 386