1 /*
2  * Copyright (C) 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server;
18 
19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.any;
20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
22 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
23 import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
24 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
25 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
26 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
27 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
28 import static com.android.server.RescueParty.LEVEL_FACTORY_RESET;
29 
30 import static org.junit.Assert.assertEquals;
31 import static org.junit.Assert.assertFalse;
32 import static org.junit.Assert.assertTrue;
33 import static org.mockito.ArgumentMatchers.eq;
34 import static org.mockito.ArgumentMatchers.isNull;
35 import static org.mockito.Mockito.never;
36 import static org.mockito.Mockito.times;
37 
38 import android.content.ContentResolver;
39 import android.content.Context;
40 import android.content.pm.ApplicationInfo;
41 import android.content.pm.PackageManager;
42 import android.content.pm.VersionedPackage;
43 import android.os.RecoverySystem;
44 import android.os.SystemProperties;
45 import android.os.UserHandle;
46 import android.provider.DeviceConfig;
47 import android.provider.Settings;
48 import android.util.ArraySet;
49 
50 import com.android.dx.mockito.inline.extended.ExtendedMockito;
51 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
52 import com.android.server.RescueParty.RescuePartyObserver;
53 import com.android.server.am.SettingsToPropertiesMapper;
54 
55 import org.junit.After;
56 import org.junit.Before;
57 import org.junit.Test;
58 import org.mockito.Answers;
59 import org.mockito.ArgumentCaptor;
60 import org.mockito.Captor;
61 import org.mockito.Mock;
62 import org.mockito.MockitoSession;
63 import org.mockito.quality.Strictness;
64 import org.mockito.stubbing.Answer;
65 
66 import java.util.Arrays;
67 import java.util.HashMap;
68 import java.util.HashSet;
69 import java.util.List;
70 import java.util.concurrent.Executor;
71 import java.util.concurrent.TimeUnit;
72 
73 /**
74  * Test RescueParty.
75  */
76 public class RescuePartyTest {
77     private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
78     private static final String FAKE_NATIVE_NAMESPACE1 = "native1";
79     private static final String FAKE_NATIVE_NAMESPACE2 = "native2";
80     private static final String[] FAKE_RESET_NATIVE_NAMESPACES =
81             {FAKE_NATIVE_NAMESPACE1, FAKE_NATIVE_NAMESPACE2};
82 
83     private static VersionedPackage sFailingPackage = new VersionedPackage("com.package.name", 1);
84     private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue";
85     private static final String CALLING_PACKAGE1 = "com.package.name1";
86     private static final String CALLING_PACKAGE2 = "com.package.name2";
87     private static final String CALLING_PACKAGE3 = "com.package.name3";
88     private static final String PERSISTENT_PACKAGE = "com.persistent.package";
89     private static final String NON_PERSISTENT_PACKAGE = "com.nonpersistent.package";
90     private static final String NAMESPACE1 = "namespace1";
91     private static final String NAMESPACE2 = "namespace2";
92     private static final String NAMESPACE3 = "namespace3";
93     private static final String NAMESPACE4 = "namespace4";
94     private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG =
95             "persist.device_config.configuration.disable_rescue_party";
96     private static final String PROP_DISABLE_FACTORY_RESET_FLAG =
97             "persist.device_config.configuration.disable_rescue_party_factory_reset";
98     private static final String PROP_LAST_FACTORY_RESET_TIME_MS = "persist.sys.last_factory_reset";
99 
100     private static final int THROTTLING_DURATION_MIN = 10;
101 
102     private MockitoSession mSession;
103     private HashMap<String, String> mSystemSettingsMap;
104     //Records the namespaces wiped by setProperties().
105     private HashSet<String> mNamespacesWiped;
106 
107     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
108     private Context mMockContext;
109     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
110     private PackageWatchdog mMockPackageWatchdog;
111     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
112     private ContentResolver mMockContentResolver;
113     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
114     private PackageManager mPackageManager;
115 
116     @Captor
117     private ArgumentCaptor<DeviceConfig.MonitorCallback> mMonitorCallbackCaptor;
118     @Captor
119     private ArgumentCaptor<List<String>> mPackageListCaptor;
120 
121     @Before
setUp()122     public void setUp() throws Exception {
123         mSession =
124                 ExtendedMockito.mockitoSession().initMocks(
125                         this)
126                         .strictness(Strictness.LENIENT)
127                         .spyStatic(DeviceConfig.class)
128                         .spyStatic(SystemProperties.class)
129                         .spyStatic(Settings.Global.class)
130                         .spyStatic(Settings.Secure.class)
131                         .spyStatic(SettingsToPropertiesMapper.class)
132                         .spyStatic(RecoverySystem.class)
133                         .spyStatic(RescueParty.class)
134                         .spyStatic(PackageWatchdog.class)
135                         .startMocking();
136         mSystemSettingsMap = new HashMap<>();
137         mNamespacesWiped = new HashSet<>();
138 
139         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
140         when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
141         ApplicationInfo persistentApplicationInfo = new ApplicationInfo();
142         persistentApplicationInfo.flags |=
143                 ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT;
144 
145         // If the package name is PERSISTENT_PACKAGE, then set the flags to be persistent and
146         // system. Don't set any flags otherwise.
147         when(mPackageManager.getApplicationInfo(eq(PERSISTENT_PACKAGE),
148                 anyInt())).thenReturn(persistentApplicationInfo);
149         when(mPackageManager.getApplicationInfo(eq(NON_PERSISTENT_PACKAGE),
150                 anyInt())).thenReturn(new ApplicationInfo());
151         // Reset observer instance to get new mock context on every run
152         RescuePartyObserver.reset();
153 
154         // Mock SystemProperties setter and various getters
155         doAnswer((Answer<Void>) invocationOnMock -> {
156                     String key = invocationOnMock.getArgument(0);
157                     String value = invocationOnMock.getArgument(1);
158 
159                     mSystemSettingsMap.put(key, value);
160                     return null;
161                 }
162         ).when(() -> SystemProperties.set(anyString(), anyString()));
163 
164         doAnswer((Answer<Boolean>) invocationOnMock -> {
165                     String key = invocationOnMock.getArgument(0);
166                     boolean defaultValue = invocationOnMock.getArgument(1);
167 
168                     String storedValue = mSystemSettingsMap.get(key);
169                     return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue);
170                 }
171         ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean()));
172 
173         doAnswer((Answer<Integer>) invocationOnMock -> {
174                     String key = invocationOnMock.getArgument(0);
175                     int defaultValue = invocationOnMock.getArgument(1);
176 
177                     String storedValue = mSystemSettingsMap.get(key);
178                     return storedValue == null ? defaultValue : Integer.parseInt(storedValue);
179                 }
180         ).when(() -> SystemProperties.getInt(anyString(), anyInt()));
181 
182         doAnswer((Answer<Long>) invocationOnMock -> {
183                     String key = invocationOnMock.getArgument(0);
184                     long defaultValue = invocationOnMock.getArgument(1);
185 
186                     String storedValue = mSystemSettingsMap.get(key);
187                     return storedValue == null ? defaultValue : Long.parseLong(storedValue);
188                 }
189         ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
190 
191         // Mock DeviceConfig
192         doAnswer((Answer<Boolean>) invocationOnMock -> true)
193                 .when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(),
194                         anyBoolean()));
195         doAnswer((Answer<Void>) invocationOnMock -> null)
196                 .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()));
197         doAnswer((Answer<Boolean>) invocationOnMock -> {
198                     DeviceConfig.Properties properties = invocationOnMock.getArgument(0);
199                     String namespace = properties.getNamespace();
200                     // record a wipe
201                     if (properties.getKeyset().isEmpty()) {
202                         mNamespacesWiped.add(namespace);
203                     }
204                     return true;
205                 }
206         ).when(() -> DeviceConfig.setProperties(any(DeviceConfig.Properties.class)));
207 
208         // Mock PackageWatchdog
209         doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
210                 .when(() -> PackageWatchdog.getInstance(mMockContext));
211 
212         doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime());
213 
214         SystemProperties.set(RescueParty.PROP_RESCUE_BOOT_COUNT, Integer.toString(0));
215         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
216         SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
217     }
218 
219     @After
tearDown()220     public void tearDown() throws Exception {
221         mSession.finishMocking();
222     }
223 
224     @Test
testBootLoopDetectionWithExecutionForAllRescueLevels()225     public void testBootLoopDetectionWithExecutionForAllRescueLevels() {
226         RescueParty.onSettingsProviderPublished(mMockContext);
227         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
228                 any(Executor.class),
229                 mMonitorCallbackCaptor.capture()));
230         HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
231 
232         noteBoot(1);
233 
234         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
235                 verifiedTimesMap);
236 
237         // Record DeviceConfig accesses
238         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
239         DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
240         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
241         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
242 
243         final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
244 
245         noteBoot(2);
246 
247         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedAllResetNamespaces,
248                 verifiedTimesMap);
249 
250         noteBoot(3);
251 
252         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
253                 verifiedTimesMap);
254 
255         noteBoot(4);
256         assertTrue(RescueParty.isRebootPropertySet());
257 
258         SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
259         noteBoot(5);
260         assertTrue(RescueParty.isFactoryResetPropertySet());
261     }
262 
263     @Test
testPersistentAppCrashDetectionWithExecutionForAllRescueLevels()264     public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
265         noteAppCrash(1, true);
266 
267         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
268                 /*configResetVerifiedTimesMap=*/ null);
269 
270         noteAppCrash(2, true);
271 
272         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
273                 /*configResetVerifiedTimesMap=*/ null);
274 
275         noteAppCrash(3, true);
276 
277         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
278                 /*configResetVerifiedTimesMap=*/ null);
279 
280         noteAppCrash(4, true);
281         assertTrue(RescueParty.isRebootPropertySet());
282 
283         SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
284         noteAppCrash(5, true);
285         assertTrue(RescueParty.isFactoryResetPropertySet());
286     }
287 
288     @Test
testNonPersistentAppOnlyPerformsFlagResets()289     public void testNonPersistentAppOnlyPerformsFlagResets() {
290         noteAppCrash(1, false);
291 
292         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
293                 /*configResetVerifiedTimesMap=*/ null);
294 
295         noteAppCrash(2, false);
296 
297         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
298                 /*configResetVerifiedTimesMap=*/ null);
299 
300         noteAppCrash(3, false);
301 
302         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
303                 /*configResetVerifiedTimesMap=*/ null);
304 
305         noteAppCrash(4, false);
306         assertFalse(RescueParty.isRebootPropertySet());
307 
308         noteAppCrash(5, false);
309         assertFalse(RescueParty.isFactoryResetPropertySet());
310     }
311 
312     @Test
testNonPersistentAppCrashDetectionWithScopedResets()313     public void testNonPersistentAppCrashDetectionWithScopedResets() {
314         RescueParty.onSettingsProviderPublished(mMockContext);
315         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
316                 any(Executor.class),
317                 mMonitorCallbackCaptor.capture()));
318 
319         // Record DeviceConfig accesses
320         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
321         DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
322         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
323         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
324         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
325         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
326 
327         // Fake DeviceConfig value changes
328         monitorCallback.onNamespaceUpdate(NAMESPACE1);
329         verify(mMockPackageWatchdog).startObservingHealth(observer,
330                 Arrays.asList(CALLING_PACKAGE1), RescueParty.DEFAULT_OBSERVING_DURATION_MS);
331         monitorCallback.onNamespaceUpdate(NAMESPACE2);
332         verify(mMockPackageWatchdog, times(2)).startObservingHealth(eq(observer),
333                 mPackageListCaptor.capture(),
334                 eq(RescueParty.DEFAULT_OBSERVING_DURATION_MS));
335         monitorCallback.onNamespaceUpdate(NAMESPACE3);
336         verify(mMockPackageWatchdog).startObservingHealth(observer,
337                 Arrays.asList(CALLING_PACKAGE2), RescueParty.DEFAULT_OBSERVING_DURATION_MS);
338         assertTrue(mPackageListCaptor.getValue().containsAll(
339                 Arrays.asList(CALLING_PACKAGE1, CALLING_PACKAGE2)));
340         // Perform and verify scoped resets
341         final String[] expectedResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
342         final String[] expectedAllResetNamespaces =
343                 new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3};
344         HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
345         observer.execute(new VersionedPackage(
346                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
347         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, expectedResetNamespaces,
348                 verifiedTimesMap);
349 
350         observer.execute(new VersionedPackage(
351                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2);
352         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedResetNamespaces,
353                 verifiedTimesMap);
354 
355         observer.execute(new VersionedPackage(
356                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3);
357         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
358                 verifiedTimesMap);
359 
360         observer.execute(new VersionedPackage(
361                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
362         assertFalse(RescueParty.isRebootPropertySet());
363 
364         observer.execute(new VersionedPackage(
365                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
366         assertFalse(RescueParty.isFactoryResetPropertySet());
367     }
368 
369     @Test
testNonDeviceConfigSettingsOnlyResetOncePerLevel()370     public void testNonDeviceConfigSettingsOnlyResetOncePerLevel() {
371         RescueParty.onSettingsProviderPublished(mMockContext);
372         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
373                 any(Executor.class),
374                 mMonitorCallbackCaptor.capture()));
375 
376         // Record DeviceConfig accesses
377         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
378         DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
379         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
380         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
381         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
382         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
383         // Fake DeviceConfig value changes
384         monitorCallback.onNamespaceUpdate(NAMESPACE1);
385         monitorCallback.onNamespaceUpdate(NAMESPACE2);
386         monitorCallback.onNamespaceUpdate(NAMESPACE3);
387         // Perform and verify scoped resets
388         final String[] expectedPackage1ResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
389         final String[] expectedPackage2ResetNamespaces = new String[]{NAMESPACE2, NAMESPACE3};
390         final String[] expectedAllResetNamespaces =
391                 new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3};
392         HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
393         observer.execute(new VersionedPackage(
394                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
395         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS,
396                 expectedPackage1ResetNamespaces, verifiedTimesMap);
397 
398         // Settings.Global & Settings.Secure should still remain the same execution times.
399         observer.execute(new VersionedPackage(
400                 CALLING_PACKAGE2, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
401         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS,
402                 expectedPackage2ResetNamespaces, verifiedTimesMap);
403 
404         observer.execute(new VersionedPackage(
405                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2);
406         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES,
407                 expectedPackage1ResetNamespaces, verifiedTimesMap);
408 
409         // Settings.Global & Settings.Secure should still remain the same execution times.
410         observer.execute(new VersionedPackage(
411                 CALLING_PACKAGE2, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2);
412         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES,
413                 expectedPackage2ResetNamespaces, verifiedTimesMap);
414 
415         observer.execute(new VersionedPackage(
416                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3);
417         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
418                 verifiedTimesMap);
419 
420         // Settings.Global & Settings.Secure should still remain the same execution times.
421         observer.execute(new VersionedPackage(
422                 CALLING_PACKAGE2, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3);
423         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
424                 verifiedTimesMap);
425 
426         observer.execute(new VersionedPackage(
427                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
428         assertFalse(RescueParty.isRebootPropertySet());
429 
430         observer.execute(new VersionedPackage(
431                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
432         assertFalse(RescueParty.isFactoryResetPropertySet());
433     }
434 
435     @Test
testIsAttemptingFactoryReset()436     public void testIsAttemptingFactoryReset() {
437         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
438             noteBoot(i + 1);
439         }
440         assertFalse(RescueParty.isFactoryResetPropertySet());
441         SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
442         noteBoot(LEVEL_FACTORY_RESET + 1);
443         assertTrue(RescueParty.isAttemptingFactoryReset());
444         assertTrue(RescueParty.isFactoryResetPropertySet());
445     }
446 
447     @Test
testIsAttemptingFactoryResetOnlyAfterRebootCompleted()448     public void testIsAttemptingFactoryResetOnlyAfterRebootCompleted() {
449         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
450             noteBoot(i + 1);
451         }
452         int mitigationCount = LEVEL_FACTORY_RESET + 1;
453         assertFalse(RescueParty.isFactoryResetPropertySet());
454         noteBoot(mitigationCount++);
455         assertFalse(RescueParty.isFactoryResetPropertySet());
456         noteBoot(mitigationCount++);
457         assertFalse(RescueParty.isFactoryResetPropertySet());
458         noteBoot(mitigationCount++);
459         SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
460         noteBoot(mitigationCount + 1);
461         assertTrue(RescueParty.isAttemptingFactoryReset());
462         assertTrue(RescueParty.isFactoryResetPropertySet());
463     }
464 
465     @Test
testThrottlingOnBootFailures()466     public void testThrottlingOnBootFailures() {
467         SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
468         long now = System.currentTimeMillis();
469         long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1);
470         SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout));
471         for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
472             noteBoot(i);
473         }
474         assertFalse(RescueParty.isAttemptingFactoryReset());
475     }
476 
477     @Test
testThrottlingOnAppCrash()478     public void testThrottlingOnAppCrash() {
479         SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
480         long now = System.currentTimeMillis();
481         long beforeTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN - 1);
482         SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(beforeTimeout));
483         for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
484             noteAppCrash(i + 1, true);
485         }
486         assertFalse(RescueParty.isAttemptingFactoryReset());
487     }
488 
489     @Test
testNotThrottlingAfterTimeoutOnBootFailures()490     public void testNotThrottlingAfterTimeoutOnBootFailures() {
491         SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
492         long now = System.currentTimeMillis();
493         long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1);
494         SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout));
495         for (int i = 1; i <= LEVEL_FACTORY_RESET; i++) {
496             noteBoot(i);
497         }
498         assertTrue(RescueParty.isAttemptingFactoryReset());
499     }
500     @Test
testNotThrottlingAfterTimeoutOnAppCrash()501     public void testNotThrottlingAfterTimeoutOnAppCrash() {
502         SystemProperties.set(RescueParty.PROP_ATTEMPTING_REBOOT, Boolean.toString(false));
503         long now = System.currentTimeMillis();
504         long afterTimeout = now - TimeUnit.MINUTES.toMillis(THROTTLING_DURATION_MIN + 1);
505         SystemProperties.set(PROP_LAST_FACTORY_RESET_TIME_MS, Long.toString(afterTimeout));
506         for (int i = 0; i <= LEVEL_FACTORY_RESET; i++) {
507             noteAppCrash(i + 1, true);
508         }
509         assertTrue(RescueParty.isAttemptingFactoryReset());
510     }
511 
512     @Test
testNativeRescuePartyResets()513     public void testNativeRescuePartyResets() {
514         doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed());
515         doReturn(FAKE_RESET_NATIVE_NAMESPACES).when(
516                 () -> SettingsToPropertiesMapper.getResetNativeCategories());
517 
518         RescueParty.onSettingsProviderPublished(mMockContext);
519 
520         verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
521                 FAKE_NATIVE_NAMESPACE1));
522         verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
523                 FAKE_NATIVE_NAMESPACE2));
524     }
525 
526     @Test
testExplicitlyEnablingAndDisablingRescue()527     public void testExplicitlyEnablingAndDisablingRescue() {
528         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
529         SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
530         assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
531                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
532 
533         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
534         assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
535                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1));
536     }
537 
538     @Test
testDisablingRescueByDeviceConfigFlag()539     public void testDisablingRescueByDeviceConfigFlag() {
540         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
541         SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
542 
543         assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
544                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
545 
546         // Restore the property value initialized in SetUp()
547         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
548         SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
549     }
550 
551     @Test
testDisablingFactoryResetByDeviceConfigFlag()552     public void testDisablingFactoryResetByDeviceConfigFlag() {
553         SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, Boolean.toString(true));
554 
555         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
556             noteBoot(i + 1);
557         }
558         assertFalse(RescueParty.isFactoryResetPropertySet());
559 
560         // Restore the property value initialized in SetUp()
561         SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, "");
562     }
563 
564     @Test
testHealthCheckLevels()565     public void testHealthCheckLevels() {
566         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
567 
568         // Ensure that no action is taken for cases where the failure reason is unknown
569         assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
570                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
571 
572         // Ensure the correct user impact is returned for each mitigation count.
573         assertEquals(observer.onHealthCheckFailed(null,
574                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
575                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
576 
577         assertEquals(observer.onHealthCheckFailed(null,
578                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
579                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
580 
581         assertEquals(observer.onHealthCheckFailed(null,
582                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3),
583                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
584 
585         assertEquals(observer.onHealthCheckFailed(null,
586                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4),
587                 PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
588     }
589 
590     @Test
testBootLoopLevels()591     public void testBootLoopLevels() {
592         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
593 
594         assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_LEVEL_0);
595         assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
596         assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LEVEL_10);
597         assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
598         assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_LEVEL_50);
599         assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_LEVEL_100);
600     }
601 
602     @Test
testResetDeviceConfigForPackagesOnlyRuntimeMap()603     public void testResetDeviceConfigForPackagesOnlyRuntimeMap() {
604         RescueParty.onSettingsProviderPublished(mMockContext);
605         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
606                 any(Executor.class),
607                 mMonitorCallbackCaptor.capture()));
608 
609         // Record DeviceConfig accesses
610         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
611         DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
612         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
613         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
614         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
615         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
616         // Fake DeviceConfig value changes
617         monitorCallback.onNamespaceUpdate(NAMESPACE1);
618         monitorCallback.onNamespaceUpdate(NAMESPACE2);
619         monitorCallback.onNamespaceUpdate(NAMESPACE3);
620 
621         doReturn("").when(() -> DeviceConfig.getString(
622                 eq(RescueParty.NAMESPACE_CONFIGURATION),
623                 eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
624                 eq("")));
625 
626         RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1}));
627         ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
628                 Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2}));
629         assertEquals(mNamespacesWiped, expectedNamespacesWiped);
630     }
631 
632     @Test
testResetDeviceConfigForPackagesOnlyPresetMap()633     public void testResetDeviceConfigForPackagesOnlyPresetMap() {
634         RescueParty.onSettingsProviderPublished(mMockContext);
635         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
636                 any(Executor.class),
637                 mMonitorCallbackCaptor.capture()));
638 
639         String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
640                 + NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
641                 + NAMESPACE3 +  ":" + CALLING_PACKAGE1;
642         doReturn(presetMapping).when(() -> DeviceConfig.getString(
643                 eq(RescueParty.NAMESPACE_CONFIGURATION),
644                 eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
645                 eq("")));
646 
647         RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1}));
648         ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
649                 Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3}));
650         assertEquals(mNamespacesWiped, expectedNamespacesWiped);
651     }
652 
653     @Test
testResetDeviceConfigForPackagesBothMaps()654     public void testResetDeviceConfigForPackagesBothMaps() {
655         RescueParty.onSettingsProviderPublished(mMockContext);
656         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
657                 any(Executor.class),
658                 mMonitorCallbackCaptor.capture()));
659 
660         // Record DeviceConfig accesses
661         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
662         DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
663         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
664         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE2);
665         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE2);
666         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
667         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE3, NAMESPACE4);
668         // Fake DeviceConfig value changes
669         monitorCallback.onNamespaceUpdate(NAMESPACE1);
670         monitorCallback.onNamespaceUpdate(NAMESPACE2);
671         monitorCallback.onNamespaceUpdate(NAMESPACE3);
672         monitorCallback.onNamespaceUpdate(NAMESPACE4);
673 
674         String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
675                 + NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
676                 + NAMESPACE4 + ":" + CALLING_PACKAGE3;
677         doReturn(presetMapping).when(() -> DeviceConfig.getString(
678                 eq(RescueParty.NAMESPACE_CONFIGURATION),
679                 eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
680                 eq("")));
681 
682         RescueParty.resetDeviceConfigForPackages(
683                 Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2}));
684         ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
685                 Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3}));
686         assertEquals(mNamespacesWiped, expectedNamespacesWiped);
687     }
688 
689     @Test
testResetDeviceConfigNoExceptionWhenFlagMalformed()690     public void testResetDeviceConfigNoExceptionWhenFlagMalformed() {
691         RescueParty.onSettingsProviderPublished(mMockContext);
692         verify(() -> DeviceConfig.setMonitorCallback(eq(mMockContentResolver),
693                 any(Executor.class),
694                 mMonitorCallbackCaptor.capture()));
695 
696         // Record DeviceConfig accesses
697         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
698         DeviceConfig.MonitorCallback monitorCallback = mMonitorCallbackCaptor.getValue();
699         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE1, NAMESPACE1);
700         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE2, NAMESPACE3);
701         monitorCallback.onDeviceConfigAccess(CALLING_PACKAGE3, NAMESPACE4);
702         // Fake DeviceConfig value changes
703         monitorCallback.onNamespaceUpdate(NAMESPACE1);
704         monitorCallback.onNamespaceUpdate(NAMESPACE2);
705         monitorCallback.onNamespaceUpdate(NAMESPACE3);
706         monitorCallback.onNamespaceUpdate(NAMESPACE4);
707 
708         String invalidPresetMapping = NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
709                 + NAMESPACE1 + "." + CALLING_PACKAGE2;
710         doReturn(invalidPresetMapping).when(() -> DeviceConfig.getString(
711                 eq(RescueParty.NAMESPACE_CONFIGURATION),
712                 eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
713                 eq("")));
714 
715         RescueParty.resetDeviceConfigForPackages(
716                 Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2}));
717         ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
718                 Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3}));
719         assertEquals(mNamespacesWiped, expectedNamespacesWiped);
720     }
721 
verifySettingsResets(int resetMode, String[] resetNamespaces, HashMap<String, Integer> configResetVerifiedTimesMap)722     private void verifySettingsResets(int resetMode, String[] resetNamespaces,
723             HashMap<String, Integer> configResetVerifiedTimesMap) {
724         verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
725                 resetMode, UserHandle.USER_SYSTEM));
726         verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(),
727                 eq(resetMode), anyInt()));
728         // Verify DeviceConfig resets
729         if (resetNamespaces == null) {
730             verify(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()), never());
731         } else {
732             for (String namespace : resetNamespaces) {
733                 int verifiedTimes = 0;
734                 if (configResetVerifiedTimesMap != null
735                         && configResetVerifiedTimesMap.get(namespace) != null) {
736                     verifiedTimes = configResetVerifiedTimesMap.get(namespace);
737                 }
738                 verify(() -> DeviceConfig.resetToDefaults(RescueParty.DEVICE_CONFIG_RESET_MODE,
739                         namespace), times(verifiedTimes + 1));
740                 if (configResetVerifiedTimesMap != null) {
741                     configResetVerifiedTimesMap.put(namespace, verifiedTimes + 1);
742                 }
743             }
744         }
745     }
746 
noteBoot(int mitigationCount)747     private void noteBoot(int mitigationCount) {
748         RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation(mitigationCount);
749     }
750 
noteAppCrash(int mitigationCount, boolean isPersistent)751     private void noteAppCrash(int mitigationCount, boolean isPersistent) {
752         String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE;
753         RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
754                 packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
755     }
756 }
757