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.am;
18 
19 import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
20 import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP;
21 import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY;
22 import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY;
23 import static android.app.ActivityManager.PROCESS_STATE_RECEIVER;
24 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED;
25 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
26 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE;
27 import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE;
28 
29 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
30 
31 import static com.android.server.am.ActivityManagerService.Injector;
32 
33 import static org.junit.Assert.assertEquals;
34 import static org.junit.Assert.assertNotNull;
35 import static org.junit.Assert.assertNull;
36 import static org.junit.Assert.assertTrue;
37 import static org.mockito.Matchers.anyBoolean;
38 import static org.mockito.Matchers.anyInt;
39 import static org.mockito.Matchers.anyLong;
40 import static org.mockito.Mockito.doNothing;
41 import static org.mockito.Mockito.doReturn;
42 import static org.mockito.Mockito.spy;
43 
44 import android.annotation.CurrentTimeMillisLong;
45 import android.app.ApplicationExitInfo;
46 import android.content.ComponentName;
47 import android.content.Context;
48 import android.content.pm.ApplicationInfo;
49 import android.content.pm.PackageManagerInternal;
50 import android.os.Debug;
51 import android.os.FileUtils;
52 import android.os.Handler;
53 import android.os.HandlerThread;
54 import android.os.Process;
55 import android.os.UserHandle;
56 import android.platform.test.annotations.Presubmit;
57 import android.system.OsConstants;
58 import android.text.TextUtils;
59 import android.util.Pair;
60 
61 import com.android.internal.util.ArrayUtils;
62 import com.android.server.LocalServices;
63 import com.android.server.ServiceThread;
64 import com.android.server.appop.AppOpsService;
65 import com.android.server.wm.ActivityTaskManagerService;
66 
67 import org.junit.After;
68 import org.junit.Before;
69 import org.junit.BeforeClass;
70 import org.junit.Rule;
71 import org.junit.Test;
72 import org.junit.rules.TestRule;
73 import org.junit.runner.Description;
74 import org.junit.runners.model.Statement;
75 import org.mockito.Mock;
76 import org.mockito.MockitoAnnotations;
77 
78 import java.io.BufferedInputStream;
79 import java.io.BufferedOutputStream;
80 import java.io.File;
81 import java.io.FileInputStream;
82 import java.io.FileOutputStream;
83 import java.io.IOException;
84 import java.lang.reflect.Field;
85 import java.lang.reflect.Modifier;
86 import java.util.ArrayList;
87 import java.util.Arrays;
88 import java.util.HashMap;
89 import java.util.List;
90 import java.util.Random;
91 import java.util.function.Function;
92 import java.util.zip.GZIPInputStream;
93 
94 /**
95  * Test class for {@link android.app.ApplicationExitInfo}.
96  *
97  * Build/Install/Run:
98  *  atest ApplicationExitInfoTest
99  */
100 @Presubmit
101 public class ApplicationExitInfoTest {
102     private static final String TAG = ApplicationExitInfoTest.class.getSimpleName();
103 
104     @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
105     @Mock private AppOpsService mAppOpsService;
106     @Mock private PackageManagerInternal mPackageManagerInt;
107 
108     private Context mContext = getInstrumentation().getTargetContext();
109     private TestInjector mInjector;
110     private ActivityManagerService mAms;
111     private ProcessList mProcessList;
112     private AppExitInfoTracker mAppExitInfoTracker;
113     private Handler mHandler;
114     private HandlerThread mHandlerThread;
115 
116     @BeforeClass
setUpOnce()117     public static void setUpOnce() {
118         System.setProperty("dexmaker.share_classloader", "true");
119     }
120 
121     @Before
setUp()122     public void setUp() {
123         MockitoAnnotations.initMocks(this);
124 
125         mHandlerThread = new HandlerThread(TAG);
126         mHandlerThread.start();
127         mHandler = new Handler(mHandlerThread.getLooper());
128         mProcessList = spy(new ProcessList());
129         ProcessList.sKillHandler = null;
130         mAppExitInfoTracker = spy(new AppExitInfoTracker());
131         setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mIsolatedUidRecords",
132                 spy(mAppExitInfoTracker.new IsolatedUidRecords()));
133         setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppExitInfoSourceZygote",
134                 spy(mAppExitInfoTracker.new AppExitInfoExternalSource("zygote", null)));
135         setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppExitInfoSourceLmkd",
136                 spy(mAppExitInfoTracker.new AppExitInfoExternalSource("lmkd",
137                 ApplicationExitInfo.REASON_LOW_MEMORY)));
138         setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppTraceRetriever",
139                 spy(mAppExitInfoTracker.new AppTraceRetriever()));
140         setFieldValue(ProcessList.class, mProcessList, "mAppExitInfoTracker", mAppExitInfoTracker);
141         mInjector = new TestInjector(mContext);
142         mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
143         mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
144         mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
145         mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
146         mAms.mPackageManagerInt = mPackageManagerInt;
147         doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
148         // Remove stale instance of PackageManagerInternal if there is any
149         LocalServices.removeServiceForTest(PackageManagerInternal.class);
150         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
151     }
152 
153     @After
tearDown()154     public void tearDown() {
155         LocalServices.removeServiceForTest(PackageManagerInternal.class);
156         mHandlerThread.quit();
157         ProcessList.sKillHandler = null;
158     }
159 
setFieldValue(Class clazz, Object obj, String fieldName, T val)160     private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
161         try {
162             Field field = clazz.getDeclaredField(fieldName);
163             field.setAccessible(true);
164             Field mfield = Field.class.getDeclaredField("accessFlags");
165             mfield.setAccessible(true);
166             mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
167             field.set(obj, val);
168         } catch (NoSuchFieldException | IllegalAccessException e) {
169         }
170     }
171 
updateExitInfo(ProcessRecord app, @CurrentTimeMillisLong long timestamp)172     private void updateExitInfo(ProcessRecord app, @CurrentTimeMillisLong long timestamp) {
173         ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecord(app, timestamp);
174         mAppExitInfoTracker.handleNoteProcessDiedLocked(raw);
175         mAppExitInfoTracker.recycleRawRecord(raw);
176     }
177 
noteAppKill(ProcessRecord app, int reason, int subReason, String msg, @CurrentTimeMillisLong long timestamp)178     private void noteAppKill(ProcessRecord app, int reason, int subReason, String msg,
179             @CurrentTimeMillisLong long timestamp) {
180         ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecord(app, timestamp);
181         raw.setReason(reason);
182         raw.setSubReason(subReason);
183         raw.setDescription(msg);
184         mAppExitInfoTracker.handleNoteAppKillLocked(raw);
185         mAppExitInfoTracker.recycleRawRecord(raw);
186     }
187 
188     @Test
testApplicationExitInfo()189     public void testApplicationExitInfo() throws Exception {
190         mAppExitInfoTracker.clearProcessExitInfo(true);
191         mAppExitInfoTracker.mAppExitInfoLoaded.set(true);
192         mAppExitInfoTracker.mProcExitStoreDir = new File(mContext.getFilesDir(),
193                 AppExitInfoTracker.APP_EXIT_STORE_DIR);
194         assertTrue(FileUtils.createDir(mAppExitInfoTracker.mProcExitStoreDir));
195         mAppExitInfoTracker.mProcExitInfoFile = new File(mAppExitInfoTracker.mProcExitStoreDir,
196                 AppExitInfoTracker.APP_EXIT_INFO_FILE);
197 
198         // Test application calls System.exit()
199         doNothing().when(mAppExitInfoTracker).schedulePersistProcessExitInfo(anyBoolean());
200         doReturn(true).when(mAppExitInfoTracker).isFresh(anyLong());
201 
202         final int app1Uid = 10123;
203         final int app1Pid1 = 12345;
204         final int app1Pid2 = 12346;
205         final int app1sPid1 = 13456;
206         final int app1DefiningUid = 23456;
207         final int app1ConnectiongGroup = 10;
208         final int app1UidUser2 = 1010123;
209         final int app1PidUser2 = 12347;
210         final long app1Pss1 = 34567;
211         final long app1Rss1 = 45678;
212         final long app1Pss2 = 34568;
213         final long app1Rss2 = 45679;
214         final long app1Pss3 = 34569;
215         final long app1Rss3 = 45680;
216         final long app1sPss1 = 56789;
217         final long app1sRss1 = 67890;
218         final String app1ProcessName = "com.android.test.stub1:process";
219         final String app1PackageName = "com.android.test.stub1";
220         final String app1sProcessName = "com.android.test.stub_shared:process";
221         final String app1sPackageName = "com.android.test.stub_shared";
222         final byte[] app1Cookie1 = {(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
223                 (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08};
224         final byte[] app1Cookie2 = {(byte) 0x08, (byte) 0x07, (byte) 0x06, (byte) 0x05,
225                 (byte) 0x04, (byte) 0x03, (byte) 0x02, (byte) 0x01};
226 
227         final long now1 = 1;
228         ProcessRecord app = makeProcessRecord(
229                 app1Pid1,                    // pid
230                 app1Uid,                     // uid
231                 app1Uid,                     // packageUid
232                 null,                        // definingUid
233                 0,                           // connectionGroup
234                 PROCESS_STATE_LAST_ACTIVITY, // procstate
235                 app1Pss1,                    // pss
236                 app1Rss1,                    // rss
237                 app1ProcessName,             // processName
238                 app1PackageName);            // packageName
239 
240         // Case 1: basic System.exit() test
241         int exitCode = 5;
242         mAppExitInfoTracker.setProcessStateSummary(app1Uid, app1Pid1, app1Cookie1);
243         assertTrue(ArrayUtils.equals(mAppExitInfoTracker.getProcessStateSummary(app1Uid,
244                 app1Pid1), app1Cookie1, app1Cookie1.length));
245         doReturn(new Pair<Long, Object>(now1, Integer.valueOf(makeExitStatus(exitCode))))
246                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
247                 .remove(anyInt(), anyInt());
248         doReturn(null)
249                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
250                 .remove(anyInt(), anyInt());
251         updateExitInfo(app, now1);
252 
253         ArrayList<ApplicationExitInfo> list = new ArrayList<ApplicationExitInfo>();
254         mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
255         assertEquals(1, list.size());
256 
257         ApplicationExitInfo info = list.get(0);
258 
259         verifyApplicationExitInfo(
260                 info,                                 // info
261                 now1,                                 // timestamp
262                 app1Pid1,                             // pid
263                 app1Uid,                              // uid
264                 app1Uid,                              // packageUid
265                 null,                                 // definingUid
266                 app1ProcessName,                      // processName
267                 0,                                    // connectionGroup
268                 ApplicationExitInfo.REASON_EXIT_SELF, // reason
269                 null,                                 // subReason
270                 exitCode,                             // status
271                 app1Pss1,                             // pss
272                 app1Rss1,                             // rss
273                 IMPORTANCE_CACHED,                    // importance
274                 null);                                // description
275 
276         assertTrue(ArrayUtils.equals(info.getProcessStateSummary(), app1Cookie1,
277                 app1Cookie1.length));
278         assertEquals(info.getTraceInputStream(), null);
279 
280         // Now create a process record from a different package but shared UID.
281         sleep(1);
282         final long now1s = System.currentTimeMillis();
283         app = makeProcessRecord(
284                 app1sPid1,                   // pid
285                 app1Uid,                     // uid
286                 app1Uid,                     // packageUid
287                 null,                        // definingUid
288                 0,                           // connectionGroup
289                 PROCESS_STATE_BOUND_TOP,     // procstate
290                 app1sPss1,                   // pss
291                 app1sRss1,                   // rss
292                 app1sProcessName,            // processName
293                 app1sPackageName);           // packageName
294         doReturn(new Pair<Long, Object>(now1s, Integer.valueOf(0)))
295                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
296                 .remove(anyInt(), anyInt());
297         doReturn(null)
298                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
299                 .remove(anyInt(), anyInt());
300         noteAppKill(app, ApplicationExitInfo.REASON_USER_REQUESTED,
301                 ApplicationExitInfo.SUBREASON_UNKNOWN, null, now1s);
302 
303         // Case 2: create another app1 process record with a different pid
304         sleep(1);
305         final long now2 = 2;
306         app = makeProcessRecord(
307                 app1Pid2,               // pid
308                 app1Uid,                // uid
309                 app1Uid,                // packageUid
310                 app1DefiningUid,        // definingUid
311                 app1ConnectiongGroup,   // connectionGroup
312                 PROCESS_STATE_RECEIVER, // procstate
313                 app1Pss2,               // pss
314                 app1Rss2,               // rss
315                 app1ProcessName,        // processName
316                 app1PackageName);       // packageName
317         exitCode = 6;
318 
319         mAppExitInfoTracker.setProcessStateSummary(app1Uid, app1Pid2, app1Cookie1);
320         // Override with a different cookie
321         mAppExitInfoTracker.setProcessStateSummary(app1Uid, app1Pid2, app1Cookie2);
322         assertTrue(ArrayUtils.equals(mAppExitInfoTracker.getProcessStateSummary(app1Uid,
323                 app1Pid2), app1Cookie2, app1Cookie2.length));
324         doReturn(new Pair<Long, Object>(now2, Integer.valueOf(makeExitStatus(exitCode))))
325                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
326                 .remove(anyInt(), anyInt());
327         updateExitInfo(app, now2);
328         list.clear();
329 
330         // Get all the records for app1Uid
331         mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
332         assertEquals(3, list.size());
333 
334         info = list.get(1);
335 
336         verifyApplicationExitInfo(
337                 info,                                 // info
338                 now2,                                 // timestamp
339                 app1Pid2,                             // pid
340                 app1Uid,                              // uid
341                 app1Uid,                              // packageUid
342                 app1DefiningUid,                      // definingUid
343                 app1ProcessName,                      // processName
344                 app1ConnectiongGroup,                 // connectionGroup
345                 ApplicationExitInfo.REASON_EXIT_SELF, // reason
346                 null,                                 // subReason
347                 exitCode,                             // status
348                 app1Pss2,                             // pss
349                 app1Rss2,                             // rss
350                 IMPORTANCE_SERVICE,                   // importance
351                 null);                                // description
352 
353         assertTrue(ArrayUtils.equals(info.getProcessStateSummary(), app1Cookie2,
354                 app1Cookie2.length));
355 
356         info = list.get(0);
357         verifyApplicationExitInfo(
358                 info,                                      // info
359                 now1s,                                     // timestamp
360                 app1sPid1,                                 // pid
361                 app1Uid,                                   // uid
362                 app1Uid,                                   // packageUid
363                 null,                                      // definingUid
364                 app1sProcessName,                          // processName
365                 0,                                         // connectionGroup
366                 ApplicationExitInfo.REASON_USER_REQUESTED, // reason
367                 null,                                      // subReason
368                 null,                                      // status
369                 app1sPss1,                                 // pss
370                 app1sRss1,                                 // rss
371                 IMPORTANCE_FOREGROUND,                     // importance
372                 null);                                     // description
373 
374         info = list.get(2);
375         assertTrue(ArrayUtils.equals(info.getProcessStateSummary(), app1Cookie1,
376                 app1Cookie1.length));
377 
378         // Case 3: Create an instance of app1 with different user, and died because of SIGKILL
379         sleep(1);
380         final long now3 = System.currentTimeMillis();
381         int sigNum = OsConstants.SIGKILL;
382         app = makeProcessRecord(
383                 app1PidUser2,                           // pid
384                 app1UidUser2,                           // uid
385                 app1UidUser2,                           // packageUid
386                 null,                                   // definingUid
387                 0,                                      // connectionGroup
388                 PROCESS_STATE_BOUND_FOREGROUND_SERVICE, // procstate
389                 app1Pss3,                               // pss
390                 app1Rss3,                               // rss
391                 app1ProcessName,                        // processName
392                 app1PackageName);                       // packageName
393         doReturn(new Pair<Long, Object>(now3, Integer.valueOf(makeSignalStatus(sigNum))))
394                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
395                 .remove(anyInt(), anyInt());
396         updateExitInfo(app, now3);
397         list.clear();
398         mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
399 
400         assertEquals(1, list.size());
401 
402         info = list.get(0);
403 
404         verifyApplicationExitInfo(
405                 info,                                // info
406                 now3,                                // timestamp
407                 app1PidUser2,                        // pid
408                 app1UidUser2,                        // uid
409                 app1UidUser2,                        // packageUid
410                 null,                                // definingUid
411                 app1ProcessName,                     // processName
412                 0,                                   // connectionGroup
413                 ApplicationExitInfo.REASON_SIGNALED, // reason
414                 null,                                 // subReason
415                 sigNum,                              // status
416                 app1Pss3,                            // pss
417                 app1Rss3,                            // rss
418                 IMPORTANCE_FOREGROUND_SERVICE,       // importance
419                 null);                               // description
420 
421         // try go get all from app1UidUser2
422         list.clear();
423         mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, 0, 0, list);
424         assertEquals(1, list.size());
425 
426         info = list.get(0);
427 
428         verifyApplicationExitInfo(
429                 info,                                // info
430                 now3,                                // timestamp
431                 app1PidUser2,                        // pid
432                 app1UidUser2,                        // uid
433                 app1UidUser2,                        // packageUid
434                 null,                                // definingUid
435                 app1ProcessName,                     // processName
436                 0,                                   // connectionGroup
437                 ApplicationExitInfo.REASON_SIGNALED, // reason
438                 null,                                // subReason
439                 sigNum,                              // status
440                 app1Pss3,                            // pss
441                 app1Rss3,                            // rss
442                 IMPORTANCE_FOREGROUND_SERVICE,       // importance
443                 null);                               // description
444 
445         /*
446          * Case 4: Create a process from another package with kill from lmkd
447          * We expect LMKD's reported RSS to be the process' last seen RSS.
448          */
449         final int app2UidUser2 = 1010234;
450         final int app2PidUser2 = 12348;
451         final long app2Pss1 = 54321;
452         final long app2Rss1 = 65432;
453         final long lmkd_reported_rss = 43215;
454         final String app2ProcessName = "com.android.test.stub2:process";
455         final String app2PackageName = "com.android.test.stub2";
456 
457         sleep(1);
458         final long now4 = System.currentTimeMillis();
459         doReturn(null)
460                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
461                 .remove(anyInt(), anyInt());
462         doReturn(new Pair<Long, Object>(now4, Long.valueOf(lmkd_reported_rss)))
463                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
464                 .remove(anyInt(), anyInt());
465 
466         app = makeProcessRecord(
467                 app2PidUser2,                // pid
468                 app2UidUser2,                // uid
469                 app2UidUser2,                // packageUid
470                 null,                        // definingUid
471                 0,                           // connectionGroup
472                 PROCESS_STATE_CACHED_EMPTY,  // procstate
473                 app2Pss1,                    // pss
474                 app2Rss1,                    // rss
475                 app2ProcessName,             // processName
476                 app2PackageName);            // packageName
477         updateExitInfo(app, now4);
478         list.clear();
479         mAppExitInfoTracker.getExitInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
480         assertEquals(1, list.size());
481 
482         info = list.get(0);
483 
484         verifyApplicationExitInfo(
485                 info,                                     // info
486                 now4,                                     // timestamp
487                 app2PidUser2,                             // pid
488                 app2UidUser2,                             // uid
489                 app2UidUser2,                             // packageUid
490                 null,                                     // definingUid
491                 app2ProcessName,                          // processName
492                 0,                                        // connectionGroup
493                 ApplicationExitInfo.REASON_LOW_MEMORY,    // reason
494                 null,                                     // subReason
495                 0,                                        // status
496                 app2Pss1,                                 // pss
497                 lmkd_reported_rss,                        // rss
498                 IMPORTANCE_CACHED,                        // importance
499                 null);                                    // description
500 
501         // Verify to get all from User2 regarding app2
502         list.clear();
503         mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, 0, list);
504         assertEquals(1, list.size());
505 
506         info = list.get(0);
507 
508         // Verify the AppExitInfo has the LMKD reported RSS
509         assertEquals(lmkd_reported_rss, info.getRss());
510 
511         // Case 5: App native crash
512         final int app3UidUser2 = 1010345;
513         final int app3PidUser2 = 12349;
514         final int app3ConnectiongGroup = 4;
515         final long app3Pss1 = 54320;
516         final long app3Rss1 = 65430;
517         final String app3ProcessName = "com.android.test.stub3:process";
518         final String app3PackageName = "com.android.test.stub3";
519         final String app3Description = "native crash";
520 
521         sleep(1);
522         final long now5 = System.currentTimeMillis();
523         sigNum = OsConstants.SIGABRT;
524         doReturn(new Pair<Long, Object>(now5, Integer.valueOf(makeSignalStatus(sigNum))))
525                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
526                 .remove(anyInt(), anyInt());
527         doReturn(null)
528                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
529                 .remove(anyInt(), anyInt());
530         app = makeProcessRecord(
531                 app3PidUser2,            // pid
532                 app3UidUser2,            // uid
533                 app3UidUser2,            // packageUid
534                 null,                    // definingUid
535                 app3ConnectiongGroup,    // connectionGroup
536                 PROCESS_STATE_BOUND_TOP, // procstate
537                 app3Pss1,                // pss
538                 app3Rss1,                // rss
539                 app3ProcessName,         // processName
540                 app3PackageName);        // packageName
541         noteAppKill(app, ApplicationExitInfo.REASON_CRASH_NATIVE,
542                 ApplicationExitInfo.SUBREASON_UNKNOWN, app3Description, now5);
543 
544         updateExitInfo(app, now5);
545         list.clear();
546         mAppExitInfoTracker.getExitInfo(app3PackageName, app3UidUser2, app3PidUser2, 0, list);
547         assertEquals(1, list.size());
548 
549         info = list.get(0);
550 
551         verifyApplicationExitInfo(
552                 info,                                            // info
553                 now5,                                            // timestamp
554                 app3PidUser2,                                    // pid
555                 app3UidUser2,                                    // uid
556                 app3UidUser2,                                    // packageUid
557                 null,                                            // definingUid
558                 app3ProcessName,                                 // processName
559                 app3ConnectiongGroup,                            // connectionGroup
560                 ApplicationExitInfo.REASON_CRASH_NATIVE,         // reason
561                 null,                                            // subReason
562                 sigNum,                                          // status
563                 app3Pss1,                                        // pss
564                 app3Rss1,                                        // rss
565                 IMPORTANCE_FOREGROUND,                           // importance
566                 app3Description);                                // description
567 
568         // Verify the most recent kills, sorted by timestamp
569         int maxNum = 3;
570         list.clear();
571         mAppExitInfoTracker.getExitInfo(null, app3UidUser2, 0, maxNum, list);
572         assertEquals(1, list.size());
573 
574         info = list.get(0);
575 
576         verifyApplicationExitInfo(
577                 info,                                            // info
578                 now5,                                            // timestamp
579                 app3PidUser2,                                    // pid
580                 app3UidUser2,                                    // uid
581                 app3UidUser2,                                    // packageUid
582                 null,                                            // definingUid
583                 app3ProcessName,                                 // processName
584                 app3ConnectiongGroup,                            // connectionGroup
585                 ApplicationExitInfo.REASON_CRASH_NATIVE,         // reason
586                 null,                                            // subReason
587                 sigNum,                                          // status
588                 app3Pss1,                                        // pss
589                 app3Rss1,                                        // rss
590                 IMPORTANCE_FOREGROUND,                           // importance
591                 app3Description);                                // description
592 
593         list.clear();
594         mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, maxNum, list);
595         assertEquals(1, list.size());
596         info = list.get(0);
597 
598         verifyApplicationExitInfo(
599                 info,                                     // info
600                 now4,                                     // timestamp
601                 app2PidUser2,                             // pid
602                 app2UidUser2,                             // uid
603                 app2UidUser2,                             // packageUid
604                 null,                                     // definingUid
605                 app2ProcessName,                          // processName
606                 0,                                        // connectionGroup
607                 ApplicationExitInfo.REASON_LOW_MEMORY,    // reason
608                 null,                                     // subReason
609                 0,                                        // status
610                 app2Pss1,                                 // pss
611                 lmkd_reported_rss,                        // rss
612                 IMPORTANCE_CACHED,                        // importance
613                 null);                                    // description
614 
615         list.clear();
616         mAppExitInfoTracker.getExitInfo(null, app1UidUser2, 0, maxNum, list);
617         assertEquals(1, list.size());
618         info = list.get(0);
619 
620         sigNum = OsConstants.SIGKILL;
621         verifyApplicationExitInfo(
622                 info,                                // info
623                 now3,                                // timestamp
624                 app1PidUser2,                        // pid
625                 app1UidUser2,                        // uid
626                 app1UidUser2,                        // packageUid
627                 null,                                // definingUid
628                 app1ProcessName,                     // processName
629                 0,                                   // connectionGroup
630                 ApplicationExitInfo.REASON_SIGNALED, // reason
631                 null,                                // subReason
632                 sigNum,                              // status
633                 app1Pss3,                            // pss
634                 app1Rss3,                            // rss
635                 IMPORTANCE_FOREGROUND_SERVICE,       // importance
636                 null);                               // description
637 
638         // Case 6: App Java crash
639         final int app3Uid = 10345;
640         final int app3IsolatedUid = 99001; // it's an isolated process
641         final int app3Pid = 12350;
642         final long app3Pss2 = 23232;
643         final long app3Rss2 = 32323;
644         final String app3Description2 = "force close";
645 
646         sleep(1);
647         final long now6 = System.currentTimeMillis();
648         doReturn(null)
649                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
650                 .remove(anyInt(), anyInt());
651         doReturn(null)
652                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
653                 .remove(anyInt(), anyInt());
654         app = makeProcessRecord(
655                 app3Pid,                     // pid
656                 app3IsolatedUid,             // uid
657                 app3Uid,                     // packageUid
658                 null,                        // definingUid
659                 0,                           // connectionGroup
660                 PROCESS_STATE_CACHED_EMPTY,  // procstate
661                 app3Pss2,                    // pss
662                 app3Rss2,                    // rss
663                 app3ProcessName,             // processName
664                 app3PackageName);            // packageName
665         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app3IsolatedUid, app3Uid);
666         noteAppKill(app, ApplicationExitInfo.REASON_CRASH,
667                 ApplicationExitInfo.SUBREASON_UNKNOWN, app3Description2, now6);
668 
669         assertEquals(app3Uid, mAppExitInfoTracker.mIsolatedUidRecords
670                 .getUidByIsolatedUid(app3IsolatedUid).longValue());
671         updateExitInfo(app, now6);
672         assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(app3IsolatedUid));
673 
674         list.clear();
675         mAppExitInfoTracker.getExitInfo(app3PackageName, app3Uid, 0, 1, list);
676         assertEquals(1, list.size());
677 
678         info = list.get(0);
679 
680         verifyApplicationExitInfo(
681                 info,                                     // info
682                 now6,                                     // timestamp
683                 app3Pid,                                  // pid
684                 app3IsolatedUid,                          // uid
685                 app3Uid,                                  // packageUid
686                 null,                                     // definingUid
687                 app3ProcessName,                          // processName
688                 0,                                        // connectionGroup
689                 ApplicationExitInfo.REASON_CRASH,         // reason
690                 null,                                     // subReason
691                 0,                                        // status
692                 app3Pss2,                                 // pss
693                 app3Rss2,                                 // rss
694                 IMPORTANCE_CACHED,                        // importance
695                 app3Description2);                        // description
696 
697         // Case 7: App1 is "uninstalled" from User2
698         mAppExitInfoTracker.onPackageRemoved(app1PackageName, app1UidUser2, false);
699         list.clear();
700         mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, 0, 0, list);
701         assertEquals(0, list.size());
702 
703         list.clear();
704         mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, 0, 0, list);
705         assertEquals(2, list.size());
706 
707         info = list.get(0);
708 
709         verifyApplicationExitInfo(
710                 info,                                 // info
711                 now2,                                 // timestamp
712                 app1Pid2,                             // pid
713                 app1Uid,                              // uid
714                 app1Uid,                              // packageUid
715                 app1DefiningUid,                      // definingUid
716                 app1ProcessName,                      // processName
717                 app1ConnectiongGroup,                 // connectionGroup
718                 ApplicationExitInfo.REASON_EXIT_SELF, // reason
719                 null,                                 // subReason
720                 exitCode,                             // status
721                 app1Pss2,                             // pss
722                 app1Rss2,                             // rss
723                 IMPORTANCE_SERVICE,                   // importance
724                 null);                                // description
725 
726         // Case 8: App1 gets "remove task"
727         sleep(1);
728         final int app1IsolatedUidUser2 = 1099002; // isolated uid
729         final long app1Pss4 = 34343;
730         final long app1Rss4 = 43434;
731         final long now8 = System.currentTimeMillis();
732         sigNum = OsConstants.SIGKILL;
733         doReturn(new Pair<Long, Object>(now8, makeSignalStatus(sigNum)))
734                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
735                 .remove(anyInt(), anyInt());
736         doReturn(null)
737                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
738                 .remove(anyInt(), anyInt());
739         app = makeProcessRecord(
740                 app1PidUser2,                 // pid
741                 app1IsolatedUidUser2,         // uid
742                 app1UidUser2,                 // packageUid
743                 null,                         // definingUid
744                 0,                            // connectionGroup
745                 PROCESS_STATE_CACHED_EMPTY,   // procstate
746                 app1Pss4,                     // pss
747                 app1Rss4,                     // rss
748                 app1ProcessName,              // processName
749                 app1PackageName);             // packageName
750 
751         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUidUser2, app1UidUser2);
752         noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
753                 ApplicationExitInfo.SUBREASON_REMOVE_TASK, null, now8);
754 
755         updateExitInfo(app, now8);
756         list.clear();
757         mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1PidUser2, 1, list);
758         assertEquals(1, list.size());
759 
760         info = list.get(0);
761 
762         verifyApplicationExitInfo(
763                 info,                                       // info
764                 now8,                                       // timestamp
765                 app1PidUser2,                               // pid
766                 app1IsolatedUidUser2,                       // uid
767                 app1UidUser2,                               // packageUid
768                 null,                                       // definingUid
769                 app1ProcessName,                            // processName
770                 0,                                          // connectionGroup
771                 ApplicationExitInfo.REASON_OTHER,           // reason
772                 ApplicationExitInfo.SUBREASON_REMOVE_TASK,  // subReason
773                 0,                                          // status
774                 app1Pss4,                                   // pss
775                 app1Rss4,                                   // rss
776                 IMPORTANCE_CACHED,                          // importance
777                 null);                                      // description
778 
779         // App1 gets "too many empty"
780         final String app1Description2 = "too many empty";
781         sleep(1);
782         final int app1Pid2User2 = 56565;
783         final int app1IsolatedUid2User2 = 1099003; // isolated uid
784         final long app1Pss5 = 34344;
785         final long app1Rss5 = 43435;
786         final long now9 = System.currentTimeMillis();
787         sigNum = OsConstants.SIGKILL;
788         doReturn(new Pair<Long, Object>(now9, makeSignalStatus(sigNum)))
789                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
790                 .remove(anyInt(), anyInt());
791         doReturn(null)
792                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
793                 .remove(anyInt(), anyInt());
794         app = makeProcessRecord(
795                 app1Pid2User2,                // pid
796                 app1IsolatedUid2User2,        // uid
797                 app1UidUser2,                 // packageUid
798                 null,                         // definingUid
799                 0,                            // connectionGroup
800                 PROCESS_STATE_CACHED_EMPTY,   // procstate
801                 app1Pss5,                     // pss
802                 app1Rss5,                     // rss
803                 app1ProcessName,              // processName
804                 app1PackageName);             // packageName
805 
806         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUid2User2, app1UidUser2);
807 
808         // Pretent it gets an ANR trace too (although the reason here should be REASON_ANR)
809         final File traceFile = new File(mContext.getFilesDir(), "anr_original.txt");
810         final int traceSize = 10240;
811         final int traceStart = 1024;
812         final int traceEnd = 8192;
813         createRandomFile(traceFile, traceSize);
814         assertEquals(traceSize, traceFile.length());
815         mAppExitInfoTracker.handleLogAnrTrace(app.getPid(), app.uid, app.getPackageList(),
816                 traceFile, traceStart, traceEnd);
817 
818         noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
819                 ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, app1Description2, now9);
820         updateExitInfo(app, now9);
821         list.clear();
822         mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1Pid2User2, 1, list);
823         assertEquals(1, list.size());
824 
825         info = list.get(0);
826 
827         verifyApplicationExitInfo(
828                 info,                                         // info
829                 now9,                                         // timestamp
830                 app1Pid2User2,                                // pid
831                 app1IsolatedUid2User2,                        // uid
832                 app1UidUser2,                                 // packageUid
833                 null,                                         // definingUid
834                 app1ProcessName,                              // processName
835                 0,                                            // connectionGroup
836                 ApplicationExitInfo.REASON_OTHER,             // reason
837                 ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, // subReason
838                 0,                                            // status
839                 app1Pss5,                                     // pss
840                 app1Rss5,                                     // rss
841                 IMPORTANCE_CACHED,                            // importance
842                 app1Description2);                            // description
843 
844         // Verify if the traceFile get copied into the records correctly.
845         verifyTraceFile(traceFile, traceStart, info.getTraceFile(), 0, traceEnd - traceStart);
846         traceFile.delete();
847         info.getTraceFile().delete();
848 
849         // Case 9: User2 gets removed
850         sleep(1);
851         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUidUser2, app1UidUser2);
852         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app3IsolatedUid, app3Uid);
853 
854         mAppExitInfoTracker.onUserRemoved(UserHandle.getUserId(app1UidUser2));
855 
856         assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
857                 app1IsolatedUidUser2));
858         assertNotNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
859                 app3IsolatedUid));
860         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(
861                 app1IsolatedUidUser2, app1UidUser2);
862         mAppExitInfoTracker.mIsolatedUidRecords.removeAppUid(app1UidUser2, false);
863         assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
864                 app1IsolatedUidUser2));
865         mAppExitInfoTracker.mIsolatedUidRecords.removeAppUid(app3Uid, true);
866         assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(app3IsolatedUid));
867 
868         list.clear();
869         mAppExitInfoTracker.getExitInfo(null, app1UidUser2, 0, 0, list);
870         assertEquals(0, list.size());
871 
872         list.clear();
873         mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
874         assertEquals(3, list.size());
875 
876         info = list.get(1);
877 
878         exitCode = 6;
879         verifyApplicationExitInfo(
880                 info,                                 // info
881                 now2,                                 // timestamp
882                 app1Pid2,                             // pid
883                 app1Uid,                              // uid
884                 app1Uid,                              // packageUid
885                 app1DefiningUid,                      // definingUid
886                 app1ProcessName,                      // processName
887                 app1ConnectiongGroup,                 // connectionGroup
888                 ApplicationExitInfo.REASON_EXIT_SELF, // reason
889                 null,                                 // subReason
890                 exitCode,                             // status
891                 app1Pss2,                             // pss
892                 app1Rss2,                             // rss
893                 IMPORTANCE_SERVICE,                   // importance
894                 null);                                // description
895 
896         info = list.get(0);
897         verifyApplicationExitInfo(
898                 info,                                      // info
899                 now1s,                                     // timestamp
900                 app1sPid1,                                 // pid
901                 app1Uid,                                   // uid
902                 app1Uid,                                   // packageUid
903                 null,                                      // definingUid
904                 app1sProcessName,                          // processName
905                 0,                                         // connectionGroup
906                 ApplicationExitInfo.REASON_USER_REQUESTED, // reason
907                 null,                                      // subReason
908                 null,                                      // status
909                 app1sPss1,                                 // pss
910                 app1sRss1,                                 // rss
911                 IMPORTANCE_FOREGROUND,                     // importance
912                 null);                                     // description
913 
914         info = list.get(2);
915         exitCode = 5;
916         verifyApplicationExitInfo(
917                 info,                                 // info
918                 now1,                                 // timestamp
919                 app1Pid1,                             // pid
920                 app1Uid,                              // uid
921                 app1Uid,                              // packageUid
922                 null,                                 // definingUid
923                 app1ProcessName,                      // processName
924                 0,                                    // connectionGroup
925                 ApplicationExitInfo.REASON_EXIT_SELF, // reason
926                 null,                                 // subReason
927                 exitCode,                             // status
928                 app1Pss1,                             // pss
929                 app1Rss1,                             // rss
930                 IMPORTANCE_CACHED,                    // importance
931                 null);                                // description
932 
933         // Case 10: Save the info and load them again
934         ArrayList<ApplicationExitInfo> original = new ArrayList<ApplicationExitInfo>();
935         mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, original);
936         assertTrue(original.size() > 0);
937 
938         mAppExitInfoTracker.persistProcessExitInfo();
939         assertTrue(mAppExitInfoTracker.mProcExitInfoFile.exists());
940 
941         mAppExitInfoTracker.clearProcessExitInfo(false);
942         list.clear();
943         mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
944         assertEquals(0, list.size());
945 
946         mAppExitInfoTracker.loadExistingProcessExitInfo();
947         list.clear();
948         mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
949         assertEquals(original.size(), list.size());
950 
951         for (int i = list.size() - 1; i >= 0; i--) {
952             assertTrue(list.get(i).equals(original.get(i)));
953         }
954     }
955 
createExitInfo(int i)956     private ApplicationExitInfo createExitInfo(int i) {
957         ApplicationExitInfo info = new ApplicationExitInfo();
958         info.setPid(i);
959         info.setTimestamp(1000 + i);
960         info.setPackageUid(2000);
961         return info;
962     }
963 
964     @SuppressWarnings("GuardedBy")
getExitInfosHelper( AppExitInfoTracker.AppExitInfoContainer container, int filterPid, int maxNum)965     private ArrayList<ApplicationExitInfo> getExitInfosHelper(
966             AppExitInfoTracker.AppExitInfoContainer container, int filterPid, int maxNum) {
967         ArrayList<ApplicationExitInfo> infos = new ArrayList<ApplicationExitInfo>();
968         container.getExitInfosLocked(filterPid, maxNum, infos);
969         return infos;
970     }
971 
972     @SuppressWarnings("GuardedBy")
checkAreHelper(AppExitInfoTracker.AppExitInfoContainer container, int filterPid, int maxNum, List<Integer> expected, Function<ApplicationExitInfo, Integer> func)973     private void checkAreHelper(AppExitInfoTracker.AppExitInfoContainer container, int filterPid,
974             int maxNum, List<Integer> expected, Function<ApplicationExitInfo, Integer> func) {
975         ArrayList<Integer> values = new ArrayList<Integer>();
976         getExitInfosHelper(container, filterPid, maxNum)
977                 .forEach((exitInfo) -> values.add(func.apply(exitInfo)));
978         assertEquals(values, expected);
979 
980         HashMap<Integer, Integer> expectedMultiset = new HashMap<Integer, Integer>();
981         expected.forEach(
982                 (elem) -> expectedMultiset.put(elem, expectedMultiset.getOrDefault(elem, 0) + 1));
983         // `maxNum` isn't a parameter supported by `forEachRecordLocked()s`, but we can emulate it
984         // by stopping iteration when we've seen enough elements.
985         int[] numElementsToObserveWrapped = {maxNum};
986         container.forEachRecordLocked((exitInfo) -> {
987             // Same thing as above, `filterPid` isn't a parameter supported out of the box for
988             // `forEachRecordLocked()`, but we emulate it here.
989             if (filterPid > 0 && filterPid != exitInfo.getPid()) {
990                 return AppExitInfoTracker.FOREACH_ACTION_NONE;
991             }
992 
993             Integer key = func.apply(exitInfo);
994             assertTrue(expectedMultiset.toString(), expectedMultiset.containsKey(key));
995             Integer references = expectedMultiset.get(key);
996             if (references == 1) {
997                 expectedMultiset.remove(key);
998             } else {
999                 expectedMultiset.put(key, references - 1);
1000             }
1001             if (--numElementsToObserveWrapped[0] == 0) {
1002                 return AppExitInfoTracker.FOREACH_ACTION_STOP_ITERATION;
1003             }
1004             return AppExitInfoTracker.FOREACH_ACTION_NONE;
1005         });
1006         assertEquals(expectedMultiset.size(), 0);
1007     }
1008 
checkPidsAre(AppExitInfoTracker.AppExitInfoContainer container, int filterPid, int maxNum, List<Integer> expectedPids)1009     private void checkPidsAre(AppExitInfoTracker.AppExitInfoContainer container, int filterPid,
1010             int maxNum, List<Integer> expectedPids) {
1011         checkAreHelper(container, filterPid, maxNum, expectedPids, (exitInfo) -> exitInfo.getPid());
1012     }
1013 
checkPidsAre( AppExitInfoTracker.AppExitInfoContainer container, List<Integer> expectedPids)1014     private void checkPidsAre(
1015             AppExitInfoTracker.AppExitInfoContainer container, List<Integer> expectedPids) {
1016         checkPidsAre(container, 0, 0, expectedPids);
1017     }
1018 
checkTimestampsAre(AppExitInfoTracker.AppExitInfoContainer container, int filterPid, int maxNum, List<Integer> expectedTimestamps)1019     private void checkTimestampsAre(AppExitInfoTracker.AppExitInfoContainer container,
1020             int filterPid, int maxNum, List<Integer> expectedTimestamps) {
1021         checkAreHelper(container, filterPid, maxNum, expectedTimestamps,
1022                 (exitInfo) -> (int) exitInfo.getTimestamp());
1023     }
1024 
checkTimestampsAre( AppExitInfoTracker.AppExitInfoContainer container, List<Integer> expectedTimestamps)1025     private void checkTimestampsAre(
1026             AppExitInfoTracker.AppExitInfoContainer container, List<Integer> expectedTimestamps) {
1027         checkTimestampsAre(container, 0, 0, expectedTimestamps);
1028     }
1029 
1030     @SuppressWarnings("GuardedBy")
createBasicContainer()1031     private AppExitInfoTracker.AppExitInfoContainer createBasicContainer() {
1032         AppExitInfoTracker.AppExitInfoContainer container =
1033                 mAppExitInfoTracker.new AppExitInfoContainer(3);
1034         container.addExitInfoLocked(createExitInfo(10));
1035         container.addExitInfoLocked(createExitInfo(30));
1036         container.addExitInfoLocked(createExitInfo(20));
1037         return container;
1038     }
1039 
1040     @Test
1041     @SuppressWarnings("GuardedBy")
testContainerGetExitInfosIsSortedNewestFirst()1042     public void testContainerGetExitInfosIsSortedNewestFirst() throws Exception {
1043         AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
1044         checkPidsAre(container, Arrays.asList(30, 20, 10));
1045     }
1046 
1047     @Test
1048     @SuppressWarnings("GuardedBy")
testContainerRemovesOldestReports()1049     public void testContainerRemovesOldestReports() throws Exception {
1050         AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
1051         container.addExitInfoLocked(createExitInfo(40));
1052         checkPidsAre(container, Arrays.asList(40, 30, 20));
1053 
1054         container.addExitInfoLocked(createExitInfo(50));
1055         checkPidsAre(container, Arrays.asList(50, 40, 30));
1056 
1057         container.addExitInfoLocked(createExitInfo(45));
1058         checkPidsAre(container, Arrays.asList(50, 45, 40));
1059 
1060         // Adding an older report shouldn't remove the newer ones.
1061         container.addExitInfoLocked(createExitInfo(15));
1062         checkPidsAre(container, Arrays.asList(50, 45, 40));
1063     }
1064 
1065     @Test
1066     @SuppressWarnings("GuardedBy")
testContainerFilterByPid()1067     public void testContainerFilterByPid() throws Exception {
1068         AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
1069         assertEquals(1, getExitInfosHelper(container, 30, 0).size());
1070         assertEquals(30, getExitInfosHelper(container, 0, 0).get(0).getPid());
1071 
1072         assertEquals(1, getExitInfosHelper(container, 30, 0).size());
1073         assertEquals(20, getExitInfosHelper(container, 20, 0).get(0).getPid());
1074 
1075         assertEquals(1, getExitInfosHelper(container, 10, 0).size());
1076         assertEquals(10, getExitInfosHelper(container, 10, 0).get(0).getPid());
1077 
1078         assertEquals(0, getExitInfosHelper(container, 1337, 0).size());
1079     }
1080 
1081     @Test
1082     @SuppressWarnings("GuardedBy")
testContainerLimitQuantityOfResults()1083     public void testContainerLimitQuantityOfResults() throws Exception {
1084         AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
1085         checkPidsAre(container, /* filterPid */ 30, /* maxNum */ 1, Arrays.asList(30));
1086         checkPidsAre(container, /* filterPid */ 30, /* maxNum */ 1000, Arrays.asList(30));
1087 
1088         checkPidsAre(container, /* filterPid */ 20, /* maxNum */ 1, Arrays.asList(20));
1089         checkPidsAre(container, /* filterPid */ 20, /* maxNum */ 1000, Arrays.asList(20));
1090 
1091         checkPidsAre(container, /* filterPid */ 10, /* maxNum */ 1, Arrays.asList(10));
1092         checkPidsAre(container, /* filterPid */ 10, /* maxNum */ 1000, Arrays.asList(10));
1093 
1094         checkPidsAre(container, /* filterPid */ 1337, /* maxNum */ 1, Arrays.asList());
1095         checkPidsAre(container, /* filterPid */ 1337, /* maxNum */ 1000, Arrays.asList());
1096     }
1097 
1098     @Test
1099     @SuppressWarnings("GuardedBy")
testContainerLastExitInfoForPid()1100     public void testContainerLastExitInfoForPid() throws Exception {
1101         AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
1102         assertEquals(30, container.getLastExitInfoForPid(30).getPid());
1103         assertEquals(20, container.getLastExitInfoForPid(20).getPid());
1104         assertEquals(10, container.getLastExitInfoForPid(10).getPid());
1105         assertEquals(null, container.getLastExitInfoForPid(1337));
1106     }
1107 
1108     @Test
1109     @SuppressWarnings("GuardedBy")
testContainerCanHoldMultipleFromSamePid()1110     public void testContainerCanHoldMultipleFromSamePid() throws Exception {
1111         AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
1112         ApplicationExitInfo info = createExitInfo(100);
1113         ApplicationExitInfo info2 = createExitInfo(100);
1114         ApplicationExitInfo info3 = createExitInfo(100);
1115         info2.setTimestamp(1337);
1116         info3.setTimestamp(31337);
1117 
1118         container.addExitInfoLocked(info);
1119         assertEquals(1100, container.getLastExitInfoForPid(100).getTimestamp());
1120         container.addExitInfoLocked(info2);
1121         assertEquals(1337, container.getLastExitInfoForPid(100).getTimestamp());
1122         container.addExitInfoLocked(info3);
1123         assertEquals(31337, container.getLastExitInfoForPid(100).getTimestamp());
1124 
1125         checkPidsAre(container, Arrays.asList(100, 100, 100));
1126         checkTimestampsAre(container, Arrays.asList(31337, 1337, 1100));
1127 
1128         checkPidsAre(container, /* filterPid */ 100, /* maxNum */ 0, Arrays.asList(100, 100, 100));
1129         checkTimestampsAre(
1130                 container, /* filterPid */ 100, /* maxNum */ 0, Arrays.asList(31337, 1337, 1100));
1131 
1132         checkPidsAre(container, /* filterPid */ 100, /* maxNum */ 2, Arrays.asList(100, 100));
1133         checkTimestampsAre(
1134                 container, /* filterPid */ 100, /* maxNum */ 2, Arrays.asList(31337, 1337));
1135     }
1136 
1137     @Test
1138     @SuppressWarnings("GuardedBy")
testContainerIteration()1139     public void testContainerIteration() throws Exception {
1140         AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
1141         checkPidsAre(container, Arrays.asList(30, 20, 10));
1142 
1143         // Unfortunately relying on order for this test, which is implemented as "last inserted" ->
1144         // "first inserted". Note that this is insertion order, not timestamp. Thus, it's 20 -> 30
1145         // -> 10, as defined by `createBasicContainer()`.
1146         List<Integer> elements = Arrays.asList(20, 30, 10);
1147         for (int i = 0, size = elements.size(); i < size; i++) {
1148             ArrayList<Integer> processedEntries = new ArrayList<Integer>();
1149             final int finalIndex = i;
1150             container.forEachRecordLocked((exitInfo) -> {
1151                 processedEntries.add(new Integer(exitInfo.getPid()));
1152                 if (exitInfo.getPid() == elements.get(finalIndex)) {
1153                     return AppExitInfoTracker.FOREACH_ACTION_STOP_ITERATION;
1154                 }
1155                 return AppExitInfoTracker.FOREACH_ACTION_NONE;
1156             });
1157             assertEquals(processedEntries, elements.subList(0, i + 1));
1158         }
1159     }
1160 
1161     @Test
1162     @SuppressWarnings("GuardedBy")
testContainerIterationRemove()1163     public void testContainerIterationRemove() throws Exception {
1164         for (int pidToRemove : Arrays.asList(30, 20, 10)) {
1165             AppExitInfoTracker.AppExitInfoContainer container = createBasicContainer();
1166             container.forEachRecordLocked((exitInfo) -> {
1167                 if (exitInfo.getPid() == pidToRemove) {
1168                     return AppExitInfoTracker.FOREACH_ACTION_REMOVE_ITEM;
1169                 }
1170                 return AppExitInfoTracker.FOREACH_ACTION_NONE;
1171             });
1172             ArrayList<Integer> pidsRemaining = new ArrayList<Integer>(Arrays.asList(30, 20, 10));
1173             pidsRemaining.remove(new Integer(pidToRemove));
1174             checkPidsAre(container, pidsRemaining);
1175         }
1176     }
1177 
makeExitStatus(int exitCode)1178     private static int makeExitStatus(int exitCode) {
1179         return (exitCode << 8) & 0xff00;
1180     }
1181 
makeSignalStatus(int sigNum)1182     private static int makeSignalStatus(int sigNum) {
1183         return sigNum & 0x7f;
1184     }
1185 
sleep(long ms)1186     private void sleep(long ms) {
1187         try {
1188             Thread.sleep(ms);
1189         } catch (InterruptedException e) {
1190         }
1191     }
1192 
createRandomFile(File file, int size)1193     private static void createRandomFile(File file, int size) throws IOException {
1194         try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
1195             Random random = new Random();
1196             byte[] buf = random.ints('a', 'z').limit(size).collect(
1197                     StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
1198                     .toString().getBytes();
1199             out.write(buf);
1200         }
1201     }
1202 
verifyTraceFile(File originFile, int originStart, File traceFile, int traceStart, int length)1203     private static void verifyTraceFile(File originFile, int originStart, File traceFile,
1204             int traceStart, int length) throws IOException {
1205         assertTrue(originFile.exists());
1206         assertTrue(traceFile.exists());
1207         assertTrue(originStart < originFile.length());
1208         try (GZIPInputStream traceIn = new GZIPInputStream(new FileInputStream(traceFile));
1209             BufferedInputStream originIn = new BufferedInputStream(
1210                     new FileInputStream(originFile))) {
1211             assertEquals(traceStart, traceIn.skip(traceStart));
1212             assertEquals(originStart, originIn.skip(originStart));
1213             byte[] buf1 = new byte[8192];
1214             byte[] buf2 = new byte[8192];
1215             while (length > 0) {
1216                 int len = traceIn.read(buf1, 0, Math.min(buf1.length, length));
1217                 assertEquals(len, originIn.read(buf2, 0, len));
1218                 assertTrue(ArrayUtils.equals(buf1, buf2, len));
1219                 length -= len;
1220             }
1221         }
1222     }
1223 
makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid, int connectionGroup, int procState, long pss, long rss, String processName, String packageName)1224     private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
1225             int connectionGroup, int procState, long pss, long rss,
1226             String processName, String packageName) {
1227         return makeProcessRecord(pid, uid, packageUid, definingUid, connectionGroup,
1228                 procState, pss, rss, processName, packageName, mAms);
1229     }
1230 
makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid, int connectionGroup, int procState, long pss, long rss, String processName, String packageName, ActivityManagerService ams)1231     static ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
1232             int connectionGroup, int procState, long pss, long rss,
1233             String processName, String packageName, ActivityManagerService ams) {
1234         ApplicationInfo ai = new ApplicationInfo();
1235         ai.packageName = packageName;
1236         ProcessRecord app = new ProcessRecord(ams, ai, processName, uid);
1237         app.setPid(pid);
1238         app.info.uid = packageUid;
1239         if (definingUid != null) {
1240             final String dummyPackageName = "com.android.test";
1241             final String dummyClassName = ".Foo";
1242             app.setHostingRecord(HostingRecord.byAppZygote(new ComponentName(
1243                     dummyPackageName, dummyClassName), "", definingUid, ""));
1244         }
1245         app.mServices.setConnectionGroup(connectionGroup);
1246         app.mState.setReportedProcState(procState);
1247         app.mProfile.setLastMemInfo(spy(new Debug.MemoryInfo()));
1248         app.mProfile.setLastPss(pss);
1249         app.mProfile.setLastRss(rss);
1250         return app;
1251     }
1252 
verifyApplicationExitInfo(ApplicationExitInfo info, Long timestamp, Integer pid, Integer uid, Integer packageUid, Integer definingUid, String processName, Integer connectionGroup, Integer reason, Integer subReason, Integer status, Long pss, Long rss, Integer importance, String description)1253     private void verifyApplicationExitInfo(ApplicationExitInfo info,
1254             Long timestamp, Integer pid, Integer uid, Integer packageUid,
1255             Integer definingUid, String processName, Integer connectionGroup,
1256             Integer reason, Integer subReason, Integer status,
1257             Long pss, Long rss, Integer importance, String description) {
1258         assertNotNull(info);
1259 
1260         if (timestamp != null) {
1261             final long tolerance = 10000; // ms
1262             assertTrue(timestamp - tolerance <= info.getTimestamp());
1263             assertTrue(timestamp + tolerance >= info.getTimestamp());
1264         }
1265         if (pid != null) {
1266             assertEquals(pid.intValue(), info.getPid());
1267         }
1268         if (uid != null) {
1269             assertEquals(uid.intValue(), info.getRealUid());
1270         }
1271         if (packageUid != null) {
1272             assertEquals(packageUid.intValue(), info.getPackageUid());
1273         }
1274         if (definingUid != null) {
1275             assertEquals(definingUid.intValue(), info.getDefiningUid());
1276         }
1277         if (processName != null) {
1278             assertTrue(TextUtils.equals(processName, info.getProcessName()));
1279         }
1280         if (connectionGroup != null) {
1281             assertEquals(connectionGroup.intValue(), info.getConnectionGroup());
1282         }
1283         if (reason != null) {
1284             assertEquals(reason.intValue(), info.getReason());
1285         }
1286         if (subReason != null) {
1287             assertEquals(subReason.intValue(), info.getSubReason());
1288         }
1289         if (status != null) {
1290             assertEquals(status.intValue(), info.getStatus());
1291         }
1292         if (pss != null) {
1293             assertEquals(pss.longValue(), info.getPss());
1294         }
1295         if (rss != null) {
1296             assertEquals(rss.longValue(), info.getRss());
1297         }
1298         if (importance != null) {
1299             assertEquals(importance.intValue(), info.getImportance());
1300         }
1301 
1302         // info.getDescription returns a combination of subReason & description
1303         if ((subReason != null) && (subReason != ApplicationExitInfo.SUBREASON_UNKNOWN)
1304                 && (description != null)) {
1305             assertTrue(TextUtils.equals(
1306                     "[" + info.subreasonToString(subReason) + "] " + description,
1307                     info.getDescription()));
1308         } else if ((subReason != null) && (subReason != ApplicationExitInfo.SUBREASON_UNKNOWN)) {
1309             assertTrue(TextUtils.equals(
1310                     "[" + info.subreasonToString(subReason) + "]",
1311                     info.getDescription()));
1312         } else if (description != null) {
1313             assertTrue(TextUtils.equals(description, info.getDescription()));
1314         }
1315     }
1316 
1317     private class TestInjector extends Injector {
TestInjector(Context context)1318         TestInjector(Context context) {
1319             super(context);
1320         }
1321 
1322         @Override
getAppOpsService(File recentAccessesFile, File storageFile, Handler handler)1323         public AppOpsService getAppOpsService(File recentAccessesFile, File storageFile,
1324                 Handler handler) {
1325             return mAppOpsService;
1326         }
1327 
1328         @Override
getUiHandler(ActivityManagerService service)1329         public Handler getUiHandler(ActivityManagerService service) {
1330             return mHandler;
1331         }
1332 
1333         @Override
getProcessList(ActivityManagerService service)1334         public ProcessList getProcessList(ActivityManagerService service) {
1335             return mProcessList;
1336         }
1337     }
1338 
1339     static class ServiceThreadRule implements TestRule {
1340 
1341         private ServiceThread mThread;
1342 
getThread()1343         ServiceThread getThread() {
1344             return mThread;
1345         }
1346 
1347         @Override
apply(Statement base, Description description)1348         public Statement apply(Statement base, Description description) {
1349             return new Statement() {
1350                 @Override
1351                 public void evaluate() throws Throwable {
1352                     mThread = new ServiceThread("TestServiceThread",
1353                             Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
1354                     mThread.start();
1355                     try {
1356                         base.evaluate();
1357                     } finally {
1358                         mThread.getThreadHandler().runWithScissors(mThread::quit, 0 /* timeout */);
1359                     }
1360                 }
1361             };
1362         }
1363     }
1364 
1365     // TODO: [b/302724778] Remove manual JNI load
1366     static {
1367         System.loadLibrary("mockingservicestestjni");
1368     }
1369 }
1370