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.Bundle;
44 import android.os.RecoverySystem;
45 import android.os.RemoteCallback;
46 import android.os.SystemProperties;
47 import android.os.UserHandle;
48 import android.provider.DeviceConfig;
49 import android.provider.Settings;
50 import android.util.ArraySet;
51 
52 import com.android.dx.mockito.inline.extended.ExtendedMockito;
53 import com.android.server.PackageWatchdog.PackageHealthObserverImpact;
54 import com.android.server.RescueParty.RescuePartyObserver;
55 import com.android.server.am.SettingsToPropertiesMapper;
56 
57 import org.junit.After;
58 import org.junit.Before;
59 import org.junit.Test;
60 import org.mockito.Answers;
61 import org.mockito.ArgumentCaptor;
62 import org.mockito.Captor;
63 import org.mockito.Mock;
64 import org.mockito.MockitoSession;
65 import org.mockito.quality.Strictness;
66 import org.mockito.stubbing.Answer;
67 
68 import java.util.Arrays;
69 import java.util.HashMap;
70 import java.util.HashSet;
71 import java.util.List;
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 
99     private MockitoSession mSession;
100     private HashMap<String, String> mSystemSettingsMap;
101     //Records the namespaces wiped by setProperties().
102     private HashSet<String> mNamespacesWiped;
103 
104     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
105     private Context mMockContext;
106     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
107     private PackageWatchdog mMockPackageWatchdog;
108     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
109     private ContentResolver mMockContentResolver;
110     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
111     private PackageManager mPackageManager;
112 
113     @Captor
114     private ArgumentCaptor<RemoteCallback> mMonitorCallbackCaptor;
115     @Captor
116     private ArgumentCaptor<List<String>> mPackageListCaptor;
117 
118     @Before
setUp()119     public void setUp() throws Exception {
120         mSession =
121                 ExtendedMockito.mockitoSession().initMocks(
122                         this)
123                         .strictness(Strictness.LENIENT)
124                         .spyStatic(DeviceConfig.class)
125                         .spyStatic(SystemProperties.class)
126                         .spyStatic(Settings.Global.class)
127                         .spyStatic(Settings.Secure.class)
128                         .spyStatic(Settings.Config.class)
129                         .spyStatic(SettingsToPropertiesMapper.class)
130                         .spyStatic(RecoverySystem.class)
131                         .spyStatic(RescueParty.class)
132                         .spyStatic(PackageWatchdog.class)
133                         .startMocking();
134         mSystemSettingsMap = new HashMap<>();
135         mNamespacesWiped = new HashSet<>();
136 
137         when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
138         when(mMockContext.getPackageManager()).thenReturn(mPackageManager);
139         ApplicationInfo persistentApplicationInfo = new ApplicationInfo();
140         persistentApplicationInfo.flags |=
141                 ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT;
142 
143         // If the package name is PERSISTENT_PACKAGE, then set the flags to be persistent and
144         // system. Don't set any flags otherwise.
145         when(mPackageManager.getApplicationInfo(eq(PERSISTENT_PACKAGE),
146                 anyInt())).thenReturn(persistentApplicationInfo);
147         when(mPackageManager.getApplicationInfo(eq(NON_PERSISTENT_PACKAGE),
148                 anyInt())).thenReturn(new ApplicationInfo());
149         // Reset observer instance to get new mock context on every run
150         RescuePartyObserver.reset();
151 
152         // Mock SystemProperties setter and various getters
153         doAnswer((Answer<Void>) invocationOnMock -> {
154                     String key = invocationOnMock.getArgument(0);
155                     String value = invocationOnMock.getArgument(1);
156 
157                     mSystemSettingsMap.put(key, value);
158                     return null;
159                 }
160         ).when(() -> SystemProperties.set(anyString(), anyString()));
161 
162         doAnswer((Answer<Boolean>) invocationOnMock -> {
163                     String key = invocationOnMock.getArgument(0);
164                     boolean defaultValue = invocationOnMock.getArgument(1);
165 
166                     String storedValue = mSystemSettingsMap.get(key);
167                     return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue);
168                 }
169         ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean()));
170 
171         doAnswer((Answer<Integer>) invocationOnMock -> {
172                     String key = invocationOnMock.getArgument(0);
173                     int defaultValue = invocationOnMock.getArgument(1);
174 
175                     String storedValue = mSystemSettingsMap.get(key);
176                     return storedValue == null ? defaultValue : Integer.parseInt(storedValue);
177                 }
178         ).when(() -> SystemProperties.getInt(anyString(), anyInt()));
179 
180         doAnswer((Answer<Long>) invocationOnMock -> {
181                     String key = invocationOnMock.getArgument(0);
182                     long defaultValue = invocationOnMock.getArgument(1);
183 
184                     String storedValue = mSystemSettingsMap.get(key);
185                     return storedValue == null ? defaultValue : Long.parseLong(storedValue);
186                 }
187         ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
188 
189         // Mock DeviceConfig
190         doAnswer((Answer<Boolean>) invocationOnMock -> true)
191                 .when(() -> DeviceConfig.setProperty(anyString(), anyString(), anyString(),
192                         anyBoolean()));
193         doAnswer((Answer<Void>) invocationOnMock -> null)
194                 .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()));
195         doAnswer((Answer<Boolean>) invocationOnMock -> {
196                     DeviceConfig.Properties properties = invocationOnMock.getArgument(0);
197                     String namespace = properties.getNamespace();
198                     // record a wipe
199                     if (properties.getKeyset().isEmpty()) {
200                         mNamespacesWiped.add(namespace);
201                     }
202                     return true;
203                 }
204         ).when(() -> DeviceConfig.setProperties(any(DeviceConfig.Properties.class)));
205 
206         // Mock PackageWatchdog
207         doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog)
208                 .when(() -> PackageWatchdog.getInstance(mMockContext));
209 
210         doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime());
211 
212         SystemProperties.set(RescueParty.PROP_RESCUE_BOOT_COUNT, Integer.toString(0));
213         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
214         SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
215     }
216 
217     @After
tearDown()218     public void tearDown() throws Exception {
219         mSession.finishMocking();
220     }
221 
222     @Test
testBootLoopDetectionWithExecutionForAllRescueLevels()223     public void testBootLoopDetectionWithExecutionForAllRescueLevels() {
224         RescueParty.onSettingsProviderPublished(mMockContext);
225         verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
226                 mMonitorCallbackCaptor.capture()));
227         HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
228 
229         noteBoot(1);
230 
231         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
232                 verifiedTimesMap);
233 
234         // Record DeviceConfig accesses
235         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
236         RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
237         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
238         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
239 
240         final String[] expectedAllResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
241 
242         noteBoot(2);
243 
244         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedAllResetNamespaces,
245                 verifiedTimesMap);
246 
247         noteBoot(3);
248 
249         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
250                 verifiedTimesMap);
251 
252         noteBoot(4);
253         assertTrue(RescueParty.isRebootPropertySet());
254 
255         noteBoot(5);
256         assertTrue(RescueParty.isFactoryResetPropertySet());
257     }
258 
259     @Test
testPersistentAppCrashDetectionWithExecutionForAllRescueLevels()260     public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
261         noteAppCrash(1, true);
262 
263         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
264                 /*configResetVerifiedTimesMap=*/ null);
265 
266         noteAppCrash(2, true);
267 
268         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
269                 /*configResetVerifiedTimesMap=*/ null);
270 
271         noteAppCrash(3, true);
272 
273         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
274                 /*configResetVerifiedTimesMap=*/ null);
275 
276         noteAppCrash(4, true);
277         assertTrue(RescueParty.isRebootPropertySet());
278 
279         noteAppCrash(5, true);
280         assertTrue(RescueParty.isFactoryResetPropertySet());
281     }
282 
283     @Test
testNonPersistentAppOnlyPerformsFlagResets()284     public void testNonPersistentAppOnlyPerformsFlagResets() {
285         noteAppCrash(1, false);
286 
287         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
288                 /*configResetVerifiedTimesMap=*/ null);
289 
290         noteAppCrash(2, false);
291 
292         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, /*resetNamespaces=*/ null,
293                 /*configResetVerifiedTimesMap=*/ null);
294 
295         noteAppCrash(3, false);
296 
297         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, /*resetNamespaces=*/ null,
298                 /*configResetVerifiedTimesMap=*/ null);
299 
300         noteAppCrash(4, false);
301         assertFalse(RescueParty.isRebootPropertySet());
302 
303         noteAppCrash(5, false);
304         assertFalse(RescueParty.isFactoryResetPropertySet());
305     }
306 
307     @Test
testNonPersistentAppCrashDetectionWithScopedResets()308     public void testNonPersistentAppCrashDetectionWithScopedResets() {
309         RescueParty.onSettingsProviderPublished(mMockContext);
310         verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
311                 mMonitorCallbackCaptor.capture()));
312 
313         // Record DeviceConfig accesses
314         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
315         RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
316         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
317         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
318         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
319         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
320         // Fake DeviceConfig value changes
321         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
322         verify(mMockPackageWatchdog).startObservingHealth(observer,
323                 Arrays.asList(CALLING_PACKAGE1), RescueParty.DEFAULT_OBSERVING_DURATION_MS);
324         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
325         verify(mMockPackageWatchdog, times(2)).startObservingHealth(eq(observer),
326                 mPackageListCaptor.capture(),
327                 eq(RescueParty.DEFAULT_OBSERVING_DURATION_MS));
328         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
329         verify(mMockPackageWatchdog).startObservingHealth(observer,
330                 Arrays.asList(CALLING_PACKAGE2), RescueParty.DEFAULT_OBSERVING_DURATION_MS);
331         assertTrue(mPackageListCaptor.getValue().containsAll(
332                 Arrays.asList(CALLING_PACKAGE1, CALLING_PACKAGE2)));
333         // Perform and verify scoped resets
334         final String[] expectedResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
335         final String[] expectedAllResetNamespaces =
336                 new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3};
337         HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
338         observer.execute(new VersionedPackage(
339                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
340         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS, expectedResetNamespaces,
341                 verifiedTimesMap);
342 
343         observer.execute(new VersionedPackage(
344                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2);
345         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES, expectedResetNamespaces,
346                 verifiedTimesMap);
347 
348         observer.execute(new VersionedPackage(
349                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3);
350         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
351                 verifiedTimesMap);
352 
353         observer.execute(new VersionedPackage(
354                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
355         assertFalse(RescueParty.isRebootPropertySet());
356 
357         observer.execute(new VersionedPackage(
358                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
359         assertFalse(RescueParty.isFactoryResetPropertySet());
360     }
361 
362     @Test
testNonDeviceConfigSettingsOnlyResetOncePerLevel()363     public void testNonDeviceConfigSettingsOnlyResetOncePerLevel() {
364         RescueParty.onSettingsProviderPublished(mMockContext);
365         verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
366                 mMonitorCallbackCaptor.capture()));
367 
368         // Record DeviceConfig accesses
369         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
370         RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
371         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
372         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
373         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
374         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
375         // Fake DeviceConfig value changes
376         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
377         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
378         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
379         // Perform and verify scoped resets
380         final String[] expectedPackage1ResetNamespaces = new String[]{NAMESPACE1, NAMESPACE2};
381         final String[] expectedPackage2ResetNamespaces = new String[]{NAMESPACE2, NAMESPACE3};
382         final String[] expectedAllResetNamespaces =
383                 new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3};
384         HashMap<String, Integer> verifiedTimesMap = new HashMap<String, Integer>();
385         observer.execute(new VersionedPackage(
386                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
387         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS,
388                 expectedPackage1ResetNamespaces, verifiedTimesMap);
389 
390         // Settings.Global & Settings.Secure should still remain the same execution times.
391         observer.execute(new VersionedPackage(
392                 CALLING_PACKAGE2, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, 1);
393         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS,
394                 expectedPackage2ResetNamespaces, verifiedTimesMap);
395 
396         observer.execute(new VersionedPackage(
397                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2);
398         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES,
399                 expectedPackage1ResetNamespaces, verifiedTimesMap);
400 
401         // Settings.Global & Settings.Secure should still remain the same execution times.
402         observer.execute(new VersionedPackage(
403                 CALLING_PACKAGE2, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2);
404         verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES,
405                 expectedPackage2ResetNamespaces, verifiedTimesMap);
406 
407         observer.execute(new VersionedPackage(
408                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3);
409         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
410                 verifiedTimesMap);
411 
412         // Settings.Global & Settings.Secure should still remain the same execution times.
413         observer.execute(new VersionedPackage(
414                 CALLING_PACKAGE2, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3);
415         verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS, expectedAllResetNamespaces,
416                 verifiedTimesMap);
417 
418         observer.execute(new VersionedPackage(
419                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4);
420         assertFalse(RescueParty.isRebootPropertySet());
421 
422         observer.execute(new VersionedPackage(
423                 CALLING_PACKAGE1, 1), PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 5);
424         assertFalse(RescueParty.isFactoryResetPropertySet());
425     }
426 
427     @Test
testIsAttemptingFactoryReset()428     public void testIsAttemptingFactoryReset() {
429         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
430             noteBoot(i + 1);
431         }
432         assertTrue(RescueParty.isAttemptingFactoryReset());
433         assertTrue(RescueParty.isFactoryResetPropertySet());
434     }
435 
436     @Test
testNativeRescuePartyResets()437     public void testNativeRescuePartyResets() {
438         doReturn(true).when(() -> SettingsToPropertiesMapper.isNativeFlagsResetPerformed());
439         doReturn(FAKE_RESET_NATIVE_NAMESPACES).when(
440                 () -> SettingsToPropertiesMapper.getResetNativeCategories());
441 
442         RescueParty.onSettingsProviderPublished(mMockContext);
443 
444         verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
445                 FAKE_NATIVE_NAMESPACE1));
446         verify(() -> DeviceConfig.resetToDefaults(Settings.RESET_MODE_TRUSTED_DEFAULTS,
447                 FAKE_NATIVE_NAMESPACE2));
448     }
449 
450     @Test
testExplicitlyEnablingAndDisablingRescue()451     public void testExplicitlyEnablingAndDisablingRescue() {
452         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
453         SystemProperties.set(PROP_DISABLE_RESCUE, Boolean.toString(true));
454         assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
455                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
456 
457         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
458         assertTrue(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
459                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1));
460     }
461 
462     @Test
testDisablingRescueByDeviceConfigFlag()463     public void testDisablingRescueByDeviceConfigFlag() {
464         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(false));
465         SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(true));
466 
467         assertEquals(RescuePartyObserver.getInstance(mMockContext).execute(sFailingPackage,
468                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1), false);
469 
470         // Restore the property value initialized in SetUp()
471         SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
472         SystemProperties.set(PROP_DEVICE_CONFIG_DISABLE_FLAG, Boolean.toString(false));
473     }
474 
475     @Test
testDisablingFactoryResetByDeviceConfigFlag()476     public void testDisablingFactoryResetByDeviceConfigFlag() {
477         SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, Boolean.toString(true));
478 
479         for (int i = 0; i < LEVEL_FACTORY_RESET; i++) {
480             noteBoot(i + 1);
481         }
482         assertFalse(RescueParty.isFactoryResetPropertySet());
483 
484         // Restore the property value initialized in SetUp()
485         SystemProperties.set(PROP_DISABLE_FACTORY_RESET_FLAG, "");
486     }
487 
488     @Test
testHealthCheckLevels()489     public void testHealthCheckLevels() {
490         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
491 
492         // Ensure that no action is taken for cases where the failure reason is unknown
493         assertEquals(observer.onHealthCheckFailed(null, PackageWatchdog.FAILURE_REASON_UNKNOWN, 1),
494                 PackageHealthObserverImpact.USER_IMPACT_NONE);
495 
496         // Ensure the correct user impact is returned for each mitigation count.
497         assertEquals(observer.onHealthCheckFailed(null,
498                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 1),
499                 PackageHealthObserverImpact.USER_IMPACT_LOW);
500 
501         assertEquals(observer.onHealthCheckFailed(null,
502                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 2),
503                 PackageHealthObserverImpact.USER_IMPACT_LOW);
504 
505         assertEquals(observer.onHealthCheckFailed(null,
506                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 3),
507                 PackageHealthObserverImpact.USER_IMPACT_HIGH);
508 
509         assertEquals(observer.onHealthCheckFailed(null,
510                 PackageWatchdog.FAILURE_REASON_APP_NOT_RESPONDING, 4),
511                 PackageHealthObserverImpact.USER_IMPACT_HIGH);
512     }
513 
514     @Test
testBootLoopLevels()515     public void testBootLoopLevels() {
516         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
517 
518         assertEquals(observer.onBootLoop(0), PackageHealthObserverImpact.USER_IMPACT_NONE);
519         assertEquals(observer.onBootLoop(1), PackageHealthObserverImpact.USER_IMPACT_LOW);
520         assertEquals(observer.onBootLoop(2), PackageHealthObserverImpact.USER_IMPACT_LOW);
521         assertEquals(observer.onBootLoop(3), PackageHealthObserverImpact.USER_IMPACT_HIGH);
522         assertEquals(observer.onBootLoop(4), PackageHealthObserverImpact.USER_IMPACT_HIGH);
523         assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_HIGH);
524     }
525 
526     @Test
testResetDeviceConfigForPackagesOnlyRuntimeMap()527     public void testResetDeviceConfigForPackagesOnlyRuntimeMap() {
528         RescueParty.onSettingsProviderPublished(mMockContext);
529         verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
530                 mMonitorCallbackCaptor.capture()));
531 
532         // Record DeviceConfig accesses
533         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
534         RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
535         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
536         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
537         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
538         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
539         // Fake DeviceConfig value changes
540         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
541         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
542         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
543 
544         doReturn("").when(() -> DeviceConfig.getString(
545                 eq(RescueParty.NAMESPACE_CONFIGURATION),
546                 eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
547                 eq("")));
548 
549         RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1}));
550         ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
551                 Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2}));
552         assertEquals(mNamespacesWiped, expectedNamespacesWiped);
553     }
554 
555     @Test
testResetDeviceConfigForPackagesOnlyPresetMap()556     public void testResetDeviceConfigForPackagesOnlyPresetMap() {
557         RescueParty.onSettingsProviderPublished(mMockContext);
558         verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
559                 mMonitorCallbackCaptor.capture()));
560 
561         String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
562                 + NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
563                 + NAMESPACE3 +  ":" + CALLING_PACKAGE1;
564         doReturn(presetMapping).when(() -> DeviceConfig.getString(
565                 eq(RescueParty.NAMESPACE_CONFIGURATION),
566                 eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
567                 eq("")));
568 
569         RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1}));
570         ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
571                 Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3}));
572         assertEquals(mNamespacesWiped, expectedNamespacesWiped);
573     }
574 
575     @Test
testResetDeviceConfigForPackagesBothMaps()576     public void testResetDeviceConfigForPackagesBothMaps() {
577         RescueParty.onSettingsProviderPublished(mMockContext);
578         verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
579                 mMonitorCallbackCaptor.capture()));
580 
581         // Record DeviceConfig accesses
582         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
583         RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
584         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
585         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2));
586         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2));
587         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
588         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE3, NAMESPACE4));
589         // Fake DeviceConfig value changes
590         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
591         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
592         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
593         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE4));
594 
595         String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + ","
596                 + NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
597                 + NAMESPACE4 + ":" + CALLING_PACKAGE3;
598         doReturn(presetMapping).when(() -> DeviceConfig.getString(
599                 eq(RescueParty.NAMESPACE_CONFIGURATION),
600                 eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
601                 eq("")));
602 
603         RescueParty.resetDeviceConfigForPackages(
604                 Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2}));
605         ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
606                 Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3}));
607         assertEquals(mNamespacesWiped, expectedNamespacesWiped);
608     }
609 
610     @Test
testResetDeviceConfigNoExceptionWhenFlagMalformed()611     public void testResetDeviceConfigNoExceptionWhenFlagMalformed() {
612         RescueParty.onSettingsProviderPublished(mMockContext);
613         verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver),
614                 mMonitorCallbackCaptor.capture()));
615 
616         // Record DeviceConfig accesses
617         RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext);
618         RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue();
619         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1));
620         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3));
621         monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE3, NAMESPACE4));
622         // Fake DeviceConfig value changes
623         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1));
624         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2));
625         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3));
626         monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE4));
627 
628         String invalidPresetMapping = NAMESPACE2 + ":" + CALLING_PACKAGE2 + ","
629                 + NAMESPACE1 + "." + CALLING_PACKAGE2;
630         doReturn(invalidPresetMapping).when(() -> DeviceConfig.getString(
631                 eq(RescueParty.NAMESPACE_CONFIGURATION),
632                 eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG),
633                 eq("")));
634 
635         RescueParty.resetDeviceConfigForPackages(
636                 Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2}));
637         ArraySet<String> expectedNamespacesWiped = new ArraySet<String>(
638                 Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3}));
639         assertEquals(mNamespacesWiped, expectedNamespacesWiped);
640     }
641 
verifySettingsResets(int resetMode, String[] resetNamespaces, HashMap<String, Integer> configResetVerifiedTimesMap)642     private void verifySettingsResets(int resetMode, String[] resetNamespaces,
643             HashMap<String, Integer> configResetVerifiedTimesMap) {
644         verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
645                 resetMode, UserHandle.USER_SYSTEM));
646         verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(),
647                 eq(resetMode), anyInt()));
648         // Verify DeviceConfig resets
649         if (resetNamespaces == null) {
650             verify(() -> DeviceConfig.resetToDefaults(anyInt(), anyString()), never());
651         } else {
652             for (String namespace : resetNamespaces) {
653                 int verifiedTimes = 0;
654                 if (configResetVerifiedTimesMap != null
655                         && configResetVerifiedTimesMap.get(namespace) != null) {
656                     verifiedTimes = configResetVerifiedTimesMap.get(namespace);
657                 }
658                 verify(() -> DeviceConfig.resetToDefaults(RescueParty.DEVICE_CONFIG_RESET_MODE,
659                         namespace), times(verifiedTimes + 1));
660                 if (configResetVerifiedTimesMap != null) {
661                     configResetVerifiedTimesMap.put(namespace, verifiedTimes + 1);
662                 }
663             }
664         }
665     }
666 
noteBoot(int mitigationCount)667     private void noteBoot(int mitigationCount) {
668         RescuePartyObserver.getInstance(mMockContext).executeBootLoopMitigation(mitigationCount);
669     }
670 
noteAppCrash(int mitigationCount, boolean isPersistent)671     private void noteAppCrash(int mitigationCount, boolean isPersistent) {
672         String packageName = isPersistent ? PERSISTENT_PACKAGE : NON_PERSISTENT_PACKAGE;
673         RescuePartyObserver.getInstance(mMockContext).execute(new VersionedPackage(
674                 packageName, 1), PackageWatchdog.FAILURE_REASON_APP_CRASH, mitigationCount);
675     }
676 
getConfigAccessBundle(String callingPackage, String namespace)677     private Bundle getConfigAccessBundle(String callingPackage, String namespace) {
678         Bundle result = new Bundle();
679         result.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE, Settings.EXTRA_ACCESS_CALLBACK);
680         result.putString(Settings.EXTRA_CALLING_PACKAGE, callingPackage);
681         result.putString(Settings.EXTRA_NAMESPACE, namespace);
682         return result;
683     }
684 
getConfigNamespaceUpdateBundle(String updatedNamespace)685     private Bundle getConfigNamespaceUpdateBundle(String updatedNamespace) {
686         Bundle result = new Bundle();
687         result.putString(Settings.EXTRA_MONITOR_CALLBACK_TYPE,
688                 Settings.EXTRA_NAMESPACE_UPDATED_CALLBACK);
689         result.putString(Settings.EXTRA_NAMESPACE, updatedNamespace);
690         return result;
691     }
692 }
693