Home | History | Annotate | Download | only in server
      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
     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
    113     public void tearDown() throws Exception {
    114         dropShellPermissions();
    115     }
    116 
    117     /**
    118      * Test registration, unregistration, package expiry and duration reduction
    119      */
    120     @Test
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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
    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 
    749     private void adoptShellPermissions(String... permissions) {
    750         InstrumentationRegistry
    751                 .getInstrumentation()
    752                 .getUiAutomation()
    753                 .adoptShellPermissionIdentity(permissions);
    754     }
    755 
    756     private void dropShellPermissions() {
    757         InstrumentationRegistry
    758                 .getInstrumentation()
    759                 .getUiAutomation()
    760                 .dropShellPermissionIdentity();
    761     }
    762 
    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 
    775     private PackageWatchdog createWatchdog() {
    776         return createWatchdog(new TestController(), true /* withPackagesReady */);
    777     }
    778 
    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 
    806         TestObserver(String name) {
    807             mName = name;
    808             mImpact = PackageHealthObserverImpact.USER_IMPACT_MEDIUM;
    809         }
    810 
    811         TestObserver(String name, int impact) {
    812             mName = name;
    813             mImpact = impact;
    814         }
    815 
    816         public int onHealthCheckFailed(VersionedPackage versionedPackage) {
    817             return mImpact;
    818         }
    819 
    820         public boolean execute(VersionedPackage versionedPackage) {
    821             mFailedPackages.add(versionedPackage.getPackageName());
    822             return true;
    823         }
    824 
    825         public String getName() {
    826             return mName;
    827         }
    828     }
    829 
    830     private static class TestController extends ExplicitHealthCheckController {
    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
    843         public void setEnabled(boolean enabled) {
    844             mIsEnabled = enabled;
    845             if (!mIsEnabled) {
    846                 mSupportedPackages.clear();
    847             }
    848         }
    849 
    850         @Override
    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
    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 
    874         public void setSupportedPackages(List<String> packages) {
    875             mSupportedPackages.clear();
    876             mSupportedPackages.addAll(packages);
    877         }
    878 
    879         public void setPackagePassed(String packageName) {
    880             mPassedConsumer.accept(packageName);
    881         }
    882 
    883         public List<String> getRequestedPackages() {
    884             if (mIsEnabled) {
    885                 return mRequestedPackages;
    886             } else {
    887                 return Collections.emptyList();
    888             }
    889         }
    890     }
    891 }
    892