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 com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
22 
23 import static com.google.common.truth.Truth.assertThat;
24 
25 import static org.junit.Assert.fail;
26 import static org.mockito.ArgumentMatchers.anyInt;
27 import static org.mockito.ArgumentMatchers.anyLong;
28 import static org.mockito.ArgumentMatchers.anyString;
29 import static org.mockito.Mockito.reset;
30 import static org.mockito.Mockito.spy;
31 import static org.mockito.Mockito.verify;
32 import static org.mockito.Mockito.when;
33 
34 import android.Manifest;
35 import android.content.Context;
36 import android.content.pm.PackageInfo;
37 import android.content.pm.PackageManager;
38 import android.content.pm.VersionedPackage;
39 import android.net.ConnectivityModuleConnector;
40 import android.net.ConnectivityModuleConnector.ConnectivityModuleHealthListener;
41 import android.os.Handler;
42 import android.os.SystemProperties;
43 import android.os.test.TestLooper;
44 import android.provider.DeviceConfig;
45 import android.util.AtomicFile;
46 
47 import androidx.test.InstrumentationRegistry;
48 
49 import com.android.dx.mockito.inline.extended.ExtendedMockito;
50 import com.android.server.PackageWatchdog.HealthCheckState;
51 import com.android.server.PackageWatchdog.MonitoredPackage;
52 import com.android.server.PackageWatchdog.PackageHealthObserver;
53 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
54 
55 import org.junit.After;
56 import org.junit.Before;
57 import org.junit.Test;
58 import org.mockito.ArgumentCaptor;
59 import org.mockito.Captor;
60 import org.mockito.Mock;
61 import org.mockito.MockitoAnnotations;
62 import org.mockito.MockitoSession;
63 import org.mockito.quality.Strictness;
64 import org.mockito.stubbing.Answer;
65 
66 import java.io.File;
67 import java.util.ArrayList;
68 import java.util.Arrays;
69 import java.util.Collections;
70 import java.util.HashMap;
71 import java.util.List;
72 import java.util.Set;
73 import java.util.concurrent.TimeUnit;
74 import java.util.function.Consumer;
75 
76 /**
77  * Test PackageWatchdog.
78  */
79 public class PackageWatchdogTest {
80     private static final String APP_A = "com.package.a";
81     private static final String APP_B = "com.package.b";
82     private static final String APP_C = "com.package.c";
83     private static final String APP_D = "com.package.d";
84     private static final long VERSION_CODE = 1L;
85     private static final String OBSERVER_NAME_1 = "observer1";
86     private static final String OBSERVER_NAME_2 = "observer2";
87     private static final String OBSERVER_NAME_3 = "observer3";
88     private static final String OBSERVER_NAME_4 = "observer4";
89     private static final long SHORT_DURATION = TimeUnit.SECONDS.toMillis(1);
90     private static final long LONG_DURATION = TimeUnit.SECONDS.toMillis(5);
91     private final TestClock mTestClock = new TestClock();
92     private TestLooper mTestLooper;
93     private Context mSpyContext;
94     @Mock
95     private ConnectivityModuleConnector mConnectivityModuleConnector;
96     @Mock
97     private PackageManager mMockPackageManager;
98     @Captor
99     private ArgumentCaptor<ConnectivityModuleHealthListener> mConnectivityModuleCallbackCaptor;
100     private MockitoSession mSession;
101     private HashMap<String, String> mSystemSettingsMap;
102 
103     @Before
setUp()104     public void setUp() throws Exception {
105         MockitoAnnotations.initMocks(this);
106         new File(InstrumentationRegistry.getContext().getFilesDir(),
107                 "package-watchdog.xml").delete();
108         adoptShellPermissions(Manifest.permission.READ_DEVICE_CONFIG);
109         mTestLooper = new TestLooper();
110         mSpyContext = spy(InstrumentationRegistry.getContext());
111         when(mSpyContext.getPackageManager()).thenReturn(mMockPackageManager);
112         when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
113             final PackageInfo res = new PackageInfo();
114             res.packageName = inv.getArgument(0);
115             res.setLongVersionCode(VERSION_CODE);
116             return res;
117         });
118         mSession = ExtendedMockito.mockitoSession()
119                 .initMocks(this)
120                 .strictness(Strictness.LENIENT)
121                 .spyStatic(SystemProperties.class)
122                 .startMocking();
123         mSystemSettingsMap = new HashMap<>();
124 
125 
126         // Mock SystemProperties setter and various getters
127         doAnswer((Answer<Void>) invocationOnMock -> {
128                     String key = invocationOnMock.getArgument(0);
129                     String value = invocationOnMock.getArgument(1);
130 
131                     mSystemSettingsMap.put(key, value);
132                     return null;
133                 }
134         ).when(() -> SystemProperties.set(anyString(), anyString()));
135 
136         doAnswer((Answer<Integer>) invocationOnMock -> {
137                     String key = invocationOnMock.getArgument(0);
138                     int defaultValue = invocationOnMock.getArgument(1);
139 
140                     String storedValue = mSystemSettingsMap.get(key);
141                     return storedValue == null ? defaultValue : Integer.parseInt(storedValue);
142                 }
143         ).when(() -> SystemProperties.getInt(anyString(), anyInt()));
144 
145         doAnswer((Answer<Long>) invocationOnMock -> {
146                     String key = invocationOnMock.getArgument(0);
147                     long defaultValue = invocationOnMock.getArgument(1);
148 
149                     String storedValue = mSystemSettingsMap.get(key);
150                     return storedValue == null ? defaultValue : Long.parseLong(storedValue);
151                 }
152         ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
153     }
154 
155     @After
tearDown()156     public void tearDown() throws Exception {
157         dropShellPermissions();
158         mSession.finishMocking();
159     }
160 
161     @Test
testRegistration_singleObserver()162     public void testRegistration_singleObserver() {
163         PackageWatchdog watchdog = createWatchdog();
164         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
165 
166         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
167         raiseFatalFailureAndDispatch(watchdog,
168                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
169                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
170 
171         // The failed packages should be the same as the registered ones to ensure registration is
172         // done successfully
173         assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
174     }
175 
176     @Test
testRegistration_multiObservers()177     public void testRegistration_multiObservers() {
178         PackageWatchdog watchdog = createWatchdog();
179         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
180         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
181 
182         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
183         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
184         raiseFatalFailureAndDispatch(watchdog,
185                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
186                         new VersionedPackage(APP_B, VERSION_CODE)),
187                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
188 
189         // The failed packages should be the same as the registered ones to ensure registration is
190         // done successfully
191         assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
192         assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
193     }
194 
195     @Test
testUnregistration_singleObserver()196     public void testUnregistration_singleObserver() {
197         PackageWatchdog watchdog = createWatchdog();
198         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
199 
200         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
201         watchdog.unregisterHealthObserver(observer);
202         raiseFatalFailureAndDispatch(watchdog,
203                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
204                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
205 
206         // We should have no failed packages to ensure unregistration is done successfully
207         assertThat(observer.mHealthCheckFailedPackages).isEmpty();
208     }
209 
210     @Test
testUnregistration_multiObservers()211     public void testUnregistration_multiObservers() {
212         PackageWatchdog watchdog = createWatchdog();
213         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
214         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
215 
216         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
217         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
218         watchdog.unregisterHealthObserver(observer2);
219         raiseFatalFailureAndDispatch(watchdog,
220                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
221                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
222 
223         // observer1 should receive failed packages as intended.
224         assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
225         // observer2 should have no failed packages to ensure unregistration is done successfully
226         assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
227     }
228 
229     @Test
testExpiration_singleObserver()230     public void testExpiration_singleObserver() {
231         PackageWatchdog watchdog = createWatchdog();
232         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
233 
234         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
235         moveTimeForwardAndDispatch(SHORT_DURATION);
236         raiseFatalFailureAndDispatch(watchdog,
237                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
238                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
239 
240         // We should have no failed packages for the fatal failure is raised after expiration
241         assertThat(observer.mHealthCheckFailedPackages).isEmpty();
242     }
243 
244     @Test
testExpiration_multiObservers()245     public void testExpiration_multiObservers() {
246         PackageWatchdog watchdog = createWatchdog();
247         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
248         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
249 
250         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
251         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), LONG_DURATION);
252         moveTimeForwardAndDispatch(SHORT_DURATION);
253         raiseFatalFailureAndDispatch(watchdog,
254                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
255                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
256 
257         // We should have no failed packages for the fatal failure is raised after expiration
258         assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
259         // We should have failed packages since observer2 hasn't expired
260         assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A);
261     }
262 
263     /** Observing already observed package extends the observation time. */
264     @Test
testObserveAlreadyObservedPackage()265     public void testObserveAlreadyObservedPackage() {
266         PackageWatchdog watchdog = createWatchdog();
267         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
268 
269         // Start observing APP_A
270         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
271 
272         // Then advance time half-way
273         moveTimeForwardAndDispatch(SHORT_DURATION / 2);
274 
275         // Start observing APP_A again
276         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
277 
278         // Then advance time such that it should have expired were it not for the second observation
279         moveTimeForwardAndDispatch((SHORT_DURATION / 2) + 1);
280 
281         raiseFatalFailureAndDispatch(watchdog,
282                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
283                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
284 
285         // Verify that we receive failed packages as expected for APP_A not expired
286         assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
287     }
288 
289     /**
290      * Test package observers are persisted and loaded on startup
291      */
292     @Test
testPersistence()293     public void testPersistence() {
294         PackageWatchdog watchdog1 = createWatchdog();
295         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
296         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
297 
298         watchdog1.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
299         watchdog1.startObservingHealth(observer2, Arrays.asList(APP_A, APP_B), SHORT_DURATION);
300         // Then advance time and run IO Handler so file is saved
301         mTestLooper.dispatchAll();
302         // Then start a new watchdog
303         PackageWatchdog watchdog2 = createWatchdog();
304         // Then resume observer1 and observer2
305         watchdog2.registerHealthObserver(observer1);
306         watchdog2.registerHealthObserver(observer2);
307         raiseFatalFailureAndDispatch(watchdog2,
308                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
309                         new VersionedPackage(APP_B, VERSION_CODE)),
310                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
311 
312         // We should receive failed packages as expected to ensure observers are persisted and
313         // resumed correctly
314         assertThat(observer1.mHealthCheckFailedPackages).containsExactly(APP_A);
315         assertThat(observer2.mHealthCheckFailedPackages).containsExactly(APP_A, APP_B);
316     }
317 
318     /**
319      * Test package failure under threshold does not notify observers
320      */
321     @Test
testNoPackageFailureBeforeThreshold()322     public void testNoPackageFailureBeforeThreshold() throws Exception {
323         PackageWatchdog watchdog = createWatchdog();
324         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
325         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
326 
327         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
328         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
329 
330         // Then fail APP_A below the threshold
331         for (int i = 0; i < watchdog.getTriggerFailureCount() - 1; i++) {
332             watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
333                     PackageWatchdog.FAILURE_REASON_UNKNOWN);
334         }
335 
336         // Run handler so package failures are dispatched to observers
337         mTestLooper.dispatchAll();
338 
339         // Verify that observers are not notified
340         assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
341         assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
342     }
343 
344     /**
345      * Test package failure and does not notify any observer because they are not observing
346      * the failed packages.
347      */
348     @Test
testPackageFailureDifferentPackageNotifyNone()349     public void testPackageFailureDifferentPackageNotifyNone() throws Exception {
350         PackageWatchdog watchdog = createWatchdog();
351         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
352         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
353 
354 
355         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
356         watchdog.startObservingHealth(observer1, Arrays.asList(APP_B), SHORT_DURATION);
357 
358         // Then fail APP_C (not observed) above the threshold
359         raiseFatalFailureAndDispatch(watchdog,
360                 Arrays.asList(new VersionedPackage(APP_C, VERSION_CODE)),
361                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
362 
363         // Verify that observers are not notified
364         assertThat(observer1.mHealthCheckFailedPackages).isEmpty();
365         assertThat(observer2.mHealthCheckFailedPackages).isEmpty();
366     }
367 
368     /**
369      * Test package failure and does not notify any observer because the failed package version
370      * does not match the available rollback-from-version.
371      */
372     @Test
testPackageFailureDifferentVersionNotifyNone()373     public void testPackageFailureDifferentVersionNotifyNone() throws Exception {
374         PackageWatchdog watchdog = createWatchdog();
375         long differentVersionCode = 2L;
376         TestObserver observer = new TestObserver(OBSERVER_NAME_1) {
377                 @Override
378                 public int onHealthCheckFailed(VersionedPackage versionedPackage,
379                         int failureReason) {
380                     if (versionedPackage.getVersionCode() == VERSION_CODE) {
381                         // Only rollback for specific versionCode
382                         return PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
383                     }
384                     return PackageHealthObserverImpact.USER_IMPACT_NONE;
385                 }
386             };
387 
388         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
389 
390         // Then fail APP_A (different version) above the threshold
391         raiseFatalFailureAndDispatch(watchdog,
392                 Arrays.asList(new VersionedPackage(APP_A, differentVersionCode)),
393                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
394 
395         // Verify that observers are not notified
396         assertThat(observer.mHealthCheckFailedPackages).isEmpty();
397     }
398 
399 
400     /**
401      * Test package failure and notifies only least impact observers.
402      */
403     @Test
testPackageFailureNotifyAllDifferentImpacts()404     public void testPackageFailureNotifyAllDifferentImpacts() throws Exception {
405         PackageWatchdog watchdog = createWatchdog();
406         TestObserver observerNone = new TestObserver(OBSERVER_NAME_1,
407                 PackageHealthObserverImpact.USER_IMPACT_NONE);
408         TestObserver observerHigh = new TestObserver(OBSERVER_NAME_2,
409                 PackageHealthObserverImpact.USER_IMPACT_HIGH);
410         TestObserver observerMid = new TestObserver(OBSERVER_NAME_3,
411                 PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
412         TestObserver observerLow = new TestObserver(OBSERVER_NAME_4,
413                 PackageHealthObserverImpact.USER_IMPACT_LOW);
414 
415         // Start observing for all impact observers
416         watchdog.startObservingHealth(observerNone, Arrays.asList(APP_A, APP_B, APP_C, APP_D),
417                 SHORT_DURATION);
418         watchdog.startObservingHealth(observerHigh, Arrays.asList(APP_A, APP_B, APP_C),
419                 SHORT_DURATION);
420         watchdog.startObservingHealth(observerMid, Arrays.asList(APP_A, APP_B),
421                 SHORT_DURATION);
422         watchdog.startObservingHealth(observerLow, Arrays.asList(APP_A),
423                 SHORT_DURATION);
424 
425         // Then fail all apps above the threshold
426         raiseFatalFailureAndDispatch(watchdog,
427                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE),
428                         new VersionedPackage(APP_B, VERSION_CODE),
429                         new VersionedPackage(APP_C, VERSION_CODE),
430                         new VersionedPackage(APP_D, VERSION_CODE)),
431                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
432 
433         // Verify least impact observers are notifed of package failures
434         List<String> observerNonePackages = observerNone.mMitigatedPackages;
435         List<String> observerHighPackages = observerHigh.mMitigatedPackages;
436         List<String> observerMidPackages = observerMid.mMitigatedPackages;
437         List<String> observerLowPackages = observerLow.mMitigatedPackages;
438 
439         // APP_D failure observed by only observerNone is not caught cos its impact is none
440         assertThat(observerNonePackages).isEmpty();
441         // APP_C failure is caught by observerHigh cos it's the lowest impact observer
442         assertThat(observerHighPackages).containsExactly(APP_C);
443         // APP_B failure is caught by observerMid cos it's the lowest impact observer
444         assertThat(observerMidPackages).containsExactly(APP_B);
445         // APP_A failure is caught by observerLow cos it's the lowest impact observer
446         assertThat(observerLowPackages).containsExactly(APP_A);
447     }
448 
449     /**
450      * Test package failure and least impact observers are notified successively.
451      * State transistions:
452      *
453      * <ul>
454      * <li>(observer1:low, observer2:mid) -> {observer1}
455      * <li>(observer1:high, observer2:mid) -> {observer2}
456      * <li>(observer1:high, observer2:none) -> {observer1}
457      * <li>(observer1:none, observer2:none) -> {}
458      * <ul>
459      */
460     @Test
testPackageFailureNotifyLeastImpactSuccessively()461     public void testPackageFailureNotifyLeastImpactSuccessively() throws Exception {
462         PackageWatchdog watchdog = createWatchdog();
463         TestObserver observerFirst = new TestObserver(OBSERVER_NAME_1,
464                 PackageHealthObserverImpact.USER_IMPACT_LOW);
465         TestObserver observerSecond = new TestObserver(OBSERVER_NAME_2,
466                 PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
467 
468         // Start observing for observerFirst and observerSecond with failure handling
469         watchdog.startObservingHealth(observerFirst, Arrays.asList(APP_A), LONG_DURATION);
470         watchdog.startObservingHealth(observerSecond, Arrays.asList(APP_A), LONG_DURATION);
471 
472         // Then fail APP_A above the threshold
473         raiseFatalFailureAndDispatch(watchdog,
474                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
475                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
476 
477         // Verify only observerFirst is notifed
478         assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
479         assertThat(observerSecond.mMitigatedPackages).isEmpty();
480 
481         // After observerFirst handles failure, next action it has is high impact
482         observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_HIGH;
483         observerFirst.mMitigatedPackages.clear();
484         observerSecond.mMitigatedPackages.clear();
485 
486         // Then fail APP_A again above the threshold
487         raiseFatalFailureAndDispatch(watchdog,
488                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
489                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
490 
491         // Verify only observerSecond is notifed cos it has least impact
492         assertThat(observerSecond.mMitigatedPackages).containsExactly(APP_A);
493         assertThat(observerFirst.mMitigatedPackages).isEmpty();
494 
495         // After observerSecond handles failure, it has no further actions
496         observerSecond.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
497         observerFirst.mMitigatedPackages.clear();
498         observerSecond.mMitigatedPackages.clear();
499 
500         // Then fail APP_A again above the threshold
501         raiseFatalFailureAndDispatch(watchdog,
502                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
503                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
504 
505         // Verify only observerFirst is notifed cos it has the only action
506         assertThat(observerFirst.mMitigatedPackages).containsExactly(APP_A);
507         assertThat(observerSecond.mMitigatedPackages).isEmpty();
508 
509         // After observerFirst handles failure, it too has no further actions
510         observerFirst.mImpact = PackageHealthObserverImpact.USER_IMPACT_NONE;
511         observerFirst.mMitigatedPackages.clear();
512         observerSecond.mMitigatedPackages.clear();
513 
514         // Then fail APP_A again above the threshold
515         raiseFatalFailureAndDispatch(watchdog,
516                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
517                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
518 
519         // Verify no observer is notified cos no actions left
520         assertThat(observerFirst.mMitigatedPackages).isEmpty();
521         assertThat(observerSecond.mMitigatedPackages).isEmpty();
522     }
523 
524     /**
525      * Test package failure and notifies only one observer even with observer impact tie.
526      */
527     @Test
testPackageFailureNotifyOneSameImpact()528     public void testPackageFailureNotifyOneSameImpact() throws Exception {
529         PackageWatchdog watchdog = createWatchdog();
530         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
531                 PackageHealthObserverImpact.USER_IMPACT_HIGH);
532         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
533                 PackageHealthObserverImpact.USER_IMPACT_HIGH);
534 
535         // Start observing for observer1 and observer2 with failure handling
536         watchdog.startObservingHealth(observer2, Arrays.asList(APP_A), SHORT_DURATION);
537         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
538 
539         // Then fail APP_A above the threshold
540         raiseFatalFailureAndDispatch(watchdog,
541                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
542                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
543 
544         // Verify only one observer is notifed
545         assertThat(observer1.mMitigatedPackages).containsExactly(APP_A);
546         assertThat(observer2.mMitigatedPackages).isEmpty();
547     }
548 
549     /**
550      * Test package passing explicit health checks does not fail and vice versa.
551      */
552     @Test
testExplicitHealthChecks()553     public void testExplicitHealthChecks() throws Exception {
554         TestController controller = new TestController();
555         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
556         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1,
557                 PackageHealthObserverImpact.USER_IMPACT_HIGH);
558         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2,
559                 PackageHealthObserverImpact.USER_IMPACT_HIGH);
560         TestObserver observer3 = new TestObserver(OBSERVER_NAME_3,
561                 PackageHealthObserverImpact.USER_IMPACT_HIGH);
562 
563 
564         // Start observing with explicit health checks for APP_A and APP_B respectively
565         // with observer1 and observer2
566         controller.setSupportedPackages(Arrays.asList(APP_A, APP_B));
567         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
568         watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
569 
570         // Run handler so requests are dispatched to the controller
571         mTestLooper.dispatchAll();
572 
573         // Verify we requested health checks for APP_A and APP_B
574         List<String> requestedPackages = controller.getRequestedPackages();
575         assertThat(requestedPackages).containsExactly(APP_A, APP_B);
576 
577         // Then health check passed for APP_A (observer1 is aware)
578         controller.setPackagePassed(APP_A);
579 
580         // Then start observing APP_A with explicit health checks for observer3.
581         // Observer3 didn't exist when we got the explicit health check above, so
582         // it starts out with a non-passing explicit health check and has to wait for a pass
583         // otherwise it would be notified of APP_A failure on expiry
584         watchdog.startObservingHealth(observer3, Arrays.asList(APP_A), SHORT_DURATION);
585 
586         // Then expire observers
587         moveTimeForwardAndDispatch(SHORT_DURATION);
588 
589         // Verify we cancelled all requests on expiry
590         assertThat(controller.getRequestedPackages()).isEmpty();
591 
592         // Verify observer1 is not notified
593         assertThat(observer1.mMitigatedPackages).isEmpty();
594 
595         // Verify observer2 is notifed because health checks for APP_B never passed
596         assertThat(observer2.mMitigatedPackages).containsExactly(APP_B);
597 
598         // Verify observer3 is notifed because health checks for APP_A did not pass before expiry
599         assertThat(observer3.mMitigatedPackages).containsExactly(APP_A);
600     }
601 
602     /**
603      * Test explicit health check state can be disabled and enabled correctly.
604      */
605     @Test
testExplicitHealthCheckStateChanges()606     public void testExplicitHealthCheckStateChanges() throws Exception {
607         adoptShellPermissions(
608                 Manifest.permission.WRITE_DEVICE_CONFIG,
609                 Manifest.permission.READ_DEVICE_CONFIG);
610 
611         TestController controller = new TestController();
612         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
613         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
614                 PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
615 
616         // Start observing with explicit health checks for APP_A and APP_B
617         controller.setSupportedPackages(Arrays.asList(APP_A, APP_B, APP_C));
618         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
619         watchdog.startObservingHealth(observer, Arrays.asList(APP_B), LONG_DURATION);
620 
621         // Run handler so requests are dispatched to the controller
622         mTestLooper.dispatchAll();
623 
624         // Verify we requested health checks for APP_A and APP_B
625         List<String> requestedPackages = controller.getRequestedPackages();
626         assertThat(requestedPackages).containsExactly(APP_A, APP_B);
627 
628         // Disable explicit health checks (marks APP_A and APP_B as passed)
629         setExplicitHealthCheckEnabled(false);
630 
631         // Run handler so requests/cancellations are dispatched to the controller
632         mTestLooper.dispatchAll();
633 
634         // Verify all checks are cancelled
635         assertThat(controller.getRequestedPackages()).isEmpty();
636 
637         // Then expire APP_A
638         moveTimeForwardAndDispatch(SHORT_DURATION);
639 
640         // Verify APP_A is not failed (APP_B) is not expired yet
641         assertThat(observer.mMitigatedPackages).isEmpty();
642 
643         // Re-enable explicit health checks
644         setExplicitHealthCheckEnabled(true);
645 
646         // Run handler so requests/cancellations are dispatched to the controller
647         mTestLooper.dispatchAll();
648 
649         // Verify no requests are made cos APP_A is expired and APP_B was marked as passed
650         assertThat(controller.getRequestedPackages()).isEmpty();
651 
652         // Then set new supported packages
653         controller.setSupportedPackages(Arrays.asList(APP_C));
654         // Start observing APP_A and APP_C; only APP_C has support for explicit health checks
655         watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_C), SHORT_DURATION);
656 
657         // Run handler so requests/cancellations are dispatched to the controller
658         mTestLooper.dispatchAll();
659 
660         // Verify requests are only made for APP_C
661         requestedPackages = controller.getRequestedPackages();
662         assertThat(requestedPackages).containsExactly(APP_C);
663 
664         // Then expire APP_A and APP_C
665         moveTimeForwardAndDispatch(SHORT_DURATION);
666 
667         // Verify only APP_C is failed because explicit health checks was not supported for APP_A
668         assertThat(observer.mMitigatedPackages).containsExactly(APP_C);
669     }
670 
671     /**
672      * Tests failure when health check duration is different from package observation duration
673      * Failure is also notified only once.
674      */
675     @Test
testExplicitHealthCheckFailureBeforeExpiry()676     public void testExplicitHealthCheckFailureBeforeExpiry() throws Exception {
677         TestController controller = new TestController();
678         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
679         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
680                 PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
681 
682         // Start observing with explicit health checks for APP_A and
683         // package observation duration == LONG_DURATION
684         // health check duration == SHORT_DURATION (set by default in the TestController)
685         controller.setSupportedPackages(Arrays.asList(APP_A));
686         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), LONG_DURATION);
687 
688         // Then APP_A has exceeded health check duration
689         moveTimeForwardAndDispatch(SHORT_DURATION);
690 
691         // Verify that health check is failed
692         assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
693 
694         // Clear failed packages and forward time to expire the observation duration
695         observer.mMitigatedPackages.clear();
696         moveTimeForwardAndDispatch(LONG_DURATION);
697 
698         // Verify that health check failure is not notified again
699         assertThat(observer.mMitigatedPackages).isEmpty();
700     }
701 
702     /**
703      * Tests failure when health check duration is different from package observation duration
704      * Failure is also notified only once.
705      */
706     @Test
testExplicitHealthCheckFailureAfterExpiry()707     public void testExplicitHealthCheckFailureAfterExpiry() {
708         TestController controller = new TestController();
709         PackageWatchdog watchdog = createWatchdog(controller, true /* withPackagesReady */);
710         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
711                 PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
712 
713         // Start observing with explicit health checks for APP_A and
714         // package observation duration == SHORT_DURATION / 2
715         // health check duration == SHORT_DURATION (set by default in the TestController)
716         controller.setSupportedPackages(Arrays.asList(APP_A));
717         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION / 2);
718 
719         // Forward time to expire the observation duration
720         moveTimeForwardAndDispatch(SHORT_DURATION / 2);
721 
722         // Verify that health check is failed
723         assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
724 
725         // Clear failed packages and forward time to expire the health check duration
726         observer.mMitigatedPackages.clear();
727         moveTimeForwardAndDispatch(SHORT_DURATION);
728 
729         // Verify that health check failure is not notified again
730         assertThat(observer.mMitigatedPackages).isEmpty();
731     }
732 
733     /** Tests {@link MonitoredPackage} health check state transitions. */
734     @Test
testPackageHealthCheckStateTransitions()735     public void testPackageHealthCheckStateTransitions() {
736         TestController controller = new TestController();
737         PackageWatchdog wd = createWatchdog(controller, true /* withPackagesReady */);
738         MonitoredPackage m1 = wd.newMonitoredPackage(APP_A, LONG_DURATION,
739                 false /* hasPassedHealthCheck */);
740         MonitoredPackage m2 = wd.newMonitoredPackage(APP_B, LONG_DURATION, false);
741         MonitoredPackage m3 = wd.newMonitoredPackage(APP_C, LONG_DURATION, false);
742         MonitoredPackage m4 = wd.newMonitoredPackage(APP_D, LONG_DURATION, SHORT_DURATION, true);
743 
744         // Verify transition: inactive -> active -> passed
745         // Verify initially inactive
746         assertThat(m1.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
747         // Verify still inactive, until we #setHealthCheckActiveLocked
748         assertThat(m1.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.INACTIVE);
749         // Verify now active
750         assertThat(m1.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo(
751                 HealthCheckState.ACTIVE);
752         // Verify now passed
753         assertThat(m1.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.PASSED);
754 
755         // Verify transition: inactive -> active -> failed
756         // Verify initially inactive
757         assertThat(m2.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
758         // Verify now active
759         assertThat(m2.setHealthCheckActiveLocked(SHORT_DURATION)).isEqualTo(
760                 HealthCheckState.ACTIVE);
761         // Verify now failed
762         assertThat(m2.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.FAILED);
763 
764         // Verify transition: inactive -> failed
765         // Verify initially inactive
766         assertThat(m3.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.INACTIVE);
767         // Verify now failed because package expired
768         assertThat(m3.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.FAILED);
769         // Verify remains failed even when asked to pass
770         assertThat(m3.tryPassHealthCheckLocked()).isEqualTo(HealthCheckState.FAILED);
771 
772         // Verify transition: passed
773         assertThat(m4.getHealthCheckStateLocked()).isEqualTo(HealthCheckState.PASSED);
774         // Verify remains passed even if health check fails
775         assertThat(m4.handleElapsedTimeLocked(SHORT_DURATION)).isEqualTo(HealthCheckState.PASSED);
776         // Verify remains passed even if package expires
777         assertThat(m4.handleElapsedTimeLocked(LONG_DURATION)).isEqualTo(HealthCheckState.PASSED);
778     }
779 
780     @Test
testNetworkStackFailure()781     public void testNetworkStackFailure() {
782         final PackageWatchdog wd = createWatchdog();
783 
784         // Start observing with failure handling
785         TestObserver observer = new TestObserver(OBSERVER_NAME_1,
786                 PackageHealthObserverImpact.USER_IMPACT_HIGH);
787         wd.startObservingHealth(observer, Collections.singletonList(APP_A), SHORT_DURATION);
788 
789         // Notify of NetworkStack failure
790         mConnectivityModuleCallbackCaptor.getValue().onNetworkStackFailure(APP_A);
791 
792         // Run handler so package failures are dispatched to observers
793         mTestLooper.dispatchAll();
794 
795         // Verify the NetworkStack observer is notified
796         assertThat(observer.mMitigatedPackages).containsExactly(APP_A);
797     }
798 
799     /** Test default values are used when device property is invalid. */
800     @Test
testInvalidConfig_watchdogTriggerFailureCount()801     public void testInvalidConfig_watchdogTriggerFailureCount() {
802         adoptShellPermissions(
803                 Manifest.permission.WRITE_DEVICE_CONFIG,
804                 Manifest.permission.READ_DEVICE_CONFIG);
805         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
806                 PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
807                 Integer.toString(-1), /*makeDefault*/false);
808         PackageWatchdog watchdog = createWatchdog();
809         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
810 
811         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), SHORT_DURATION);
812         // Fail APP_A below the threshold which should not trigger package failures
813         for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
814             watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
815                     PackageWatchdog.FAILURE_REASON_UNKNOWN);
816         }
817         mTestLooper.dispatchAll();
818         assertThat(observer.mHealthCheckFailedPackages).isEmpty();
819 
820         // One more to trigger the package failure
821         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
822                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
823         mTestLooper.dispatchAll();
824         assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
825     }
826 
827     /** Test default values are used when device property is invalid. */
828     @Test
testInvalidConfig_watchdogTriggerDurationMillis()829     public void testInvalidConfig_watchdogTriggerDurationMillis() {
830         adoptShellPermissions(
831                 Manifest.permission.WRITE_DEVICE_CONFIG,
832                 Manifest.permission.READ_DEVICE_CONFIG);
833         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
834                 PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
835                 Integer.toString(2), /*makeDefault*/false);
836         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
837                 PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
838                 Integer.toString(-1), /*makeDefault*/false);
839         PackageWatchdog watchdog = createWatchdog();
840         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
841 
842         watchdog.startObservingHealth(observer, Arrays.asList(APP_A, APP_B), Long.MAX_VALUE);
843         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
844                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
845         mTestLooper.dispatchAll();
846         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS + 1);
847         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
848                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
849         mTestLooper.dispatchAll();
850 
851         // We shouldn't receive APP_A since the interval of 2 failures is greater than
852         // DEFAULT_TRIGGER_FAILURE_DURATION_MS.
853         assertThat(observer.mHealthCheckFailedPackages).isEmpty();
854 
855         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
856                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
857         mTestLooper.dispatchAll();
858         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_TRIGGER_FAILURE_DURATION_MS - 1);
859         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_B, VERSION_CODE)),
860                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
861         mTestLooper.dispatchAll();
862 
863         // We should receive APP_B since the interval of 2 failures is less than
864         // DEFAULT_TRIGGER_FAILURE_DURATION_MS.
865         assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_B);
866     }
867 
868     /**
869      * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
870      * an invalid durationMs.
871      */
872     @Test
testInvalidMonitoringDuration_beforeExpiry()873     public void testInvalidMonitoringDuration_beforeExpiry() {
874         PackageWatchdog watchdog = createWatchdog();
875         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
876 
877         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
878         // Note: Don't move too close to the expiration time otherwise the handler will be thrashed
879         // by PackageWatchdog#scheduleNextSyncStateLocked which keeps posting runnables with very
880         // small timeouts.
881         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS - 100);
882         raiseFatalFailureAndDispatch(watchdog,
883                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
884                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
885 
886         // We should receive APP_A since the observer hasn't expired
887         assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
888     }
889 
890     /**
891      * Test default monitoring duration is used when PackageWatchdog#startObservingHealth is offered
892      * an invalid durationMs.
893      */
894     @Test
testInvalidMonitoringDuration_afterExpiry()895     public void testInvalidMonitoringDuration_afterExpiry() {
896         PackageWatchdog watchdog = createWatchdog();
897         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
898 
899         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), -1);
900         moveTimeForwardAndDispatch(PackageWatchdog.DEFAULT_OBSERVING_DURATION_MS + 1);
901         raiseFatalFailureAndDispatch(watchdog,
902                 Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
903                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
904 
905         // We should receive nothing since the observer has expired
906         assertThat(observer.mHealthCheckFailedPackages).isEmpty();
907     }
908 
909     /** Test we are notified when enough failures are triggered within any window. */
910     @Test
testFailureTriggerWindow()911     public void testFailureTriggerWindow() {
912         adoptShellPermissions(
913                 Manifest.permission.WRITE_DEVICE_CONFIG,
914                 Manifest.permission.READ_DEVICE_CONFIG);
915         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
916                 PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_FAILURE_COUNT,
917                 Integer.toString(3), /*makeDefault*/false);
918         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
919                 PackageWatchdog.PROPERTY_WATCHDOG_TRIGGER_DURATION_MILLIS,
920                 Integer.toString(1000), /*makeDefault*/false);
921         PackageWatchdog watchdog = createWatchdog();
922         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
923 
924         watchdog.startObservingHealth(observer, Arrays.asList(APP_A), Long.MAX_VALUE);
925         // Raise 2 failures at t=0 and t=900 respectively
926         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
927                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
928         mTestLooper.dispatchAll();
929         moveTimeForwardAndDispatch(900);
930         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
931                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
932         mTestLooper.dispatchAll();
933 
934         // Raise 2 failures at t=1100
935         moveTimeForwardAndDispatch(200);
936         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
937                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
938         watchdog.onPackageFailure(Arrays.asList(new VersionedPackage(APP_A, VERSION_CODE)),
939                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
940         mTestLooper.dispatchAll();
941 
942         // We should receive APP_A since there are 3 failures within 1000ms window
943         assertThat(observer.mHealthCheckFailedPackages).containsExactly(APP_A);
944     }
945 
946     /** Test that observers execute correctly for failures reasons that go through thresholding. */
947     @Test
testNonImmediateFailureReasons()948     public void testNonImmediateFailureReasons() {
949         PackageWatchdog watchdog = createWatchdog();
950         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
951         TestObserver observer2 = new TestObserver(OBSERVER_NAME_2);
952 
953         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
954         watchdog.startObservingHealth(observer2, Arrays.asList(APP_B), SHORT_DURATION);
955 
956         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
957                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_CRASH);
958         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
959                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
960 
961         assertThat(observer1.getLastFailureReason()).isEqualTo(
962                 PackageWatchdog.FAILURE_REASON_APP_CRASH);
963         assertThat(observer2.getLastFailureReason()).isEqualTo(
964                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING);
965     }
966 
967     /** Test that observers execute correctly for failures reasons that skip thresholding. */
968     @Test
testImmediateFailures()969     public void testImmediateFailures() {
970         PackageWatchdog watchdog = createWatchdog();
971         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
972 
973         watchdog.startObservingHealth(observer1, Arrays.asList(APP_A), SHORT_DURATION);
974 
975         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
976                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_NATIVE_CRASH);
977         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_B,
978                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK);
979 
980         assertThat(observer1.mMitigatedPackages).containsExactly(APP_A, APP_B);
981     }
982 
983     /**
984      * Test that a persistent observer will mitigate failures if it wishes to observe a package.
985      */
986     @Test
testPersistentObserverWatchesPackage()987     public void testPersistentObserverWatchesPackage() {
988         PackageWatchdog watchdog = createWatchdog();
989         TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
990         persistentObserver.setPersistent(true);
991         persistentObserver.setMayObservePackages(true);
992 
993         watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
994 
995         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
996                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
997         assertThat(persistentObserver.mHealthCheckFailedPackages).containsExactly(APP_A);
998     }
999 
1000     /**
1001      * Test that a persistent observer will not mitigate failures if it does not wish to observe
1002      * a given package.
1003      */
1004     @Test
testPersistentObserverDoesNotWatchPackage()1005     public void testPersistentObserverDoesNotWatchPackage() {
1006         PackageWatchdog watchdog = createWatchdog();
1007         TestObserver persistentObserver = new TestObserver(OBSERVER_NAME_1);
1008         persistentObserver.setPersistent(true);
1009         persistentObserver.setMayObservePackages(false);
1010 
1011         watchdog.startObservingHealth(persistentObserver, Arrays.asList(APP_B), SHORT_DURATION);
1012 
1013         raiseFatalFailureAndDispatch(watchdog, Arrays.asList(new VersionedPackage(APP_A,
1014                 VERSION_CODE)), PackageWatchdog.FAILURE_REASON_UNKNOWN);
1015         assertThat(persistentObserver.mHealthCheckFailedPackages).isEmpty();
1016     }
1017 
1018 
1019     /** Ensure that boot loop mitigation is done when the number of boots meets the threshold. */
1020     @Test
testBootLoopDetection_meetsThreshold()1021     public void testBootLoopDetection_meetsThreshold() {
1022         PackageWatchdog watchdog = createWatchdog();
1023         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
1024         watchdog.registerHealthObserver(bootObserver);
1025         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
1026             watchdog.noteBoot();
1027         }
1028         assertThat(bootObserver.mitigatedBootLoop()).isTrue();
1029     }
1030 
1031 
1032     /**
1033      * Ensure that boot loop mitigation is not done when the number of boots does not meet the
1034      * threshold.
1035      */
1036     @Test
testBootLoopDetection_doesNotMeetThreshold()1037     public void testBootLoopDetection_doesNotMeetThreshold() {
1038         PackageWatchdog watchdog = createWatchdog();
1039         TestObserver bootObserver = new TestObserver(OBSERVER_NAME_1);
1040         watchdog.registerHealthObserver(bootObserver);
1041         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT - 1; i++) {
1042             watchdog.noteBoot();
1043         }
1044         assertThat(bootObserver.mitigatedBootLoop()).isFalse();
1045     }
1046 
1047     /**
1048      * Ensure that boot loop mitigation is done for the observer with the lowest user impact
1049      */
1050     @Test
testBootLoopMitigationDoneForLowestUserImpact()1051     public void testBootLoopMitigationDoneForLowestUserImpact() {
1052         PackageWatchdog watchdog = createWatchdog();
1053         TestObserver bootObserver1 = new TestObserver(OBSERVER_NAME_1);
1054         bootObserver1.setImpact(PackageHealthObserverImpact.USER_IMPACT_LOW);
1055         TestObserver bootObserver2 = new TestObserver(OBSERVER_NAME_2);
1056         bootObserver2.setImpact(PackageHealthObserverImpact.USER_IMPACT_MEDIUM);
1057         watchdog.registerHealthObserver(bootObserver1);
1058         watchdog.registerHealthObserver(bootObserver2);
1059         for (int i = 0; i < PackageWatchdog.DEFAULT_BOOT_LOOP_TRIGGER_COUNT; i++) {
1060             watchdog.noteBoot();
1061         }
1062         assertThat(bootObserver1.mitigatedBootLoop()).isTrue();
1063         assertThat(bootObserver2.mitigatedBootLoop()).isFalse();
1064     }
1065 
1066     /**
1067      * Ensure that passing a null list of failed packages does not cause any mitigation logic to
1068      * execute.
1069      */
1070     @Test
testNullFailedPackagesList()1071     public void testNullFailedPackagesList() {
1072         PackageWatchdog watchdog = createWatchdog();
1073         TestObserver observer1 = new TestObserver(OBSERVER_NAME_1);
1074         watchdog.startObservingHealth(observer1, List.of(APP_A), LONG_DURATION);
1075 
1076         raiseFatalFailureAndDispatch(watchdog, null, PackageWatchdog.FAILURE_REASON_APP_CRASH);
1077         assertThat(observer1.mMitigatedPackages).isEmpty();
1078     }
1079 
1080     /**
1081      * Test to verify that Package Watchdog syncs health check requests with the controller
1082      * correctly, and that the requests are only synced when the set of observed packages
1083      * changes.
1084      */
1085     @Test
testSyncHealthCheckRequests()1086     public void testSyncHealthCheckRequests() {
1087         TestController testController = spy(TestController.class);
1088         testController.setSupportedPackages(List.of(APP_A, APP_B, APP_C));
1089         PackageWatchdog watchdog = createWatchdog(testController, true);
1090 
1091         TestObserver testObserver1 = new TestObserver(OBSERVER_NAME_1);
1092         watchdog.registerHealthObserver(testObserver1);
1093         watchdog.startObservingHealth(testObserver1, List.of(APP_A), LONG_DURATION);
1094         mTestLooper.dispatchAll();
1095 
1096         TestObserver testObserver2 = new TestObserver(OBSERVER_NAME_2);
1097         watchdog.registerHealthObserver(testObserver2);
1098         watchdog.startObservingHealth(testObserver2, List.of(APP_B), LONG_DURATION);
1099         mTestLooper.dispatchAll();
1100 
1101         TestObserver testObserver3 = new TestObserver(OBSERVER_NAME_3);
1102         watchdog.registerHealthObserver(testObserver3);
1103         watchdog.startObservingHealth(testObserver3, List.of(APP_C), LONG_DURATION);
1104         mTestLooper.dispatchAll();
1105 
1106         watchdog.unregisterHealthObserver(testObserver1);
1107         mTestLooper.dispatchAll();
1108 
1109         watchdog.unregisterHealthObserver(testObserver2);
1110         mTestLooper.dispatchAll();
1111 
1112         watchdog.unregisterHealthObserver(testObserver3);
1113         mTestLooper.dispatchAll();
1114 
1115         List<Set> expectedSyncRequests = List.of(
1116                 Set.of(),
1117                 Set.of(APP_A),
1118                 Set.of(APP_A, APP_B),
1119                 Set.of(APP_A, APP_B, APP_C),
1120                 Set.of(APP_B, APP_C),
1121                 Set.of(APP_C),
1122                 Set.of()
1123         );
1124         assertThat(testController.getSyncRequests()).isEqualTo(expectedSyncRequests);
1125     }
1126 
1127     /**
1128      * Ensure that the failure history of a package is preserved when making duplicate calls to
1129      * observe the package.
1130      */
1131     @Test
testFailureHistoryIsPreserved()1132     public void testFailureHistoryIsPreserved() {
1133         PackageWatchdog watchdog = createWatchdog();
1134         TestObserver observer = new TestObserver(OBSERVER_NAME_1);
1135         watchdog.startObservingHealth(observer, List.of(APP_A), SHORT_DURATION);
1136         for (int i = 0; i < PackageWatchdog.DEFAULT_TRIGGER_FAILURE_COUNT - 1; i++) {
1137             watchdog.onPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
1138                     PackageWatchdog.FAILURE_REASON_UNKNOWN);
1139         }
1140         mTestLooper.dispatchAll();
1141         assertThat(observer.mMitigatedPackages).isEmpty();
1142         watchdog.startObservingHealth(observer, List.of(APP_A), LONG_DURATION);
1143         watchdog.onPackageFailure(List.of(new VersionedPackage(APP_A, VERSION_CODE)),
1144                 PackageWatchdog.FAILURE_REASON_UNKNOWN);
1145         mTestLooper.dispatchAll();
1146         assertThat(observer.mMitigatedPackages).isEqualTo(List.of(APP_A));
1147     }
1148 
adoptShellPermissions(String... permissions)1149     private void adoptShellPermissions(String... permissions) {
1150         InstrumentationRegistry
1151                 .getInstrumentation()
1152                 .getUiAutomation()
1153                 .adoptShellPermissionIdentity(permissions);
1154     }
1155 
dropShellPermissions()1156     private void dropShellPermissions() {
1157         InstrumentationRegistry
1158                 .getInstrumentation()
1159                 .getUiAutomation()
1160                 .dropShellPermissionIdentity();
1161     }
1162 
setExplicitHealthCheckEnabled(boolean enabled)1163     private void setExplicitHealthCheckEnabled(boolean enabled) {
1164         DeviceConfig.setProperty(DeviceConfig.NAMESPACE_ROLLBACK,
1165                 PackageWatchdog.PROPERTY_WATCHDOG_EXPLICIT_HEALTH_CHECK_ENABLED,
1166                 Boolean.toString(enabled), /*makeDefault*/false);
1167         //give time for DeviceConfig to broadcast the property value change
1168         try {
1169             Thread.sleep(SHORT_DURATION);
1170         } catch (InterruptedException e) {
1171             fail("Thread.sleep unexpectedly failed!");
1172         }
1173     }
1174 
moveTimeForwardAndDispatch(long milliSeconds)1175     private void moveTimeForwardAndDispatch(long milliSeconds) {
1176         mTestClock.moveTimeForward(milliSeconds);
1177         mTestLooper.moveTimeForward(milliSeconds);
1178         mTestLooper.dispatchAll();
1179     }
1180 
1181     /** Trigger package failures above the threshold. */
raiseFatalFailureAndDispatch(PackageWatchdog watchdog, List<VersionedPackage> packages, int failureReason)1182     private void raiseFatalFailureAndDispatch(PackageWatchdog watchdog,
1183             List<VersionedPackage> packages, int failureReason) {
1184         long triggerFailureCount = watchdog.getTriggerFailureCount();
1185         if (failureReason == PackageWatchdog.FAILURE_REASON_EXPLICIT_HEALTH_CHECK
1186                 || failureReason == PackageWatchdog.FAILURE_REASON_NATIVE_CRASH) {
1187             triggerFailureCount = 1;
1188         }
1189         for (int i = 0; i < triggerFailureCount; i++) {
1190             watchdog.onPackageFailure(packages, failureReason);
1191         }
1192         mTestLooper.dispatchAll();
1193     }
1194 
createWatchdog()1195     private PackageWatchdog createWatchdog() {
1196         return createWatchdog(new TestController(), true /* withPackagesReady */);
1197     }
1198 
createWatchdog(TestController controller, boolean withPackagesReady)1199     private PackageWatchdog createWatchdog(TestController controller, boolean withPackagesReady) {
1200         AtomicFile policyFile =
1201                 new AtomicFile(new File(mSpyContext.getFilesDir(), "package-watchdog.xml"));
1202         Handler handler = new Handler(mTestLooper.getLooper());
1203         PackageWatchdog watchdog =
1204                 new PackageWatchdog(mSpyContext, policyFile, handler, handler, controller,
1205                         mConnectivityModuleConnector, mTestClock);
1206         // Verify controller is not automatically started
1207         assertThat(controller.mIsEnabled).isFalse();
1208         if (withPackagesReady) {
1209             // Only capture the NetworkStack callback for the latest registered watchdog
1210             reset(mConnectivityModuleConnector);
1211             watchdog.onPackagesReady();
1212             // Verify controller by default is started when packages are ready
1213             assertThat(controller.mIsEnabled).isTrue();
1214 
1215             verify(mConnectivityModuleConnector).registerHealthListener(
1216                     mConnectivityModuleCallbackCaptor.capture());
1217         }
1218         return watchdog;
1219     }
1220 
1221     private static class TestObserver implements PackageHealthObserver {
1222         private final String mName;
1223         private int mImpact;
1224         private int mLastFailureReason;
1225         private boolean mIsPersistent = false;
1226         private boolean mMayObservePackages = false;
1227         private boolean mMitigatedBootLoop = false;
1228         final List<String> mHealthCheckFailedPackages = new ArrayList<>();
1229         final List<String> mMitigatedPackages = new ArrayList<>();
1230 
TestObserver(String name)1231         TestObserver(String name) {
1232             mName = name;
1233             mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
1234         }
1235 
TestObserver(String name, int impact)1236         TestObserver(String name, int impact) {
1237             mName = name;
1238             mImpact = impact;
1239         }
1240 
onHealthCheckFailed(VersionedPackage versionedPackage, int failureReason)1241         public int onHealthCheckFailed(VersionedPackage versionedPackage, int failureReason) {
1242             mHealthCheckFailedPackages.add(versionedPackage.getPackageName());
1243             return mImpact;
1244         }
1245 
execute(VersionedPackage versionedPackage, int failureReason)1246         public boolean execute(VersionedPackage versionedPackage, int failureReason) {
1247             mMitigatedPackages.add(versionedPackage.getPackageName());
1248             mLastFailureReason = failureReason;
1249             return true;
1250         }
1251 
getName()1252         public String getName() {
1253             return mName;
1254         }
1255 
isPersistent()1256         public boolean isPersistent() {
1257             return mIsPersistent;
1258         }
1259 
mayObservePackage(String packageName)1260         public boolean mayObservePackage(String packageName) {
1261             return mMayObservePackages;
1262         }
1263 
onBootLoop()1264         public int onBootLoop() {
1265             return mImpact;
1266         }
1267 
executeBootLoopMitigation()1268         public boolean executeBootLoopMitigation() {
1269             mMitigatedBootLoop = true;
1270             return true;
1271         }
1272 
mitigatedBootLoop()1273         public boolean mitigatedBootLoop() {
1274             return mMitigatedBootLoop;
1275         }
1276 
getLastFailureReason()1277         public int getLastFailureReason() {
1278             return mLastFailureReason;
1279         }
1280 
setPersistent(boolean persistent)1281         public void setPersistent(boolean persistent) {
1282             mIsPersistent = persistent;
1283         }
1284 
setImpact(int impact)1285         public void setImpact(int impact) {
1286             mImpact = impact;
1287         }
1288 
setMayObservePackages(boolean mayObservePackages)1289         public void setMayObservePackages(boolean mayObservePackages) {
1290             mMayObservePackages = mayObservePackages;
1291         }
1292     }
1293 
1294     private static class TestController extends ExplicitHealthCheckController {
TestController()1295         TestController() {
1296             super(null /* controller */);
1297         }
1298 
1299         private boolean mIsEnabled;
1300         private List<String> mSupportedPackages = new ArrayList<>();
1301         private List<String> mRequestedPackages = new ArrayList<>();
1302         private Consumer<String> mPassedConsumer;
1303         private Consumer<List<PackageConfig>> mSupportedConsumer;
1304         private Runnable mNotifySyncRunnable;
1305         private List<Set> mSyncRequests = new ArrayList<>();
1306 
1307         @Override
setEnabled(boolean enabled)1308         public void setEnabled(boolean enabled) {
1309             mIsEnabled = enabled;
1310             if (!mIsEnabled) {
1311                 mSupportedPackages.clear();
1312             }
1313         }
1314 
1315         @Override
setCallbacks(Consumer<String> passedConsumer, Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable)1316         public void setCallbacks(Consumer<String> passedConsumer,
1317                 Consumer<List<PackageConfig>> supportedConsumer, Runnable notifySyncRunnable) {
1318             mPassedConsumer = passedConsumer;
1319             mSupportedConsumer = supportedConsumer;
1320             mNotifySyncRunnable = notifySyncRunnable;
1321         }
1322 
1323         @Override
syncRequests(Set<String> packages)1324         public void syncRequests(Set<String> packages) {
1325             mSyncRequests.add(packages);
1326             mRequestedPackages.clear();
1327             if (mIsEnabled) {
1328                 packages.retainAll(mSupportedPackages);
1329                 mRequestedPackages.addAll(packages);
1330                 List<PackageConfig> packageConfigs = new ArrayList<>();
1331                 for (String packageName: packages) {
1332                     packageConfigs.add(new PackageConfig(packageName, SHORT_DURATION));
1333                 }
1334                 mSupportedConsumer.accept(packageConfigs);
1335             } else {
1336                 mSupportedConsumer.accept(Collections.emptyList());
1337             }
1338         }
1339 
setSupportedPackages(List<String> packages)1340         public void setSupportedPackages(List<String> packages) {
1341             mSupportedPackages.clear();
1342             mSupportedPackages.addAll(packages);
1343         }
1344 
setPackagePassed(String packageName)1345         public void setPackagePassed(String packageName) {
1346             mPassedConsumer.accept(packageName);
1347         }
1348 
getRequestedPackages()1349         public List<String> getRequestedPackages() {
1350             if (mIsEnabled) {
1351                 return mRequestedPackages;
1352             } else {
1353                 return Collections.emptyList();
1354             }
1355         }
1356 
getSyncRequests()1357         public List<Set> getSyncRequests() {
1358             return mSyncRequests;
1359         }
1360     }
1361 
1362     private static class TestClock implements PackageWatchdog.SystemClock {
1363         // Note 0 is special to the internal clock of PackageWatchdog. We need to start from
1364         // a non-zero value in order not to disrupt the logic of PackageWatchdog.
1365         private long mUpTimeMillis = 1;
1366         @Override
uptimeMillis()1367         public long uptimeMillis() {
1368             return mUpTimeMillis;
1369         }
moveTimeForward(long milliSeconds)1370         public void moveTimeForward(long milliSeconds) {
1371             mUpTimeMillis += milliSeconds;
1372         }
1373     }
1374 }
1375