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