diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 884870bff3af5..96ced41f36ca6 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -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("", UserHandle.myUserId()); diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl index 902f172b6ad7e..3edaabde5f2a7 100644 --- a/core/java/android/app/IActivityManager.aidl +++ b/core/java/android/app/IActivityManager.aidl @@ -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 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. diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index 35b46c1104f7b..50be45804e4f6 100644 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -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. - *

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 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); diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java index ecea96e927e32..937bbc9cced69 100644 --- a/services/core/java/com/android/server/am/ProcessList.java +++ b/services/core/java/com/android/server/am/ProcessList.java @@ -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(); diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index 0a8c6400a6fd0..4706c26889def 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -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; } diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java new file mode 100644 index 0000000000000..ea14ffbe35722 --- /dev/null +++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java @@ -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; + } +}