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.ConnectivityModuleConnector; 39 import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener; 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 ConnectivityModuleConnector mConnectivityModuleConnector; 90 @Mock 91 private PackageManager mMockPackageManager; 92 @Captor 93 private ArgumentCaptor<ConnectivityModuleHealthListener> mConnectivityModuleCallbackCaptor; 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 PackageWatchdog.FAILURE_REASON_UNKNOWN); 274 } 275 276 // Run handler so package failures are dispatched to observers 277 mTestLooper.dispatchAll(); 278 279 // Verify that observers are not notified 280 assertEquals(0, observer1.mFailedPackages.size()); 281 assertEquals(0, observer2.mFailedPackages.size()); 282 } 283 284 /** 285 * Test package failure and does not notify any observer because they are not observing 286 * the failed packages. 287 */ 288 @Test testPackageFailureDifferentPackageNotifyNone()289 public void testPackageFailureDifferentPackageNotifyNone() throws Exception { 290 PackageWatchdog watchdog = createWatchdog(); 291 TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); 292 TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); 293 294 295 watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); 296 watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION); 297 298 // Then fail APP_C (not observed) above the threshold 299 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 300 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)), 301 PackageWatchdog.FAILURE_REASON_UNKNOWN); 302 } 303 304 // Run handler so package failures are dispatched to observers 305 mTestLooper.dispatchAll(); 306 307 // Verify that observers are not notified 308 assertEquals(0, observer1.mFailedPackages.size()); 309 assertEquals(0, observer2.mFailedPackages.size()); 310 } 311 312 /** 313 * Test package failure and does not notify any observer because the failed package version 314 * does not match the available rollback-from-version. 315 */ 316 @Test testPackageFailureDifferentVersionNotifyNone()317 public void testPackageFailureDifferentVersionNotifyNone() throws Exception { 318 PackageWatchdog watchdog = createWatchdog(); 319 long differentVersionCode = 2L; 320 TestObserver observer = new TestObserver(OBSERVER_NAME_1) { 321 @Override 322 public int onHealthCheckFailed(VersionedPackage versionedPackage) { 323 if (versionedPackage.getVersionCode() == VERSION_CODE) { 324 // Only rollback for specific versionCode 325 return PackageHealthObserverImpact.USER_IMPACT_MEDIUM; 326 } 327 return PackageHealthObserverImpact.USER_IMPACT_NONE; 328 } 329 }; 330 331 watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); 332 333 // Then fail APP_A (different version) above the threshold 334 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 335 watchdog.onPackageFailure(Arrays.asList( 336 new VersionedPackage(APP_A, differentVersionCode)), 337 PackageWatchdog.FAILURE_REASON_UNKNOWN); 338 } 339 340 // Run handler so package failures are dispatched to observers 341 mTestLooper.dispatchAll(); 342 343 // Verify that observers are not notified 344 assertEquals(0, observer.mFailedPackages.size()); 345 } 346 347 348 /** 349 * Test package failure and notifies only least impact observers. 350 */ 351 @Test testPackageFailureNotifyAllDifferentImpacts()352 public void testPackageFailureNotifyAllDifferentImpacts() throws Exception { 353 PackageWatchdog watchdog = createWatchdog(); 354 TestObserver observerNone = new TestObserver(OBSERVER_NAME_1, 355 PackageHealthObserverImpact.USER_IMPACT_NONE); 356 TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2, 357 PackageHealthObserverImpact.USER_IMPACT_HIGH); 358 TestObserver observerMid = new TestObserver(OBSERVER_NAME_3, 359 PackageHealthObserverImpact.USER_IMPACT_MEDIUM); 360 TestObserver observerLow = new TestObserver(OBSERVER_NAME_4, 361 PackageHealthObserverImpact.USER_IMPACT_LOW); 362 363 // Start observing for all impact observers 364 watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D), 365 SHORT_DURATION); 366 watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C), 367 SHORT_DURATION); 368 watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B), 369 SHORT_DURATION); 370 watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A), 371 SHORT_DURATION); 372 373 // Then fail all apps above the threshold 374 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 375 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE), 376 new VersionedPackage(APP_B, VERSION_CODE), 377 new VersionedPackage(APP_C, VERSION_CODE), 378 new VersionedPackage(APP_D, VERSION_CODE)), 379 PackageWatchdog.FAILURE_REASON_UNKNOWN); 380 } 381 382 // Run handler so package failures are dispatched to observers 383 mTestLooper.dispatchAll(); 384 385 // Verify least impact observers are notifed of package failures 386 List<String> observerNonePackages = observerNone.mFailedPackages; 387 List<String> observerHighPackages = observerHigh.mFailedPackages; 388 List<String> observerMidPackages = observerMid.mFailedPackages; 389 List<String> observerLowPackages = observerLow.mFailedPackages; 390 391 // APP_D failure observed by only observerNone is not caught cos its impact is none 392 assertEquals(0, observerNonePackages.size()); 393 // APP_C failure is caught by observerHigh cos it's the lowest impact observer 394 assertEquals(1, observerHighPackages.size()); 395 assertEquals(APP_C, observerHighPackages.get(0)); 396 // APP_B failure is caught by observerMid cos it's the lowest impact observer 397 assertEquals(1, observerMidPackages.size()); 398 assertEquals(APP_B, observerMidPackages.get(0)); 399 // APP_A failure is caught by observerLow cos it's the lowest impact observer 400 assertEquals(1, observerLowPackages.size()); 401 assertEquals(APP_A, observerLowPackages.get(0)); 402 } 403 404 /** 405 * Test package failure and least impact observers are notified successively. 406 * State transistions: 407 * 408 * <ul> 409 * <li>(observer1:low, observer2:mid) -> {observer1} 410 * <li>(observer1:high, observer2:mid) -> {observer2} 411 * <li>(observer1:high, observer2:none) -> {observer1} 412 * <li>(observer1:none, observer2:none) -> {} 413 * <ul> 414 */ 415 @Test testPackageFailureNotifyLeastImpactSuccessively()416 public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception { 417 PackageWatchdog watchdog = createWatchdog(); 418 TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1, 419 PackageHealthObserverImpact.USER_IMPACT_LOW); 420 TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2, 421 PackageHealthObserverImpact.USER_IMPACT_MEDIUM); 422 423 // Start observing for observerFirst and observerSecond with failure handling 424 watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION); 425 watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION); 426 427 // Then fail APP_A above the threshold 428 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 429 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), 430 PackageWatchdog.FAILURE_REASON_UNKNOWN); 431 } 432 // Run handler so package failures are dispatched to observers 433 mTestLooper.dispatchAll(); 434 435 // Verify only observerFirst is notifed 436 assertEquals(1, observerFirst.mFailedPackages.size()); 437 assertEquals(APP_A, observerFirst.mFailedPackages.get(0)); 438 assertEquals(0, observerSecond.mFailedPackages.size()); 439 440 // After observerFirst handles failure, next action it has is high impact 441 observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH; 442 observerFirst.mFailedPackages.clear(); 443 observerSecond.mFailedPackages.clear(); 444 445 // Then fail APP_A again above the threshold 446 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 447 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), 448 PackageWatchdog.FAILURE_REASON_UNKNOWN); 449 } 450 // Run handler so package failures are dispatched to observers 451 mTestLooper.dispatchAll(); 452 453 // Verify only observerSecond is notifed cos it has least impact 454 assertEquals(1, observerSecond.mFailedPackages.size()); 455 assertEquals(APP_A, observerSecond.mFailedPackages.get(0)); 456 assertEquals(0, observerFirst.mFailedPackages.size()); 457 458 // After observerSecond handles failure, it has no further actions 459 observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; 460 observerFirst.mFailedPackages.clear(); 461 observerSecond.mFailedPackages.clear(); 462 463 // Then fail APP_A again above the threshold 464 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 465 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), 466 PackageWatchdog.FAILURE_REASON_UNKNOWN); 467 } 468 // Run handler so package failures are dispatched to observers 469 mTestLooper.dispatchAll(); 470 471 // Verify only observerFirst is notifed cos it has the only action 472 assertEquals(1, observerFirst.mFailedPackages.size()); 473 assertEquals(APP_A, observerFirst.mFailedPackages.get(0)); 474 assertEquals(0, observerSecond.mFailedPackages.size()); 475 476 // After observerFirst handles failure, it too has no further actions 477 observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE; 478 observerFirst.mFailedPackages.clear(); 479 observerSecond.mFailedPackages.clear(); 480 481 // Then fail APP_A again above the threshold 482 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 483 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), 484 PackageWatchdog.FAILURE_REASON_UNKNOWN); 485 } 486 // Run handler so package failures are dispatched to observers 487 mTestLooper.dispatchAll(); 488 489 // Verify no observer is notified cos no actions left 490 assertEquals(0, observerFirst.mFailedPackages.size()); 491 assertEquals(0, observerSecond.mFailedPackages.size()); 492 } 493 494 /** 495 * Test package failure and notifies only one observer even with observer impact tie. 496 */ 497 @Test testPackageFailureNotifyOneSameImpact()498 public void testPackageFailureNotifyOneSameImpact() throws Exception { 499 PackageWatchdog watchdog = createWatchdog(); 500 TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, 501 PackageHealthObserverImpact.USER_IMPACT_HIGH); 502 TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, 503 PackageHealthObserverImpact.USER_IMPACT_HIGH); 504 505 // Start observing for observer1 and observer2 with failure handling 506 watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION); 507 watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); 508 509 // Then fail APP_A above the threshold 510 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 511 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), 512 PackageWatchdog.FAILURE_REASON_UNKNOWN); 513 } 514 515 // Run handler so package failures are dispatched to observers 516 mTestLooper.dispatchAll(); 517 518 // Verify only one observer is notifed 519 assertEquals(1, observer1.mFailedPackages.size()); 520 assertEquals(APP_A, observer1.mFailedPackages.get(0)); 521 assertEquals(0, observer2.mFailedPackages.size()); 522 } 523 524 /** 525 * Test package passing explicit health checks does not fail and vice versa. 526 */ 527 @Test testExplicitHealthChecks()528 public void testExplicitHealthChecks() throws Exception { 529 TestController controller = new TestController(); 530 PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); 531 TestObserver observer1 = new TestObserver(OBSERVER_NAME_1, 532 PackageHealthObserverImpact.USER_IMPACT_HIGH); 533 TestObserver observer2 = new TestObserver(OBSERVER_NAME_2, 534 PackageHealthObserverImpact.USER_IMPACT_HIGH); 535 TestObserver observer3 = new TestObserver(OBSERVER_NAME_3, 536 PackageHealthObserverImpact.USER_IMPACT_HIGH); 537 538 539 // Start observing with explicit health checks for APP_A and APP_B respectively 540 // with observer1 and observer2 541 controller.setSupportedPackages(Arrays.asList(APP_A, APP_B)); 542 watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); 543 watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION); 544 545 // Run handler so requests are dispatched to the controller 546 mTestLooper.dispatchAll(); 547 548 // Verify we requested health checks for APP_A and APP_B 549 List<String> requestedPackages = controller.getRequestedPackages(); 550 assertEquals(2, requestedPackages.size()); 551 assertEquals(APP_A, requestedPackages.get(0)); 552 assertEquals(APP_B, requestedPackages.get(1)); 553 554 // Then health check passed for APP_A (observer1 is aware) 555 controller.setPackagePassed(APP_A); 556 557 // Then start observing APP_A with explicit health checks for observer3. 558 // Observer3 didn't exist when we got the explicit health check above, so 559 // it starts out with a non-passing explicit health check and has to wait for a pass 560 // otherwise it would be notified of APP_A failure on expiry 561 watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION); 562 563 // Then expire observers 564 Thread.sleep(SHORT_DURATION); 565 // Run handler so package failures are dispatched to observers 566 mTestLooper.dispatchAll(); 567 568 // Verify we cancelled all requests on expiry 569 assertEquals(0, controller.getRequestedPackages().size()); 570 571 // Verify observer1 is not notified 572 assertEquals(0, observer1.mFailedPackages.size()); 573 574 // Verify observer2 is notifed because health checks for APP_B never passed 575 assertEquals(1, observer2.mFailedPackages.size()); 576 assertEquals(APP_B, observer2.mFailedPackages.get(0)); 577 578 // Verify observer3 is notifed because health checks for APP_A did not pass before expiry 579 assertEquals(1, observer3.mFailedPackages.size()); 580 assertEquals(APP_A, observer3.mFailedPackages.get(0)); 581 } 582 583 /** 584 * Test explicit health check state can be disabled and enabled correctly. 585 */ 586 @Test testExplicitHealthCheckStateChanges()587 public void testExplicitHealthCheckStateChanges() throws Exception { 588 adoptShellPermissions( 589 Manifest.permission.WRITE_DEVICE_CONFIG, 590 Manifest.permission.READ_DEVICE_CONFIG); 591 592 TestController controller = new TestController(); 593 PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); 594 TestObserver observer = new TestObserver(OBSERVER_NAME_1, 595 PackageHealthObserverImpact.USER_IMPACT_MEDIUM); 596 597 // Start observing with explicit health checks for APP_A and APP_B 598 controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C)); 599 watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION); 600 watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION); 601 602 // Run handler so requests are dispatched to the controller 603 mTestLooper.dispatchAll(); 604 605 // Verify we requested health checks for APP_A and APP_B 606 List<String> requestedPackages = controller.getRequestedPackages(); 607 assertEquals(2, requestedPackages.size()); 608 assertEquals(APP_A, requestedPackages.get(0)); 609 assertEquals(APP_B, requestedPackages.get(1)); 610 611 // Disable explicit health checks (marks APP_A and APP_B as passed) 612 setExplicitHealthCheckEnabled(false); 613 614 // Run handler so requests/cancellations are dispatched to the controller 615 mTestLooper.dispatchAll(); 616 617 // Verify all checks are cancelled 618 assertEquals(0, controller.getRequestedPackages().size()); 619 620 // Then expire APP_A 621 Thread.sleep(SHORT_DURATION); 622 mTestLooper.dispatchAll(); 623 624 // Verify APP_A is not failed (APP_B) is not expired yet 625 assertEquals(0, observer.mFailedPackages.size()); 626 627 // Re-enable explicit health checks 628 setExplicitHealthCheckEnabled(true); 629 630 // Run handler so requests/cancellations are dispatched to the controller 631 mTestLooper.dispatchAll(); 632 633 // Verify no requests are made cos APP_A is expired and APP_B was marked as passed 634 assertEquals(0, controller.getRequestedPackages().size()); 635 636 // Then set new supported packages 637 controller.setSupportedPackages(Arrays.asList(APP_C)); 638 // Start observing APP_A and APP_C; only APP_C has support for explicit health checks 639 watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION); 640 641 // Run handler so requests/cancellations are dispatched to the controller 642 mTestLooper.dispatchAll(); 643 644 // Verify requests are only made for APP_C 645 requestedPackages = controller.getRequestedPackages(); 646 assertEquals(1, requestedPackages.size()); 647 assertEquals(APP_C, requestedPackages.get(0)); 648 649 // Then expire APP_A and APP_C 650 Thread.sleep(SHORT_DURATION); 651 mTestLooper.dispatchAll(); 652 653 // Verify only APP_C is failed because explicit health checks was not supported for APP_A 654 assertEquals(1, observer.mFailedPackages.size()); 655 assertEquals(APP_C, observer.mFailedPackages.get(0)); 656 } 657 658 /** 659 * Tests failure when health check duration is different from package observation duration 660 * Failure is also notified only once. 661 */ 662 @Test testExplicitHealthCheckFailureBeforeExpiry()663 public void testExplicitHealthCheckFailureBeforeExpiry() throws Exception { 664 TestController controller = new TestController(); 665 PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */); 666 TestObserver observer = new TestObserver(OBSERVER_NAME_1, 667 PackageHealthObserverImpact.USER_IMPACT_MEDIUM); 668 669 // Start observing with explicit health checks for APP_A and 670 // package observation duration == LONG_DURATION 671 // health check duration == SHORT_DURATION (set by default in the TestController) 672 controller.setSupportedPackages(Arrays.asList(APP_A)); 673 watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION); 674 675 // Then APP_A has exceeded health check duration 676 Thread.sleep(SHORT_DURATION); 677 mTestLooper.dispatchAll(); 678 679 // Verify that health check is failed 680 assertEquals(1, observer.mFailedPackages.size()); 681 assertEquals(APP_A, observer.mFailedPackages.get(0)); 682 683 // Then clear failed packages and start observing a random package so requests are synced 684 // and PackageWatchdog#onSupportedPackages is called and APP_A has a chance to fail again 685 // this time due to package expiry. 686 observer.mFailedPackages.clear(); 687 watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION); 688 689 // Verify that health check failure is not notified again 690 assertTrue(observer.mFailedPackages.isEmpty()); 691 } 692 693 /** Tests {@link MonitoredPackage} health check state transitions. */ 694 @Test testPackageHealthCheckStateTransitions()695 public void testPackageHealthCheckStateTransitions() { 696 TestController controller = new TestController(); 697 PackageWatchdog wd = createWatchdog(controller, true /* withPackagesReady */); 698 MonitoredPackage m1 = wd.new MonitoredPackage(APP_A, LONG_DURATION, 699 false /* hasPassedHealthCheck */); 700 MonitoredPackage m2 = wd.new MonitoredPackage(APP_B, LONG_DURATION, false); 701 MonitoredPackage m3 = wd.new MonitoredPackage(APP_C, LONG_DURATION, false); 702 MonitoredPackage m4 = wd.new MonitoredPackage(APP_D, LONG_DURATION, SHORT_DURATION, true); 703 704 // Verify transition: inactive -> active -> passed 705 // Verify initially inactive 706 assertEquals(MonitoredPackage.STATE_INACTIVE, m1.getHealthCheckStateLocked()); 707 // Verify still inactive, until we #setHealthCheckActiveLocked 708 assertEquals(MonitoredPackage.STATE_INACTIVE, m1.handleElapsedTimeLocked(SHORT_DURATION)); 709 // Verify now active 710 assertEquals(MonitoredPackage.STATE_ACTIVE, m1.setHealthCheckActiveLocked(SHORT_DURATION)); 711 // Verify now passed 712 assertEquals(MonitoredPackage.STATE_PASSED, m1.tryPassHealthCheckLocked()); 713 714 // Verify transition: inactive -> active -> failed 715 // Verify initially inactive 716 assertEquals(MonitoredPackage.STATE_INACTIVE, m2.getHealthCheckStateLocked()); 717 // Verify now active 718 assertEquals(MonitoredPackage.STATE_ACTIVE, m2.setHealthCheckActiveLocked(SHORT_DURATION)); 719 // Verify now failed 720 assertEquals(MonitoredPackage.STATE_FAILED, m2.handleElapsedTimeLocked(SHORT_DURATION)); 721 722 // Verify transition: inactive -> failed 723 // Verify initially inactive 724 assertEquals(MonitoredPackage.STATE_INACTIVE, m3.getHealthCheckStateLocked()); 725 // Verify now failed because package expired 726 assertEquals(MonitoredPackage.STATE_FAILED, m3.handleElapsedTimeLocked(LONG_DURATION)); 727 // Verify remains failed even when asked to pass 728 assertEquals(MonitoredPackage.STATE_FAILED, m3.tryPassHealthCheckLocked()); 729 730 // Verify transition: passed 731 assertEquals(MonitoredPackage.STATE_PASSED, m4.getHealthCheckStateLocked()); 732 // Verify remains passed even if health check fails 733 assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(SHORT_DURATION)); 734 // Verify remains passed even if package expires 735 assertEquals(MonitoredPackage.STATE_PASSED, m4.handleElapsedTimeLocked(LONG_DURATION)); 736 } 737 738 @Test testNetworkStackFailure()739 public void testNetworkStackFailure() { 740 final PackageWatchdog wd = createWatchdog(); 741 742 // Start observing with failure handling 743 TestObserver observer = new TestObserver(OBSERVER_NAME_1, 744 PackageHealthObserverImpact.USER_IMPACT_HIGH); 745 wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION); 746 747 // Notify of NetworkStack failure 748 mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A); 749 750 // Run handler so package failures are dispatched to observers 751 mTestLooper.dispatchAll(); 752 753 // Verify the NetworkStack observer is notified 754 assertEquals(1, observer.mFailedPackages.size()); 755 assertEquals(APP_A, observer.mFailedPackages.get(0)); 756 } 757 758 /** Test that observers execute correctly for different failure reasons */ 759 @Test testFailureReasons()760 public void testFailureReasons() { 761 PackageWatchdog watchdog = createWatchdog(); 762 TestObserver observer1 = new TestObserver(OBSERVER_NAME_1); 763 TestObserver observer2 = new TestObserver(OBSERVER_NAME_2); 764 TestObserver observer3 = new TestObserver(OBSERVER_NAME_3); 765 TestObserver observer4 = new TestObserver(OBSERVER_NAME_4); 766 767 watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION); 768 watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION); 769 watchdog.startObservingHealth(observer3, Arrays.asList(APP_C), SHORT_DURATION); 770 watchdog.startObservingHealth(observer4, Arrays.asList(APP_D), SHORT_DURATION); 771 772 for (int i = 0; i < watchdog.getTriggerFailureCount(); i++) { 773 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)), 774 PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); 775 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)), 776 PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); 777 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)), 778 PackageWatchdog.FAILURE_REASON_APP_CRASH); 779 watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_D, VERSION_CODE)), 780 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); 781 } 782 783 // Run handler so requests are dispatched to the controller 784 mTestLooper.dispatchAll(); 785 786 assertTrue(observer1.getLastFailureReason() 787 == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH); 788 assertTrue(observer2.getLastFailureReason() 789 == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK); 790 assertTrue(observer3.getLastFailureReason() 791 == PackageWatchdog.FAILURE_REASON_APP_CRASH); 792 assertTrue(observer4.getLastFailureReason() 793 == PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING); 794 } 795 adoptShellPermissions(String... permissions)796 private void adoptShellPermissions(String... permissions) { 797 InstrumentationRegistry 798 .getInstrumentation() 799 .getUiAutomation() 800 .adoptShellPermissionIdentity(permissions); 801 } 802 dropShellPermissions()803 private void dropShellPermissions() { 804 InstrumentationRegistry 805 .getInstrumentation() 806 .getUiAutomation() 807 .dropShellPermissionIdentity(); 808 } 809 setExplicitHealthCheckEnabled(boolean enabled)810 private void setExplicitHealthCheckEnabled(boolean enabled) { 811 DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK, 812 PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED, 813 Boolean.toString(enabled), /*makeDefault*/false); 814 //give time for DeviceConfig to broadcast the property value change 815 try { 816 Thread.sleep(SHORT_DURATION); 817 } catch (InterruptedException e) { 818 fail("Thread.sleep unexpectedly failed!"); 819 } 820 } 821 createWatchdog()822 private PackageWatchdog createWatchdog() { 823 return createWatchdog(new TestController(), true /* withPackagesReady */); 824 } 825 createWatchdog(TestController controller, boolean withPackagesReady)826 private PackageWatchdog createWatchdog(TestController controller, boolean withPackagesReady) { 827 AtomicFile policyFile = 828 new AtomicFile(new File(mSpyContext.getFilesDir(), "package-watchdog.xml")); 829 Handler handler = new Handler(mTestLooper.getLooper()); 830 PackageWatchdog watchdog = 831 new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller, 832 mConnectivityModuleConnector); 833 // Verify controller is not automatically started 834 assertFalse(controller.mIsEnabled); 835 if (withPackagesReady) { 836 // Only capture the NetworkStack callback for the latest registered watchdog 837 reset(mConnectivityModuleConnector); 838 watchdog.onPackagesReady(); 839 // Verify controller by default is started when packages are ready 840 assertTrue(controller.mIsEnabled); 841 842 verify(mConnectivityModuleConnector).registerHealthListener( 843 mConnectivityModuleCallbackCaptor.capture()); 844 } 845 return watchdog; 846 } 847 848 private static class TestObserver implements PackageHealthObserver { 849 private final String mName; 850 private int mImpact; 851 private int mLastFailureReason; 852 final List<String> mFailedPackages = new ArrayList<>(); 853 TestObserver(String name)854 TestObserver(String name) { 855 mName = name; 856 mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM; 857 } 858 TestObserver(String name, int impact)859 TestObserver(String name, int impact) { 860 mName = name; 861 mImpact = impact; 862 } 863 onHealthCheckFailed(VersionedPackage versionedPackage)864 public int onHealthCheckFailed(VersionedPackage versionedPackage) { 865 return mImpact; 866 } 867 execute(VersionedPackage versionedPackage, int failureReason)868 public boolean execute(VersionedPackage versionedPackage, int failureReason) { 869 mFailedPackages.add(versionedPackage.getPackageName()); 870 mLastFailureReason = failureReason; 871 return true; 872 } 873 getName()874 public String getName() { 875 return mName; 876 } 877 getLastFailureReason()878 public int getLastFailureReason() { 879 return mLastFailureReason; 880 } 881 } 882 883 private static class TestController extends ExplicitHealthCheckController { TestController()884 TestController() { 885 super(null /* controller */); 886 } 887 888 private boolean mIsEnabled; 889 private List<String> mSupportedPackages = new ArrayList<>(); 890 private List<String> mRequestedPackages = new ArrayList<>(); 891 private Consumer<String> mPassedConsumer; 892 private Consumer<List<PackageConfig>> mSupportedConsumer; 893 private Runnable mNotifySyncRunnable; 894 895 @Override setEnabled(boolean enabled)896 public void setEnabled(boolean enabled) { 897 mIsEnabled = enabled; 898 if (!mIsEnabled) { 899 mSupportedPackages.clear(); 900 } 901 } 902 903 @Override setCallbacks(Consumer<String> passedConsumer, Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable)904 public void setCallbacks(Consumer<String> passedConsumer, 905 Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) { 906 mPassedConsumer = passedConsumer; 907 mSupportedConsumer = supportedConsumer; 908 mNotifySyncRunnable = notifySyncRunnable; 909 } 910 911 @Override syncRequests(Set<String> packages)912 public void syncRequests(Set<String> packages) { 913 mRequestedPackages.clear(); 914 if (mIsEnabled) { 915 packages.retainAll(mSupportedPackages); 916 mRequestedPackages.addAll(packages); 917 List<PackageConfig> packageConfigs = new ArrayList<>(); 918 for (String packageName: packages) { 919 packageConfigs.add(new PackageConfig(packageName, SHORT_DURATION)); 920 } 921 mSupportedConsumer.accept(packageConfigs); 922 } else { 923 mSupportedConsumer.accept(Collections.emptyList()); 924 } 925 } 926 setSupportedPackages(List<String> packages)927 public void setSupportedPackages(List<String> packages) { 928 mSupportedPackages.clear(); 929 mSupportedPackages.addAll(packages); 930 } 931 setPackagePassed(String packageName)932 public void setPackagePassed(String packageName) { 933 mPassedConsumer.accept(packageName); 934 } 935 getRequestedPackages()936 public List<String> getRequestedPackages() { 937 if (mIsEnabled) { 938 return mRequestedPackages; 939 } else { 940 return Collections.emptyList(); 941 } 942 } 943 } 944 } 945