• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Random;
88 import java.util.zip.GZIPInputStream;
89 
90 /**
91  * Test class for {@link android.app.ApplicationExitInfo}.
92  *
93  * Build/Install/Run:
94  *  atest ApplicationExitInfoTest
95  */
96 @Presubmit
97 public class ApplicationExitInfoTest {
98     private static final String TAG = ApplicationExitInfoTest.class.getSimpleName();
99 
100     @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
101     @Mock private AppOpsService mAppOpsService;
102     @Mock private PackageManagerInternal mPackageManagerInt;
103 
104     private Context mContext = getInstrumentation().getTargetContext();
105     private TestInjector mInjector;
106     private ActivityManagerService mAms;
107     private ProcessList mProcessList;
108     private AppExitInfoTracker mAppExitInfoTracker;
109     private Handler mHandler;
110     private HandlerThread mHandlerThread;
111 
112     @BeforeClass
setUpOnce()113     public static void setUpOnce() {
114         System.setProperty("dexmaker.share_classloader", "true");
115     }
116 
117     @Before
setUp()118     public void setUp() {
119         MockitoAnnotations.initMocks(this);
120 
121         mHandlerThread = new HandlerThread(TAG);
122         mHandlerThread.start();
123         mHandler = new Handler(mHandlerThread.getLooper());
124         mProcessList = spy(new ProcessList());
125         ProcessList.sKillHandler = null;
126         mAppExitInfoTracker = spy(new AppExitInfoTracker());
127         setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mIsolatedUidRecords",
128                 spy(mAppExitInfoTracker.new IsolatedUidRecords()));
129         setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppExitInfoSourceZygote",
130                 spy(mAppExitInfoTracker.new AppExitInfoExternalSource("zygote", null)));
131         setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppExitInfoSourceLmkd",
132                 spy(mAppExitInfoTracker.new AppExitInfoExternalSource("lmkd",
133                 ApplicationExitInfo.REASON_LOW_MEMORY)));
134         setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppTraceRetriever",
135                 spy(mAppExitInfoTracker.new AppTraceRetriever()));
136         setFieldValue(ProcessList.class, mProcessList, "mAppExitInfoTracker", mAppExitInfoTracker);
137         mInjector = new TestInjector(mContext);
138         mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread());
139         mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
140         mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
141         mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal());
142         mAms.mPackageManagerInt = mPackageManagerInt;
143         doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
144         // Remove stale instance of PackageManagerInternal if there is any
145         LocalServices.removeServiceForTest(PackageManagerInternal.class);
146         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
147     }
148 
149     @After
tearDown()150     public void tearDown() {
151         LocalServices.removeServiceForTest(PackageManagerInternal.class);
152         mHandlerThread.quit();
153         ProcessList.sKillHandler = null;
154     }
155 
setFieldValue(Class clazz, Object obj, String fieldName, T val)156     private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) {
157         try {
158             Field field = clazz.getDeclaredField(fieldName);
159             field.setAccessible(true);
160             Field mfield = Field.class.getDeclaredField("accessFlags");
161             mfield.setAccessible(true);
162             mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE));
163             field.set(obj, val);
164         } catch (NoSuchFieldException | IllegalAccessException e) {
165         }
166     }
167 
updateExitInfo(ProcessRecord app, @CurrentTimeMillisLong long timestamp)168     private void updateExitInfo(ProcessRecord app, @CurrentTimeMillisLong long timestamp) {
169         ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecord(app, timestamp);
170         mAppExitInfoTracker.handleNoteProcessDiedLocked(raw);
171         mAppExitInfoTracker.recycleRawRecord(raw);
172     }
173 
noteAppKill(ProcessRecord app, int reason, int subReason, String msg, @CurrentTimeMillisLong long timestamp)174     private void noteAppKill(ProcessRecord app, int reason, int subReason, String msg,
175             @CurrentTimeMillisLong long timestamp) {
176         ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecord(app, timestamp);
177         raw.setReason(reason);
178         raw.setSubReason(subReason);
179         raw.setDescription(msg);
180         mAppExitInfoTracker.handleNoteAppKillLocked(raw);
181         mAppExitInfoTracker.recycleRawRecord(raw);
182     }
183 
184     @Test
testApplicationExitInfo()185     public void testApplicationExitInfo() throws Exception {
186         mAppExitInfoTracker.clearProcessExitInfo(true);
187         mAppExitInfoTracker.mAppExitInfoLoaded.set(true);
188         mAppExitInfoTracker.mProcExitStoreDir = new File(mContext.getFilesDir(),
189                 AppExitInfoTracker.APP_EXIT_STORE_DIR);
190         assertTrue(FileUtils.createDir(mAppExitInfoTracker.mProcExitStoreDir));
191         mAppExitInfoTracker.mProcExitInfoFile = new File(mAppExitInfoTracker.mProcExitStoreDir,
192                 AppExitInfoTracker.APP_EXIT_INFO_FILE);
193 
194         // Test application calls System.exit()
195         doNothing().when(mAppExitInfoTracker).schedulePersistProcessExitInfo(anyBoolean());
196         doReturn(true).when(mAppExitInfoTracker).isFresh(anyLong());
197 
198         final int app1Uid = 10123;
199         final int app1Pid1 = 12345;
200         final int app1Pid2 = 12346;
201         final int app1sPid1 = 13456;
202         final int app1DefiningUid = 23456;
203         final int app1ConnectiongGroup = 10;
204         final int app1UidUser2 = 1010123;
205         final int app1PidUser2 = 12347;
206         final long app1Pss1 = 34567;
207         final long app1Rss1 = 45678;
208         final long app1Pss2 = 34568;
209         final long app1Rss2 = 45679;
210         final long app1Pss3 = 34569;
211         final long app1Rss3 = 45680;
212         final long app1sPss1 = 56789;
213         final long app1sRss1 = 67890;
214         final String app1ProcessName = "com.android.test.stub1:process";
215         final String app1PackageName = "com.android.test.stub1";
216         final String app1sProcessName = "com.android.test.stub_shared:process";
217         final String app1sPackageName = "com.android.test.stub_shared";
218         final byte[] app1Cookie1 = {(byte) 0x01, (byte) 0x02, (byte) 0x03, (byte) 0x04,
219                 (byte) 0x05, (byte) 0x06, (byte) 0x07, (byte) 0x08};
220         final byte[] app1Cookie2 = {(byte) 0x08, (byte) 0x07, (byte) 0x06, (byte) 0x05,
221                 (byte) 0x04, (byte) 0x03, (byte) 0x02, (byte) 0x01};
222 
223         final long now1 = 1;
224         ProcessRecord app = makeProcessRecord(
225                 app1Pid1,                    // pid
226                 app1Uid,                     // uid
227                 app1Uid,                     // packageUid
228                 null,                        // definingUid
229                 0,                           // connectionGroup
230                 PROCESS_STATE_LAST_ACTIVITY, // procstate
231                 app1Pss1,                    // pss
232                 app1Rss1,                    // rss
233                 app1ProcessName,             // processName
234                 app1PackageName);            // packageName
235 
236         // Case 1: basic System.exit() test
237         int exitCode = 5;
238         mAppExitInfoTracker.setProcessStateSummary(app1Uid, app1Pid1, app1Cookie1);
239         assertTrue(ArrayUtils.equals(mAppExitInfoTracker.getProcessStateSummary(app1Uid,
240                 app1Pid1), app1Cookie1, app1Cookie1.length));
241         doReturn(new Pair<Long, Object>(now1, Integer.valueOf(makeExitStatus(exitCode))))
242                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
243                 .remove(anyInt(), anyInt());
244         doReturn(null)
245                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
246                 .remove(anyInt(), anyInt());
247         updateExitInfo(app, now1);
248 
249         ArrayList<ApplicationExitInfo> list = new ArrayList<ApplicationExitInfo>();
250         mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, app1Pid1, 0, list);
251         assertEquals(1, list.size());
252 
253         ApplicationExitInfo info = list.get(0);
254 
255         verifyApplicationExitInfo(
256                 info,                                 // info
257                 now1,                                 // timestamp
258                 app1Pid1,                             // pid
259                 app1Uid,                              // uid
260                 app1Uid,                              // packageUid
261                 null,                                 // definingUid
262                 app1ProcessName,                      // processName
263                 0,                                    // connectionGroup
264                 ApplicationExitInfo.REASON_EXIT_SELF, // reason
265                 null,                                 // subReason
266                 exitCode,                             // status
267                 app1Pss1,                             // pss
268                 app1Rss1,                             // rss
269                 IMPORTANCE_CACHED,                    // importance
270                 null);                                // description
271 
272         assertTrue(ArrayUtils.equals(info.getProcessStateSummary(), app1Cookie1,
273                 app1Cookie1.length));
274         assertEquals(info.getTraceInputStream(), null);
275 
276         // Now create a process record from a different package but shared UID.
277         sleep(1);
278         final long now1s = System.currentTimeMillis();
279         app = makeProcessRecord(
280                 app1sPid1,                   // pid
281                 app1Uid,                     // uid
282                 app1Uid,                     // packageUid
283                 null,                        // definingUid
284                 0,                           // connectionGroup
285                 PROCESS_STATE_BOUND_TOP,     // procstate
286                 app1sPss1,                   // pss
287                 app1sRss1,                   // rss
288                 app1sProcessName,            // processName
289                 app1sPackageName);           // packageName
290         doReturn(new Pair<Long, Object>(now1s, Integer.valueOf(0)))
291                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
292                 .remove(anyInt(), anyInt());
293         doReturn(null)
294                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
295                 .remove(anyInt(), anyInt());
296         noteAppKill(app, ApplicationExitInfo.REASON_USER_REQUESTED,
297                 ApplicationExitInfo.SUBREASON_UNKNOWN, null, now1s);
298 
299         // Case 2: create another app1 process record with a different pid
300         sleep(1);
301         final long now2 = 2;
302         app = makeProcessRecord(
303                 app1Pid2,               // pid
304                 app1Uid,                // uid
305                 app1Uid,                // packageUid
306                 app1DefiningUid,        // definingUid
307                 app1ConnectiongGroup,   // connectionGroup
308                 PROCESS_STATE_RECEIVER, // procstate
309                 app1Pss2,               // pss
310                 app1Rss2,               // rss
311                 app1ProcessName,        // processName
312                 app1PackageName);       // packageName
313         exitCode = 6;
314 
315         mAppExitInfoTracker.setProcessStateSummary(app1Uid, app1Pid2, app1Cookie1);
316         // Override with a different cookie
317         mAppExitInfoTracker.setProcessStateSummary(app1Uid, app1Pid2, app1Cookie2);
318         assertTrue(ArrayUtils.equals(mAppExitInfoTracker.getProcessStateSummary(app1Uid,
319                 app1Pid2), app1Cookie2, app1Cookie2.length));
320         doReturn(new Pair<Long, Object>(now2, Integer.valueOf(makeExitStatus(exitCode))))
321                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
322                 .remove(anyInt(), anyInt());
323         updateExitInfo(app, now2);
324         list.clear();
325 
326         // Get all the records for app1Uid
327         mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
328         assertEquals(3, list.size());
329 
330         info = list.get(1);
331 
332         verifyApplicationExitInfo(
333                 info,                                 // info
334                 now2,                                 // timestamp
335                 app1Pid2,                             // pid
336                 app1Uid,                              // uid
337                 app1Uid,                              // packageUid
338                 app1DefiningUid,                      // definingUid
339                 app1ProcessName,                      // processName
340                 app1ConnectiongGroup,                 // connectionGroup
341                 ApplicationExitInfo.REASON_EXIT_SELF, // reason
342                 null,                                 // subReason
343                 exitCode,                             // status
344                 app1Pss2,                             // pss
345                 app1Rss2,                             // rss
346                 IMPORTANCE_SERVICE,                   // importance
347                 null);                                // description
348 
349         assertTrue(ArrayUtils.equals(info.getProcessStateSummary(), app1Cookie2,
350                 app1Cookie2.length));
351 
352         info = list.get(0);
353         verifyApplicationExitInfo(
354                 info,                                      // info
355                 now1s,                                     // timestamp
356                 app1sPid1,                                 // pid
357                 app1Uid,                                   // uid
358                 app1Uid,                                   // packageUid
359                 null,                                      // definingUid
360                 app1sProcessName,                          // processName
361                 0,                                         // connectionGroup
362                 ApplicationExitInfo.REASON_USER_REQUESTED, // reason
363                 null,                                      // subReason
364                 null,                                      // status
365                 app1sPss1,                                 // pss
366                 app1sRss1,                                 // rss
367                 IMPORTANCE_FOREGROUND,                     // importance
368                 null);                                     // description
369 
370         info = list.get(2);
371         assertTrue(ArrayUtils.equals(info.getProcessStateSummary(), app1Cookie1,
372                 app1Cookie1.length));
373 
374         // Case 3: Create an instance of app1 with different user, and died because of SIGKILL
375         sleep(1);
376         final long now3 = System.currentTimeMillis();
377         int sigNum = OsConstants.SIGKILL;
378         app = makeProcessRecord(
379                 app1PidUser2,                           // pid
380                 app1UidUser2,                           // uid
381                 app1UidUser2,                           // packageUid
382                 null,                                   // definingUid
383                 0,                                      // connectionGroup
384                 PROCESS_STATE_BOUND_FOREGROUND_SERVICE, // procstate
385                 app1Pss3,                               // pss
386                 app1Rss3,                               // rss
387                 app1ProcessName,                        // processName
388                 app1PackageName);                       // packageName
389         doReturn(new Pair<Long, Object>(now3, Integer.valueOf(makeSignalStatus(sigNum))))
390                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
391                 .remove(anyInt(), anyInt());
392         updateExitInfo(app, now3);
393         list.clear();
394         mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list);
395 
396         assertEquals(1, list.size());
397 
398         info = list.get(0);
399 
400         verifyApplicationExitInfo(
401                 info,                                // info
402                 now3,                                // timestamp
403                 app1PidUser2,                        // pid
404                 app1UidUser2,                        // uid
405                 app1UidUser2,                        // packageUid
406                 null,                                // definingUid
407                 app1ProcessName,                     // processName
408                 0,                                   // connectionGroup
409                 ApplicationExitInfo.REASON_SIGNALED, // reason
410                 null,                                 // subReason
411                 sigNum,                              // status
412                 app1Pss3,                            // pss
413                 app1Rss3,                            // rss
414                 IMPORTANCE_FOREGROUND_SERVICE,       // importance
415                 null);                               // description
416 
417         // try go get all from app1UidUser2
418         list.clear();
419         mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, 0, 0, list);
420         assertEquals(1, list.size());
421 
422         info = list.get(0);
423 
424         verifyApplicationExitInfo(
425                 info,                                // info
426                 now3,                                // timestamp
427                 app1PidUser2,                        // pid
428                 app1UidUser2,                        // uid
429                 app1UidUser2,                        // packageUid
430                 null,                                // definingUid
431                 app1ProcessName,                     // processName
432                 0,                                   // connectionGroup
433                 ApplicationExitInfo.REASON_SIGNALED, // reason
434                 null,                                // subReason
435                 sigNum,                              // status
436                 app1Pss3,                            // pss
437                 app1Rss3,                            // rss
438                 IMPORTANCE_FOREGROUND_SERVICE,       // importance
439                 null);                               // description
440 
441         // Case 4: Create a process from another package with kill from lmkd
442         final int app2UidUser2 = 1010234;
443         final int app2PidUser2 = 12348;
444         final long app2Pss1 = 54321;
445         final long app2Rss1 = 65432;
446         final String app2ProcessName = "com.android.test.stub2:process";
447         final String app2PackageName = "com.android.test.stub2";
448 
449         sleep(1);
450         final long now4 = System.currentTimeMillis();
451         doReturn(new Pair<Long, Object>(now4, Integer.valueOf(0)))
452                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
453                 .remove(anyInt(), anyInt());
454         doReturn(new Pair<Long, Object>(now4, null))
455                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
456                 .remove(anyInt(), anyInt());
457 
458         app = makeProcessRecord(
459                 app2PidUser2,                // pid
460                 app2UidUser2,                // uid
461                 app2UidUser2,                // packageUid
462                 null,                        // definingUid
463                 0,                           // connectionGroup
464                 PROCESS_STATE_CACHED_EMPTY,  // procstate
465                 app2Pss1,                    // pss
466                 app2Rss1,                    // rss
467                 app2ProcessName,             // processName
468                 app2PackageName);            // packageName
469         updateExitInfo(app, now4);
470         list.clear();
471         mAppExitInfoTracker.getExitInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list);
472         assertEquals(1, list.size());
473 
474         info = list.get(0);
475 
476         verifyApplicationExitInfo(
477                 info,                                     // info
478                 now4,                                     // timestamp
479                 app2PidUser2,                             // pid
480                 app2UidUser2,                             // uid
481                 app2UidUser2,                             // packageUid
482                 null,                                     // definingUid
483                 app2ProcessName,                          // processName
484                 0,                                        // connectionGroup
485                 ApplicationExitInfo.REASON_LOW_MEMORY,    // reason
486                 null,                                     // subReason
487                 0,                                        // status
488                 app2Pss1,                                 // pss
489                 app2Rss1,                                 // rss
490                 IMPORTANCE_CACHED,                        // importance
491                 null);                                    // description
492 
493         // Verify to get all from User2 regarding app2
494         list.clear();
495         mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, 0, list);
496         assertEquals(1, list.size());
497 
498         // Case 5: App native crash
499         final int app3UidUser2 = 1010345;
500         final int app3PidUser2 = 12349;
501         final int app3ConnectiongGroup = 4;
502         final long app3Pss1 = 54320;
503         final long app3Rss1 = 65430;
504         final String app3ProcessName = "com.android.test.stub3:process";
505         final String app3PackageName = "com.android.test.stub3";
506         final String app3Description = "native crash";
507 
508         sleep(1);
509         final long now5 = System.currentTimeMillis();
510         sigNum = OsConstants.SIGABRT;
511         doReturn(new Pair<Long, Object>(now5, Integer.valueOf(makeSignalStatus(sigNum))))
512                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
513                 .remove(anyInt(), anyInt());
514         doReturn(null)
515                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
516                 .remove(anyInt(), anyInt());
517         app = makeProcessRecord(
518                 app3PidUser2,            // pid
519                 app3UidUser2,            // uid
520                 app3UidUser2,            // packageUid
521                 null,                    // definingUid
522                 app3ConnectiongGroup,    // connectionGroup
523                 PROCESS_STATE_BOUND_TOP, // procstate
524                 app3Pss1,                // pss
525                 app3Rss1,                // rss
526                 app3ProcessName,         // processName
527                 app3PackageName);        // packageName
528         noteAppKill(app, ApplicationExitInfo.REASON_CRASH_NATIVE,
529                 ApplicationExitInfo.SUBREASON_UNKNOWN, app3Description, now5);
530 
531         updateExitInfo(app, now5);
532         list.clear();
533         mAppExitInfoTracker.getExitInfo(app3PackageName, app3UidUser2, app3PidUser2, 0, list);
534         assertEquals(1, list.size());
535 
536         info = list.get(0);
537 
538         verifyApplicationExitInfo(
539                 info,                                            // info
540                 now5,                                            // timestamp
541                 app3PidUser2,                                    // pid
542                 app3UidUser2,                                    // uid
543                 app3UidUser2,                                    // packageUid
544                 null,                                            // definingUid
545                 app3ProcessName,                                 // processName
546                 app3ConnectiongGroup,                            // connectionGroup
547                 ApplicationExitInfo.REASON_CRASH_NATIVE,         // reason
548                 null,                                            // subReason
549                 sigNum,                                          // status
550                 app3Pss1,                                        // pss
551                 app3Rss1,                                        // rss
552                 IMPORTANCE_FOREGROUND,                           // importance
553                 app3Description);                                // description
554 
555         // Verify the most recent kills, sorted by timestamp
556         int maxNum = 3;
557         list.clear();
558         mAppExitInfoTracker.getExitInfo(null, app3UidUser2, 0, maxNum, list);
559         assertEquals(1, list.size());
560 
561         info = list.get(0);
562 
563         verifyApplicationExitInfo(
564                 info,                                            // info
565                 now5,                                            // timestamp
566                 app3PidUser2,                                    // pid
567                 app3UidUser2,                                    // uid
568                 app3UidUser2,                                    // packageUid
569                 null,                                            // definingUid
570                 app3ProcessName,                                 // processName
571                 app3ConnectiongGroup,                            // connectionGroup
572                 ApplicationExitInfo.REASON_CRASH_NATIVE,         // reason
573                 null,                                            // subReason
574                 sigNum,                                          // status
575                 app3Pss1,                                        // pss
576                 app3Rss1,                                        // rss
577                 IMPORTANCE_FOREGROUND,                           // importance
578                 app3Description);                                // description
579 
580         list.clear();
581         mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, maxNum, list);
582         assertEquals(1, list.size());
583         info = list.get(0);
584 
585         verifyApplicationExitInfo(
586                 info,                                     // info
587                 now4,                                     // timestamp
588                 app2PidUser2,                             // pid
589                 app2UidUser2,                             // uid
590                 app2UidUser2,                             // packageUid
591                 null,                                     // definingUid
592                 app2ProcessName,                          // processName
593                 0,                                        // connectionGroup
594                 ApplicationExitInfo.REASON_LOW_MEMORY,    // reason
595                 null,                                     // subReason
596                 0,                                        // status
597                 app2Pss1,                                 // pss
598                 app2Rss1,                                 // rss
599                 IMPORTANCE_CACHED,                        // importance
600                 null);                                    // description
601 
602         list.clear();
603         mAppExitInfoTracker.getExitInfo(null, app1UidUser2, 0, maxNum, list);
604         assertEquals(1, list.size());
605         info = list.get(0);
606 
607         sigNum = OsConstants.SIGKILL;
608         verifyApplicationExitInfo(
609                 info,                                // info
610                 now3,                                // timestamp
611                 app1PidUser2,                        // pid
612                 app1UidUser2,                        // uid
613                 app1UidUser2,                        // packageUid
614                 null,                                // definingUid
615                 app1ProcessName,                     // processName
616                 0,                                   // connectionGroup
617                 ApplicationExitInfo.REASON_SIGNALED, // reason
618                 null,                                // subReason
619                 sigNum,                              // status
620                 app1Pss3,                            // pss
621                 app1Rss3,                            // rss
622                 IMPORTANCE_FOREGROUND_SERVICE,       // importance
623                 null);                               // description
624 
625         // Case 6: App Java crash
626         final int app3Uid = 10345;
627         final int app3IsolatedUid = 99001; // it's an isolated process
628         final int app3Pid = 12350;
629         final long app3Pss2 = 23232;
630         final long app3Rss2 = 32323;
631         final String app3Description2 = "force close";
632 
633         sleep(1);
634         final long now6 = System.currentTimeMillis();
635         doReturn(null)
636                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
637                 .remove(anyInt(), anyInt());
638         doReturn(null)
639                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
640                 .remove(anyInt(), anyInt());
641         app = makeProcessRecord(
642                 app3Pid,                     // pid
643                 app3IsolatedUid,             // uid
644                 app3Uid,                     // packageUid
645                 null,                        // definingUid
646                 0,                           // connectionGroup
647                 PROCESS_STATE_CACHED_EMPTY,  // procstate
648                 app3Pss2,                    // pss
649                 app3Rss2,                    // rss
650                 app3ProcessName,             // processName
651                 app3PackageName);            // packageName
652         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app3IsolatedUid, app3Uid);
653         noteAppKill(app, ApplicationExitInfo.REASON_CRASH,
654                 ApplicationExitInfo.SUBREASON_UNKNOWN, app3Description2, now6);
655 
656         assertEquals(app3Uid, mAppExitInfoTracker.mIsolatedUidRecords
657                 .getUidByIsolatedUid(app3IsolatedUid).longValue());
658         updateExitInfo(app, now6);
659         assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(app3IsolatedUid));
660 
661         list.clear();
662         mAppExitInfoTracker.getExitInfo(app3PackageName, app3Uid, 0, 1, list);
663         assertEquals(1, list.size());
664 
665         info = list.get(0);
666 
667         verifyApplicationExitInfo(
668                 info,                                     // info
669                 now6,                                     // timestamp
670                 app3Pid,                                  // pid
671                 app3IsolatedUid,                          // uid
672                 app3Uid,                                  // packageUid
673                 null,                                     // definingUid
674                 app3ProcessName,                          // processName
675                 0,                                        // connectionGroup
676                 ApplicationExitInfo.REASON_CRASH,         // reason
677                 null,                                     // subReason
678                 0,                                        // status
679                 app3Pss2,                                 // pss
680                 app3Rss2,                                 // rss
681                 IMPORTANCE_CACHED,                        // importance
682                 app3Description2);                        // description
683 
684         // Case 7: App1 is "uninstalled" from User2
685         mAppExitInfoTracker.onPackageRemoved(app1PackageName, app1UidUser2, false);
686         list.clear();
687         mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, 0, 0, list);
688         assertEquals(0, list.size());
689 
690         list.clear();
691         mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, 0, 0, list);
692         assertEquals(2, list.size());
693 
694         info = list.get(0);
695 
696         verifyApplicationExitInfo(
697                 info,                                 // info
698                 now2,                                 // timestamp
699                 app1Pid2,                             // pid
700                 app1Uid,                              // uid
701                 app1Uid,                              // packageUid
702                 app1DefiningUid,                      // definingUid
703                 app1ProcessName,                      // processName
704                 app1ConnectiongGroup,                 // connectionGroup
705                 ApplicationExitInfo.REASON_EXIT_SELF, // reason
706                 null,                                 // subReason
707                 exitCode,                             // status
708                 app1Pss2,                             // pss
709                 app1Rss2,                             // rss
710                 IMPORTANCE_SERVICE,                   // importance
711                 null);                                // description
712 
713         // Case 8: App1 gets "remove task"
714         final String app1Description = "remove task";
715 
716         sleep(1);
717         final int app1IsolatedUidUser2 = 1099002; // isolated uid
718         final long app1Pss4 = 34343;
719         final long app1Rss4 = 43434;
720         final long now8 = System.currentTimeMillis();
721         sigNum = OsConstants.SIGKILL;
722         doReturn(new Pair<Long, Object>(now8, makeSignalStatus(sigNum)))
723                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
724                 .remove(anyInt(), anyInt());
725         doReturn(null)
726                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
727                 .remove(anyInt(), anyInt());
728         app = makeProcessRecord(
729                 app1PidUser2,                 // pid
730                 app1IsolatedUidUser2,         // uid
731                 app1UidUser2,                 // packageUid
732                 null,                         // definingUid
733                 0,                            // connectionGroup
734                 PROCESS_STATE_CACHED_EMPTY,   // procstate
735                 app1Pss4,                     // pss
736                 app1Rss4,                     // rss
737                 app1ProcessName,              // processName
738                 app1PackageName);             // packageName
739 
740         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUidUser2, app1UidUser2);
741         noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
742                 ApplicationExitInfo.SUBREASON_UNKNOWN, app1Description, now8);
743 
744         updateExitInfo(app, now8);
745         list.clear();
746         mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1PidUser2, 1, list);
747         assertEquals(1, list.size());
748 
749         info = list.get(0);
750 
751         verifyApplicationExitInfo(
752                 info,                                     // info
753                 now8,                                     // timestamp
754                 app1PidUser2,                             // pid
755                 app1IsolatedUidUser2,                     // uid
756                 app1UidUser2,                             // packageUid
757                 null,                                     // definingUid
758                 app1ProcessName,                          // processName
759                 0,                                        // connectionGroup
760                 ApplicationExitInfo.REASON_OTHER,         // reason
761                 ApplicationExitInfo.SUBREASON_UNKNOWN,    // subReason
762                 0,                                        // status
763                 app1Pss4,                                 // pss
764                 app1Rss4,                                 // rss
765                 IMPORTANCE_CACHED,                        // importance
766                 app1Description);                         // description
767 
768         // App1 gets "too many empty"
769         final String app1Description2 = "too many empty";
770         sleep(1);
771         final int app1Pid2User2 = 56565;
772         final int app1IsolatedUid2User2 = 1099003; // isolated uid
773         final long app1Pss5 = 34344;
774         final long app1Rss5 = 43435;
775         final long now9 = System.currentTimeMillis();
776         sigNum = OsConstants.SIGKILL;
777         doReturn(new Pair<Long, Object>(now9, makeSignalStatus(sigNum)))
778                 .when(mAppExitInfoTracker.mAppExitInfoSourceZygote)
779                 .remove(anyInt(), anyInt());
780         doReturn(null)
781                 .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd)
782                 .remove(anyInt(), anyInt());
783         app = makeProcessRecord(
784                 app1Pid2User2,                // pid
785                 app1IsolatedUid2User2,        // uid
786                 app1UidUser2,                 // packageUid
787                 null,                         // definingUid
788                 0,                            // connectionGroup
789                 PROCESS_STATE_CACHED_EMPTY,   // procstate
790                 app1Pss5,                     // pss
791                 app1Rss5,                     // rss
792                 app1ProcessName,              // processName
793                 app1PackageName);             // packageName
794 
795         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUid2User2, app1UidUser2);
796 
797         // Pretent it gets an ANR trace too (although the reason here should be REASON_ANR)
798         final File traceFile = new File(mContext.getFilesDir(), "anr_original.txt");
799         final int traceSize = 10240;
800         final int traceStart = 1024;
801         final int traceEnd = 8192;
802         createRandomFile(traceFile, traceSize);
803         assertEquals(traceSize, traceFile.length());
804         mAppExitInfoTracker.handleLogAnrTrace(app.getPid(), app.uid, app.getPackageList(),
805                 traceFile, traceStart, traceEnd);
806 
807         noteAppKill(app, ApplicationExitInfo.REASON_OTHER,
808                 ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, app1Description2, now9);
809         updateExitInfo(app, now9);
810         list.clear();
811         mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1Pid2User2, 1, list);
812         assertEquals(1, list.size());
813 
814         info = list.get(0);
815 
816         verifyApplicationExitInfo(
817                 info,                                         // info
818                 now9,                                         // timestamp
819                 app1Pid2User2,                                // pid
820                 app1IsolatedUid2User2,                        // uid
821                 app1UidUser2,                                 // packageUid
822                 null,                                         // definingUid
823                 app1ProcessName,                              // processName
824                 0,                                            // connectionGroup
825                 ApplicationExitInfo.REASON_OTHER,             // reason
826                 ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, // subReason
827                 0,                                            // status
828                 app1Pss5,                                     // pss
829                 app1Rss5,                                     // rss
830                 IMPORTANCE_CACHED,                            // importance
831                 app1Description2);                            // description
832 
833         // Verify if the traceFile get copied into the records correctly.
834         verifyTraceFile(traceFile, traceStart, info.getTraceFile(), 0, traceEnd - traceStart);
835         traceFile.delete();
836         info.getTraceFile().delete();
837 
838         // Case 9: User2 gets removed
839         sleep(1);
840         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUidUser2, app1UidUser2);
841         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app3IsolatedUid, app3Uid);
842 
843         mAppExitInfoTracker.onUserRemoved(UserHandle.getUserId(app1UidUser2));
844 
845         assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
846                 app1IsolatedUidUser2));
847         assertNotNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
848                 app3IsolatedUid));
849         mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(
850                 app1IsolatedUidUser2, app1UidUser2);
851         mAppExitInfoTracker.mIsolatedUidRecords.removeAppUid(app1UidUser2, false);
852         assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(
853                 app1IsolatedUidUser2));
854         mAppExitInfoTracker.mIsolatedUidRecords.removeAppUid(app3Uid, true);
855         assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(app3IsolatedUid));
856 
857         list.clear();
858         mAppExitInfoTracker.getExitInfo(null, app1UidUser2, 0, 0, list);
859         assertEquals(0, list.size());
860 
861         list.clear();
862         mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
863         assertEquals(3, list.size());
864 
865         info = list.get(1);
866 
867         exitCode = 6;
868         verifyApplicationExitInfo(
869                 info,                                 // info
870                 now2,                                 // timestamp
871                 app1Pid2,                             // pid
872                 app1Uid,                              // uid
873                 app1Uid,                              // packageUid
874                 app1DefiningUid,                      // definingUid
875                 app1ProcessName,                      // processName
876                 app1ConnectiongGroup,                 // connectionGroup
877                 ApplicationExitInfo.REASON_EXIT_SELF, // reason
878                 null,                                 // subReason
879                 exitCode,                             // status
880                 app1Pss2,                             // pss
881                 app1Rss2,                             // rss
882                 IMPORTANCE_SERVICE,                   // importance
883                 null);                                // description
884 
885         info = list.get(0);
886         verifyApplicationExitInfo(
887                 info,                                      // info
888                 now1s,                                     // timestamp
889                 app1sPid1,                                 // pid
890                 app1Uid,                                   // uid
891                 app1Uid,                                   // packageUid
892                 null,                                      // definingUid
893                 app1sProcessName,                          // processName
894                 0,                                         // connectionGroup
895                 ApplicationExitInfo.REASON_USER_REQUESTED, // reason
896                 null,                                      // subReason
897                 null,                                      // status
898                 app1sPss1,                                 // pss
899                 app1sRss1,                                 // rss
900                 IMPORTANCE_FOREGROUND,                     // importance
901                 null);                                     // description
902 
903         info = list.get(2);
904         exitCode = 5;
905         verifyApplicationExitInfo(
906                 info,                                 // info
907                 now1,                                 // timestamp
908                 app1Pid1,                             // pid
909                 app1Uid,                              // uid
910                 app1Uid,                              // packageUid
911                 null,                                 // definingUid
912                 app1ProcessName,                      // processName
913                 0,                                    // connectionGroup
914                 ApplicationExitInfo.REASON_EXIT_SELF, // reason
915                 null,                                 // subReason
916                 exitCode,                             // status
917                 app1Pss1,                             // pss
918                 app1Rss1,                             // rss
919                 IMPORTANCE_CACHED,                    // importance
920                 null);                                // description
921 
922         // Case 10: Save the info and load them again
923         ArrayList<ApplicationExitInfo> original = new ArrayList<ApplicationExitInfo>();
924         mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, original);
925         assertTrue(original.size() > 0);
926 
927         mAppExitInfoTracker.persistProcessExitInfo();
928         assertTrue(mAppExitInfoTracker.mProcExitInfoFile.exists());
929 
930         mAppExitInfoTracker.clearProcessExitInfo(false);
931         list.clear();
932         mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
933         assertEquals(0, list.size());
934 
935         mAppExitInfoTracker.loadExistingProcessExitInfo();
936         list.clear();
937         mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list);
938         assertEquals(original.size(), list.size());
939 
940         for (int i = list.size() - 1; i >= 0; i--) {
941             assertTrue(list.get(i).equals(original.get(i)));
942         }
943     }
944 
makeExitStatus(int exitCode)945     private static int makeExitStatus(int exitCode) {
946         return (exitCode << 8) & 0xff00;
947     }
948 
makeSignalStatus(int sigNum)949     private static int makeSignalStatus(int sigNum) {
950         return sigNum & 0x7f;
951     }
952 
sleep(long ms)953     private void sleep(long ms) {
954         try {
955             Thread.sleep(ms);
956         } catch (InterruptedException e) {
957         }
958     }
959 
createRandomFile(File file, int size)960     private static void createRandomFile(File file, int size) throws IOException {
961         try (BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file))) {
962             Random random = new Random();
963             byte[] buf = random.ints('a', 'z').limit(size).collect(
964                     StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
965                     .toString().getBytes();
966             out.write(buf);
967         }
968     }
969 
verifyTraceFile(File originFile, int originStart, File traceFile, int traceStart, int length)970     private static void verifyTraceFile(File originFile, int originStart, File traceFile,
971             int traceStart, int length) throws IOException {
972         assertTrue(originFile.exists());
973         assertTrue(traceFile.exists());
974         assertTrue(originStart < originFile.length());
975         try (GZIPInputStream traceIn = new GZIPInputStream(new FileInputStream(traceFile));
976             BufferedInputStream originIn = new BufferedInputStream(
977                     new FileInputStream(originFile))) {
978             assertEquals(traceStart, traceIn.skip(traceStart));
979             assertEquals(originStart, originIn.skip(originStart));
980             byte[] buf1 = new byte[8192];
981             byte[] buf2 = new byte[8192];
982             while (length > 0) {
983                 int len = traceIn.read(buf1, 0, Math.min(buf1.length, length));
984                 assertEquals(len, originIn.read(buf2, 0, len));
985                 assertTrue(ArrayUtils.equals(buf1, buf2, len));
986                 length -= len;
987             }
988         }
989     }
990 
makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid, int connectionGroup, int procState, long pss, long rss, String processName, String packageName)991     private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid,
992             int connectionGroup, int procState, long pss, long rss,
993             String processName, String packageName) {
994         ApplicationInfo ai = new ApplicationInfo();
995         ai.packageName = packageName;
996         ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid);
997         app.setPid(pid);
998         app.info.uid = packageUid;
999         if (definingUid != null) {
1000             final String dummyPackageName = "com.android.test";
1001             final String dummyClassName = ".Foo";
1002             app.setHostingRecord(HostingRecord.byAppZygote(new ComponentName(
1003                     dummyPackageName, dummyClassName), "", definingUid, ""));
1004         }
1005         app.mServices.setConnectionGroup(connectionGroup);
1006         app.mState.setReportedProcState(procState);
1007         app.mProfile.setLastMemInfo(spy(new Debug.MemoryInfo()));
1008         app.mProfile.setLastPss(pss);
1009         app.mProfile.setLastRss(rss);
1010         return app;
1011     }
1012 
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)1013     private void verifyApplicationExitInfo(ApplicationExitInfo info,
1014             Long timestamp, Integer pid, Integer uid, Integer packageUid,
1015             Integer definingUid, String processName, Integer connectionGroup,
1016             Integer reason, Integer subReason, Integer status,
1017             Long pss, Long rss, Integer importance, String description) {
1018         assertNotNull(info);
1019 
1020         if (timestamp != null) {
1021             final long tolerance = 10000; // ms
1022             assertTrue(timestamp - tolerance <= info.getTimestamp());
1023             assertTrue(timestamp + tolerance >= info.getTimestamp());
1024         }
1025         if (pid != null) {
1026             assertEquals(pid.intValue(), info.getPid());
1027         }
1028         if (uid != null) {
1029             assertEquals(uid.intValue(), info.getRealUid());
1030         }
1031         if (packageUid != null) {
1032             assertEquals(packageUid.intValue(), info.getPackageUid());
1033         }
1034         if (definingUid != null) {
1035             assertEquals(definingUid.intValue(), info.getDefiningUid());
1036         }
1037         if (processName != null) {
1038             assertTrue(TextUtils.equals(processName, info.getProcessName()));
1039         }
1040         if (connectionGroup != null) {
1041             assertEquals(connectionGroup.intValue(), info.getConnectionGroup());
1042         }
1043         if (reason != null) {
1044             assertEquals(reason.intValue(), info.getReason());
1045         }
1046         if (subReason != null) {
1047             assertEquals(subReason.intValue(), info.getSubReason());
1048         }
1049         if (status != null) {
1050             assertEquals(status.intValue(), info.getStatus());
1051         }
1052         if (pss != null) {
1053             assertEquals(pss.longValue(), info.getPss());
1054         }
1055         if (rss != null) {
1056             assertEquals(rss.longValue(), info.getRss());
1057         }
1058         if (importance != null) {
1059             assertEquals(importance.intValue(), info.getImportance());
1060         }
1061         if (description != null) {
1062             assertTrue(TextUtils.equals(description, info.getDescription()));
1063         }
1064     }
1065 
1066     private class TestInjector extends Injector {
TestInjector(Context context)1067         TestInjector(Context context) {
1068             super(context);
1069         }
1070 
1071         @Override
getAppOpsService(File file, Handler handler)1072         public AppOpsService getAppOpsService(File file, Handler handler) {
1073             return mAppOpsService;
1074         }
1075 
1076         @Override
getUiHandler(ActivityManagerService service)1077         public Handler getUiHandler(ActivityManagerService service) {
1078             return mHandler;
1079         }
1080 
1081         @Override
getProcessList(ActivityManagerService service)1082         public ProcessList getProcessList(ActivityManagerService service) {
1083             return mProcessList;
1084         }
1085     }
1086 
1087     static class ServiceThreadRule implements TestRule {
1088 
1089         private ServiceThread mThread;
1090 
getThread()1091         ServiceThread getThread() {
1092             return mThread;
1093         }
1094 
1095         @Override
apply(Statement base, Description description)1096         public Statement apply(Statement base, Description description) {
1097             return new Statement() {
1098                 @Override
1099                 public void evaluate() throws Throwable {
1100                     mThread = new ServiceThread("TestServiceThread",
1101                             Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
1102                     mThread.start();
1103                     try {
1104                         base.evaluate();
1105                     } finally {
1106                         mThread.getThreadHandler().runWithScissors(mThread::quit, 0 /* timeout */);
1107                     }
1108                 }
1109             };
1110         }
1111     }
1112 }
1113