• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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.rollback;
18 
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
20 
21 import static com.google.common.truth.Truth.assertThat;
22 
23 import static org.junit.Assert.assertEquals;
24 import static org.junit.Assert.assertFalse;
25 import static org.junit.Assert.assertTrue;
26 import static org.junit.Assert.fail;
27 import static org.mockito.ArgumentMatchers.any;
28 import static org.mockito.ArgumentMatchers.anyBoolean;
29 import static org.mockito.ArgumentMatchers.anyInt;
30 import static org.mockito.ArgumentMatchers.anyString;
31 import static org.mockito.ArgumentMatchers.eq;
32 import static org.mockito.Mockito.never;
33 import static org.mockito.Mockito.spy;
34 import static org.mockito.Mockito.times;
35 import static org.mockito.Mockito.verify;
36 import static org.mockito.Mockito.when;
37 
38 import android.content.Context;
39 import android.content.pm.ApplicationInfo;
40 import android.content.pm.PackageInfo;
41 import android.content.pm.PackageManager;
42 import android.content.pm.VersionedPackage;
43 import android.content.rollback.PackageRollbackInfo;
44 import android.content.rollback.RollbackInfo;
45 import android.content.rollback.RollbackManager;
46 import android.os.Handler;
47 import android.os.MessageQueue;
48 import android.os.SystemProperties;
49 import android.platform.test.flag.junit.SetFlagsRule;
50 
51 import androidx.test.platform.app.InstrumentationRegistry;
52 import androidx.test.runner.AndroidJUnit4;
53 
54 import com.android.dx.mockito.inline.extended.ExtendedMockito;
55 import com.android.server.PackageWatchdog;
56 import com.android.server.SystemConfig;
57 import com.android.server.pm.ApexManager;
58 
59 import org.junit.After;
60 import org.junit.Before;
61 import org.junit.Rule;
62 import org.junit.Test;
63 import org.junit.rules.TemporaryFolder;
64 import org.junit.runner.RunWith;
65 import org.mockito.Answers;
66 import org.mockito.ArgumentCaptor;
67 import org.mockito.Mock;
68 import org.mockito.MockitoSession;
69 import org.mockito.quality.Strictness;
70 import org.mockito.stubbing.Answer;
71 
72 import java.time.Duration;
73 import java.util.HashMap;
74 import java.util.List;
75 import java.util.concurrent.CountDownLatch;
76 import java.util.concurrent.TimeUnit;
77 
78 
79 @RunWith(AndroidJUnit4.class)
80 public class RollbackPackageHealthObserverTest {
81     @Mock
82     private Context mMockContext;
83     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
84     private PackageWatchdog mMockPackageWatchdog;
85     @Mock
86     RollbackManager mRollbackManager;
87     @Mock
88     RollbackInfo mRollbackInfo;
89     @Mock
90     PackageRollbackInfo mPackageRollbackInfo;
91     @Mock
92     PackageManager mMockPackageManager;
93 
94     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
95     private ApexManager mApexManager;
96 
97     @Rule
98     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
99     private HashMap<String, String> mSystemSettingsMap;
100     private MockitoSession mSession;
101     private static final String APP_A = "com.package.a";
102     private static final String APP_B = "com.package.b";
103     private static final String APP_C = "com.package.c";
104     private static final long VERSION_CODE = 1L;
105     private static final long VERSION_CODE_2 = 2L;
106     private static final String LOG_TAG = "RollbackPackageHealthObserverTest";
107 
108     private static final String PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG =
109             "persist.device_config.configuration.disable_high_impact_rollback";
110 
111 
112     @Rule public TemporaryFolder mTemporaryFolder = new TemporaryFolder();
113 
114     @Before
setup()115     public void setup() {
116         mSession = ExtendedMockito.mockitoSession()
117                 .initMocks(this)
118                 .strictness(Strictness.LENIENT)
119                 .spyStatic(PackageWatchdog.class)
120                 .spyStatic(SystemProperties.class)
121                 .startMocking();
122         mSystemSettingsMap = new HashMap<>();
123 
124         // Mock PackageWatchdog
125         doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
126                 .when(() -> PackageWatchdog.getInstance(mMockContext));
127 
128         // Mock SystemProperties setter and various getters
129         doAnswer((Answer<Void>) invocationOnMock -> {
130                     String key = invocationOnMock.getArgument(0);
131                     String value = invocationOnMock.getArgument(1);
132 
133                     mSystemSettingsMap.put(key, value);
134                     return null;
135                 }
136         ).when(() -> SystemProperties.set(anyString(), anyString()));
137 
138         doAnswer((Answer<Boolean>) invocationOnMock -> {
139                     String key = invocationOnMock.getArgument(0);
140                     boolean defaultValue = invocationOnMock.getArgument(1);
141 
142                     String storedValue = mSystemSettingsMap.get(key);
143                     return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue);
144                 }
145         ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean()));
146 
147         try {
148             when(mMockPackageManager.getPackageInfo(anyString(), anyInt())).then(inv -> {
149                 final PackageInfo res = new PackageInfo();
150                 res.packageName = inv.getArgument(0);
151                 res.setApexPackageName(res.packageName);
152                 return res;
153             });
154         } catch (PackageManager.NameNotFoundException e) {
155             throw new RuntimeException(e);
156         }
157 
158         Context testContext = InstrumentationRegistry.getInstrumentation()
159                 .getTargetContext();
160         when(mMockContext.getUser()).thenReturn(testContext.getUser());
161         when(mMockContext.getPackageName()).thenReturn(testContext.getPackageName());
162 
163         SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(false));
164     }
165 
166     @After
tearDown()167     public void tearDown() throws Exception {
168         mSession.finishMocking();
169     }
170 
171     /**
172      * Subclass of SystemConfig without running the constructor.
173      */
174     private class SystemConfigTestClass extends SystemConfig {
SystemConfigTestClass()175         SystemConfigTestClass() {
176             super(false);
177         }
178     }
179 
180     @Test
testHealthCheckLevels()181     public void testHealthCheckLevels() {
182         RollbackPackageHealthObserver observer =
183                 spy(new RollbackPackageHealthObserver(mMockContext));
184         VersionedPackage testFailedPackage = new VersionedPackage(APP_A, VERSION_CODE);
185         VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
186 
187         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
188 
189         // Crashes with no rollbacks available
190         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
191                 observer.onHealthCheckFailed(null,
192                         PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1));
193         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
194                 observer.onHealthCheckFailed(null,
195                         PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
196 
197         // Make the rollbacks available
198         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
199         when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
200         when(mPackageRollbackInfo.getVersionRolledBackFrom()).thenReturn(testFailedPackage);
201 
202         // native crash
203         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
204                 observer.onHealthCheckFailed(null,
205                         PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1));
206         // non-native crash for the package
207         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_30,
208                 observer.onHealthCheckFailed(testFailedPackage,
209                         PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
210         // non-native crash for a different package
211         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
212                 observer.onHealthCheckFailed(secondFailedPackage,
213                         PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
214         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
215                 observer.onHealthCheckFailed(secondFailedPackage,
216                         PackageWatchdog.FAILURE_REASON_APP_CRASH, 2));
217         // Subsequent crashes when rollbacks have completed
218         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of());
219         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
220                 observer.onHealthCheckFailed(testFailedPackage,
221                         PackageWatchdog.FAILURE_REASON_APP_CRASH, 3));
222     }
223 
224     @Test
testIsPersistent()225     public void testIsPersistent() {
226         RollbackPackageHealthObserver observer =
227                 spy(new RollbackPackageHealthObserver(mMockContext));
228         assertTrue(observer.isPersistent());
229     }
230 
231     @Test
testMayObservePackage_withoutAnyRollback()232     public void testMayObservePackage_withoutAnyRollback() {
233         RollbackPackageHealthObserver observer =
234                 spy(new RollbackPackageHealthObserver(mMockContext));
235         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
236         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of());
237         assertFalse(observer.mayObservePackage(APP_A));
238     }
239 
240     @Test
testMayObservePackage_forPersistentApp()241     public void testMayObservePackage_forPersistentApp()
242             throws PackageManager.NameNotFoundException {
243         RollbackPackageHealthObserver observer =
244                 spy(new RollbackPackageHealthObserver(mMockContext));
245         ApplicationInfo info = new ApplicationInfo();
246         info.flags = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM;
247         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
248         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
249         when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
250         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
251         when(mMockPackageManager.getApplicationInfo(APP_A, 0)).thenReturn(info);
252         assertTrue(observer.mayObservePackage(APP_A));
253     }
254 
255     @Test
testMayObservePackage_forNonPersistentApp()256     public void testMayObservePackage_forNonPersistentApp()
257             throws PackageManager.NameNotFoundException {
258         RollbackPackageHealthObserver observer =
259                 spy(new RollbackPackageHealthObserver(mMockContext));
260         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
261         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(mRollbackInfo));
262         when(mRollbackInfo.getPackages()).thenReturn(List.of(mPackageRollbackInfo));
263         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
264         when(mMockPackageManager.getApplicationInfo(APP_A, 0))
265                 .thenThrow(new PackageManager.NameNotFoundException());
266         assertFalse(observer.mayObservePackage(APP_A));
267     }
268 
269     /**
270      * Test that when impactLevel is low returns user impact level 70
271      */
272     @Test
healthCheckFailed_impactLevelLow_onePackage()273     public void healthCheckFailed_impactLevelLow_onePackage()
274             throws PackageManager.NameNotFoundException {
275         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
276         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
277         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
278                 null, null , false, false,
279                 null);
280         RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
281                 false, null, 111,
282                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
283         RollbackPackageHealthObserver observer =
284                 spy(new RollbackPackageHealthObserver(mMockContext));
285         VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
286 
287         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
288         // Make the rollbacks available
289         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
290         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
291         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
292 
293         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
294                 observer.onHealthCheckFailed(secondFailedPackage,
295                         PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
296     }
297 
298     /**
299      * HealthCheckFailed should only return low impact rollbacks. High impact rollbacks are only
300      * for bootloop.
301      */
302     @Test
healthCheckFailed_impactLevelHigh_onePackage()303     public void healthCheckFailed_impactLevelHigh_onePackage()
304             throws PackageManager.NameNotFoundException {
305         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
306         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
307         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
308                 null, null, false, false,
309                 null);
310         RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
311                 false, null, 111,
312                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
313         RollbackPackageHealthObserver observer =
314                 spy(new RollbackPackageHealthObserver(mMockContext));
315         VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
316 
317         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
318         // Make the rollbacks available
319         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
320         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
321         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
322 
323         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
324                 observer.onHealthCheckFailed(secondFailedPackage,
325                         PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
326     }
327 
328     /**
329      * When the rollback impact level is manual only return user impact level 0. (User impact level
330      * 0 is ignored by package watchdog)
331      */
332     @Test
healthCheckFailed_impactLevelManualOnly_onePackage()333     public void healthCheckFailed_impactLevelManualOnly_onePackage()
334             throws PackageManager.NameNotFoundException {
335         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
336         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
337         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
338                 null, null, false, false,
339                 null);
340         RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
341                 false, null, 111,
342                 PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
343         RollbackPackageHealthObserver observer =
344                 spy(new RollbackPackageHealthObserver(mMockContext));
345         VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
346 
347         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
348         // Make the rollbacks available
349         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
350         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
351         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
352 
353         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
354                 observer.onHealthCheckFailed(secondFailedPackage,
355                         PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
356     }
357 
358     /**
359      * When both low impact and high impact are present, return 70.
360      */
361     @Test
healthCheckFailed_impactLevelLowAndHigh_onePackage()362     public void healthCheckFailed_impactLevelLowAndHigh_onePackage()
363             throws PackageManager.NameNotFoundException {
364         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
365         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
366         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
367                 null, null, false, false,
368                 null);
369         RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
370                 false, null, 111,
371                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
372         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
373         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
374         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
375                 null, null, false, false,
376                 null);
377         RollbackInfo rollbackInfo2 = new RollbackInfo(2, List.of(packageRollbackInfoB),
378                 false, null, 222,
379                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
380         RollbackPackageHealthObserver observer =
381                 spy(new RollbackPackageHealthObserver(mMockContext));
382         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
383 
384         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
385         // Make the rollbacks available
386         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
387                 List.of(rollbackInfo1, rollbackInfo2));
388         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
389         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
390 
391         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
392                 observer.onHealthCheckFailed(failedPackage,
393                         PackageWatchdog.FAILURE_REASON_APP_CRASH, 1));
394     }
395 
396     /**
397      * When low impact rollback is available roll it back.
398      */
399     @Test
execute_impactLevelLow_nativeCrash_rollback()400     public void execute_impactLevelLow_nativeCrash_rollback()
401             throws PackageManager.NameNotFoundException {
402         int rollbackId = 1;
403         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
404         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
405         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
406                 null, null , false, false,
407                 null);
408         RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId, List.of(packageRollbackInfo),
409                 false, null, 111,
410                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
411         VersionedPackage secondFailedPackage = new VersionedPackage(APP_B, VERSION_CODE);
412         RollbackPackageHealthObserver observer =
413                 spy(new RollbackPackageHealthObserver(mMockContext));
414 
415         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
416         // Make the rollbacks available
417         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
418         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
419         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
420 
421         observer.onExecuteHealthCheckMitigation(secondFailedPackage,
422                 PackageWatchdog.FAILURE_REASON_NATIVE_CRASH, 1);
423         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
424 
425         verify(mRollbackManager).getAvailableRollbacks();
426         verify(mRollbackManager).commitRollback(eq(rollbackId), any(), any());
427     }
428 
429     /**
430      * Rollback the failing package if rollback is available for it
431      */
432     @Test
execute_impactLevelLow_rollbackFailedPackage()433     public void execute_impactLevelLow_rollbackFailedPackage()
434             throws PackageManager.NameNotFoundException {
435         int rollbackId1 = 1;
436         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
437         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
438         PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
439                 null, null , false, false,
440                 null);
441         RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
442                 false, null, 111,
443                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
444         int rollbackId2 = 2;
445         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
446         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
447         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
448                 null, null , false, false,
449                 null);
450         RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
451                 false, null, 222,
452                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
453         RollbackPackageHealthObserver observer =
454                 spy(new RollbackPackageHealthObserver(mMockContext));
455         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
456 
457         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
458         // Make the rollbacks available
459         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
460                 List.of(rollbackInfo1, rollbackInfo2));
461         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
462         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
463 
464         observer.onExecuteHealthCheckMitigation(appBFrom, PackageWatchdog.FAILURE_REASON_APP_CRASH,
465                 1);
466         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
467 
468         verify(mRollbackManager).commitRollback(argument.capture(), any(), any());
469         // Rollback package App B as the failing package is B
470         assertThat(argument.getValue()).isEqualTo(rollbackId2);
471     }
472 
473     /**
474      * Rollback all available rollbacks if the rollback is not available for failing package.
475      */
476     @Test
execute_impactLevelLow_rollbackAll()477     public void execute_impactLevelLow_rollbackAll()
478             throws PackageManager.NameNotFoundException {
479         int rollbackId1 = 1;
480         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
481         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
482         PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
483                 null, null , false, false,
484                 null);
485         RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
486                 false, null, 111,
487                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
488         int rollbackId2 = 2;
489         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
490         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
491         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
492                 null, null , false, false,
493                 null);
494         RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
495                 false, null, 222,
496                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
497         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
498         RollbackPackageHealthObserver observer =
499                 spy(new RollbackPackageHealthObserver(mMockContext));
500         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
501 
502         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
503         // Make the rollbacks available
504         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
505                 List.of(rollbackInfo1, rollbackInfo2));
506         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
507         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
508 
509         observer.onExecuteHealthCheckMitigation(failedPackage,
510                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
511         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
512 
513         verify(mRollbackManager, times(2)).commitRollback(
514                 argument.capture(), any(), any());
515         // Rollback A and B when the failing package doesn't have a rollback
516         assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1, rollbackId2));
517     }
518 
519     /**
520      * rollback low impact package if both low and high impact packages are available
521      */
522     @Test
execute_impactLevelLowAndHigh_rollbackLow()523     public void execute_impactLevelLowAndHigh_rollbackLow()
524             throws PackageManager.NameNotFoundException {
525         int rollbackId1 = 1;
526         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
527         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
528         PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
529                 null, null , false, false,
530                 null);
531         RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
532                 false, null, 111,
533                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
534         int rollbackId2 = 2;
535         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
536         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
537         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
538                 null, null , false, false,
539                 null);
540         RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
541                 false, null, 222,
542                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
543         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
544         RollbackPackageHealthObserver observer =
545                 spy(new RollbackPackageHealthObserver(mMockContext));
546         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
547 
548         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
549         // Make the rollbacks available
550         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
551                 List.of(rollbackInfo1, rollbackInfo2));
552         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
553         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
554 
555         observer.onExecuteHealthCheckMitigation(failedPackage,
556                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
557         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
558 
559         verify(mRollbackManager, times(1)).commitRollback(
560                 argument.capture(), any(), any());
561         // Rollback A and B when the failing package doesn't have a rollback
562         assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
563     }
564 
565     /**
566      * Don't roll back high impact package if only high impact package is available. high impact
567      * rollback to be rolled back only on bootloop.
568      */
569     @Test
execute_impactLevelHigh_rollbackHigh()570     public void execute_impactLevelHigh_rollbackHigh()
571             throws PackageManager.NameNotFoundException {
572         int rollbackId2 = 2;
573         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
574         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
575         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
576                 null, null , false, false,
577                 null);
578         RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
579                 false, null, 111,
580                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
581         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
582         RollbackPackageHealthObserver observer =
583                 spy(new RollbackPackageHealthObserver(mMockContext));
584         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
585 
586         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
587         // Make the rollbacks available
588         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo2));
589         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
590         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
591 
592         observer.onExecuteHealthCheckMitigation(failedPackage,
593                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
594         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
595 
596         verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
597 
598     }
599 
600     /**
601      * Test that when impactLevel is low returns user impact level 70
602      */
603     @Test
onBootLoop_impactLevelLow_onePackage()604     public void onBootLoop_impactLevelLow_onePackage() throws PackageManager.NameNotFoundException {
605         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
606         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
607         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
608                 null, null , false, false,
609                 null);
610         RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
611                 false, null, 111,
612                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
613         RollbackPackageHealthObserver observer =
614                 spy(new RollbackPackageHealthObserver(mMockContext));
615 
616         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
617         // Make the rollbacks available
618         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
619         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
620         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
621 
622         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
623                 observer.onBootLoop(1));
624     }
625 
626     @Test
onBootLoop_impactLevelHigh_onePackage()627     public void onBootLoop_impactLevelHigh_onePackage()
628             throws PackageManager.NameNotFoundException {
629         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
630         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
631         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
632                 null, null, false, false,
633                 null);
634         RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
635                 false, null, 111,
636                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
637         RollbackPackageHealthObserver observer =
638                 spy(new RollbackPackageHealthObserver(mMockContext));
639 
640         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
641         // Make the rollbacks available
642         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
643         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
644         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
645 
646         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_90,
647                 observer.onBootLoop(1));
648     }
649 
650     @Test
onBootLoop_impactLevelHighDisableHighImpactRollback_onePackage()651     public void onBootLoop_impactLevelHighDisableHighImpactRollback_onePackage()
652             throws PackageManager.NameNotFoundException {
653         SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true));
654         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
655         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
656         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
657                 null, null, false, false,
658                 null);
659         RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
660                 false, null, 111,
661                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
662         RollbackPackageHealthObserver observer =
663                 spy(new RollbackPackageHealthObserver(mMockContext));
664 
665         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
666         // Make the rollbacks available
667         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
668         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
669         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
670 
671         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
672                 observer.onBootLoop(1));
673     }
674 
675     /**
676      * When the rollback impact level is manual only return user impact level 0. (User impact level
677      * 0 is ignored by package watchdog)
678      */
679     @Test
onBootLoop_impactLevelManualOnly_onePackage()680     public void onBootLoop_impactLevelManualOnly_onePackage()
681             throws PackageManager.NameNotFoundException {
682         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
683         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
684         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
685                 null, null, false, false,
686                 null);
687         RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
688                 false, null, 111,
689                 PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
690         RollbackPackageHealthObserver observer =
691                 spy(new RollbackPackageHealthObserver(mMockContext));
692 
693         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
694         // Make the rollbacks available
695         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
696         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
697         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
698 
699         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_0,
700                 observer.onBootLoop(1));
701     }
702 
703     /**
704      * When both low impact and high impact are present, return 70.
705      */
706     @Test
onBootLoop_impactLevelLowAndHigh_onePackage()707     public void onBootLoop_impactLevelLowAndHigh_onePackage()
708             throws PackageManager.NameNotFoundException {
709         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
710         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
711         PackageRollbackInfo packageRollbackInfo = new PackageRollbackInfo(appAFrom, appATo,
712                 null, null, false, false,
713                 null);
714         RollbackInfo rollbackInfo1 = new RollbackInfo(1, List.of(packageRollbackInfo),
715                 false, null, 111,
716                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
717         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
718         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
719         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
720                 null, null, false, false,
721                 null);
722         RollbackInfo rollbackInfo2 = new RollbackInfo(2, List.of(packageRollbackInfoB),
723                 false, null, 222,
724                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
725         RollbackPackageHealthObserver observer =
726                 spy(new RollbackPackageHealthObserver(mMockContext));
727 
728         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
729         // Make the rollbacks available
730         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
731                 List.of(rollbackInfo1, rollbackInfo2));
732         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
733         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
734 
735         assertEquals(PackageWatchdog.PackageHealthObserverImpact.USER_IMPACT_LEVEL_70,
736                 observer.onBootLoop(1));
737     }
738 
739     /**
740      * Rollback all available rollbacks if the rollback is not available for failing package.
741      */
742     @Test
executeBootLoopMitigation_impactLevelLow_rollbackAll()743     public void executeBootLoopMitigation_impactLevelLow_rollbackAll()
744             throws PackageManager.NameNotFoundException {
745         int rollbackId1 = 1;
746         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
747         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
748         PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
749                 null, null , false, false,
750                 null);
751         RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
752                 false, null, 111,
753                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
754         int rollbackId2 = 2;
755         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
756         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
757         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
758                 null, null , false, false,
759                 null);
760         RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
761                 false, null, 222,
762                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
763         RollbackPackageHealthObserver observer =
764                 spy(new RollbackPackageHealthObserver(mMockContext));
765         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
766 
767         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
768         // Make the rollbacks available
769         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
770                 List.of(rollbackInfo1, rollbackInfo2));
771         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
772         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
773 
774         observer.onExecuteBootLoopMitigation(1);
775         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
776 
777         verify(mRollbackManager, times(2)).commitRollback(
778                 argument.capture(), any(), any());
779         // Rollback A and B when the failing package doesn't have a rollback
780         assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1, rollbackId2));
781     }
782 
783     /**
784      * rollback low impact package if both low and high impact packages are available
785      */
786     @Test
executeBootLoopMitigation_impactLevelLowAndHigh_rollbackLow()787     public void executeBootLoopMitigation_impactLevelLowAndHigh_rollbackLow()
788             throws PackageManager.NameNotFoundException {
789         int rollbackId1 = 1;
790         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
791         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
792         PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
793                 null, null , false, false,
794                 null);
795         RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
796                 false, null, 111,
797                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
798         int rollbackId2 = 2;
799         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
800         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
801         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
802                 null, null , false, false,
803                 null);
804         RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
805                 false, null, 222,
806                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
807         RollbackPackageHealthObserver observer =
808                 spy(new RollbackPackageHealthObserver(mMockContext));
809         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
810 
811         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
812         // Make the rollbacks available
813         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
814                 List.of(rollbackInfo1, rollbackInfo2));
815         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
816         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
817 
818         observer.onExecuteBootLoopMitigation(1);
819         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
820 
821         verify(mRollbackManager, times(1)).commitRollback(
822                 argument.capture(), any(), any());
823         // Rollback A and B when the failing package doesn't have a rollback
824         assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
825     }
826 
827     /**
828      * Rollback high impact package if only high impact package is available
829      */
830     @Test
executeBootLoopMitigation_impactLevelHigh_rollbackHigh()831     public void executeBootLoopMitigation_impactLevelHigh_rollbackHigh()
832             throws PackageManager.NameNotFoundException {
833         int rollbackId2 = 2;
834         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
835         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
836         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
837                 null, null , false, false,
838                 null);
839         RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
840                 false, null, 111,
841                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
842         RollbackPackageHealthObserver observer =
843                 spy(new RollbackPackageHealthObserver(mMockContext));
844         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
845 
846         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
847         // Make the rollbacks available
848         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo2));
849         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
850         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
851 
852         observer.onExecuteBootLoopMitigation(1);
853         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
854 
855         verify(mRollbackManager, times(1)).commitRollback(
856                 argument.capture(), any(), any());
857         // Rollback high impact packages when no other rollback available
858         assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2));
859     }
860 
861     /**
862      * Rollback only low impact available rollbacks if both low and manual only are available.
863      */
864     @Test
execute_impactLevelLowAndManual_rollbackLowImpactOnly()865     public void execute_impactLevelLowAndManual_rollbackLowImpactOnly()
866             throws PackageManager.NameNotFoundException, InterruptedException {
867         int rollbackId1 = 1;
868         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
869         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
870         PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
871                 null, null , false, false,
872                 null);
873         RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
874                 false, null, 111,
875                 PackageManager.ROLLBACK_USER_IMPACT_LOW);
876         int rollbackId2 = 2;
877         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
878         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
879         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
880                 null, null , false, false,
881                 null);
882         RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoB),
883                 false, null, 222,
884                 PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
885         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
886         RollbackPackageHealthObserver observer =
887                 spy(new RollbackPackageHealthObserver(mMockContext));
888         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
889 
890         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
891         // Make the rollbacks available
892         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
893                 List.of(rollbackInfo1, rollbackInfo2));
894         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
895         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
896 
897         observer.onExecuteHealthCheckMitigation(failedPackage,
898                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
899         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
900 
901         verify(mRollbackManager, times(1)).commitRollback(
902                 argument.capture(), any(), any());
903         assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId1));
904     }
905 
906     /**
907      * Do not roll back if only manual rollback is available.
908      */
909     @Test
execute_impactLevelManual_rollbackLowImpactOnly()910     public void execute_impactLevelManual_rollbackLowImpactOnly()
911             throws PackageManager.NameNotFoundException {
912         int rollbackId1 = 1;
913         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
914         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
915         PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
916                 null, null , false, false,
917                 null);
918         RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoA),
919                 false, null, 111,
920                 PackageManager.ROLLBACK_USER_IMPACT_ONLY_MANUAL);
921         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
922         RollbackPackageHealthObserver observer =
923                 spy(new RollbackPackageHealthObserver(mMockContext));
924         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
925 
926         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
927         // Make the rollbacks available
928         when(mRollbackManager.getAvailableRollbacks()).thenReturn(List.of(rollbackInfo1));
929         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
930         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
931 
932         observer.onExecuteHealthCheckMitigation(failedPackage,
933                 PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
934         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
935 
936         verify(mRollbackManager, never()).commitRollback(argument.capture(), any(), any());
937     }
938 
939     /**
940      * Rollback alphabetically first package if multiple high impact rollbacks are available.
941      */
942     @Test
executeBootLoopMitigation_impactLevelHighMultiplePackage_rollbackHigh()943     public void executeBootLoopMitigation_impactLevelHighMultiplePackage_rollbackHigh()
944             throws PackageManager.NameNotFoundException {
945         int rollbackId1 = 1;
946         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
947         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
948         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
949                 null, null , false, false,
950                 null);
951         RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoB),
952                 false, null, 111,
953                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
954         int rollbackId2 = 2;
955         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
956         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
957         PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
958                 null, null , false, false,
959                 null);
960         RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoA),
961                 false, null, 111,
962                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
963         VersionedPackage failedPackage = new VersionedPackage(APP_C, VERSION_CODE);
964         RollbackPackageHealthObserver observer =
965                 spy(new RollbackPackageHealthObserver(mMockContext));
966         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
967 
968         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
969         // Make the rollbacks available
970         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
971                 List.of(rollbackInfo1, rollbackInfo2));
972         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
973         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
974 
975         observer.onExecuteBootLoopMitigation(1);
976         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
977 
978         verify(mRollbackManager, times(1)).commitRollback(
979                 argument.capture(), any(), any());
980         // Rollback APP_A because it is first alphabetically
981         assertThat(argument.getAllValues()).isEqualTo(List.of(rollbackId2));
982     }
983 
984     /**
985      * Don't roll back if kill switch is enabled.
986      */
987     @Test
executeBootLoopMitigation_impactLevelHighKillSwitchTrue_rollbackHigh()988     public void executeBootLoopMitigation_impactLevelHighKillSwitchTrue_rollbackHigh()
989             throws PackageManager.NameNotFoundException {
990         SystemProperties.set(PROP_DISABLE_HIGH_IMPACT_ROLLBACK_FLAG, Boolean.toString(true));
991         int rollbackId1 = 1;
992         VersionedPackage appBFrom = new VersionedPackage(APP_B, VERSION_CODE_2);
993         VersionedPackage appBTo = new VersionedPackage(APP_B, VERSION_CODE);
994         PackageRollbackInfo packageRollbackInfoB = new PackageRollbackInfo(appBFrom, appBTo,
995                 null, null , false, false,
996                 null);
997         RollbackInfo rollbackInfo1 = new RollbackInfo(rollbackId1, List.of(packageRollbackInfoB),
998                 false, null, 111,
999                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
1000         int rollbackId2 = 2;
1001         VersionedPackage appAFrom = new VersionedPackage(APP_A, VERSION_CODE_2);
1002         VersionedPackage appATo = new VersionedPackage(APP_A, VERSION_CODE);
1003         PackageRollbackInfo packageRollbackInfoA = new PackageRollbackInfo(appAFrom, appATo,
1004                 null, null , false, false,
1005                 null);
1006         RollbackInfo rollbackInfo2 = new RollbackInfo(rollbackId2, List.of(packageRollbackInfoA),
1007                 false, null, 111,
1008                 PackageManager.ROLLBACK_USER_IMPACT_HIGH);
1009         RollbackPackageHealthObserver observer =
1010                 spy(new RollbackPackageHealthObserver(mMockContext));
1011         ArgumentCaptor<Integer> argument = ArgumentCaptor.forClass(Integer.class);
1012 
1013         when(mMockContext.getSystemService(RollbackManager.class)).thenReturn(mRollbackManager);
1014         // Make the rollbacks available
1015         when(mRollbackManager.getAvailableRollbacks()).thenReturn(
1016                 List.of(rollbackInfo1, rollbackInfo2));
1017         when(mMockContext.getPackageManager()).thenReturn(mMockPackageManager);
1018         when(mMockPackageManager.getModuleInfo(any(), eq(0))).thenReturn(null);
1019 
1020         observer.onExecuteBootLoopMitigation(1);
1021         waitForIdleHandler(observer.getHandler(), Duration.ofSeconds(10));
1022 
1023         verify(mRollbackManager, never()).commitRollback(
1024                 argument.capture(), any(), any());
1025     }
1026 
waitForIdleHandler(Handler handler, Duration timeout)1027     private void waitForIdleHandler(Handler handler, Duration timeout) {
1028         final MessageQueue queue = handler.getLooper().getQueue();
1029         final CountDownLatch latch = new CountDownLatch(1);
1030         queue.addIdleHandler(() -> {
1031             latch.countDown();
1032             // Remove idle handler
1033             return false;
1034         });
1035         try {
1036             latch.await(timeout.toMillis(), TimeUnit.MILLISECONDS);
1037         } catch (InterruptedException e) {
1038             fail("Interrupted unexpectedly: " + e);
1039         }
1040     }
1041 }
1042