1 /* 2 * Copyright (C) 2019 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.server; 18 19 import static android.service.watchdog.ExplicitHealthCheckService.PackageConfig; 20 21 import static org.junit.Assert.assertEquals; 22 import static org.junit.Assert.assertFalse; 23 import static org.junit.Assert.assertNull; 24 import static org.junit.Assert.assertTrue; 25 import static org.junit.Assert.fail; 26 import static org.mockito.ArgumentMatchers.anyInt; 27 import static org.mockito.ArgumentMatchers.anyString; 28 import static org.mockito.Mockito.reset; 29 import static org.mockito.Mockito.spy; 30 import static org.mockito.Mockito.verify; 31 import static org.mockito.Mockito.when; 32 33 import android.Manifest; 34 import android.content.Context; 35 import android.content.pm.PackageInfo; 36 import android.content.pm.PackageManager; 37 import android.content.pm.VersionedPackage; 38 import android.net.NetworkStackClient; 39 import android.net.NetworkStackClient.NetworkStackHealthListener; 40 import android.os.Handler; 41 import android.os.test.TestLooper; 42 import android.provider.DeviceConfig; 43 import android.util.AtomicFile; 44 45 import androidx.test.InstrumentationRegistry; 46 47 import com.android.server.PackageWatchdog.MonitoredPackage; 48 import com.android.server.PackageWatchdog.PackageHealthObserver; 49 import com.android.server.PackageWatchdog.PackageHealthObserverImpact; 50 51 import org.junit.After; 52 import org.junit.Before; 53 import org.junit.Test; 54 import org.mockito.ArgumentCaptor; 55 import org.mockito.Captor; 56 import org.mockito.Mock; 57 import org.mockito.MockitoAnnotations; 58 59 import java.io.File; 60 import java.util.ArrayList; 61 import java.util.Arrays; 62 import java.util.Collections; 63 import java.util.List; 64 import java.util.Set; 65 import java.util.concurrent.TimeUnit; 66 import java.util.function.Consumer; 67 68 // TODO: Write test without using PackageWatchdog#getPackages. Just rely on 69 // behavior of observers receiving crash notifications or not to determine if it's registered 70 // TODO: Use Truth in tests. 71 /** 72 * Test PackageWatchdog. 73 */ 74 public class PackageWatchdogTest { 75 private static final String APP_A = "com.package.a"; 76 private static final String APP_B = "com.package.b"; 77 private static final String APP_C = "com.package.c"; 78 private static final String APP_D = "com.package.d"; 79 private static final long VERSION_CODE = 1L; 80 private static final String OBSERVER_NAME_1 = "observer1"; 81 private static final String OBSERVER_NAME_2 = "observer2"; 82 private static final String OBSERVER_NAME_3 = "observer3"; 83 private static final String OBSERVER_NAME_4 = "observer4"; 84 private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1); 85 private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5); 86 private TestLooper mTestLooper; 87 private Context mSpyContext; 88 @Mock 89 private NetworkStackClient mMockNetworkStackClient; 90 @Mock 91 private PackageManager mMockPackageManager; 92 @Captor 93 private ArgumentCaptor<NetworkStackHealthListener> mNetworkStackCallbackCaptor; 94 95 @Before setUp()96 public void setUp() throws Exception { 97 MockitoAnnotations.initMocks(this); 98 new File(InstrumentationRegistry.getContext().getFilesDir(), 99 "package-watchdog.xml").delete(); 100 adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG); 101 mTestLooper = new TestLooper(); 102 mSpyContext = spy(InstrumentationRegistry.getContext()); 103 when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager); 104 when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> { 105 final PackageInfo res = new PackageInfo(); 106 res.packageName = inv.getArgument(0); 107 res.setLongVersionCode(VERSION_CODE); 108 return res; 109 }); 110 } 111 112 @After tearDown()113 public void tearDown() throws Exception { 114 dropShellPermissions(); 115 } 116 117 /** 118 * Test registration, unregistration, package expiry and duration reduction 119 */ 120 @Test testRegistration()121 public void testRegistration() throws Exception { 122 PackageWatchdog watchdog = createWatchdog(); 123 TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); 124 TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); 125 TestObserver observer3 = new TestObserver(OBSERVER_NAME_3); 126 127 // Start observing for observer1 which will be unregistered 128 watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); 129 // Start observing for observer2 which will expire 130 watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); 131 // Start observing for observer3 which will have expiry duration reduced 132 watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), LONG_DURATION); 133 134 // Verify packages observed at start 135 // 1 136 assertEquals(1, watchdog.getPackages(observer1).size()); 137 assertTrue(watchdog.getPackages(observer1).contains(APP_A)); 138 // 2 139 assertEquals(2, watchdog.getPackages(observer2).size()); 140 assertTrue(watchdog.getPackages(observer2).contains(APP_A)); 141 assertTrue(watchdog.getPackages(observer2).contains(APP_B)); 142 // 3 143 assertEquals(1, watchdog.getPackages(observer3).size()); 144 assertTrue(watchdog.getPackages(observer3).contains(APP_A)); 145 146 // Then unregister observer1 147 watchdog.unregisterHealthObserver(observer1); 148 149 // Verify observer2 and observer3 left 150 // 1 151 assertNull(watchdog.getPackages(observer1)); 152 // 2 153 assertEquals(2, watchdog.getPackages(observer2).size()); 154 assertTrue(watchdog.getPackages(observer2).contains(APP_A)); 155 assertTrue(watchdog.getPackages(observer2).contains(APP_B)); 156 // 3 157 assertEquals(1, watchdog.getPackages(observer3).size()); 158 assertTrue(watchdog.getPackages(observer3).contains(APP_A)); 159 160 // Then advance time a little and run messages in Handlers so observer2 expires 161 Thread.sleep(SHORT_DURATION); 162 mTestLooper.dispatchAll(); 163 164 // Verify observer3 left with reduced expiry duration 165 // 1 166 assertNull(watchdog.getPackages(observer1)); 167 // 2 168 assertNull(watchdog.getPackages(observer2)); 169 // 3 170 assertEquals(1, watchdog.getPackages(observer3).size()); 171 assertTrue(watchdog.getPackages(observer3).contains(APP_A)); 172 173 // Then advance time some more and run messages in Handlers so observer3 expires 174 Thread.sleep(LONG_DURATION); 175 mTestLooper.dispatchAll(); 176 177 // Verify observer3 expired 178 // 1 179 assertNull(watchdog.getPackages(observer1)); 180 // 2 181 assertNull(watchdog.getPackages(observer2)); 182 // 3 183 assertNull(watchdog.getPackages(observer3)); 184 } 185 186 /** Observing already observed package extends the observation time. */ 187 @Test testObserveAlreadyObservedPackage()188 public void testObserveAlreadyObservedPackage() throws Exception { 189 PackageWatchdog watchdog = createWatchdog(); 190 TestObserver observer = new TestObserver(OBSERVER_NAME_1); 191 192 // Start observing APP_A 193 watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); 194 195 // Then advance time half-way 196 Thread.sleep(SHORT_DURATION / 2); 197 mTestLooper.dispatchAll(); 198 199 // Start observing APP_A again 200 watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); 201 202 // Then advance time such that it should have expired were it not for the second observation 203 Thread.sleep((SHORT_DURATION / 2) + 1); 204 mTestLooper.dispatchAll(); 205 206 // Verify that APP_A not expired since second observation extended the time 207 assertEquals(1, watchdog.getPackages(observer).size()); 208 assertTrue(watchdog.getPackages(observer).contains(APP_A)); 209 } 210 211 /** 212 * Test package observers are persisted and loaded on startup 213 */ 214 @Test testPersistence()215 public void testPersistence() throws Exception { 216 PackageWatchdog watchdog1 = createWatchdog(); 217 TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); 218 TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); 219 220 watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); 221 watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION); 222 223 // Verify 2 observers are registered and saved internally 224 // 1 225 assertEquals(1, watchdog1.getPackages(observer1).size()); 226 assertTrue(watchdog1.getPackages(observer1).contains(APP_A)); 227 // 2 228 assertEquals(2, watchdog1.getPackages(observer2).size()); 229 assertTrue(watchdog1.getPackages(observer2).contains(APP_A)); 230 assertTrue(watchdog1.getPackages(observer2).contains(APP_B)); 231 232 // Then advance time and run IO Handler so file is saved 233 mTestLooper.dispatchAll(); 234 235 // Then start a new watchdog 236 PackageWatchdog watchdog2 = createWatchdog(); 237 238 // Verify the new watchdog loads observers on startup but nothing registered 239 assertEquals(0, watchdog2.getPackages(observer1).size()); 240 assertEquals(0, watchdog2.getPackages(observer2).size()); 241 // Verify random observer not saved returns null 242 assertNull(watchdog2.getPackages(new TestObserver(OBSERVER_NAME_3))); 243 244 // Then register observer1 245 watchdog2.registerHealthObserver(observer1); 246 watchdog2.registerHealthObserver(observer2); 247 248 // Verify 2 observers are registered after reload 249 // 1 250 assertEquals(1, watchdog1.getPackages(observer1).size()); 251 assertTrue(watchdog1.getPackages(observer1).contains(APP_A)); 252 // 2 253 assertEquals(2, watchdog1.getPackages(observer2).size()); 254 assertTrue(watchdog1.getPackages(observer2).contains(APP_A)); 255 assertTrue(watchdog1.getPackages(observer2).contains(APP_B)); 256 } 257 258 /** 259 * Test package failure under threshold does not notify observers 260 */ 261 @Test testNoPackageFailureBeforeThreshold()262 public void testNoPackageFailureBeforeThreshold() throws Exception { 263 PackageWatchdog watchdog = createWatchdog(); 264 TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); 265 TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); 266 267 watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); 268 watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); 269 270 // Then fail APP_A below the threshold 271 for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) { 272 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); 273 } 274 275 // Run handler so package failures are dispatched to observers 276 mTestLooper.dispatchAll(); 277 278 // Verify that observers are not notified 279 assertEquals(0, observer1.mFailedPackages.size()); 280 assertEquals(0, observer2.mFailedPackages.size()); 281 } 282 283 /** 284 * Test package failure and does not notify any observer because they are not observing 285 * the failed packages. 286 */ 287 @Test testPackageFailureDifferentPackageNotifyNone()288 public void testPackageFailureDifferentPackageNotifyNone() throws Exception { 289 PackageWatchdog watchdog = createWatchdog(); 290 TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); 291 TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); 292 293 294 watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); 295 watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION); 296 297 // Then fail APP_C (not observed) above the threshold 298 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 299 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE))); 300 } 301 302 // Run handler so package failures are dispatched to observers 303 mTestLooper.dispatchAll(); 304 305 // Verify that observers are not notified 306 assertEquals(0, observer1.mFailedPackages.size()); 307 assertEquals(0, observer2.mFailedPackages.size()); 308 } 309 310 /** 311 * Test package failure and does not notify any observer because the failed package version 312 * does not match the available rollback-from-version. 313 */ 314 @Test testPackageFailureDifferentVersionNotifyNone()315 public void testPackageFailureDifferentVersionNotifyNone() throws Exception { 316 PackageWatchdog watchdog = createWatchdog(); 317 long differentVersionCode = 2L; 318 TestObserver observer = new TestObserver(OBSERVER_NAME_1) { 319 @Override 320 public int onHealthCheckFailed(VersionedPackage versionedPackage) { 321 if (versionedPackage.getVersionCode() == VERSION_CODE) { 322 // Only rollback for specific versionCode 323 return PackageHealthObserverImpact.USER_IMPACT_MEDIUM; 324 } 325 return PackageHealthObserverImpact.USER_IMPACT_NONE; 326 } 327 }; 328 329 watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); 330 331 // Then fail APP_A (different version) above the threshold 332 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 333 watchdog.onPackageFailure(Arrays.asList( 334 new VersionedPackage(APP_A, differentVersionCode))); 335 } 336 337 // Run handler so package failures are dispatched to observers 338 mTestLooper.dispatchAll(); 339 340 // Verify that observers are not notified 341 assertEquals(0, observer.mFailedPackages.size()); 342 } 343 344 345 /** 346 * Test package failure and notifies only least impact observers. 347 */ 348 @Test testPackageFailureNotifyAllDifferentImpacts()349 public void testPackageFailureNotifyAllDifferentImpacts() throws Exception { 350 PackageWatchdog watchdog = createWatchdog(); 351 TestObserver observerNone = new TestObserver(OBSERVER_NAME_1, 352 PackageHealthObserverImpact.USER_IMPACT_NONE); 353 TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2, 354 PackageHealthObserverImpact.USER_IMPACT_HIGH); 355 TestObserver observerMid = new TestObserver(OBSERVER_NAME_3, 356 PackageHealthObserverImpact.USER_IMPACT_MEDIUM); 357 TestObserver observerLow = new TestObserver(OBSERVER_NAME_4, 358 PackageHealthObserverImpact.USER_IMPACT_LOW); 359 360 // Start observing for all impact observers 361 watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), 362 SHORT_DURATION); 363 watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C), 364 SHORT_DURATION); 365 watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B), 366 SHORT_DURATION); 367 watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A), 368 SHORT_DURATION); 369 370 // Then fail all apps above the threshold 371 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 372 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE), 373 new VersionedPackage(APP_B, VERSION_CODE), 374 new VersionedPackage(APP_C, VERSION_CODE), 375 new VersionedPackage(APP_D, VERSION_CODE))); 376 } 377 378 // Run handler so package failures are dispatched to observers 379 mTestLooper.dispatchAll(); 380 381 // Verify least impact observers are notifed of package failures 382 List<String> observerNonePackages = observerNone.mFailedPackages; 383 List<String> observerHighPackages = observerHigh.mFailedPackages; 384 List<String> observerMidPackages = observerMid.mFailedPackages; 385 List<String> observerLowPackages = observerLow.mFailedPackages; 386 387 // APP_D failure observed by only observerNone is not caught cos its impact is none 388 assertEquals(0, observerNonePackages.size()); 389 // APP_C failure is caught by observerHigh cos it's the lowest impact observer 390 assertEquals(1, observerHighPackages.size()); 391 assertEquals(APP_C, observerHighPackages.get(0)); 392 // APP_B failure is caught by observerMid cos it's the lowest impact observer 393 assertEquals(1, observerMidPackages.size()); 394 assertEquals(APP_B, observerMidPackages.get(0)); 395 // APP_A failure is caught by observerLow cos it's the lowest impact observer 396 assertEquals(1, observerLowPackages.size()); 397 assertEquals(APP_A, observerLowPackages.get(0)); 398 } 399 400 /** 401 * Test package failure and least impact observers are notified successively. 402 * State transistions: 403 * 404 * <ul> 405 * <li>(observer1:low, observer2:mid) -> {observer1} 406 * <li>(observer1:high, observer2:mid) -> {observer2} 407 * <li>(observer1:high, observer2:none) -> {observer1} 408 * <li>(observer1:none, observer2:none) -> {} 409 * <ul> 410 */ 411 @Test testPackageFailureNotifyLeastImpactSuccessively()412 public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception { 413 PackageWatchdog watchdog = createWatchdog(); 414 TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1, 415 PackageHealthObserverImpact.USER_IMPACT_LOW); 416 TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2, 417 PackageHealthObserverImpact.USER_IMPACT_MEDIUM); 418 419 // Start observing for observerFirst and observerSecond with failure handling 420 watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION); 421 watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION); 422 423 // Then fail APP_A above the threshold 424 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 425 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); 426 } 427 // Run handler so package failures are dispatched to observers 428 mTestLooper.dispatchAll(); 429 430 // Verify only observerFirst is notifed 431 assertEquals(1, observerFirst.mFailedPackages.size()); 432 assertEquals(APP_A, observerFirst.mFailedPackages.get(0)); 433 assertEquals(0, observerSecond.mFailedPackages.size()); 434 435 // After observerFirst handles failure, next action it has is high impact 436 observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH; 437 observerFirst.mFailedPackages.clear(); 438 observerSecond.mFailedPackages.clear(); 439 440 // Then fail APP_A again above the threshold 441 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 442 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); 443 } 444 // Run handler so package failures are dispatched to observers 445 mTestLooper.dispatchAll(); 446 447 // Verify only observerSecond is notifed cos it has least impact 448 assertEquals(1, observerSecond.mFailedPackages.size()); 449 assertEquals(APP_A, observerSecond.mFailedPackages.get(0)); 450 assertEquals(0, observerFirst.mFailedPackages.size()); 451 452 // After observerSecond handles failure, it has no further actions 453 observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; 454 observerFirst.mFailedPackages.clear(); 455 observerSecond.mFailedPackages.clear(); 456 457 // Then fail APP_A again above the threshold 458 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 459 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); 460 } 461 // Run handler so package failures are dispatched to observers 462 mTestLooper.dispatchAll(); 463 464 // Verify only observerFirst is notifed cos it has the only action 465 assertEquals(1, observerFirst.mFailedPackages.size()); 466 assertEquals(APP_A, observerFirst.mFailedPackages.get(0)); 467 assertEquals(0, observerSecond.mFailedPackages.size()); 468 469 // After observerFirst handles failure, it too has no further actions 470 observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; 471 observerFirst.mFailedPackages.clear(); 472 observerSecond.mFailedPackages.clear(); 473 474 // Then fail APP_A again above the threshold 475 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 476 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); 477 } 478 // Run handler so package failures are dispatched to observers 479 mTestLooper.dispatchAll(); 480 481 // Verify no observer is notified cos no actions left 482 assertEquals(0, observerFirst.mFailedPackages.size()); 483 assertEquals(0, observerSecond.mFailedPackages.size()); 484 } 485 486 /** 487 * Test package failure and notifies only one observer even with observer impact tie. 488 */ 489 @Test testPackageFailureNotifyOneSameImpact()490 public void testPackageFailureNotifyOneSameImpact() throws Exception { 491 PackageWatchdog watchdog = createWatchdog(); 492 TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, 493 PackageHealthObserverImpact.USER_IMPACT_HIGH); 494 TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, 495 PackageHealthObserverImpact.USER_IMPACT_HIGH); 496 497 // Start observing for observer1 and observer2 with failure handling 498 watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); 499 watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); 500 501 // Then fail APP_A above the threshold 502 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 503 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE))); 504 } 505 506 // Run handler so package failures are dispatched to observers 507 mTestLooper.dispatchAll(); 508 509 // Verify only one observer is notifed 510 assertEquals(1, observer1.mFailedPackages.size()); 511 assertEquals(APP_A, observer1.mFailedPackages.get(0)); 512 assertEquals(0, observer2.mFailedPackages.size()); 513 } 514 515 /** 516 * Test package passing explicit health checks does not fail and vice versa. 517 */ 518 @Test testExplicitHealthChecks()519 public void testExplicitHealthChecks() throws Exception { 520 TestController controller = new TestController(); 521 PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); 522 TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, 523 PackageHealthObserverImpact.USER_IMPACT_HIGH); 524 TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, 525 PackageHealthObserverImpact.USER_IMPACT_HIGH); 526 TestObserver observer3 = new TestObserver(OBSERVER_NAME_3, 527 PackageHealthObserverImpact.USER_IMPACT_HIGH); 528 529 530 // Start observing with explicit health checks for APP_A and APP_B respectively 531 // with observer1 and observer2 532 controller.setSupportedPackages(Arrays.asList(APP_A, APP_B)); 533 watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); 534 watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION); 535 536 // Run handler so requests are dispatched to the controller 537 mTestLooper.dispatchAll(); 538 539 // Verify we requested health checks for APP_A and APP_B 540 List<String> requestedPackages = controller.getRequestedPackages(); 541 assertEquals(2, requestedPackages.size()); 542 assertEquals(APP_A, requestedPackages.get(0)); 543 assertEquals(APP_B, requestedPackages.get(1)); 544 545 // Then health check passed for APP_A (observer1 is aware) 546 controller.setPackagePassed(APP_A); 547 548 // Then start observing APP_A with explicit health checks for observer3. 549 // Observer3 didn't exist when we got the explicit health check above, so 550 // it starts out with a non-passing explicit health check and has to wait for a pass 551 // otherwise it would be notified of APP_A failure on expiry 552 watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION); 553 554 // Then expire observers 555 Thread.sleep(SHORT_DURATION); 556 // Run handler so package failures are dispatched to observers 557 mTestLooper.dispatchAll(); 558 559 // Verify we cancelled all requests on expiry 560 assertEquals(0, controller.getRequestedPackages().size()); 561 562 // Verify observer1 is not notified 563 assertEquals(0, observer1.mFailedPackages.size()); 564 565 // Verify observer2 is notifed because health checks for APP_B never passed 566 assertEquals(1, observer2.mFailedPackages.size()); 567 assertEquals(APP_B, observer2.mFailedPackages.get(0)); 568 569 // Verify observer3 is notifed because health checks for APP_A did not pass before expiry 570 assertEquals(1, observer3.mFailedPackages.size()); 571 assertEquals(APP_A, observer3.mFailedPackages.get(0)); 572 } 573 574 /** 575 * Test explicit health check state can be disabled and enabled correctly. 576 */ 577 @Test testExplicitHealthCheckStateChanges()578 public void testExplicitHealthCheckStateChanges() throws Exception { 579 adoptShellPermissions( 580 Manifest.permission.WRITE_DEVICE_CONFIG, 581 Manifest.permission.READ_DEVICE_CONFIG); 582 583 TestController controller = new TestController(); 584 PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); 585 TestObserver observer = new TestObserver(OBSERVER_NAME_1, 586 PackageHealthObserverImpact.USER_IMPACT_MEDIUM); 587 588 // Start observing with explicit health checks for APP_A and APP_B 589 controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C)); 590 watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); 591 watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION); 592 593 // Run handler so requests are dispatched to the controller 594 mTestLooper.dispatchAll(); 595 596 // Verify we requested health checks for APP_A and APP_B 597 List<String> requestedPackages = controller.getRequestedPackages(); 598 assertEquals(2, requestedPackages.size()); 599 assertEquals(APP_A, requestedPackages.get(0)); 600 assertEquals(APP_B, requestedPackages.get(1)); 601 602 // Disable explicit health checks (marks APP_A and APP_B as passed) 603 setExplicitHealthCheckEnabled(false); 604 605 // Run handler so requests/cancellations are dispatched to the controller 606 mTestLooper.dispatchAll(); 607 608 // Verify all checks are cancelled 609 assertEquals(0, controller.getRequestedPackages().size()); 610 611 // Then expire APP_A 612 Thread.sleep(SHORT_DURATION); 613 mTestLooper.dispatchAll(); 614 615 // Verify APP_A is not failed (APP_B) is not expired yet 616 assertEquals(0, observer.mFailedPackages.size()); 617 618 // Re-enable explicit health checks 619 setExplicitHealthCheckEnabled(true); 620 621 // Run handler so requests/cancellations are dispatched to the controller 622 mTestLooper.dispatchAll(); 623 624 // Verify no requests are made cos APP_A is expired and APP_B was marked as passed 625 assertEquals(0, controller.getRequestedPackages().size()); 626 627 // Then set new supported packages 628 controller.setSupportedPackages(Arrays.asList(APP_C)); 629 // Start observing APP_A and APP_C; only APP_C has support for explicit health checks 630 watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION); 631 632 // Run handler so requests/cancellations are dispatched to the controller 633 mTestLooper.dispatchAll(); 634 635 // Verify requests are only made for APP_C 636 requestedPackages = controller.getRequestedPackages(); 637 assertEquals(1, requestedPackages.size()); 638 assertEquals(APP_C, requestedPackages.get(0)); 639 640 // Then expire APP_A and APP_C 641 Thread.sleep(SHORT_DURATION); 642 mTestLooper.dispatchAll(); 643 644 // Verify only APP_C is failed because explicit health checks was not supported for APP_A 645 assertEquals(1, observer.mFailedPackages.size()); 646 assertEquals(APP_C, observer.mFailedPackages.get(0)); 647 } 648 649 /** 650 * Tests failure when health check duration is different from package observation duration 651 * Failure is also notified only once. 652 */ 653 @Test testExplicitHealthCheckFailureBeforeExpiry()654 public void testExplicitHealthCheckFailureBeforeExpiry() throws Exception { 655 TestController controller = new TestController(); 656 PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); 657 TestObserver observer = new TestObserver(OBSERVER_NAME_1, 658 PackageHealthObserverImpact.USER_IMPACT_MEDIUM); 659 660 // Start observing with explicit health checks for APP_A and 661 // package observation duration == LONG_DURATION 662 // health check duration == SHORT_DURATION (set by default in the TestController) 663 controller.setSupportedPackages(Arrays.asList(APP_A)); 664 watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION); 665 666 // Then APP_A has exceeded health check duration 667 Thread.sleep(SHORT_DURATION); 668 mTestLooper.dispatchAll(); 669 670 // Verify that health check is failed 671 assertEquals(1, observer.mFailedPackages.size()); 672 assertEquals(APP_A, observer.mFailedPackages.get(0)); 673 674 // Then clear failed packages and start observing a random package so requests are synced 675 // and PackageWatchdog#onSupportedPackages is called and APP_A has a chance to fail again 676 // this time due to package expiry. 677 observer.mFailedPackages.clear(); 678 watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION); 679 680 // Verify that health check failure is not notified again 681 assertTrue(observer.mFailedPackages.isEmpty()); 682 } 683 684 /** Tests {@link MonitoredPackage} health check state transitions. */ 685 @Test testPackageHealthCheckStateTransitions()686 public void testPackageHealthCheckStateTransitions() { 687 TestController controller = new TestController(); 688 PackageWatchdog wd = createWatchdog(controller, true /* withPackagesReady */); 689 MonitoredPackage m1 = wd.new MonitoredPackage(APP_A, LONG_DURATION, 690 false /* hasPassedHealthCheck */); 691 MonitoredPackage m2 = wd.new MonitoredPackage(APP_B, LONG_DURATION, false); 692 MonitoredPackage m3 = wd.new MonitoredPackage(APP_C, LONG_DURATION, false); 693 MonitoredPackage m4 = wd.new MonitoredPackage(APP_D, LONG_DURATION, SHORT_DURATION, true); 694 695 // Verify transition: inactive -> active -> passed 696 // Verify initially inactive 697 assertEquals(MonitoredPackage.STATE_INACTIVE, m1.getHealthCheckStateLocked()); 698 // Verify still inactive, until we #setHealthCheckActiveLocked 699 assertEquals(MonitoredPackage.STATE_INACTIVE, m1.handleElapsedTimeLocked(SHORT_DURATION)); 700 // Verify now active 701 assertEquals(MonitoredPackage.STATE_ACTIVE, m1.setHealthCheckActiveLocked(SHORT_DURATION)); 702 // Verify now passed 703 assertEquals(MonitoredPackage.STATE_PASSED, m1.tryPassHealthCheckLocked()); 704 705 // Verify transition: inactive -> active -> failed 706 // Verify initially inactive 707 assertEquals(MonitoredPackage.STATE_INACTIVE, m2.getHealthCheckStateLocked()); 708 // Verify now active 709 assertEquals(MonitoredPackage.STATE_ACTIVE, m2.setHealthCheckActiveLocked(SHORT_DURATION)); 710 // Verify now failed 711 assertEquals(MonitoredPackage.STATE_FAILED, m2.handleElapsedTimeLocked(SHORT_DURATION)); 712 713 // Verify transition: inactive -> failed 714 // Verify initially inactive 715 assertEquals(MonitoredPackage.STATE_INACTIVE, m3.getHealthCheckStateLocked()); 716 // Verify now failed because package expired 717 assertEquals(MonitoredPackage.STATE_FAILED, m3.handleElapsedTimeLocked(LONG_DURATION)); 718 // Verify remains failed even when asked to pass 719 assertEquals(MonitoredPackage.STATE_FAILED, m3.tryPassHealthCheckLocked()); 720 721 // Verify transition: passed 722 assertEquals(MonitoredPackage.STATE_PASSED, m4.getHealthCheckStateLocked()); 723 // Verify remains passed even if health check fails 724 assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(SHORT_DURATION)); 725 // Verify remains passed even if package expires 726 assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(LONG_DURATION)); 727 } 728 729 @Test testNetworkStackFailure()730 public void testNetworkStackFailure() { 731 final PackageWatchdog wd = createWatchdog(); 732 733 // Start observing with failure handling 734 TestObserver observer = new TestObserver(OBSERVER_NAME_1, 735 PackageHealthObserverImpact.USER_IMPACT_HIGH); 736 wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION); 737 738 // Notify of NetworkStack failure 739 mNetworkStackCallbackCaptor.getValue().onNetworkStackFailure(APP_A); 740 741 // Run handler so package failures are dispatched to observers 742 mTestLooper.dispatchAll(); 743 744 // Verify the NetworkStack observer is notified 745 assertEquals(1, observer.mFailedPackages.size()); 746 assertEquals(APP_A, observer.mFailedPackages.get(0)); 747 } 748 adoptShellPermissions(String... permissions)749 private void adoptShellPermissions(String... permissions) { 750 InstrumentationRegistry 751 .getInstrumentation() 752 .getUiAutomation() 753 .adoptShellPermissionIdentity(permissions); 754 } 755 dropShellPermissions()756 private void dropShellPermissions() { 757 InstrumentationRegistry 758 .getInstrumentation() 759 .getUiAutomation() 760 .dropShellPermissionIdentity(); 761 } 762 setExplicitHealthCheckEnabled(boolean enabled)763 private void setExplicitHealthCheckEnabled(boolean enabled) { 764 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, 765 PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED, 766 Boolean.toString(enabled), /*makeDefault*/false); 767 //give time for DeviceConfig to broadcast the property value change 768 try { 769 Thread.sleep(SHORT_DURATION); 770 } catch (InterruptedException e) { 771 fail("Thread.sleep unexpectedly failed!"); 772 } 773 } 774 createWatchdog()775 private PackageWatchdog createWatchdog() { 776 return createWatchdog(new TestController(), true /* withPackagesReady */); 777 } 778 createWatchdog(TestController controller, boolean withPackagesReady)779 private PackageWatchdog createWatchdog(TestController controller, boolean withPackagesReady) { 780 AtomicFile policyFile = 781 new AtomicFile(new File(mSpyContext.getFilesDir(), "package-watchdog.xml")); 782 Handler handler = new Handler(mTestLooper.getLooper()); 783 PackageWatchdog watchdog = 784 new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller, 785 mMockNetworkStackClient); 786 // Verify controller is not automatically started 787 assertFalse(controller.mIsEnabled); 788 if (withPackagesReady) { 789 // Only capture the NetworkStack callback for the latest registered watchdog 790 reset(mMockNetworkStackClient); 791 watchdog.onPackagesReady(); 792 // Verify controller by default is started when packages are ready 793 assertTrue(controller.mIsEnabled); 794 795 verify(mMockNetworkStackClient).registerHealthListener( 796 mNetworkStackCallbackCaptor.capture()); 797 } 798 return watchdog; 799 } 800 801 private static class TestObserver implements PackageHealthObserver { 802 private final String mName; 803 private int mImpact; 804 final List<String> mFailedPackages = new ArrayList<>(); 805 TestObserver(String name)806 TestObserver(String name) { 807 mName = name; 808 mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM; 809 } 810 TestObserver(String name, int impact)811 TestObserver(String name, int impact) { 812 mName = name; 813 mImpact = impact; 814 } 815 onHealthCheckFailed(VersionedPackage versionedPackage)816 public int onHealthCheckFailed(VersionedPackage versionedPackage) { 817 return mImpact; 818 } 819 execute(VersionedPackage versionedPackage)820 public boolean execute(VersionedPackage versionedPackage) { 821 mFailedPackages.add(versionedPackage.getPackageName()); 822 return true; 823 } 824 getName()825 public String getName() { 826 return mName; 827 } 828 } 829 830 private static class TestController extends ExplicitHealthCheckController { TestController()831 TestController() { 832 super(null /* controller */); 833 } 834 835 private boolean mIsEnabled; 836 private List<String> mSupportedPackages = new ArrayList<>(); 837 private List<String> mRequestedPackages = new ArrayList<>(); 838 private Consumer<String> mPassedConsumer; 839 private Consumer<List<PackageConfig>> mSupportedConsumer; 840 private Runnable mNotifySyncRunnable; 841 842 @Override setEnabled(boolean enabled)843 public void setEnabled(boolean enabled) { 844 mIsEnabled = enabled; 845 if (!mIsEnabled) { 846 mSupportedPackages.clear(); 847 } 848 } 849 850 @Override setCallbacks(Consumer<String> passedConsumer, Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable)851 public void setCallbacks(Consumer<String> passedConsumer, 852 Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) { 853 mPassedConsumer = passedConsumer; 854 mSupportedConsumer = supportedConsumer; 855 mNotifySyncRunnable = notifySyncRunnable; 856 } 857 858 @Override syncRequests(Set<String> packages)859 public void syncRequests(Set<String> packages) { 860 mRequestedPackages.clear(); 861 if (mIsEnabled) { 862 packages.retainAll(mSupportedPackages); 863 mRequestedPackages.addAll(packages); 864 List<PackageConfig> packageConfigs = new ArrayList<>(); 865 for (String packageName: packages) { 866 packageConfigs.add(new PackageConfig(packageName, SHORT_DURATION)); 867 } 868 mSupportedConsumer.accept(packageConfigs); 869 } else { 870 mSupportedConsumer.accept(Collections.emptyList()); 871 } 872 } 873 setSupportedPackages(List<String> packages)874 public void setSupportedPackages(List<String> packages) { 875 mSupportedPackages.clear(); 876 mSupportedPackages.addAll(packages); 877 } 878 setPackagePassed(String packageName)879 public void setPackagePassed(String packageName) { 880 mPassedConsumer.accept(packageName); 881 } 882 getRequestedPackages()883 public List<String> getRequestedPackages() { 884 if (mIsEnabled) { 885 return mRequestedPackages; 886 } else { 887 return Collections.emptyList(); 888 } 889 } 890 } 891 } 892