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