Split attachApplication to more accurately identify app startup

Previously, while handling the attachApplication from an app at
startup, the system started the broadcast (and service) AnR timeout
countdown as soon as it sent async bindApplication request to the app.

This increased the likelyhood of AnRs under CPU contention since some
fixed process initialization costs were included in the broadcast handling
delay.

Now, we split the the attachApplication into 2 phases, there's now a new
finishAttachApplication which apps call right before they start executing any
custom code. This new call unblocks any pending broadcast/service scheduling
and starts the appropriate AnR timeout countdowns.

Test: atest AsyncProcessStartTest
Bug: 253908737
Change-Id: I61046201281acf6686290d0ab83174ab134d5ef2
This commit is contained in:
Zim
2022-10-24 11:53:10 +01:00
parent 28b621620b
commit 331ef23f78
6 changed files with 467 additions and 125 deletions

View File

@@ -502,6 +502,7 @@ public final class ActivityThread extends ClientTransactionHandler
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
static volatile Handler sMainThreadHandler; // set once in main()
private long mStartSeq; // Only accesssed from the main thread
Bundle mCoreSettings = null;
@@ -6809,6 +6810,14 @@ public final class ActivityThread extends ClientTransactionHandler
Application app;
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
final IActivityManager mgr = ActivityManager.getService();
try {
mgr.finishAttachApplication(mStartSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
@@ -7649,6 +7658,8 @@ public final class ActivityThread extends ClientTransactionHandler
sCurrentActivityThread = this;
mConfigurationController = new ConfigurationController(this);
mSystemThread = system;
mStartSeq = startSeq;
if (!system) {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());

View File

@@ -147,6 +147,7 @@ interface IActivityManager {
oneway void finishReceiver(in IBinder who, int resultCode, in String resultData, in Bundle map,
boolean abortBroadcast, int flags);
void attachApplication(in IApplicationThread app, long startSeq);
void finishAttachApplication(long startSeq);
List<ActivityManager.RunningTaskInfo> getTasks(int maxNum);
@UnsupportedAppUsage
void moveTaskToFront(in IApplicationThread caller, in String callingPackage, int task,
@@ -718,8 +719,8 @@ interface IActivityManager {
/**
* Control the app freezer state. Returns true in case of success, false if the operation
* didn't succeed (for example, when the app freezer isn't supported).
* Handling the freezer state via this method is reentrant, that is it can be
* didn't succeed (for example, when the app freezer isn't supported).
* Handling the freezer state via this method is reentrant, that is it can be
* disabled and re-enabled multiple times in parallel. As long as there's a 1:1 disable to
* enable match, the freezer is re-enabled at last enable only.
* @param enable set it to true to enable the app freezer, false to disable it.

View File

@@ -87,6 +87,7 @@ import static android.os.Process.getTotalMemory;
import static android.os.Process.isSdkSandboxUid;
import static android.os.Process.isThreadInProcess;
import static android.os.Process.killProcess;
import static android.os.Process.killProcessGroup;
import static android.os.Process.killProcessQuiet;
import static android.os.Process.myPid;
import static android.os.Process.myUid;
@@ -952,13 +953,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
return false;
}
boolean doRemoveIfNoThreadInternal(int pid, ProcessRecord app) {
if (app == null || app.getThread() != null) {
return false;
}
return doRemoveInternal(pid, app);
}
}
private final PendingStartActivityUids mPendingStartActivityUids;
@@ -990,7 +984,7 @@ public class ActivityManagerService extends IActivityManager.Stub
* method.
*/
@GuardedBy("this")
void removePidLocked(int pid, ProcessRecord app) {
boolean removePidLocked(int pid, ProcessRecord app) {
final boolean removed;
synchronized (mPidsSelfLocked) {
removed = mPidsSelfLocked.doRemoveInternal(pid, app);
@@ -1001,26 +995,6 @@ public class ActivityManagerService extends IActivityManager.Stub
}
mAtmInternal.onProcessUnMapped(pid);
}
}
/**
* Removes the process record from the map if it doesn't have a thread.
* <p>NOTE: Callers should avoid acquiring the mPidsSelfLocked lock before calling this
* method.
*/
@GuardedBy("this")
private boolean removePidIfNoThreadLocked(ProcessRecord app) {
final boolean removed;
final int pid = app.getPid();
synchronized (mPidsSelfLocked) {
removed = mPidsSelfLocked.doRemoveIfNoThreadInternal(pid, app);
}
if (removed) {
synchronized (sActiveProcessInfoSelfLocked) {
sActiveProcessInfoSelfLocked.remove(pid);
}
mAtmInternal.onProcessUnMapped(pid);
}
return removed;
}
@@ -2364,7 +2338,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mAppErrors = null;
mPackageWatchdog = null;
mAppOpsService = mInjector.getAppOpsService(null /* file */, null /* handler */);
mBatteryStatsService = null;
mBatteryStatsService = mInjector.getBatteryStatsService();
mHandler = new MainHandler(handlerThread.getLooper());
mHandlerThread = handlerThread;
mConstants = new ActivityManagerConstants(mContext, this, mHandler);
@@ -2379,7 +2353,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mIntentFirewall = null;
mProcessStats = new ProcessStatsService(this, mContext.getCacheDir());
mCpHelper = new ContentProviderHelper(this, false);
mServices = null;
mServices = mInjector.getActiveServices(this);
mSystemThread = null;
mUiHandler = injector.getUiHandler(null /* service */);
mUidObserverController = new UidObserverController(mUiHandler);
@@ -4771,7 +4745,7 @@ public class ActivityManagerService extends IActivityManager.Stub
@GuardedBy("this")
void handleProcessStartOrKillTimeoutLocked(ProcessRecord app, boolean isKillTimeout) {
final int pid = app.getPid();
boolean gone = isKillTimeout || removePidIfNoThreadLocked(app);
boolean gone = isKillTimeout || removePidLocked(pid, app);
if (gone) {
if (isKillTimeout) {
@@ -4852,7 +4826,7 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@GuardedBy("this")
private boolean attachApplicationLocked(@NonNull IApplicationThread thread,
private void attachApplicationLocked(@NonNull IApplicationThread thread,
int pid, int callingUid, long startSeq) {
// Find the application record that is being attached... either via
@@ -4917,7 +4891,7 @@ public class ActivityManagerService extends IActivityManager.Stub
// Ignore exceptions.
}
}
return false;
return;
}
// If this application record is still attached to a previous
@@ -4942,7 +4916,7 @@ public class ActivityManagerService extends IActivityManager.Stub
mProcessList.startProcessLocked(app,
new HostingRecord(HostingRecord.HOSTING_TYPE_LINK_FAIL, processName),
ZYGOTE_POLICY_FLAG_EMPTY);
return false;
return;
}
EventLogTags.writeAmProcBound(app.userId, pid, app.processName);
@@ -4965,8 +4939,6 @@ public class ActivityManagerService extends IActivityManager.Stub
app.setUnlocked(StorageManager.isUserKeyUnlocked(app.userId));
}
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
List<ProviderInfo> providers = normalMode
? mCpHelper.generateApplicationProvidersLocked(app)
@@ -5132,7 +5104,7 @@ public class ActivityManagerService extends IActivityManager.Stub
app.killLocked("error during bind", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
true);
handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
return false;
return;
}
// Remove this record from the list of starting applications.
@@ -5140,91 +5112,6 @@ public class ActivityManagerService extends IActivityManager.Stub
if (DEBUG_PROCESSES && mProcessesOnHold.contains(app)) Slog.v(TAG_PROCESSES,
"Attach application locked removing on hold: " + app);
mProcessesOnHold.remove(app);
boolean badApp = false;
boolean didSomething = false;
// See if the top visible activity is waiting to run in this process...
if (normalMode) {
try {
didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
} catch (Exception e) {
Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
badApp = true;
}
}
// Find any services that should be running in this process...
if (!badApp) {
try {
didSomething |= mServices.attachApplicationLocked(app, processName);
checkTime(startTime, "attachApplicationLocked: after mServices.attachApplicationLocked");
} catch (Exception e) {
Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
badApp = true;
}
}
// Check if a next-broadcast receiver is in this process...
if (!badApp) {
try {
for (BroadcastQueue queue : mBroadcastQueues) {
didSomething |= queue.onApplicationAttachedLocked(app);
}
checkTime(startTime, "attachApplicationLocked: after dispatching broadcasts");
} catch (Exception e) {
// If the app died trying to launch the receiver we declare it 'bad'
Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
badApp = true;
}
}
// Check whether the next backup agent is in this process...
if (!badApp && backupTarget != null && backupTarget.app == app) {
if (DEBUG_BACKUP) Slog.v(TAG_BACKUP,
"New app is backup target, launching agent for " + app);
notifyPackageUse(backupTarget.appInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
try {
thread.scheduleCreateBackupAgent(backupTarget.appInfo,
backupTarget.backupMode, backupTarget.userId,
backupTarget.backupDestination);
} catch (Exception e) {
Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
badApp = true;
}
}
if (badApp) {
app.killLocked("error during init", ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
true);
handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
return false;
}
if (!didSomething) {
updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
checkTime(startTime, "attachApplicationLocked: after updateOomAdjLocked");
}
final HostingRecord hostingRecord = app.getHostingRecord();
String shortAction = getShortAction(hostingRecord.getAction());
FrameworkStatsLog.write(
FrameworkStatsLog.PROCESS_START_TIME,
app.info.uid,
pid,
app.info.packageName,
FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
app.getStartElapsedTime(),
(int) (bindApplicationTimeMillis - app.getStartUptime()),
(int) (SystemClock.uptimeMillis() - app.getStartUptime()),
hostingRecord.getType(),
hostingRecord.getName(),
shortAction,
HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
return true;
}
@Override
@@ -5241,6 +5128,143 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
private void finishAttachApplicationInner(long startSeq, int uid, int pid) {
final long startTime = SystemClock.uptimeMillis();
// Find the application record that is being attached... either via
// the pid if we are running in multiple processes, or just pull the
// next app record if we are emulating process with anonymous threads.
final ProcessRecord app;
synchronized (mPidsSelfLocked) {
app = mPidsSelfLocked.get(pid);
}
if (app != null && app.getStartUid() == uid && app.getStartSeq() == startSeq) {
mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app);
} else {
Slog.wtf(TAG, "Mismatched or missing ProcessRecord: " + app + ". Pid: " + pid
+ ". Uid: " + uid);
killProcess(pid);
killProcessGroup(uid, pid);
mProcessList.noteAppKill(pid, uid,
ApplicationExitInfo.REASON_INITIALIZATION_FAILURE,
ApplicationExitInfo.SUBREASON_UNKNOWN,
"wrong startSeq");
app.killLocked("unexpected process record",
ApplicationExitInfo.REASON_OTHER, true);
return;
}
synchronized (this) {
final boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info);
final String processName = app.processName;
boolean badApp = false;
boolean didSomething = false;
// See if the top visible activity is waiting to run in this process...
if (normalMode) {
try {
didSomething = mAtmInternal.attachApplication(app.getWindowProcessController());
} catch (Exception e) {
Slog.wtf(TAG, "Exception thrown launching activities in " + app, e);
badApp = true;
}
}
// Find any services that should be running in this process...
if (!badApp) {
try {
didSomething |= mServices.attachApplicationLocked(app, processName);
checkTime(startTime, "finishAttachApplicationInner: "
+ "after mServices.attachApplicationLocked");
} catch (Exception e) {
Slog.wtf(TAG, "Exception thrown starting services in " + app, e);
badApp = true;
}
}
// Check if a next-broadcast receiver is in this process...
if (!badApp) {
try {
for (BroadcastQueue queue : mBroadcastQueues) {
didSomething |= queue.onApplicationAttachedLocked(app);
}
checkTime(startTime, "finishAttachApplicationInner: "
+ "after dispatching broadcasts");
} catch (Exception e) {
// If the app died trying to launch the receiver we declare it 'bad'
Slog.wtf(TAG, "Exception thrown dispatching broadcasts in " + app, e);
badApp = true;
}
}
// Check whether the next backup agent is in this process...
final BackupRecord backupTarget = mBackupTargets.get(app.userId);
if (!badApp && backupTarget != null && backupTarget.app == app) {
if (DEBUG_BACKUP) {
Slog.v(TAG_BACKUP,
"New app is backup target, launching agent for " + app);
}
notifyPackageUse(backupTarget.appInfo.packageName,
PackageManager.NOTIFY_PACKAGE_USE_BACKUP);
try {
app.getThread().scheduleCreateBackupAgent(backupTarget.appInfo,
backupTarget.backupMode, backupTarget.userId,
backupTarget.backupDestination);
} catch (Exception e) {
Slog.wtf(TAG, "Exception thrown creating backup agent in " + app, e);
badApp = true;
}
}
if (badApp) {
app.killLocked("error during init",
ApplicationExitInfo.REASON_INITIALIZATION_FAILURE, true);
handleAppDiedLocked(app, pid, false, true, false /* fromBinderDied */);
return;
}
if (!didSomething) {
updateOomAdjLocked(app, OomAdjuster.OOM_ADJ_REASON_PROCESS_BEGIN);
checkTime(startTime, "finishAttachApplicationInner: after updateOomAdjLocked");
}
final HostingRecord hostingRecord = app.getHostingRecord();
final String shortAction = getShortAction(hostingRecord.getAction());
FrameworkStatsLog.write(
FrameworkStatsLog.PROCESS_START_TIME,
app.info.uid,
pid,
app.info.packageName,
FrameworkStatsLog.PROCESS_START_TIME__TYPE__COLD,
app.getStartElapsedTime(),
(int) (app.getBindApplicationTime() - app.getStartUptime()),
(int) (SystemClock.uptimeMillis() - app.getStartUptime()),
hostingRecord.getType(),
hostingRecord.getName(),
shortAction,
HostingRecord.getHostingTypeIdStatsd(hostingRecord.getType()),
HostingRecord.getTriggerTypeForStatsd(hostingRecord.getTriggerType()));
}
}
@Override
public final void finishAttachApplication(long startSeq) {
final int pid = Binder.getCallingPid();
final int uid = Binder.getCallingUid();
if (pid == MY_PID && uid == SYSTEM_UID) {
return;
}
final long origId = Binder.clearCallingIdentity();
try {
finishAttachApplicationInner(startSeq, uid, pid);
} finally {
Binder.restoreCallingIdentity(origId);
}
}
/**
* @return The last part of the string of an intent's action.
*/
@@ -18805,6 +18829,21 @@ public class ActivityManagerService extends IActivityManager.Stub
return new ProcessList();
}
/**
* Returns the {@link BatteryStatsService} instance
*/
public BatteryStatsService getBatteryStatsService() {
return new BatteryStatsService(mContext, SystemServiceManager.ensureSystemDir(),
BackgroundThread.get().getHandler());
}
/**
* Returns the {@link ActiveServices} instance
*/
public ActiveServices getActiveServices(ActivityManagerService service) {
return new ActiveServices(service);
}
private boolean ensureHasNetworkManagementInternal() {
if (mNmi == null) {
mNmi = LocalServices.getService(NetworkManagementInternal.class);

View File

@@ -2508,7 +2508,7 @@ public final class ProcessList {
}
@GuardedBy("mService")
private String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
String isProcStartValidLocked(ProcessRecord app, long expectedStartSeq) {
StringBuilder sb = null;
if (app.isKilledByAm()) {
if (sb == null) sb = new StringBuilder();

View File

@@ -199,6 +199,11 @@ class ProcessRecord implements WindowProcessListener {
*/
private volatile long mStartElapsedTime;
/**
* When the process was sent the bindApplication request
*/
private volatile long mBindApplicationTime;
/**
* This will be same as {@link #uid} usually except for some apps used during factory testing.
*/
@@ -739,6 +744,10 @@ class ProcessRecord implements WindowProcessListener {
return mStartElapsedTime;
}
long getBindApplicationTime() {
return mBindApplicationTime;
}
int getStartUid() {
return mStartUid;
}

View File

@@ -0,0 +1,282 @@
/*
* Copyright (C) 2022 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.os.Process.myPid;
import static android.os.Process.myUid;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import android.app.ActivityManagerInternal;
import android.app.IApplicationThread;
import android.app.usage.UsageStatsManagerInternal;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import com.android.server.DropBoxManagerInternal;
import com.android.server.LocalServices;
import com.android.server.am.ActivityManagerService.Injector;
import com.android.server.appop.AppOpsService;
import com.android.server.wm.ActivityTaskManagerInternal;
import com.android.server.wm.ActivityTaskManagerService;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.File;
import java.util.Arrays;
/**
* Tests to verify process starts are completed or timeout correctly
*/
@MediumTest
@SuppressWarnings("GuardedBy")
public class AsyncProcessStartTest {
private static final String TAG = "AsyncProcessStartTest";
private static final String PACKAGE = "com.foo";
@Rule
public final ApplicationExitInfoTest.ServiceThreadRule
mServiceThreadRule = new ApplicationExitInfoTest.ServiceThreadRule();
private Context mContext;
private HandlerThread mHandlerThread;
@Mock
private AppOpsService mAppOpsService;
@Mock
private DropBoxManagerInternal mDropBoxManagerInt;
@Mock
private PackageManagerInternal mPackageManagerInt;
@Mock
private UsageStatsManagerInternal mUsageStatsManagerInt;
@Mock
private ActivityManagerInternal mActivityManagerInt;
@Mock
private ActivityTaskManagerInternal mActivityTaskManagerInt;
@Mock
private BatteryStatsService mBatteryStatsService;
private ActivityManagerService mRealAms;
private ActivityManagerService mAms;
private ProcessList mRealProcessList = new ProcessList();
private ProcessList mProcessList;
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
mHandlerThread = new HandlerThread(TAG);
mHandlerThread.start();
LocalServices.removeServiceForTest(DropBoxManagerInternal.class);
LocalServices.addService(DropBoxManagerInternal.class, mDropBoxManagerInt);
LocalServices.removeServiceForTest(PackageManagerInternal.class);
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInt);
LocalServices.removeServiceForTest(ActivityManagerInternal.class);
LocalServices.addService(ActivityManagerInternal.class, mActivityManagerInt);
LocalServices.removeServiceForTest(ActivityTaskManagerInternal.class);
LocalServices.addService(ActivityTaskManagerInternal.class, mActivityTaskManagerInt);
doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
doReturn(true).when(mActivityTaskManagerInt).attachApplication(any());
doNothing().when(mActivityTaskManagerInt).onProcessMapped(anyInt(), any());
mRealAms = new ActivityManagerService(
new TestInjector(mContext), mServiceThreadRule.getThread());
mRealAms.mActivityTaskManager = new ActivityTaskManagerService(mContext);
mRealAms.mActivityTaskManager.initialize(null, null, mContext.getMainLooper());
mRealAms.mAtmInternal = mActivityTaskManagerInt;
mRealAms.mPackageManagerInt = mPackageManagerInt;
mRealAms.mUsageStatsService = mUsageStatsManagerInt;
mRealAms.mProcessesReady = true;
mAms = spy(mRealAms);
mRealProcessList.mService = mAms;
mProcessList = spy(mRealProcessList);
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting isProcStartValidLocked() for "
+ Arrays.toString(invocation.getArguments()));
return null;
}).when(mProcessList).isProcStartValidLocked(any(), anyLong());
}
@After
public void tearDown() throws Exception {
mHandlerThread.quit();
}
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 mHandlerThread.getThreadHandler();
}
@Override
public ProcessList getProcessList(ActivityManagerService service) {
return mRealProcessList;
}
@Override
public BatteryStatsService getBatteryStatsService() {
return mBatteryStatsService;
}
}
private ProcessRecord makeActiveProcessRecord(String packageName, boolean wedge)
throws Exception {
final ApplicationInfo ai = makeApplicationInfo(packageName);
return makeActiveProcessRecord(ai, wedge);
}
private ProcessRecord makeActiveProcessRecord(ApplicationInfo ai, boolean wedge)
throws Exception {
final IApplicationThread thread = mock(IApplicationThread.class);
final IBinder threadBinder = new Binder();
doReturn(threadBinder).when(thread).asBinder();
doAnswer((invocation) -> {
Log.v(TAG, "Intercepting bindApplication() for "
+ Arrays.toString(invocation.getArguments()));
if (!wedge) {
mRealAms.finishAttachApplication(0);
}
return null;
}).when(thread).bindApplication(
any(), any(),
any(), any(),
any(), any(),
any(), any(),
any(),
any(), anyInt(),
anyBoolean(), anyBoolean(),
anyBoolean(), anyBoolean(), any(),
any(), any(), any(),
any(), any(),
any(), any(),
any(),
anyLong(), anyLong());
final ProcessRecord r = spy(new ProcessRecord(mAms, ai, ai.processName, ai.uid));
r.setPid(myPid());
r.setStartUid(myUid());
r.setHostingRecord(new HostingRecord(HostingRecord.HOSTING_TYPE_BROADCAST));
r.makeActive(thread, mAms.mProcessStats);
doNothing().when(r).killLocked(any(), any(), anyInt(), anyInt(), anyBoolean(),
anyBoolean());
return r;
}
static ApplicationInfo makeApplicationInfo(String packageName) {
final ApplicationInfo ai = new ApplicationInfo();
ai.packageName = packageName;
ai.processName = packageName;
ai.uid = myUid();
return ai;
}
/**
* Verify that we don't kill a normal process
*/
@Test
public void testNormal() throws Exception {
ProcessRecord app = startProcessAndWait(false);
verify(app, never()).killLocked(any(), anyInt(), anyBoolean());
}
/**
* Verify that we kill a wedged process after the process start timeout
*/
@Test
public void testWedged() throws Exception {
ProcessRecord app = startProcessAndWait(true);
verify(app).killLocked(any(), anyInt(), anyBoolean());
}
private ProcessRecord startProcessAndWait(boolean wedge) throws Exception {
final ProcessRecord app = makeActiveProcessRecord(PACKAGE, wedge);
final ApplicationInfo appInfo = makeApplicationInfo(PACKAGE);
mProcessList.handleProcessStartedLocked(app, app.getPid(), /* usingWrapper */ false,
/* expectedStartSeq */ 0, /* procAttached */ false);
app.getThread().bindApplication(PACKAGE, appInfo,
null, null,
null,
null,
null, null,
null,
null, 0,
false, false,
true, false,
null,
null, null,
null,
null, null, null,
null, null,
0, 0);
// Sleep until timeout should have triggered
SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
return app;
}
}