blob: b13735cbf3b76dfa985f4037c514f9d0536913cc [file] [log] [blame]
bpetrivsa7101952019-02-07 16:01:24 +00001/*
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
17package com.android.server;
18
19import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean;
20import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt;
21import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong;
22import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyString;
23import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
24import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
25import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
26import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
27
28import static org.junit.Assert.assertEquals;
29import static org.junit.Assert.assertTrue;
30import static org.mockito.ArgumentMatchers.eq;
31import static org.mockito.ArgumentMatchers.isNull;
32
33import android.content.ContentResolver;
34import android.content.Context;
35import android.os.RecoverySystem;
36import android.os.SystemProperties;
37import android.os.UserHandle;
38import android.provider.Settings;
39
40import com.android.dx.mockito.inline.extended.ExtendedMockito;
41
42import org.junit.After;
43import org.junit.Before;
44import org.junit.Test;
45import org.mockito.Answers;
46import org.mockito.Mock;
47import org.mockito.MockitoSession;
48import org.mockito.quality.Strictness;
49import org.mockito.stubbing.Answer;
50
51import java.util.HashMap;
52
53/**
54 * Test RescueParty.
55 */
56public class RescuePartyTest {
57 private static final int PERSISTENT_APP_UID = 12;
58 private static final long CURRENT_NETWORK_TIME_MILLIS = 0L;
59
60 private MockitoSession mSession;
61
62 @Mock(answer = Answers.RETURNS_DEEP_STUBS)
63 private Context mMockContext;
64
65 @Mock(answer = Answers.RETURNS_DEEP_STUBS)
66 private ContentResolver mMockContentResolver;
67
68 private HashMap<String, String> mSystemSettingsMap;
69
70 @Before
71 public void setUp() throws Exception {
72 mSession =
73 ExtendedMockito.mockitoSession().initMocks(
74 this)
75 .strictness(Strictness.LENIENT)
76 .spyStatic(SystemProperties.class)
77 .spyStatic(Settings.Global.class)
78 .spyStatic(Settings.Secure.class)
79 .spyStatic(RecoverySystem.class)
80 .spyStatic(RescueParty.class)
81 .startMocking();
82 mSystemSettingsMap = new HashMap<>();
83
84 when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver);
85
86
87 // Mock SystemProperties setter and various getters
88 doAnswer((Answer<Void>) invocationOnMock -> {
89 String key = invocationOnMock.getArgument(0);
90 String value = invocationOnMock.getArgument(1);
91
92 mSystemSettingsMap.put(key, value);
93 return null;
94 }
95 ).when(() -> SystemProperties.set(anyString(), anyString()));
96
97 doAnswer((Answer<Boolean>) invocationOnMock -> {
98 String key = invocationOnMock.getArgument(0);
99 boolean defaultValue = invocationOnMock.getArgument(1);
100
101 String storedValue = mSystemSettingsMap.get(key);
102 return storedValue == null ? defaultValue : Boolean.parseBoolean(storedValue);
103 }
104 ).when(() -> SystemProperties.getBoolean(anyString(), anyBoolean()));
105
106 doAnswer((Answer<Integer>) invocationOnMock -> {
107 String key = invocationOnMock.getArgument(0);
108 int defaultValue = invocationOnMock.getArgument(1);
109
110 String storedValue = mSystemSettingsMap.get(key);
111 return storedValue == null ? defaultValue : Integer.parseInt(storedValue);
112 }
113 ).when(() -> SystemProperties.getInt(anyString(), anyInt()));
114
115 doAnswer((Answer<Long>) invocationOnMock -> {
116 String key = invocationOnMock.getArgument(0);
117 long defaultValue = invocationOnMock.getArgument(1);
118
119 String storedValue = mSystemSettingsMap.get(key);
120 return storedValue == null ? defaultValue : Long.parseLong(storedValue);
121 }
122 ).when(() -> SystemProperties.getLong(anyString(), anyLong()));
123
124 doReturn(CURRENT_NETWORK_TIME_MILLIS).when(() -> RescueParty.getElapsedRealtime());
125 RescueParty.resetAllThresholds();
126
127 SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL,
128 Integer.toString(RescueParty.LEVEL_NONE));
129 SystemProperties.set(RescueParty.PROP_RESCUE_BOOT_COUNT, Integer.toString(0));
130 SystemProperties.set(RescueParty.PROP_ENABLE_RESCUE, Boolean.toString(true));
131 }
132
133 @After
134 public void tearDown() throws Exception {
135 mSession.finishMocking();
136 }
137
138 @Test
139 public void testBootLoopDetectionWithExecutionForAllRescueLevels() {
140 noteBoot(RescueParty.TRIGGER_COUNT);
141
142 verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
143 assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
144 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
145
146 noteBoot(RescueParty.TRIGGER_COUNT);
147
148 verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES);
149 assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
150 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
151
152 noteBoot(RescueParty.TRIGGER_COUNT);
153
154 verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS);
155 assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
156 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
157
158 noteBoot(RescueParty.TRIGGER_COUNT);
159
160 verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
161 assertEquals(RescueParty.LEVEL_FACTORY_RESET,
162 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
163 }
164
165 @Test
166 public void testPersistentAppCrashDetectionWithExecutionForAllRescueLevels() {
167 notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
168
169 verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
170 assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
171 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
172
173 notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
174
175 verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_CHANGES);
176 assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_CHANGES,
177 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
178
179 notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
180
181 verifySettingsResets(Settings.RESET_MODE_TRUSTED_DEFAULTS);
182 assertEquals(RescueParty.LEVEL_RESET_SETTINGS_TRUSTED_DEFAULTS,
183 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
184
185 notePersistentAppCrash(RescueParty.TRIGGER_COUNT);
186
187 verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
188 assertEquals(RescueParty.LEVEL_FACTORY_RESET,
189 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
190 }
191
192 @Test
193 public void testBootLoopDetectionWithWrongInterval() {
194 noteBoot(RescueParty.TRIGGER_COUNT - 1);
195
196 // last boot is just outside of the boot loop detection window
197 doReturn(CURRENT_NETWORK_TIME_MILLIS + RescueParty.BOOT_TRIGGER_WINDOW_MILLIS + 1).when(
198 () -> RescueParty.getElapsedRealtime());
199 noteBoot(/*numTimes=*/1);
200
201 assertEquals(RescueParty.LEVEL_NONE,
202 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
203 }
204
205 @Test
206 public void testPersistentAppCrashDetectionWithWrongInterval() {
207 notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
208
209 // last persistent app crash is just outside of the boot loop detection window
210 doReturn(CURRENT_NETWORK_TIME_MILLIS
211 + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS + 1)
212 .when(() -> RescueParty.getElapsedRealtime());
213 notePersistentAppCrash(/*numTimes=*/1);
214
215 assertEquals(RescueParty.LEVEL_NONE,
216 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
217 }
218
219 @Test
220 public void testBootLoopDetectionWithProperInterval() {
221 noteBoot(RescueParty.TRIGGER_COUNT - 1);
222
223 // last boot is just inside of the boot loop detection window
224 doReturn(CURRENT_NETWORK_TIME_MILLIS + RescueParty.BOOT_TRIGGER_WINDOW_MILLIS).when(
225 () -> RescueParty.getElapsedRealtime());
226 noteBoot(/*numTimes=*/1);
227
228 verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
229 assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
230 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
231 }
232
233 @Test
234 public void testPersistentAppCrashDetectionWithProperInterval() {
235 notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
236
237 // last persistent app crash is just inside of the boot loop detection window
238 doReturn(CURRENT_NETWORK_TIME_MILLIS
239 + RescueParty.PERSISTENT_APP_CRASH_TRIGGER_WINDOW_MILLIS)
240 .when(() -> RescueParty.getElapsedRealtime());
241 notePersistentAppCrash(/*numTimes=*/1);
242
243 verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
244 assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
245 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
246 }
247
248 @Test
249 public void testBootLoopDetectionWithWrongTriggerCount() {
250 noteBoot(RescueParty.TRIGGER_COUNT - 1);
251 assertEquals(RescueParty.LEVEL_NONE,
252 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
253 }
254
255 @Test
256 public void testPersistentAppCrashDetectionWithWrongTriggerCount() {
257 notePersistentAppCrash(RescueParty.TRIGGER_COUNT - 1);
258 assertEquals(RescueParty.LEVEL_NONE,
259 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
260 }
261
262 @Test
263 public void testIsAttemptingFactoryReset() {
264 noteBoot(RescueParty.TRIGGER_COUNT * 4);
265
266 verify(() -> RecoverySystem.rebootPromptAndWipeUserData(mMockContext, RescueParty.TAG));
267 assertTrue(RescueParty.isAttemptingFactoryReset());
268 }
269
270 @Test
271 public void testOnSettingsProviderPublishedExecutesRescueLevels() {
272 SystemProperties.set(RescueParty.PROP_RESCUE_LEVEL, Integer.toString(1));
273
274 RescueParty.onSettingsProviderPublished(mMockContext);
275
276 verifySettingsResets(Settings.RESET_MODE_UNTRUSTED_DEFAULTS);
277 assertEquals(RescueParty.LEVEL_RESET_SETTINGS_UNTRUSTED_DEFAULTS,
278 SystemProperties.getInt(RescueParty.PROP_RESCUE_LEVEL, RescueParty.LEVEL_NONE));
279 }
280
281 private void verifySettingsResets(int resetMode) {
282 verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null,
283 resetMode,
284 UserHandle.USER_SYSTEM));
285 verify(() -> Settings.Secure.resetToDefaultsAsUser(eq(mMockContentResolver), isNull(),
286 eq(resetMode), anyInt()));
287 }
288
289 private void noteBoot(int numTimes) {
290 for (int i = 0; i < numTimes; i++) {
291 RescueParty.noteBoot(mMockContext);
292 }
293 }
294
295 private void notePersistentAppCrash(int numTimes) {
296 for (int i = 0; i < numTimes; i++) {
297 RescueParty.notePersistentAppCrash(mMockContext, PERSISTENT_APP_UID);
298 }
299 }
300}