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