1 /*
2  * Copyright (C) 2020 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.power;
18 
19 import static org.junit.Assert.assertEquals;
20 import static org.junit.Assert.assertTrue;
21 import static org.mockito.Mockito.when;
22 
23 import android.app.ActivityManager;
24 import android.app.IActivityManager;
25 import android.os.Process;
26 import android.os.RemoteException;
27 import android.platform.test.annotations.Presubmit;
28 
29 import org.junit.Before;
30 import org.junit.Rule;
31 import org.junit.Test;
32 import org.mockito.Mock;
33 import org.mockito.junit.MockitoJUnit;
34 import org.mockito.junit.MockitoRule;
35 
36 import java.io.File;
37 import java.io.IOException;
38 import java.io.PrintWriter;
39 import java.io.StringWriter;
40 import java.nio.charset.StandardCharsets;
41 import java.nio.file.Files;
42 import java.nio.file.Paths;
43 import java.util.ArrayList;
44 import java.util.Arrays;
45 import java.util.List;
46 import java.util.Locale;
47 import java.util.TimeZone;
48 
49 /**
50  * Run: atest FrameworksServicesTests:ShutdownCheckPointsTest
51  */
52 @Presubmit
53 public class ShutdownCheckPointsTest {
54 
55     @Rule
56     public MockitoRule rule = MockitoJUnit.rule();
57 
58     @Mock
59     private IActivityManager mActivityManager;
60 
61     private TestInjector mTestInjector;
62     private ShutdownCheckPoints mInstance;
63 
64     @Before
setUp()65     public void setUp() {
66         Locale.setDefault(Locale.UK);
67         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
68         mTestInjector = new TestInjector(mActivityManager);
69         mInstance = new ShutdownCheckPoints(mTestInjector);
70     }
71 
72     @Test
testSystemServerEntry()73     public void testSystemServerEntry() {
74         mTestInjector.setCurrentTime(1000);
75         mInstance.recordCheckPointInternal("reason1");
76 
77         assertTrue(dumpToString().startsWith(
78                 "Shutdown request from SYSTEM for reason reason1 "
79                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
80                         + "com.android.server.power.ShutdownCheckPointsTest"
81                         + ".testSystemServerEntry\n at "));
82     }
83 
84     @Test
testSystemServerEntryWithoutReason()85     public void testSystemServerEntryWithoutReason() {
86         mTestInjector.setCurrentTime(1000);
87         mInstance.recordCheckPointInternal(null);
88 
89         assertTrue(dumpToString().startsWith(
90                 "Shutdown request from SYSTEM at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
91     }
92 
93     @Test
testSystemServiceBinderEntry()94     public void testSystemServiceBinderEntry() {
95         mTestInjector.setCurrentTime(1000);
96         mInstance.recordCheckPointInternal(Process.myPid(), "reason1");
97 
98         assertTrue(dumpToString().startsWith(
99                 "Shutdown request from SYSTEM for reason reason1 "
100                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
101                         + "com.android.server.power.ShutdownCheckPointsTest"
102                         + ".testSystemServiceBinderEntry\n at "));
103     }
104 
105     @Test
testCallerProcessBinderEntries()106     public void testCallerProcessBinderEntries() throws RemoteException {
107         List<ActivityManager.RunningAppProcessInfo> runningAppProcessInfos = new ArrayList<>();
108         runningAppProcessInfos.add(
109                 new ActivityManager.RunningAppProcessInfo("process_name", 1, new String[0]));
110         when(mActivityManager.getRunningAppProcesses()).thenReturn(runningAppProcessInfos);
111 
112         mTestInjector.setCurrentTime(1000);
113         // Matching pid in getRunningAppProcesses
114         mInstance.recordCheckPointInternal(1, "reason1");
115         // Missing pid in getRunningAppProcesses
116         mInstance.recordCheckPointInternal(2, "reason2");
117 
118         assertEquals(
119                 "Shutdown request from BINDER for reason reason1 "
120                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
121                         + "com.android.server.power.ShutdownCheckPointsTest"
122                         + ".testCallerProcessBinderEntries\n"
123                         + "From process process_name (pid=1)\n\n"
124                         + "Shutdown request from BINDER for reason reason2 "
125                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
126                         + "com.android.server.power.ShutdownCheckPointsTest"
127                         + ".testCallerProcessBinderEntries\n"
128                         + "From process ? (pid=2)\n\n",
129                 dumpToString());
130     }
131 
132     @Test
testNullCallerProcessBinderEntries()133     public void testNullCallerProcessBinderEntries() throws RemoteException {
134         when(mActivityManager.getRunningAppProcesses()).thenReturn(null);
135 
136         mTestInjector.setCurrentTime(1000);
137         mInstance.recordCheckPointInternal(1, "reason1");
138 
139         assertEquals(
140                 "Shutdown request from BINDER for reason reason1 "
141                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
142                         + "com.android.server.power.ShutdownCheckPointsTest"
143                         + ".testNullCallerProcessBinderEntries\n"
144                         + "From process ? (pid=1)\n\n",
145                 dumpToString());
146     }
147 
148     @Test
testRemoteExceptionOnBinderEntry()149     public void testRemoteExceptionOnBinderEntry() throws RemoteException {
150         when(mActivityManager.getRunningAppProcesses()).thenThrow(new RemoteException("Error"));
151 
152         mTestInjector.setCurrentTime(1000);
153         mInstance.recordCheckPointInternal(1, "reason1");
154 
155         assertEquals(
156                 "Shutdown request from BINDER for reason reason1 "
157                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
158                         + "com.android.server.power.ShutdownCheckPointsTest"
159                         + ".testRemoteExceptionOnBinderEntry\n"
160                         + "From process ? (pid=1)\n\n",
161                 dumpToString());
162     }
163 
164     @Test
testUnknownProcessBinderEntry()165     public void testUnknownProcessBinderEntry() {
166         mTestInjector.setCurrentTime(1000);
167         mInstance.recordCheckPointInternal(1, "reason1");
168 
169         assertEquals(
170                 "Shutdown request from BINDER for reason reason1 "
171                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
172                         + "com.android.server.power.ShutdownCheckPointsTest"
173                         + ".testUnknownProcessBinderEntry\n"
174                         + "From process ? (pid=1)\n\n",
175                 dumpToString());
176     }
177 
178     @Test
testBinderEntryWithoutReason()179     public void testBinderEntryWithoutReason() throws RemoteException {
180         mTestInjector.setCurrentTime(1000);
181         mInstance.recordCheckPointInternal(1, null);
182 
183         assertTrue(dumpToString().startsWith(
184                 "Shutdown request from BINDER at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
185     }
186 
187     @Test
testSystemServiceIntentEntry()188     public void testSystemServiceIntentEntry() {
189         mTestInjector.setCurrentTime(1000);
190         mInstance.recordCheckPointInternal("some.intent", "android", "reason1");
191 
192         assertTrue(dumpToString().startsWith(
193                 "Shutdown request from SYSTEM for reason reason1 "
194                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
195                         + "com.android.server.power.ShutdownCheckPointsTest"
196                         + ".testSystemServiceIntentEntry\n at "));
197     }
198 
199     @Test
testIntentEntry()200     public void testIntentEntry() {
201         mTestInjector.setCurrentTime(1000);
202         mInstance.recordCheckPointInternal("some.intent", "some.app", "reason1");
203 
204         assertEquals(
205                 "Shutdown request from INTENT for reason reason1 "
206                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
207                         + "Intent: some.intent\n"
208                         + "Package: some.app\n\n",
209                 dumpToString());
210     }
211 
212     @Test
testIntentEntryWithoutReason()213     public void testIntentEntryWithoutReason() {
214         mTestInjector.setCurrentTime(1000);
215         mInstance.recordCheckPointInternal("some.intent", "some.app", null);
216 
217         assertTrue(dumpToString().startsWith(
218                 "Shutdown request from INTENT at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"));
219     }
220 
221     @Test
testMultipleEntries()222     public void testMultipleEntries() {
223         mTestInjector.setCurrentTime(1000);
224         mInstance.recordCheckPointInternal(1, "reason1");
225         mTestInjector.setCurrentTime(2000);
226         mInstance.recordCheckPointInternal(2, "reason2");
227         mTestInjector.setCurrentTime(3000);
228         mInstance.recordCheckPointInternal("intent", "app", "reason3");
229 
230         assertEquals(
231                 "Shutdown request from BINDER for reason reason1 "
232                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
233                         + "com.android.server.power.ShutdownCheckPointsTest.testMultipleEntries\n"
234                         + "From process ? (pid=1)\n\n"
235                         + "Shutdown request from BINDER for reason reason2 "
236                         + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
237                         + "com.android.server.power.ShutdownCheckPointsTest.testMultipleEntries\n"
238                         + "From process ? (pid=2)\n\n"
239                         + "Shutdown request from INTENT for reason reason3 "
240                         + "at 1970-01-01 00:00:03.000 UTC (epoch=3000)\n"
241                         + "Intent: intent\n"
242                         + "Package: app\n\n",
243                 dumpToString());
244     }
245 
246     @Test
testTooManyEntriesDropsOlderOnes()247     public void testTooManyEntriesDropsOlderOnes() {
248         mTestInjector.setCheckPointsLimit(2);
249         ShutdownCheckPoints limitedInstance = new ShutdownCheckPoints(mTestInjector);
250 
251         mTestInjector.setCurrentTime(1000);
252         limitedInstance.recordCheckPointInternal("intent.1", "app.1", "reason1");
253         mTestInjector.setCurrentTime(2000);
254         limitedInstance.recordCheckPointInternal("intent.2", "app.2", "reason2");
255         mTestInjector.setCurrentTime(3000);
256         limitedInstance.recordCheckPointInternal("intent.3", "app.3", "reason3");
257 
258         // Drops first intent.
259         assertEquals(
260                 "Shutdown request from INTENT for reason reason2 "
261                         + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
262                         + "Intent: intent.2\n"
263                         + "Package: app.2\n\n"
264                         + "Shutdown request from INTENT for reason reason3 "
265                         + "at 1970-01-01 00:00:03.000 UTC (epoch=3000)\n"
266                         + "Intent: intent.3\n"
267                         + "Package: app.3\n\n",
268                 dumpToString(limitedInstance));
269     }
270 
271     @Test
testDumpToFile()272     public void testDumpToFile() throws Exception {
273         File tempDir = createTempDir();
274         File baseFile = new File(tempDir, "checkpoints");
275 
276         mTestInjector.setCurrentTime(1000);
277         mInstance.recordCheckPointInternal("first.intent", "first.app", "reason1");
278         dumpToFile(baseFile);
279 
280         mTestInjector.setCurrentTime(2000);
281         mInstance.recordCheckPointInternal("second.intent", "second.app", "reason2");
282         dumpToFile(baseFile);
283 
284         File[] dumpFiles = tempDir.listFiles();
285         Arrays.sort(dumpFiles);
286 
287         assertEquals(2, dumpFiles.length);
288         assertEquals(
289                 "Shutdown request from INTENT for reason reason1 "
290                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
291                         + "Intent: first.intent\n"
292                         + "Package: first.app\n\n",
293                 readFileAsString(dumpFiles[0].getAbsolutePath()));
294         assertEquals(
295                 "Shutdown request from INTENT for reason reason1 "
296                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
297                         + "Intent: first.intent\n"
298                         + "Package: first.app\n\n"
299                         + "Shutdown request from INTENT for reason reason2 "
300                         + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
301                         + "Intent: second.intent\n"
302                         + "Package: second.app\n\n",
303                 readFileAsString(dumpFiles[1].getAbsolutePath()));
304     }
305 
306     @Test
testTooManyFilesDropsOlderOnes()307     public void testTooManyFilesDropsOlderOnes() throws Exception {
308         mTestInjector.setDumpFilesLimit(1);
309         ShutdownCheckPoints instance = new ShutdownCheckPoints(mTestInjector);
310         File tempDir = createTempDir();
311         File baseFile = new File(tempDir, "checkpoints");
312 
313         mTestInjector.setCurrentTime(1000);
314         instance.recordCheckPointInternal("first.intent", "first.app", "reason1");
315         dumpToFile(instance, baseFile);
316 
317         mTestInjector.setCurrentTime(2000);
318         instance.recordCheckPointInternal("second.intent", "second.app", "reason2");
319         dumpToFile(instance, baseFile);
320 
321         File[] dumpFiles = tempDir.listFiles();
322         assertEquals(1, dumpFiles.length);
323         assertEquals(
324                 "Shutdown request from INTENT for reason reason1 "
325                         + "at 1970-01-01 00:00:01.000 UTC (epoch=1000)\n"
326                         + "Intent: first.intent\n"
327                         + "Package: first.app\n\n"
328                         + "Shutdown request from INTENT for reason reason2 "
329                         + "at 1970-01-01 00:00:02.000 UTC (epoch=2000)\n"
330                         + "Intent: second.intent\n"
331                         + "Package: second.app\n\n",
332                 readFileAsString(dumpFiles[0].getAbsolutePath()));
333     }
334 
dumpToString()335     private String dumpToString() {
336         return dumpToString(mInstance);
337     }
338 
dumpToString(ShutdownCheckPoints instance)339     private String dumpToString(ShutdownCheckPoints instance) {
340         StringWriter sw = new StringWriter();
341         PrintWriter pw = new PrintWriter(sw);
342         instance.dumpInternal(pw);
343         return sw.toString();
344     }
345 
dumpToFile(File baseFile)346     private void dumpToFile(File baseFile) throws InterruptedException {
347         dumpToFile(mInstance, baseFile);
348     }
349 
dumpToFile(ShutdownCheckPoints instance, File baseFile)350     private void dumpToFile(ShutdownCheckPoints instance, File baseFile)
351             throws InterruptedException {
352         Thread dumpThread = instance.newDumpThreadInternal(baseFile);
353         dumpThread.start();
354         dumpThread.join();
355     }
356 
readFileAsString(String absolutePath)357     private String readFileAsString(String absolutePath) throws IOException {
358         return new String(Files.readAllBytes(Paths.get(absolutePath)), StandardCharsets.UTF_8);
359     }
360 
createTempDir()361     private File createTempDir() throws IOException {
362         File tempDir = File.createTempFile("checkpoints", "out");
363         tempDir.delete();
364         tempDir.mkdir();
365         return tempDir;
366     }
367 
368     /** Fake system dependencies for testing. */
369     private static final class TestInjector implements ShutdownCheckPoints.Injector {
370         private long mNow;
371         private int mCheckPointsLimit;
372         private int mDumpFilesLimit;
373         private IActivityManager mActivityManager;
374 
TestInjector(IActivityManager activityManager)375         TestInjector(IActivityManager activityManager) {
376             mNow = 0;
377             mCheckPointsLimit = 100;
378             mDumpFilesLimit = 2;
379             mActivityManager = activityManager;
380         }
381 
382         @Override
currentTimeMillis()383         public long currentTimeMillis() {
384             return mNow;
385         }
386 
387         @Override
maxCheckPoints()388         public int maxCheckPoints() {
389             return mCheckPointsLimit;
390         }
391 
392         @Override
maxDumpFiles()393         public int maxDumpFiles() {
394             return mDumpFilesLimit;
395         }
396 
397         @Override
activityManager()398         public IActivityManager activityManager() {
399             return mActivityManager;
400         }
401 
setCurrentTime(long time)402         void setCurrentTime(long time) {
403             mNow = time;
404         }
405 
setCheckPointsLimit(int limit)406         void setCheckPointsLimit(int limit) {
407             mCheckPointsLimit = limit;
408         }
409 
setDumpFilesLimit(int dumpFilesLimit)410         void setDumpFilesLimit(int dumpFilesLimit) {
411             mDumpFilesLimit = dumpFilesLimit;
412         }
413     }
414 }
415