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