| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.server.am; |
| |
| import static android.app.ActivityManager.PROCESS_STATE_BOUND_FOREGROUND_SERVICE; |
| import static android.app.ActivityManager.PROCESS_STATE_BOUND_TOP; |
| import static android.app.ActivityManager.PROCESS_STATE_CACHED_EMPTY; |
| import static android.app.ActivityManager.PROCESS_STATE_LAST_ACTIVITY; |
| import static android.app.ActivityManager.PROCESS_STATE_RECEIVER; |
| import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_CACHED; |
| import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; |
| import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND_SERVICE; |
| import static android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE; |
| |
| import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; |
| |
| import static com.android.server.am.ActivityManagerService.Injector; |
| |
| import static org.junit.Assert.assertEquals; |
| import static org.junit.Assert.assertNotNull; |
| import static org.junit.Assert.assertNull; |
| import static org.junit.Assert.assertTrue; |
| import static org.mockito.Matchers.anyBoolean; |
| import static org.mockito.Matchers.anyInt; |
| import static org.mockito.Mockito.doNothing; |
| import static org.mockito.Mockito.doReturn; |
| import static org.mockito.Mockito.spy; |
| |
| import android.app.ApplicationExitInfo; |
| import android.content.ComponentName; |
| import android.content.Context; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.PackageManagerInternal; |
| import android.os.Debug; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Process; |
| import android.os.UserHandle; |
| import android.platform.test.annotations.Presubmit; |
| import android.system.OsConstants; |
| import android.text.TextUtils; |
| import android.util.Pair; |
| |
| import com.android.server.ServiceThread; |
| import com.android.server.appop.AppOpsService; |
| import com.android.server.wm.ActivityTaskManagerService; |
| |
| import org.junit.After; |
| import org.junit.Before; |
| import org.junit.BeforeClass; |
| import org.junit.Rule; |
| import org.junit.Test; |
| import org.junit.rules.TestRule; |
| import org.junit.runner.Description; |
| import org.junit.runners.model.Statement; |
| import org.mockito.Mock; |
| import org.mockito.MockitoAnnotations; |
| |
| import java.io.File; |
| import java.lang.reflect.Field; |
| import java.lang.reflect.Modifier; |
| import java.util.ArrayList; |
| |
| /** |
| * Test class for {@link android.app.ApplicationExitInfo}. |
| * |
| * Build/Install/Run: |
| * atest ApplicationExitInfoTest |
| */ |
| @Presubmit |
| public class ApplicationExitInfoTest { |
| private static final String TAG = ApplicationExitInfoTest.class.getSimpleName(); |
| |
| @Rule public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule(); |
| @Mock private AppOpsService mAppOpsService; |
| @Mock private PackageManagerInternal mPackageManagerInt; |
| |
| private Context mContext = getInstrumentation().getTargetContext(); |
| private TestInjector mInjector; |
| private ActivityManagerService mAms; |
| private ProcessList mProcessList; |
| private AppExitInfoTracker mAppExitInfoTracker; |
| private Handler mHandler; |
| private HandlerThread mHandlerThread; |
| |
| @BeforeClass |
| public static void setUpOnce() { |
| System.setProperty("dexmaker.share_classloader", "true"); |
| } |
| |
| @Before |
| public void setUp() { |
| MockitoAnnotations.initMocks(this); |
| |
| mHandlerThread = new HandlerThread(TAG); |
| mHandlerThread.start(); |
| mHandler = new Handler(mHandlerThread.getLooper()); |
| mProcessList = spy(new ProcessList()); |
| mAppExitInfoTracker = spy(new AppExitInfoTracker()); |
| setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mIsolatedUidRecords", |
| spy(mAppExitInfoTracker.new IsolatedUidRecords())); |
| setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppExitInfoSourceZygote", |
| spy(mAppExitInfoTracker.new AppExitInfoExternalSource("zygote", null))); |
| setFieldValue(AppExitInfoTracker.class, mAppExitInfoTracker, "mAppExitInfoSourceLmkd", |
| spy(mAppExitInfoTracker.new AppExitInfoExternalSource("lmkd", |
| ApplicationExitInfo.REASON_LOW_MEMORY))); |
| setFieldValue(ProcessList.class, mProcessList, "mAppExitInfoTracker", mAppExitInfoTracker); |
| mInjector = new TestInjector(mContext); |
| mAms = new ActivityManagerService(mInjector, mServiceThreadRule.getThread()); |
| mAms.mActivityTaskManager = new ActivityTaskManagerService(mContext); |
| mAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper()); |
| mAms.mAtmInternal = spy(mAms.mActivityTaskManager.getAtmInternal()); |
| mAms.mPackageManagerInt = mPackageManagerInt; |
| } |
| |
| @After |
| public void tearDown() { |
| mHandlerThread.quit(); |
| } |
| |
| private static <T> void setFieldValue(Class clazz, Object obj, String fieldName, T val) { |
| try { |
| Field field = clazz.getDeclaredField(fieldName); |
| field.setAccessible(true); |
| Field mfield = Field.class.getDeclaredField("accessFlags"); |
| mfield.setAccessible(true); |
| mfield.setInt(field, mfield.getInt(field) & ~(Modifier.FINAL | Modifier.PRIVATE)); |
| field.set(obj, val); |
| } catch (NoSuchFieldException | IllegalAccessException e) { |
| } |
| } |
| |
| private void updateExitInfo(ProcessRecord app) { |
| ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecordLocked(app); |
| mAppExitInfoTracker.handleNoteProcessDiedLocked(raw); |
| mAppExitInfoTracker.recycleRawRecordLocked(raw); |
| } |
| |
| private void noteAppKill(ProcessRecord app, int reason, int subReason, String msg) { |
| ApplicationExitInfo raw = mAppExitInfoTracker.obtainRawRecordLocked(app); |
| raw.setReason(reason); |
| raw.setSubReason(subReason); |
| raw.setDescription(msg); |
| mAppExitInfoTracker.handleNoteAppKillLocked(raw); |
| mAppExitInfoTracker.recycleRawRecordLocked(raw); |
| } |
| |
| @Test |
| public void testApplicationExitInfo() throws Exception { |
| mAppExitInfoTracker.clearProcessExitInfo(true); |
| mAppExitInfoTracker.mAppExitInfoLoaded = true; |
| |
| // Test application calls System.exit() |
| doNothing().when(mAppExitInfoTracker).schedulePersistProcessExitInfo(anyBoolean()); |
| |
| final int app1Uid = 10123; |
| final int app1Pid1 = 12345; |
| final int app1Pid2 = 12346; |
| final int app1DefiningUid = 23456; |
| final int app1ConnectiongGroup = 10; |
| final int app1UidUser2 = 1010123; |
| final int app1PidUser2 = 12347; |
| final long app1Pss1 = 34567; |
| final long app1Rss1 = 45678; |
| final long app1Pss2 = 34568; |
| final long app1Rss2 = 45679; |
| final long app1Pss3 = 34569; |
| final long app1Rss3 = 45680; |
| final String app1ProcessName = "com.android.test.stub1:process"; |
| final String app1PackageName = "com.android.test.stub1"; |
| |
| final long now1 = System.currentTimeMillis(); |
| ProcessRecord app = makeProcessRecord( |
| app1Pid1, // pid |
| app1Uid, // uid |
| app1Uid, // packageUid |
| null, // definingUid |
| 0, // connectionGroup |
| PROCESS_STATE_LAST_ACTIVITY, // procstate |
| app1Pss1, // pss |
| app1Rss1, // rss |
| app1ProcessName, // processName |
| app1PackageName); // packageName |
| |
| // Case 1: basic System.exit() test |
| int exitCode = 5; |
| doReturn(new Pair<Long, Object>(now1, Integer.valueOf(makeExitStatus(exitCode)))) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceZygote) |
| .remove(anyInt(), anyInt()); |
| doReturn(null) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd) |
| .remove(anyInt(), anyInt()); |
| updateExitInfo(app); |
| |
| ArrayList<ApplicationExitInfo> list = new ArrayList<ApplicationExitInfo>(); |
| mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, app1Pid1, 0, list); |
| assertEquals(1, list.size()); |
| |
| ApplicationExitInfo info = list.get(0); |
| |
| verifyApplicationExitInfo( |
| info, // info |
| now1, // timestamp |
| app1Pid1, // pid |
| app1Uid, // uid |
| app1Uid, // packageUid |
| null, // definingUid |
| app1ProcessName, // processName |
| 0, // connectionGroup |
| ApplicationExitInfo.REASON_EXIT_SELF, // reason |
| null, // subReason |
| exitCode, // status |
| app1Pss1, // pss |
| app1Rss1, // rss |
| IMPORTANCE_CACHED, // importance |
| null); // description |
| |
| // Case 2: create another app1 process record with a different pid |
| sleep(1); |
| final long now2 = System.currentTimeMillis(); |
| app = makeProcessRecord( |
| app1Pid2, // pid |
| app1Uid, // uid |
| app1Uid, // packageUid |
| app1DefiningUid, // definingUid |
| app1ConnectiongGroup, // connectionGroup |
| PROCESS_STATE_RECEIVER, // procstate |
| app1Pss2, // pss |
| app1Rss2, // rss |
| app1ProcessName, // processName |
| app1PackageName); // packageName |
| exitCode = 6; |
| doReturn(new Pair<Long, Object>(now2, Integer.valueOf(makeExitStatus(exitCode)))) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceZygote) |
| .remove(anyInt(), anyInt()); |
| updateExitInfo(app); |
| list.clear(); |
| |
| // Get all the records for app1Uid |
| mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, 0, 0, list); |
| assertEquals(2, list.size()); |
| |
| info = list.get(0); |
| |
| // Verify the most recent one |
| verifyApplicationExitInfo( |
| info, // info |
| now2, // timestamp |
| app1Pid2, // pid |
| app1Uid, // uid |
| app1Uid, // packageUid |
| app1DefiningUid, // definingUid |
| app1ProcessName, // processName |
| app1ConnectiongGroup, // connectionGroup |
| ApplicationExitInfo.REASON_EXIT_SELF, // reason |
| null, // subReason |
| exitCode, // status |
| app1Pss2, // pss |
| app1Rss2, // rss |
| IMPORTANCE_SERVICE, // importance |
| null); // description |
| |
| // Case 3: Create an instance of app1 with different user, and died because of SIGKILL |
| sleep(1); |
| final long now3 = System.currentTimeMillis(); |
| int sigNum = OsConstants.SIGKILL; |
| app = makeProcessRecord( |
| app1PidUser2, // pid |
| app1UidUser2, // uid |
| app1UidUser2, // packageUid |
| null, // definingUid |
| 0, // connectionGroup |
| PROCESS_STATE_BOUND_FOREGROUND_SERVICE, // procstate |
| app1Pss3, // pss |
| app1Rss3, // rss |
| app1ProcessName, // processName |
| app1PackageName); // packageName |
| doReturn(new Pair<Long, Object>(now3, Integer.valueOf(makeSignalStatus(sigNum)))) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceZygote) |
| .remove(anyInt(), anyInt()); |
| updateExitInfo(app); |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1PidUser2, 0, list); |
| |
| assertEquals(1, list.size()); |
| |
| info = list.get(0); |
| |
| verifyApplicationExitInfo( |
| info, // info |
| now3, // timestamp |
| app1PidUser2, // pid |
| app1UidUser2, // uid |
| app1UidUser2, // packageUid |
| null, // definingUid |
| app1ProcessName, // processName |
| 0, // connectionGroup |
| ApplicationExitInfo.REASON_SIGNALED, // reason |
| null, // subReason |
| sigNum, // status |
| app1Pss3, // pss |
| app1Rss3, // rss |
| IMPORTANCE_FOREGROUND_SERVICE, // importance |
| null); // description |
| |
| // try go get all from app1UidUser2 |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, 0, 0, list); |
| assertEquals(1, list.size()); |
| |
| info = list.get(0); |
| |
| verifyApplicationExitInfo( |
| info, // info |
| now3, // timestamp |
| app1PidUser2, // pid |
| app1UidUser2, // uid |
| app1UidUser2, // packageUid |
| null, // definingUid |
| app1ProcessName, // processName |
| 0, // connectionGroup |
| ApplicationExitInfo.REASON_SIGNALED, // reason |
| null, // subReason |
| sigNum, // status |
| app1Pss3, // pss |
| app1Rss3, // rss |
| IMPORTANCE_FOREGROUND_SERVICE, // importance |
| null); // description |
| |
| // Case 4: Create a process from another package with kill from lmkd |
| final int app2UidUser2 = 1010234; |
| final int app2PidUser2 = 12348; |
| final long app2Pss1 = 54321; |
| final long app2Rss1 = 65432; |
| final String app2ProcessName = "com.android.test.stub2:process"; |
| final String app2PackageName = "com.android.test.stub2"; |
| |
| sleep(1); |
| final long now4 = System.currentTimeMillis(); |
| doReturn(new Pair<Long, Object>(now4, Integer.valueOf(0))) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceZygote) |
| .remove(anyInt(), anyInt()); |
| doReturn(new Pair<Long, Object>(now4, null)) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd) |
| .remove(anyInt(), anyInt()); |
| |
| app = makeProcessRecord( |
| app2PidUser2, // pid |
| app2UidUser2, // uid |
| app2UidUser2, // packageUid |
| null, // definingUid |
| 0, // connectionGroup |
| PROCESS_STATE_CACHED_EMPTY, // procstate |
| app2Pss1, // pss |
| app2Rss1, // rss |
| app2ProcessName, // processName |
| app2PackageName); // packageName |
| updateExitInfo(app); |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(app2PackageName, app2UidUser2, app2PidUser2, 0, list); |
| assertEquals(1, list.size()); |
| |
| info = list.get(0); |
| |
| verifyApplicationExitInfo( |
| info, // info |
| now4, // timestamp |
| app2PidUser2, // pid |
| app2UidUser2, // uid |
| app2UidUser2, // packageUid |
| null, // definingUid |
| app2ProcessName, // processName |
| 0, // connectionGroup |
| ApplicationExitInfo.REASON_LOW_MEMORY, // reason |
| null, // subReason |
| 0, // status |
| app2Pss1, // pss |
| app2Rss1, // rss |
| IMPORTANCE_CACHED, // importance |
| null); // description |
| |
| // Verify to get all from User2 regarding app2 |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, 0, list); |
| assertEquals(1, list.size()); |
| |
| // Case 5: App native crash |
| final int app3UidUser2 = 1010345; |
| final int app3PidUser2 = 12349; |
| final int app3ConnectiongGroup = 4; |
| final long app3Pss1 = 54320; |
| final long app3Rss1 = 65430; |
| final String app3ProcessName = "com.android.test.stub3:process"; |
| final String app3PackageName = "com.android.test.stub3"; |
| final String app3Description = "native crash"; |
| |
| sleep(1); |
| final long now5 = System.currentTimeMillis(); |
| sigNum = OsConstants.SIGABRT; |
| doReturn(new Pair<Long, Object>(now5, Integer.valueOf(makeSignalStatus(sigNum)))) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceZygote) |
| .remove(anyInt(), anyInt()); |
| doReturn(null) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd) |
| .remove(anyInt(), anyInt()); |
| app = makeProcessRecord( |
| app3PidUser2, // pid |
| app3UidUser2, // uid |
| app3UidUser2, // packageUid |
| null, // definingUid |
| app3ConnectiongGroup, // connectionGroup |
| PROCESS_STATE_BOUND_TOP, // procstate |
| app3Pss1, // pss |
| app3Rss1, // rss |
| app3ProcessName, // processName |
| app3PackageName); // packageName |
| noteAppKill(app, ApplicationExitInfo.REASON_CRASH_NATIVE, |
| ApplicationExitInfo.SUBREASON_UNKNOWN, app3Description); |
| |
| updateExitInfo(app); |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(app3PackageName, app3UidUser2, app3PidUser2, 0, list); |
| assertEquals(1, list.size()); |
| |
| info = list.get(0); |
| |
| verifyApplicationExitInfo( |
| info, // info |
| now5, // timestamp |
| app3PidUser2, // pid |
| app3UidUser2, // uid |
| app3UidUser2, // packageUid |
| null, // definingUid |
| app3ProcessName, // processName |
| app3ConnectiongGroup, // connectionGroup |
| ApplicationExitInfo.REASON_CRASH_NATIVE, // reason |
| null, // subReason |
| sigNum, // status |
| app3Pss1, // pss |
| app3Rss1, // rss |
| IMPORTANCE_FOREGROUND, // importance |
| app3Description); // description |
| |
| // Verify the most recent kills, sorted by timestamp |
| int maxNum = 3; |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(null, app3UidUser2, 0, maxNum, list); |
| assertEquals(1, list.size()); |
| |
| info = list.get(0); |
| |
| verifyApplicationExitInfo( |
| info, // info |
| now5, // timestamp |
| app3PidUser2, // pid |
| app3UidUser2, // uid |
| app3UidUser2, // packageUid |
| null, // definingUid |
| app3ProcessName, // processName |
| app3ConnectiongGroup, // connectionGroup |
| ApplicationExitInfo.REASON_CRASH_NATIVE, // reason |
| null, // subReason |
| sigNum, // status |
| app3Pss1, // pss |
| app3Rss1, // rss |
| IMPORTANCE_FOREGROUND, // importance |
| app3Description); // description |
| |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(null, app2UidUser2, 0, maxNum, list); |
| assertEquals(1, list.size()); |
| info = list.get(0); |
| |
| verifyApplicationExitInfo( |
| info, // info |
| now4, // timestamp |
| app2PidUser2, // pid |
| app2UidUser2, // uid |
| app2UidUser2, // packageUid |
| null, // definingUid |
| app2ProcessName, // processName |
| 0, // connectionGroup |
| ApplicationExitInfo.REASON_LOW_MEMORY, // reason |
| null, // subReason |
| 0, // status |
| app2Pss1, // pss |
| app2Rss1, // rss |
| IMPORTANCE_CACHED, // importance |
| null); // description |
| |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(null, app1UidUser2, 0, maxNum, list); |
| assertEquals(1, list.size()); |
| info = list.get(0); |
| |
| sigNum = OsConstants.SIGKILL; |
| verifyApplicationExitInfo( |
| info, // info |
| now3, // timestamp |
| app1PidUser2, // pid |
| app1UidUser2, // uid |
| app1UidUser2, // packageUid |
| null, // definingUid |
| app1ProcessName, // processName |
| 0, // connectionGroup |
| ApplicationExitInfo.REASON_SIGNALED, // reason |
| null, // subReason |
| sigNum, // status |
| app1Pss3, // pss |
| app1Rss3, // rss |
| IMPORTANCE_FOREGROUND_SERVICE, // importance |
| null); // description |
| |
| // Case 6: App Java crash |
| final int app3Uid = 10345; |
| final int app3IsolatedUid = 99001; // it's an isolated process |
| final int app3Pid = 12350; |
| final long app3Pss2 = 23232; |
| final long app3Rss2 = 32323; |
| final String app3Description2 = "force close"; |
| |
| sleep(1); |
| final long now6 = System.currentTimeMillis(); |
| doReturn(null) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceZygote) |
| .remove(anyInt(), anyInt()); |
| doReturn(null) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd) |
| .remove(anyInt(), anyInt()); |
| app = makeProcessRecord( |
| app3Pid, // pid |
| app3IsolatedUid, // uid |
| app3Uid, // packageUid |
| null, // definingUid |
| 0, // connectionGroup |
| PROCESS_STATE_CACHED_EMPTY, // procstate |
| app3Pss2, // pss |
| app3Rss2, // rss |
| app3ProcessName, // processName |
| app3PackageName); // packageName |
| mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app3IsolatedUid, app3Uid); |
| noteAppKill(app, ApplicationExitInfo.REASON_CRASH, |
| ApplicationExitInfo.SUBREASON_UNKNOWN, app3Description2); |
| |
| assertEquals(app3Uid, mAppExitInfoTracker.mIsolatedUidRecords |
| .getUidByIsolatedUid(app3IsolatedUid).longValue()); |
| updateExitInfo(app); |
| assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(app3IsolatedUid)); |
| |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(app3PackageName, app3Uid, 0, 1, list); |
| assertEquals(1, list.size()); |
| |
| info = list.get(0); |
| |
| verifyApplicationExitInfo( |
| info, // info |
| now6, // timestamp |
| app3Pid, // pid |
| app3IsolatedUid, // uid |
| app3Uid, // packageUid |
| null, // definingUid |
| app3ProcessName, // processName |
| 0, // connectionGroup |
| ApplicationExitInfo.REASON_CRASH, // reason |
| null, // subReason |
| 0, // status |
| app3Pss2, // pss |
| app3Rss2, // rss |
| IMPORTANCE_CACHED, // importance |
| app3Description2); // description |
| |
| // Case 7: App1 is "uninstalled" from User2 |
| mAppExitInfoTracker.onPackageRemoved(app1PackageName, app1UidUser2, false); |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, 0, 0, list); |
| assertEquals(0, list.size()); |
| |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(app1PackageName, app1Uid, 0, 0, list); |
| assertEquals(2, list.size()); |
| |
| info = list.get(0); |
| |
| verifyApplicationExitInfo( |
| info, // info |
| now2, // timestamp |
| app1Pid2, // pid |
| app1Uid, // uid |
| app1Uid, // packageUid |
| app1DefiningUid, // definingUid |
| app1ProcessName, // processName |
| app1ConnectiongGroup, // connectionGroup |
| ApplicationExitInfo.REASON_EXIT_SELF, // reason |
| null, // subReason |
| exitCode, // status |
| app1Pss2, // pss |
| app1Rss2, // rss |
| IMPORTANCE_SERVICE, // importance |
| null); // description |
| |
| // Case 8: App1 gets "remove task" |
| final String app1Description = "remove task"; |
| |
| sleep(1); |
| final int app1IsolatedUidUser2 = 1099002; // isolated uid |
| final long app1Pss4 = 34343; |
| final long app1Rss4 = 43434; |
| final long now8 = System.currentTimeMillis(); |
| sigNum = OsConstants.SIGKILL; |
| doReturn(new Pair<Long, Object>(now8, makeSignalStatus(sigNum))) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceZygote) |
| .remove(anyInt(), anyInt()); |
| doReturn(null) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd) |
| .remove(anyInt(), anyInt()); |
| app = makeProcessRecord( |
| app1PidUser2, // pid |
| app1IsolatedUidUser2, // uid |
| app1UidUser2, // packageUid |
| null, // definingUid |
| 0, // connectionGroup |
| PROCESS_STATE_CACHED_EMPTY, // procstate |
| app1Pss4, // pss |
| app1Rss4, // rss |
| app1ProcessName, // processName |
| app1PackageName); // packageName |
| |
| mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUidUser2, app1UidUser2); |
| noteAppKill(app, ApplicationExitInfo.REASON_OTHER, |
| ApplicationExitInfo.SUBREASON_UNKNOWN, app1Description); |
| |
| updateExitInfo(app); |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1PidUser2, 1, list); |
| assertEquals(1, list.size()); |
| |
| info = list.get(0); |
| |
| verifyApplicationExitInfo( |
| info, // info |
| now8, // timestamp |
| app1PidUser2, // pid |
| app1IsolatedUidUser2, // uid |
| app1UidUser2, // packageUid |
| null, // definingUid |
| app1ProcessName, // processName |
| 0, // connectionGroup |
| ApplicationExitInfo.REASON_OTHER, // reason |
| ApplicationExitInfo.SUBREASON_UNKNOWN, // subReason |
| 0, // status |
| app1Pss4, // pss |
| app1Rss4, // rss |
| IMPORTANCE_CACHED, // importance |
| app1Description); // description |
| |
| // App1 gets "too many empty" |
| final String app1Description2 = "too many empty"; |
| sleep(1); |
| final int app1Pid2User2 = 56565; |
| final int app1IsolatedUid2User2 = 1099003; // isolated uid |
| final long app1Pss5 = 34344; |
| final long app1Rss5 = 43435; |
| final long now9 = System.currentTimeMillis(); |
| sigNum = OsConstants.SIGKILL; |
| doReturn(new Pair<Long, Object>(now9, makeSignalStatus(sigNum))) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceZygote) |
| .remove(anyInt(), anyInt()); |
| doReturn(null) |
| .when(mAppExitInfoTracker.mAppExitInfoSourceLmkd) |
| .remove(anyInt(), anyInt()); |
| app = makeProcessRecord( |
| app1Pid2User2, // pid |
| app1IsolatedUid2User2, // uid |
| app1UidUser2, // packageUid |
| null, // definingUid |
| 0, // connectionGroup |
| PROCESS_STATE_CACHED_EMPTY, // procstate |
| app1Pss5, // pss |
| app1Rss5, // rss |
| app1ProcessName, // processName |
| app1PackageName); // packageName |
| |
| mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUid2User2, app1UidUser2); |
| noteAppKill(app, ApplicationExitInfo.REASON_OTHER, |
| ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, app1Description2); |
| |
| updateExitInfo(app); |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(app1PackageName, app1UidUser2, app1Pid2User2, 1, list); |
| assertEquals(1, list.size()); |
| |
| info = list.get(0); |
| |
| verifyApplicationExitInfo( |
| info, // info |
| now9, // timestamp |
| app1Pid2User2, // pid |
| app1IsolatedUid2User2, // uid |
| app1UidUser2, // packageUid |
| null, // definingUid |
| app1ProcessName, // processName |
| 0, // connectionGroup |
| ApplicationExitInfo.REASON_OTHER, // reason |
| ApplicationExitInfo.SUBREASON_TOO_MANY_EMPTY, // subReason |
| 0, // status |
| app1Pss5, // pss |
| app1Rss5, // rss |
| IMPORTANCE_CACHED, // importance |
| app1Description2); // description |
| |
| |
| // Case 9: User2 gets removed |
| sleep(1); |
| mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app1IsolatedUidUser2, app1UidUser2); |
| mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid(app3IsolatedUid, app3Uid); |
| |
| mAppExitInfoTracker.onUserRemoved(UserHandle.getUserId(app1UidUser2)); |
| |
| assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid( |
| app1IsolatedUidUser2)); |
| assertNotNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid( |
| app3IsolatedUid)); |
| mAppExitInfoTracker.mIsolatedUidRecords.addIsolatedUid( |
| app1IsolatedUidUser2, app1UidUser2); |
| mAppExitInfoTracker.mIsolatedUidRecords.removeAppUid(app1UidUser2, false); |
| assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid( |
| app1IsolatedUidUser2)); |
| mAppExitInfoTracker.mIsolatedUidRecords.removeAppUid(app3Uid, true); |
| assertNull(mAppExitInfoTracker.mIsolatedUidRecords.getUidByIsolatedUid(app3IsolatedUid)); |
| |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(null, app1UidUser2, 0, 0, list); |
| assertEquals(0, list.size()); |
| |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list); |
| assertEquals(2, list.size()); |
| |
| info = list.get(0); |
| |
| exitCode = 6; |
| verifyApplicationExitInfo( |
| info, // info |
| now2, // timestamp |
| app1Pid2, // pid |
| app1Uid, // uid |
| app1Uid, // packageUid |
| app1DefiningUid, // definingUid |
| app1ProcessName, // processName |
| app1ConnectiongGroup, // connectionGroup |
| ApplicationExitInfo.REASON_EXIT_SELF, // reason |
| null, // subReason |
| exitCode, // status |
| app1Pss2, // pss |
| app1Rss2, // rss |
| IMPORTANCE_SERVICE, // importance |
| null); // description |
| |
| info = list.get(1); |
| exitCode = 5; |
| verifyApplicationExitInfo( |
| info, // info |
| now1, // timestamp |
| app1Pid1, // pid |
| app1Uid, // uid |
| app1Uid, // packageUid |
| null, // definingUid |
| app1ProcessName, // processName |
| 0, // connectionGroup |
| ApplicationExitInfo.REASON_EXIT_SELF, // reason |
| null, // subReason |
| exitCode, // status |
| app1Pss1, // pss |
| app1Rss1, // rss |
| IMPORTANCE_CACHED, // importance |
| null); // description |
| |
| // Case 10: Save the info and load them again |
| ArrayList<ApplicationExitInfo> original = new ArrayList<ApplicationExitInfo>(); |
| mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, original); |
| assertTrue(original.size() > 0); |
| |
| mAppExitInfoTracker.mProcExitInfoFile = new File(mContext.getFilesDir(), |
| AppExitInfoTracker.APP_EXIT_INFO_FILE); |
| mAppExitInfoTracker.persistProcessExitInfo(); |
| assertTrue(mAppExitInfoTracker.mProcExitInfoFile.exists()); |
| |
| mAppExitInfoTracker.clearProcessExitInfo(false); |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list); |
| assertEquals(0, list.size()); |
| |
| mAppExitInfoTracker.loadExistingProcessExitInfo(); |
| list.clear(); |
| mAppExitInfoTracker.getExitInfo(null, app1Uid, 0, 0, list); |
| assertEquals(original.size(), list.size()); |
| |
| for (int i = list.size() - 1; i >= 0; i--) { |
| assertTrue(list.get(i).equals(original.get(i))); |
| } |
| } |
| |
| private static int makeExitStatus(int exitCode) { |
| return (exitCode << 8) & 0xff00; |
| } |
| |
| private static int makeSignalStatus(int sigNum) { |
| return sigNum & 0x7f; |
| } |
| |
| private void sleep(long ms) { |
| try { |
| Thread.sleep(ms); |
| } catch (InterruptedException e) { |
| } |
| } |
| |
| private ProcessRecord makeProcessRecord(int pid, int uid, int packageUid, Integer definingUid, |
| int connectionGroup, int procState, long pss, long rss, |
| String processName, String packageName) { |
| ApplicationInfo ai = new ApplicationInfo(); |
| ai.packageName = packageName; |
| ProcessRecord app = new ProcessRecord(mAms, ai, processName, uid); |
| app.pid = pid; |
| app.info.uid = packageUid; |
| if (definingUid != null) { |
| final String dummyPackageName = "com.android.test"; |
| final String dummyClassName = ".Foo"; |
| app.hostingRecord = HostingRecord.byAppZygote(new ComponentName( |
| dummyPackageName, dummyClassName), "", definingUid); |
| } |
| app.connectionGroup = connectionGroup; |
| app.setProcState = procState; |
| app.lastMemInfo = spy(new Debug.MemoryInfo()); |
| doReturn((int) pss).when(app.lastMemInfo).getTotalPss(); |
| doReturn((int) rss).when(app.lastMemInfo).getTotalRss(); |
| return app; |
| } |
| |
| private void 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) { |
| assertNotNull(info); |
| |
| if (timestamp != null) { |
| final long tolerance = 1000; // ms |
| assertTrue(timestamp - tolerance <= info.getTimestamp()); |
| assertTrue(timestamp + tolerance >= info.getTimestamp()); |
| } |
| if (pid != null) { |
| assertEquals(pid.intValue(), info.getPid()); |
| } |
| if (uid != null) { |
| assertEquals(uid.intValue(), info.getRealUid()); |
| } |
| if (packageUid != null) { |
| assertEquals(packageUid.intValue(), info.getPackageUid()); |
| } |
| if (definingUid != null) { |
| assertEquals(definingUid.intValue(), info.getDefiningUid()); |
| } |
| if (processName != null) { |
| assertTrue(TextUtils.equals(processName, info.getProcessName())); |
| } |
| if (connectionGroup != null) { |
| assertEquals(connectionGroup.intValue(), info.getConnectionGroup()); |
| } |
| if (reason != null) { |
| assertEquals(reason.intValue(), info.getReason()); |
| } |
| if (subReason != null) { |
| assertEquals(subReason.intValue(), info.getSubReason()); |
| } |
| if (status != null) { |
| assertEquals(status.intValue(), info.getStatus()); |
| } |
| if (pss != null) { |
| assertEquals(pss.longValue(), info.getPss()); |
| } |
| if (rss != null) { |
| assertEquals(rss.longValue(), info.getRss()); |
| } |
| if (importance != null) { |
| assertEquals(importance.intValue(), info.getImportance()); |
| } |
| if (description != null) { |
| assertTrue(TextUtils.equals(description, info.getDescription())); |
| } |
| } |
| |
| private class TestInjector extends Injector { |
| TestInjector(Context context) { |
| super(context); |
| } |
| |
| @Override |
| public AppOpsService getAppOpsService(File file, Handler handler) { |
| return mAppOpsService; |
| } |
| |
| @Override |
| public Handler getUiHandler(ActivityManagerService service) { |
| return mHandler; |
| } |
| |
| @Override |
| public ProcessList getProcessList(ActivityManagerService service) { |
| return mProcessList; |
| } |
| } |
| |
| static class ServiceThreadRule implements TestRule { |
| |
| private ServiceThread mThread; |
| |
| ServiceThread getThread() { |
| return mThread; |
| } |
| |
| @Override |
| public Statement apply(Statement base, Description description) { |
| return new Statement() { |
| @Override |
| public void evaluate() throws Throwable { |
| mThread = new ServiceThread("TestServiceThread", |
| Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */); |
| mThread.start(); |
| try { |
| base.evaluate(); |
| } finally { |
| mThread.getThreadHandler().runWithScissors(mThread::quit, 0 /* timeout */); |
| } |
| } |
| }; |
| } |
| } |
| } |