diff options
author | Dianne Hackborn <hackbod@google.com> | 2014-08-24 16:45:38 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2014-08-26 11:16:59 -0700 |
commit | 89ad456ea49cb62615ebdcac83a2515743bbe5fa (patch) | |
tree | 4fdd6b948f74930ad10beca0a042e40f36efc50c | |
parent | 1ce1ba68acbfcbd4100d8c4be7d17a1f0623fd62 (diff) |
Fix issue #16311398: Limit number of documents a process can open
In application processes, monitor for when we start getting close
to the Dalvik heap limit, and ask the activity manager to try to
prune old activity instances in that case.
Add an explicit API for apps to ask that they have their own
activity instances cleaned up, if they want.
Fix some bugs in launching activities that were not correctly
applying the "multi task" behavior in the appropriate situations
of document-centric recents.
Clean up the activity manager's process removal code to all share
a common path.
Add a new "Spam" option to ActivityTests, which continually creates
new tasks, checking that the activity manager will now prune old
tasks rather than letting the app run out of RAM.
And while I was was doing this, I found problems with the path
for bringing an empty task to the foreground -- it could make
a new task instead of re-starting the root activity in the
existing task. This is fixed, and some code in the recents
UI for working around the bug is removed.
And as long as I am doing that, we now have nice hooks in to
the activity manager for AppTask to give some APIs for better
managing the task, so add those along with more tests for these
APIs in ActivityTests.
We should look at also having the activity manager try to prune
old tasks when it sees app processes being killed, to better balance
memory use across multiple processes when some processes may host
many documents. That however is for another CL...
Change-Id: I2bb81c3f92819350c868c7a7470b35817eb9bea9
23 files changed, 803 insertions, 169 deletions
diff --git a/api/current.txt b/api/current.txt index 4d8d6355fbb8..6909af82566e 100644 --- a/api/current.txt +++ b/api/current.txt @@ -3517,6 +3517,7 @@ package android.app { method public void postponeEnterTransition(); method public void recreate(); method public void registerForContextMenu(android.view.View); + method public boolean releaseInstance(); method public final deprecated void removeDialog(int); method public void reportFullyDrawn(); method public boolean requestVisibleBehind(boolean); @@ -3636,7 +3637,9 @@ package android.app { public static class ActivityManager.AppTask { method public void finishAndRemoveTask(); method public android.app.ActivityManager.RecentTaskInfo getTaskInfo(); + method public void moveToFront(); method public void setExcludeFromRecents(boolean); + method public void startActivity(android.content.Context, android.content.Intent, android.os.Bundle); } public static class ActivityManager.MemoryInfo implements android.os.Parcelable { diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 2e66a4c97995..9b7cc1c846d8 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -4704,6 +4704,26 @@ public class Activity extends ContextThemeWrapper } /** + * Ask that the local app instance of this activity be released to free up its memory. + * This is asking for the activity to be destroyed, but does <b>not</b> finish the activity -- + * a new instance of the activity will later be re-created if needed due to the user + * navigating back to it. + * + * @return Returns true if the activity was in a state that it has started the process + * of destroying its current instance; returns false if for any reason this could not + * be done: it is currently visible to the user, it is already being destroyed, it is + * being finished, it hasn't yet saved its state, etc. + */ + public boolean releaseInstance() { + try { + return ActivityManagerNative.getDefault().releaseActivityInstance(mToken); + } catch (RemoteException e) { + // Empty + } + return false; + } + + /** * Called when an activity you launched exits, giving you the requestCode * you started it with, the resultCode it returned, and any additional * data from it. The <var>resultCode</var> will be diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 2667bcd30a69..58790f61cab2 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2617,6 +2617,40 @@ public class ActivityManager { } /** + * Bring this task to the foreground. If it contains activities, they will be + * brought to the foreground with it and their instances re-created if needed. + * If it doesn't contain activities, the root activity of the task will be + * re-launched. + */ + public void moveToFront() { + try { + mAppTaskImpl.moveToFront(); + } catch (RemoteException e) { + Slog.e(TAG, "Invalid AppTask", e); + } + } + + /** + * Start an activity in this task. Brings the task to the foreground. If this task + * is not currently active (that is, its id < 0), then the activity being started + * needs to be started as a new task and the Intent's ComponentName must match the + * base ComponenentName of the recent task entry. Otherwise, the activity being + * started must <b>not</b> be launched as a new task -- not through explicit intent + * flags nor implicitly as the singleTask or singleInstance launch modes. + * + * <p>See {@link Activity#startActivity(android.content.Intent, android.os.Bundle) + * Activity.startActivity} for more information.</p> + * + * @param intent The Intent describing the new activity to be launched on the task. + * @param options Optional launch options. + */ + public void startActivity(Context context, Intent intent, Bundle options) { + ActivityThread thread = ActivityThread.currentActivityThread(); + thread.getInstrumentation().execStartActivityFromAppTask(context, + thread.getApplicationThread(), mAppTaskImpl, intent, options); + } + + /** * Modify the {@link Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag in the root * Intent of this AppTask. * diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 82af99bb813d..36e88929f3d9 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -365,6 +365,23 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case RELEASE_ACTIVITY_INSTANCE_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + boolean res = releaseActivityInstance(token); + reply.writeNoException(); + reply.writeInt(res ? 1 : 0); + return true; + } + + case RELEASE_SOME_ACTIVITIES_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IApplicationThread app = ApplicationThreadNative.asInterface(data.readStrongBinder()); + releaseSomeActivities(app); + reply.writeNoException(); + return true; + } + case WILL_ACTIVITY_BE_VISIBLE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); @@ -2661,6 +2678,28 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } + public boolean releaseActivityInstance(IBinder token) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + mRemote.transact(RELEASE_ACTIVITY_INSTANCE_TRANSACTION, data, reply, 0); + reply.readException(); + boolean res = reply.readInt() != 0; + data.recycle(); + reply.recycle(); + return res; + } + public void releaseSomeActivities(IApplicationThread app) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(app.asBinder()); + mRemote.transact(RELEASE_SOME_ACTIVITIES_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } public boolean willActivityBeVisible(IBinder token) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 2136209ba57c..0356093dc5a2 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -102,8 +102,6 @@ import com.android.org.conscrypt.OpenSSLSocketImpl; import com.android.org.conscrypt.TrustedCertificateStore; import com.google.android.collect.Lists; -import dalvik.system.VMRuntime; - import java.io.File; import java.io.FileDescriptor; import java.io.FileOutputStream; @@ -201,6 +199,7 @@ public final class ActivityThread { String mInstrumentedLibDir = null; boolean mSystemThread = false; boolean mJitEnabled = false; + boolean mSomeActivitiesChanged = false; // These can be accessed by multiple threads; mPackages is the lock. // XXX For now we keep around information about all packages we have @@ -2353,6 +2352,7 @@ public final class ActivityThread { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); + mSomeActivitiesChanged = true; if (r.profileFd != null) { mProfiler.setProfiler(r.profileFile, r.profileFd); @@ -2495,6 +2495,7 @@ public final class ActivityThread { public void handleCancelVisibleBehind(IBinder token) { ActivityClientRecord r = mActivities.get(token); if (r != null) { + mSomeActivitiesChanged = true; final Activity activity = r.activity; if (activity.mVisibleBehind) { activity.mCalled = false; @@ -2984,6 +2985,7 @@ public final class ActivityThread { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); + mSomeActivitiesChanged = true; // TODO Push resumeArgs into the activity for consideration ActivityClientRecord r = performResumeActivity(token, clearHide); @@ -3175,6 +3177,7 @@ public final class ActivityThread { ActivityManagerNative.getDefault().activityPaused(token, r.persistentState); } catch (RemoteException ex) { } + mSomeActivitiesChanged = true; } } @@ -3413,6 +3416,7 @@ public final class ActivityThread { info.state = r.state; info.persistentState = r.persistentState; mH.post(info); + mSomeActivitiesChanged = true; } final void performRestartActivity(IBinder token) { @@ -3446,6 +3450,7 @@ public final class ActivityThread { TAG, "Handle window " + r + " visibility: " + show); updateVisibility(r, show); } + mSomeActivitiesChanged = true; } private void handleSleeping(IBinder token, boolean sleeping) { @@ -3743,6 +3748,7 @@ public final class ActivityThread { // If the system process has died, it's game over for everyone. } } + mSomeActivitiesChanged = true; } public final void requestRelaunchActivity(IBinder token, @@ -3805,6 +3811,7 @@ public final class ActivityThread { // If we are getting ready to gc after going to the background, well // we are back active so skip it. unscheduleGcIdler(); + mSomeActivitiesChanged = true; Configuration changedConfig = null; int configChanges = 0; @@ -4107,6 +4114,8 @@ public final class ActivityThread { performConfigurationChanged(r.activity, mCompatConfiguration); freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(mCompatConfiguration)); + + mSomeActivitiesChanged = true; } final void handleProfilerControl(boolean start, ProfilerControlData pcd, int profileType) { @@ -5045,17 +5054,38 @@ public final class ActivityThread { android.ddm.DdmHandleAppName.setAppName("<pre-initialized>", UserHandle.myUserId()); RuntimeInit.setApplicationObject(mAppThread.asBinder()); - IActivityManager mgr = ActivityManagerNative.getDefault(); + final IActivityManager mgr = ActivityManagerNative.getDefault(); try { mgr.attachApplication(mAppThread); } catch (RemoteException ex) { // Ignore } + // Watch for getting close to heap limit. + BinderInternal.addGcWatcher(new Runnable() { + @Override public void run() { + if (!mSomeActivitiesChanged) { + return; + } + Runtime runtime = Runtime.getRuntime(); + long dalvikMax = runtime.maxMemory(); + long dalvikUsed = runtime.totalMemory() - runtime.freeMemory(); + if (dalvikUsed > ((3*dalvikMax)/4)) { + if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024) + + " total=" + (runtime.totalMemory()/1024) + + " used=" + (dalvikUsed/1024)); + mSomeActivitiesChanged = false; + try { + mgr.releaseSomeActivities(mAppThread); + } catch (RemoteException e) { + } + } + } + }); } else { // Don't set application object here -- if the system crashes, // we can't display an alert, we just want to die die die. android.ddm.DdmHandleAppName.setAppName("system_process", - UserHandle.myUserId()); + UserHandle.myUserId()); try { mInstrumentation = new Instrumentation(); ContextImpl context = ContextImpl.createAppContext( diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 69e17105f0dc..57c4b71f8da7 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -97,6 +97,8 @@ public interface IActivityManager extends IInterface { public void finishSubActivity(IBinder token, String resultWho, int requestCode) throws RemoteException; public boolean finishActivityAffinity(IBinder token) throws RemoteException; public void finishVoiceTask(IVoiceInteractionSession session) throws RemoteException; + public boolean releaseActivityInstance(IBinder token) throws RemoteException; + public void releaseSomeActivities(IApplicationThread app) throws RemoteException; public boolean willActivityBeVisible(IBinder token) throws RemoteException; public Intent registerReceiver(IApplicationThread caller, String callerPackage, IIntentReceiver receiver, IntentFilter filter, @@ -771,4 +773,6 @@ public interface IActivityManager extends IInterface { int START_ACTIVITY_AS_CALLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+232; int ADD_APP_TASK_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+233; int GET_APP_TASK_THUMBNAIL_SIZE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+234; + int RELEASE_ACTIVITY_INSTANCE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+235; + int RELEASE_SOME_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+236; } diff --git a/core/java/android/app/IAppTask.aidl b/core/java/android/app/IAppTask.aidl index 4e38c36865cc..37fead9e1d7a 100644 --- a/core/java/android/app/IAppTask.aidl +++ b/core/java/android/app/IAppTask.aidl @@ -17,10 +17,15 @@ package android.app; import android.app.ActivityManager; +import android.content.Intent; +import android.os.Bundle; /** @hide */ interface IAppTask { void finishAndRemoveTask(); ActivityManager.RecentTaskInfo getTaskInfo(); + void moveToFront(); + int startActivity(IBinder whoThread, String callingPackage, + in Intent intent, String resolvedType, in Bundle options); void setExcludeFromRecents(boolean exclude); } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index b28d7cc23ca5..bc71badae3bc 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1702,6 +1702,40 @@ public class Instrumentation { return null; } + /** + * Special version! + * @hide + */ + public void execStartActivityFromAppTask( + Context who, IBinder contextThread, IAppTask appTask, + Intent intent, Bundle options) { + IApplicationThread whoThread = (IApplicationThread) contextThread; + if (mActivityMonitors != null) { + synchronized (mSync) { + final int N = mActivityMonitors.size(); + for (int i=0; i<N; i++) { + final ActivityMonitor am = mActivityMonitors.get(i); + if (am.match(who, null, intent)) { + am.mHits++; + if (am.isBlocking()) { + return; + } + break; + } + } + } + } + try { + intent.migrateExtraStreamToClipData(); + intent.prepareToLeaveProcess(); + int result = appTask.startActivity(whoThread.asBinder(), who.getBasePackageName(), + intent, intent.resolveTypeIfNeeded(who.getContentResolver()), options); + checkStartActivityResult(result, intent); + } catch (RemoteException e) { + } + return; + } + /*package*/ final void init(ActivityThread thread, Context instrContext, Context appContext, ComponentName component, IInstrumentationWatcher watcher, IUiAutomationConnection uiAutomationConnection) { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 2241716544ca..95d13519ac33 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -6320,8 +6320,7 @@ public final class Settings { * processes as soon as they are no longer needed. If 0, the normal * extended lifetime is used. */ - public static final String ALWAYS_FINISH_ACTIVITIES = - "always_finish_activities"; + public static final String ALWAYS_FINISH_ACTIVITIES = "always_finish_activities"; /** * Use Dock audio output for media: diff --git a/core/java/com/android/internal/os/BinderInternal.java b/core/java/com/android/internal/os/BinderInternal.java index 3b0f0f4777dd..240d9dfd3250 100644 --- a/core/java/com/android/internal/os/BinderInternal.java +++ b/core/java/com/android/internal/os/BinderInternal.java @@ -21,6 +21,7 @@ import android.os.SystemClock; import android.util.EventLog; import java.lang.ref.WeakReference; +import java.util.ArrayList; /** * Private and debugging Binder APIs. @@ -28,19 +29,35 @@ import java.lang.ref.WeakReference; * @see IBinder */ public class BinderInternal { - static WeakReference<GcWatcher> mGcWatcher + static WeakReference<GcWatcher> sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher()); - static long mLastGcTime; - + static ArrayList<Runnable> sGcWatchers = new ArrayList<>(); + static Runnable[] sTmpWatchers = new Runnable[1]; + static long sLastGcTime; + static final class GcWatcher { @Override protected void finalize() throws Throwable { handleGc(); - mLastGcTime = SystemClock.uptimeMillis(); - mGcWatcher = new WeakReference<GcWatcher>(new GcWatcher()); + sLastGcTime = SystemClock.uptimeMillis(); + synchronized (sGcWatchers) { + sTmpWatchers = sGcWatchers.toArray(sTmpWatchers); + } + for (int i=0; i<sTmpWatchers.length; i++) { + if (sTmpWatchers[i] != null) { + sTmpWatchers[i].run(); + } + } + sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher()); } } - + + public static void addGcWatcher(Runnable watcher) { + synchronized (sGcWatchers) { + sGcWatchers.add(watcher); + } + } + /** * Add the calling thread to the IPC thread pool. This function does * not return until the current process is exiting. @@ -58,7 +75,7 @@ public class BinderInternal { * SystemClock.uptimeMillis()} of the last garbage collection. */ public static long getLastGcTime() { - return mLastGcTime; + return sLastGcTime; } /** diff --git a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java index 354eb55a1880..c2299301e023 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java @@ -212,21 +212,9 @@ public class AlternateRecentsComponent implements ActivityOptions.OnAnimationSta // Bring an active task to the foreground mSystemServicesProxy.moveTaskToFront(toTask.key.id, launchOpts); } else { - // Launch the activity anew with the desired animation - boolean isDocument = Utilities.isDocument(toTask.key.baseIntent); - Intent intent = new Intent(toTask.key.baseIntent); - intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY - | Intent.FLAG_ACTIVITY_TASK_ON_HOME); - if (!isDocument) { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } try { mSystemServicesProxy.startActivityFromRecents(toTask.key.id, launchOpts); } catch (ActivityNotFoundException anfe) {} - - // Remove the old task from activity manager - RecentsTaskLoader.getInstance().getSystemServicesProxy().removeTask(toTask.key.id, - isDocument); } } diff --git a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java index 07a7e74cfdab..0379a22fcbfe 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -446,13 +446,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV // Bring an active task to the foreground ssp.moveTaskToFront(task.key.id, launchOpts); } else { - // Launch the activity anew with the desired animation - Intent i = new Intent(task.key.baseIntent); - i.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY - | Intent.FLAG_ACTIVITY_TASK_ON_HOME); - if (!Utilities.isDocument(i)) { - i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - } try { ssp.startActivityFromRecents(task.key.id, launchOpts); if (launchOpts == null && lockToTask) { @@ -461,9 +454,6 @@ public class RecentsView extends FrameLayout implements TaskStackView.TaskStackV } catch (ActivityNotFoundException anfe) { Console.logError(getContext(), "Could not start Activity"); } - - // And clean up the old task - onTaskViewDismissed(task); } } }; diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java index 1ce073a7a836..89e3f49ef7ff 100644 --- a/services/core/java/com/android/server/Watchdog.java +++ b/services/core/java/com/android/server/Watchdog.java @@ -332,6 +332,7 @@ public class Watchdog extends Thread { final ArrayList<HandlerChecker> blockedCheckers; final String subject; final boolean allowRestart; + int debuggerWasConnected = 0; synchronized (this) { long timeout = CHECK_INTERVAL; // Make sure we (re)spin the checkers that have become idle within @@ -341,17 +342,27 @@ public class Watchdog extends Thread { hc.scheduleCheckLocked(); } + if (debuggerWasConnected > 0) { + debuggerWasConnected--; + } + // NOTE: We use uptimeMillis() here because we do not want to increment the time we // wait while asleep. If the device is asleep then the thing that we are waiting // to timeout on is asleep as well and won't have a chance to run, causing a false // positive on when to kill things. long start = SystemClock.uptimeMillis(); while (timeout > 0) { + if (Debug.isDebuggerConnected()) { + debuggerWasConnected = 2; + } try { wait(timeout); } catch (InterruptedException e) { Log.wtf(TAG, e); } + if (Debug.isDebuggerConnected()) { + debuggerWasConnected = 2; + } timeout = CHECK_INTERVAL - (SystemClock.uptimeMillis() - start); } @@ -450,7 +461,12 @@ public class Watchdog extends Thread { // Only kill the process if the debugger is not attached. if (Debug.isDebuggerConnected()) { + debuggerWasConnected = 2; + } + if (debuggerWasConnected >= 2) { Slog.w(TAG, "Debugger connected: Watchdog is *not* killing the system process"); + } else if (debuggerWasConnected > 0) { + Slog.w(TAG, "Debugger was connected: Watchdog is *not* killing the system process"); } else if (!allowRestart) { Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process"); } else { diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index e794b83c281f..296b0e26974b 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java @@ -33,6 +33,7 @@ import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; import android.Manifest; import android.app.AppOpsManager; +import android.app.ApplicationThreadNative; import android.app.IActivityContainer; import android.app.IActivityContainerCallback; import android.app.IAppTask; @@ -2831,8 +2832,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (proc.baseProcessTracker != null) { proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss); } - killUnneededProcessLocked(proc, Long.toString(proc.lastCachedPss) - + "k from cached"); + proc.kill(Long.toString(proc.lastCachedPss) + "k from cached", true); } else if (proc != null && !keepIfLarge && mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) { @@ -2841,8 +2841,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (proc.baseProcessTracker != null) { proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss); } - killUnneededProcessLocked(proc, Long.toString(proc.lastCachedPss) - + "k from cached"); + proc.kill(Long.toString(proc.lastCachedPss) + "k from cached", true); } } return proc; @@ -3318,7 +3317,8 @@ public final class ActivityManagerService extends ActivityManagerNative intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); mStackSupervisor.startActivityLocked(null, intent, null, ri.activityInfo, - null, null, null, null, 0, 0, 0, null, 0, null, false, null, null); + null, null, null, null, 0, 0, 0, null, 0, null, false, null, null, + null); } } } @@ -3462,7 +3462,7 @@ public final class ActivityManagerService extends ActivityManagerNative // TODO: Switch to user app stacks here. return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, - null, null, options, userId, null); + null, null, options, userId, null, null); } @Override @@ -3512,7 +3512,7 @@ public final class ActivityManagerService extends ActivityManagerNative int ret = mStackSupervisor.startActivityMayWait(null, targetUid, targetPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, null, null, null, null, options, UserHandle.getUserId(sourceRecord.app.uid), - null); + null, null); return ret; } catch (SecurityException e) { // XXX need to figure out how to propagate to original app. @@ -3542,7 +3542,7 @@ public final class ActivityManagerService extends ActivityManagerNative // TODO: Switch to user app stacks here. mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, - res, null, options, userId, null); + res, null, options, userId, null, null); return res; } @@ -3557,7 +3557,7 @@ public final class ActivityManagerService extends ActivityManagerNative // TODO: Switch to user app stacks here. int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, - null, null, null, config, options, userId, null); + null, null, null, config, options, userId, null, null); return ret; } @@ -3615,7 +3615,7 @@ public final class ActivityManagerService extends ActivityManagerNative // TODO: Switch to user app stacks here. return mStackSupervisor.startActivityMayWait(null, callingUid, callingPackage, intent, resolvedType, session, interactor, null, null, 0, startFlags, - profileFile, profileFd, null, null, options, userId, null); + profileFile, profileFd, null, null, options, userId, null, null); } @Override @@ -3713,7 +3713,7 @@ public final class ActivityManagerService extends ActivityManagerNative int res = mStackSupervisor.startActivityLocked(r.app.thread, intent, r.resolvedType, aInfo, null, null, resultTo != null ? resultTo.appToken : null, resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, 0, - options, false, null, null); + options, false, null, null, null); Binder.restoreCallingIdentity(origId); r.finishing = wasFinishing; @@ -3732,36 +3732,42 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } + return startActivityFromRecentsInner(taskId, options); + } + + final int startActivityFromRecentsInner(int taskId, Bundle options) { + final TaskRecord task; final int callingUid; final String callingPackage; final Intent intent; final int userId; synchronized (this) { - final TaskRecord task = recentTaskForIdLocked(taskId); + task = recentTaskForIdLocked(taskId); if (task == null) { - throw new ActivityNotFoundException("Task " + taskId + " not found."); + throw new IllegalArgumentException("Task " + taskId + " not found."); } callingUid = task.mCallingUid; callingPackage = task.mCallingPackage; intent = task.intent; + intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); userId = task.userId; } return startActivityInPackage(callingUid, callingPackage, intent, null, null, null, 0, 0, - options, userId, null); + options, userId, null, task); } final int startActivityInPackage(int uid, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, Bundle options, int userId, - IActivityContainer container) { + IActivityContainer container, TaskRecord inTask) { userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivityInPackage", null); // TODO: Switch to user app stacks here. - int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, resolvedType, - null, null, resultTo, resultWho, requestCode, startFlags, - null, null, null, null, options, userId, container); + int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, + resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, + null, null, null, null, options, userId, container, inTask); return ret; } @@ -4155,6 +4161,35 @@ public final class ActivityManagerService extends ActivityManagerNative } @Override + public boolean releaseActivityInstance(IBinder token) { + synchronized(this) { + final long origId = Binder.clearCallingIdentity(); + try { + ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r.task == null || r.task.stack == null) { + return false; + } + return r.task.stack.safelyDestroyActivityLocked(r, "app-req"); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + + @Override + public void releaseSomeActivities(IApplicationThread appInt) { + synchronized(this) { + final long origId = Binder.clearCallingIdentity(); + try { + ProcessRecord app = getRecordForAppLocked(appInt); + mStackSupervisor.releaseSomeActivitiesLocked(app, "low-mem"); + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + + @Override public boolean willActivityBeVisible(IBinder token) { synchronized(this) { ActivityStack stack = ActivityRecord.getStackLocked(token); @@ -4568,8 +4603,7 @@ public final class ActivityManagerService extends ActivityManagerNative // 0 == continue, -1 = kill process immediately int res = mController.appEarlyNotResponding(app.processName, app.pid, annotation); if (res < 0 && app.pid != MY_PID) { - Process.killProcess(app.pid); - Process.killProcessGroup(app.info.uid, app.pid); + app.kill("anr", true); } } catch (RemoteException e) { mController = null; @@ -4675,8 +4709,7 @@ public final class ActivityManagerService extends ActivityManagerNative int res = mController.appNotResponding(app.processName, app.pid, info.toString()); if (res != 0) { if (res < 0 && app.pid != MY_PID) { - Process.killProcess(app.pid); - Process.killProcessGroup(app.info.uid, app.pid); + app.kill("anr", true); } else { synchronized (this) { mServices.scheduleServiceTimeoutLocked(app); @@ -4696,7 +4729,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) { - killUnneededProcessLocked(app, "background ANR"); + app.kill("bg anr", true); return; } @@ -5420,8 +5453,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.isolated) { mBatteryStatsService.removeIsolatedUid(app.uid, app.info.uid); } - killUnneededProcessLocked(app, reason); - Process.killProcessGroup(app.info.uid, app.pid); + app.kill(reason, true); handleAppDiedLocked(app, true, allowRestart); removeLruProcessLocked(app); @@ -5469,7 +5501,7 @@ public final class ActivityManagerService extends ActivityManagerNative checkAppInLaunchingProvidersLocked(app, true); // Take care of any services that are waiting for the process. mServices.processStartTimedOutLocked(app); - killUnneededProcessLocked(app, "start timeout"); + app.kill("start timeout", true); if (mBackupTarget != null && mBackupTarget.app.pid == pid) { Slog.w(TAG, "Unattached app died before backup, skipping"); try { @@ -7915,17 +7947,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private void killUnneededProcessLocked(ProcessRecord pr, String reason) { - if (!pr.killedByAm) { - Slog.i(TAG, "Killing " + pr.toShortString() + " (adj " + pr.setAdj + "): " + reason); - EventLog.writeEvent(EventLogTags.AM_KILL, pr.userId, pr.pid, - pr.processName, pr.setAdj, reason); - pr.killedByAm = true; - Process.killProcessQuiet(pr.pid); - Process.killProcessGroup(pr.info.uid, pr.pid); - } - } - private void cleanUpRemovedTaskLocked(TaskRecord tr, int flags) { tr.disposeThumbnail(); mRecentTasks.remove(tr); @@ -7969,7 +7990,7 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { - killUnneededProcessLocked(pr, "remove task"); + pr.kill("remove task", true); } else { pr.waitingToKill = "remove task"; } @@ -8022,32 +8043,36 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_STACK) Slog.d(TAG, "moveTaskToFront: moving taskId=" + taskId); synchronized(this) { - if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(), - Binder.getCallingUid(), "Task to front")) { - ActivityOptions.abort(options); + moveTaskToFrontLocked(taskId, flags, options); + } + } + + void moveTaskToFrontLocked(int taskId, int flags, Bundle options) { + if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(), + Binder.getCallingUid(), "Task to front")) { + ActivityOptions.abort(options); + return; + } + final long origId = Binder.clearCallingIdentity(); + try { + final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); + if (task == null) { return; } - final long origId = Binder.clearCallingIdentity(); - try { - final TaskRecord task = mStackSupervisor.anyTaskForIdLocked(taskId); - if (task == null) { - return; - } - if (mStackSupervisor.isLockTaskModeViolation(task)) { - mStackSupervisor.showLockTaskToast(); - Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode"); - return; - } - final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked(); - if (prev != null && prev.isRecentsActivity()) { - task.setTaskToReturnTo(ActivityRecord.RECENTS_ACTIVITY_TYPE); - } - mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options); - } finally { - Binder.restoreCallingIdentity(origId); + if (mStackSupervisor.isLockTaskModeViolation(task)) { + mStackSupervisor.showLockTaskToast(); + Slog.e(TAG, "moveTaskToFront: Attempt to violate Lock Task Mode"); + return; } - ActivityOptions.abort(options); + final ActivityRecord prev = mStackSupervisor.topRunningActivityLocked(); + if (prev != null && prev.isRecentsActivity()) { + task.setTaskToReturnTo(ActivityRecord.RECENTS_ACTIVITY_TYPE); + } + mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options); + } finally { + Binder.restoreCallingIdentity(origId); } + ActivityOptions.abort(options); } @Override @@ -10145,7 +10170,7 @@ public final class ActivityManagerService extends ActivityManagerNative } int adj = proc.setAdj; if (adj >= worstType && !proc.killedByAm) { - killUnneededProcessLocked(proc, reason); + proc.kill(reason, true); killed = true; } } @@ -10189,7 +10214,7 @@ public final class ActivityManagerService extends ActivityManagerNative final int adj = proc.setAdj; if (adj > belowAdj && !proc.killedByAm) { - killUnneededProcessLocked(proc, reason); + proc.kill(reason, true); killed = true; } } @@ -10308,8 +10333,8 @@ public final class ActivityManagerService extends ActivityManagerNative && proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) { if (doKilling && proc.initialIdlePss != 0 && proc.lastPss > ((proc.initialIdlePss*3)/2)) { - killUnneededProcessLocked(proc, "idle maint (pss " + proc.lastPss - + " from " + proc.initialIdlePss + ")"); + proc.kill("idle maint (pss " + proc.lastPss + + " from " + proc.initialIdlePss + ")", true); } } } else if (proc.setProcState < ActivityManager.PROCESS_STATE_HOME) { @@ -10778,7 +10803,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (app.pid > 0 && app.pid != MY_PID) { handleAppCrashLocked(app, null, null, null); - killUnneededProcessLocked(app, "user request after error"); + app.kill("user request after error", true); } } } @@ -11345,8 +11370,11 @@ public final class ActivityManagerService extends ActivityManagerNative } else { Slog.w(TAG, "Force-killing crashed app " + name + " at watcher's request"); - Process.killProcess(pid); if (r != null) { + r.kill("crash", true); + } else { + // Huh. + Process.killProcess(pid); Process.killProcessGroup(uid, pid); } } @@ -13695,9 +13723,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (!capp.persistent && capp.thread != null && capp.pid != 0 && capp.pid != MY_PID) { - killUnneededProcessLocked(capp, "depends on provider " + capp.kill("depends on provider " + cpr.name.flattenToShortString() - + " in dying proc " + (proc != null ? proc.processName : "??")); + + " in dying proc " + (proc != null ? proc.processName : "??"), true); } } else if (capp.thread != null && conn.provider.provider != null) { try { @@ -16553,8 +16581,7 @@ public final class ActivityManagerService extends ActivityManagerNative stats.reportExcessiveWakeLocked(app.info.uid, app.processName, realtimeSince, wtimeUsed); } - killUnneededProcessLocked(app, "excessive wake held " + wtimeUsed - + " during " + realtimeSince); + app.kill("excessive wake held " + wtimeUsed + " during " + realtimeSince, true); app.baseProcessTracker.reportExcessiveWake(app.pkgList); } else if (doCpuKills && uptimeSince > 0 && ((cputimeUsed*100)/uptimeSince) >= 25) { @@ -16562,8 +16589,7 @@ public final class ActivityManagerService extends ActivityManagerNative stats.reportExcessiveCpuLocked(app.info.uid, app.processName, uptimeSince, cputimeUsed); } - killUnneededProcessLocked(app, "excessive cpu " + cputimeUsed - + " during " + uptimeSince); + app.kill("excessive cpu " + cputimeUsed + " during " + uptimeSince, true); app.baseProcessTracker.reportExcessiveCpu(app.pkgList); } else { app.lastWakeTime = wtime; @@ -16598,7 +16624,7 @@ public final class ActivityManagerService extends ActivityManagerNative + " to " + app.curSchedGroup); if (app.waitingToKill != null && app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { - killUnneededProcessLocked(app, app.waitingToKill); + app.kill(app.waitingToKill, true); success = false; } else { if (true) { @@ -16978,19 +17004,19 @@ public final class ActivityManagerService extends ActivityManagerNative mNumCachedHiddenProcs++; numCached++; if (numCached > cachedProcessLimit) { - killUnneededProcessLocked(app, "cached #" + numCached); + app.kill("cached #" + numCached, true); } break; case ActivityManager.PROCESS_STATE_CACHED_EMPTY: if (numEmpty > ProcessList.TRIM_EMPTY_APPS && app.lastActivityTime < oldTime) { - killUnneededProcessLocked(app, "empty for " + app.kill("empty for " + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime) - / 1000) + "s"); + / 1000) + "s", true); } else { numEmpty++; if (numEmpty > emptyProcessLimit) { - killUnneededProcessLocked(app, "empty #" + numEmpty); + app.kill("empty #" + numEmpty, true); } } break; @@ -17006,7 +17032,7 @@ public final class ActivityManagerService extends ActivityManagerNative // definition not re-use the same process again, and it is // good to avoid having whatever code was running in them // left sitting around after no longer needed. - killUnneededProcessLocked(app, "isolated not needed"); + app.kill("isolated not needed", true); } if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME @@ -17234,11 +17260,7 @@ public final class ActivityManagerService extends ActivityManagerNative + (app.thread != null ? app.thread.asBinder() : null) + ")\n"); if (app.pid > 0 && app.pid != MY_PID) { - EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid, - app.processName, app.setAdj, "empty"); - app.killedByAm = true; - Process.killProcessQuiet(app.pid); - Process.killProcessGroup(app.info.uid, app.pid); + app.kill("empty", false); } else { try { app.thread.scheduleExit(); @@ -18290,14 +18312,15 @@ public final class ActivityManagerService extends ActivityManagerNative long origId = Binder.clearCallingIdentity(); try { TaskRecord tr = recentTaskForIdLocked(mTaskId); - if (tr != null) { - // Only kill the process if we are not a new document - int flags = tr.getBaseIntent().getFlags(); - boolean isDocument = (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) == - Intent.FLAG_ACTIVITY_NEW_DOCUMENT; - removeTaskByIdLocked(mTaskId, - !isDocument ? ActivityManager.REMOVE_TASK_KILL_PROCESS : 0); - } + if (tr == null) { + throw new IllegalArgumentException("Unable to find task ID " + mTaskId); + } + // Only kill the process if we are not a new document + int flags = tr.getBaseIntent().getFlags(); + boolean isDocument = (flags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) == + Intent.FLAG_ACTIVITY_NEW_DOCUMENT; + removeTaskByIdLocked(mTaskId, + !isDocument ? ActivityManager.REMOVE_TASK_KILL_PROCESS : 0); } finally { Binder.restoreCallingIdentity(origId); } @@ -18312,17 +18335,64 @@ public final class ActivityManagerService extends ActivityManagerNative long origId = Binder.clearCallingIdentity(); try { TaskRecord tr = recentTaskForIdLocked(mTaskId); - if (tr != null) { - return createRecentTaskInfoFromTaskRecord(tr); + if (tr == null) { + throw new IllegalArgumentException("Unable to find task ID " + mTaskId); } + return createRecentTaskInfoFromTaskRecord(tr); } finally { Binder.restoreCallingIdentity(origId); } - return null; } } @Override + public void moveToFront() { + checkCaller(); + + final TaskRecord tr; + synchronized (ActivityManagerService.this) { + tr = recentTaskForIdLocked(mTaskId); + if (tr == null) { + throw new IllegalArgumentException("Unable to find task ID " + mTaskId); + } + if (tr.getRootActivity() != null) { + long origId = Binder.clearCallingIdentity(); + try { + moveTaskToFrontLocked(tr.taskId, 0, null); + return; + } finally { + Binder.restoreCallingIdentity(origId); + } + } + } + + startActivityFromRecentsInner(tr.taskId, null); + } + + @Override + public int startActivity(IBinder whoThread, String callingPackage, + Intent intent, String resolvedType, Bundle options) { + checkCaller(); + + int callingUser = UserHandle.getCallingUserId(); + TaskRecord tr; + IApplicationThread appThread; + synchronized (ActivityManagerService.this) { + tr = recentTaskForIdLocked(mTaskId); + if (tr == null) { + throw new IllegalArgumentException("Unable to find task ID " + mTaskId); + } + appThread = ApplicationThreadNative.asInterface(whoThread); + if (appThread == null) { + throw new IllegalArgumentException("Bad app thread " + appThread); + } + } + return mStackSupervisor.startActivityMayWait(appThread, -1, callingPackage, intent, + resolvedType, null, null, null, null, 0, 0, null, null, + null, null, options, callingUser, null, tr); + } + + @Override public void setExcludeFromRecents(boolean exclude) { checkCaller(); @@ -18330,14 +18400,15 @@ public final class ActivityManagerService extends ActivityManagerNative long origId = Binder.clearCallingIdentity(); try { TaskRecord tr = recentTaskForIdLocked(mTaskId); - if (tr != null) { - Intent intent = tr.getBaseIntent(); - if (exclude) { - intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - } else { - intent.setFlags(intent.getFlags() - & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); - } + if (tr == null) { + throw new IllegalArgumentException("Unable to find task ID " + mTaskId); + } + Intent intent = tr.getBaseIntent(); + if (exclude) { + intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); + } else { + intent.setFlags(intent.getFlags() + & ~Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); } } finally { Binder.restoreCallingIdentity(origId); diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java index fcbe71ebb30b..694142a27acd 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1054,6 +1054,24 @@ final class ActivityRecord { return null; } + final boolean isDestroyable() { + if (finishing || app == null || state == ActivityState.DESTROYING + || state == ActivityState.DESTROYED) { + // This would be redundant. + return false; + } + if (task == null || task.stack == null || this == task.stack.mResumedActivity + || this == task.stack.mPausingActivity || !haveState || !stopped) { + // We're not ready for this kind of thing. + return false; + } + if (visible) { + // The user would notice this! + return false; + } + return true; + } + private static String createImageFilename(long createTime, int taskId) { return String.valueOf(taskId) + ACTIVITY_ICON_SUFFIX + createTime + TaskPersister.IMAGE_EXTENSION; diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java index 41ed4ce7b757..fd1474f49da2 100755 --- a/services/core/java/com/android/server/am/ActivityStack.java +++ b/services/core/java/com/android/server/am/ActivityStack.java @@ -36,11 +36,13 @@ import static com.android.server.am.ActivityRecord.APPLICATION_ACTIVITY_TYPE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_APP; import static com.android.server.am.ActivityStackSupervisor.DEBUG_CONTAINERS; +import static com.android.server.am.ActivityStackSupervisor.DEBUG_RELEASE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE; import static com.android.server.am.ActivityStackSupervisor.DEBUG_SCREENSHOTS; import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES; import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; +import android.util.ArraySet; import com.android.internal.app.IVoiceInteractor; import com.android.internal.os.BatteryStatsImpl; import com.android.server.Watchdog; @@ -2910,7 +2912,7 @@ final class ActivityStack { int res = mStackSupervisor.startActivityLocked(srec.app.thread, destIntent, null, aInfo, null, null, parent.appToken, null, 0, -1, parent.launchedFromUid, parent.launchedFromPackage, - 0, null, true, null, null); + 0, null, true, null, null, null); foundParentInTask = res == ActivityManager.START_SUCCESS; } catch (RemoteException e) { foundParentInTask = false; @@ -3058,15 +3060,10 @@ final class ActivityStack { if (!lastIsOpaque) { continue; } - // We can destroy this one if we have its icicle saved and - // it is not in the process of pausing/stopping/finishing. - if (r.app != null && r != mResumedActivity && r != mPausingActivity - && r.haveState && !r.visible && r.stopped - && r.state != ActivityState.DESTROYING - && r.state != ActivityState.DESTROYED) { + if (r.isDestroyable()) { if (DEBUG_SWITCH) Slog.v(TAG, "Destroying " + r + " in state " + r.state + " resumed=" + mResumedActivity - + " pausing=" + mPausingActivity); + + " pausing=" + mPausingActivity + " for reason " + reason); if (destroyActivityLocked(r, true, reason)) { activityRemoved = true; } @@ -3078,6 +3075,60 @@ final class ActivityStack { } } + final boolean safelyDestroyActivityLocked(ActivityRecord r, String reason) { + if (r.isDestroyable()) { + if (DEBUG_SWITCH) Slog.v(TAG, "Destroying " + r + " in state " + r.state + + " resumed=" + mResumedActivity + + " pausing=" + mPausingActivity + " for reason " + reason); + return destroyActivityLocked(r, true, reason); + } + return false; + } + + final int releaseSomeActivitiesLocked(ProcessRecord app, ArraySet<TaskRecord> tasks, + String reason) { + // Iterate over tasks starting at the back (oldest) first. + if (DEBUG_RELEASE) Slog.d(TAG, "Trying to release some activities in " + app); + int maxTasks = tasks.size() / 4; + if (maxTasks < 1) { + maxTasks = 1; + } + int numReleased = 0; + for (int taskNdx = 0; taskNdx < mTaskHistory.size() && maxTasks > 0; taskNdx++) { + final TaskRecord task = mTaskHistory.get(taskNdx); + if (!tasks.contains(task)) { + continue; + } + if (DEBUG_RELEASE) Slog.d(TAG, "Looking for activities to release in " + task); + int curNum = 0; + final ArrayList<ActivityRecord> activities = task.mActivities; + for (int actNdx = 0; actNdx < activities.size(); actNdx++) { + final ActivityRecord activity = activities.get(actNdx); + if (activity.app == app && activity.isDestroyable()) { + if (DEBUG_RELEASE) Slog.v(TAG, "Destroying " + activity + + " in state " + activity.state + " resumed=" + mResumedActivity + + " pausing=" + mPausingActivity + " for reason " + reason); + destroyActivityLocked(activity, true, reason); + if (activities.get(actNdx) != activity) { + // Was removed from list, back up so we don't miss the next one. + actNdx--; + } + curNum++; + } + } + if (curNum > 0) { + numReleased += curNum; + maxTasks--; + if (mTaskHistory.get(taskNdx) != task) { + // The entire task got removed, back up so we don't miss the next one. + taskNdx--; + } + } + } + if (DEBUG_RELEASE) Slog.d(TAG, "Done releasing: did " + numReleased + " activities"); + return numReleased; + } + /** * Destroy the current CLIENT SIDE instance of an activity. This may be * called both when actually finishing an activity, or when performing diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java index 8aec39232248..8058c0524e42 100644 --- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java +++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java @@ -84,6 +84,7 @@ import android.os.UserHandle; import android.provider.Settings; import android.provider.Settings.SettingNotFoundException; import android.service.voice.IVoiceInteractionSession; +import android.util.ArraySet; import android.util.EventLog; import android.util.Slog; import android.util.SparseArray; @@ -115,10 +116,11 @@ public final class ActivityStackSupervisor implements DisplayListener { static final boolean DEBUG_APP = DEBUG || false; static final boolean DEBUG_CONTAINERS = DEBUG || false; static final boolean DEBUG_IDLE = DEBUG || false; - static final boolean DEBUG_VISIBLE_BEHIND = DEBUG || false; + static final boolean DEBUG_RELEASE = DEBUG || false; static final boolean DEBUG_SAVED_STATE = DEBUG || false; static final boolean DEBUG_SCREENSHOTS = DEBUG || false; static final boolean DEBUG_STATES = DEBUG || false; + static final boolean DEBUG_VISIBLE_BEHIND = DEBUG || false; public static final int HOME_STACK_ID = 0; @@ -781,7 +783,7 @@ public final class ActivityStackSupervisor implements DisplayListener { void startHomeActivity(Intent intent, ActivityInfo aInfo) { moveHomeStackTaskToTop(HOME_ACTIVITY_TYPE); startActivityLocked(null, intent, null, aInfo, null, null, null, null, 0, 0, 0, null, 0, - null, false, null, null); + null, false, null, null, null); } final int startActivityMayWait(IApplicationThread caller, int callingUid, @@ -789,7 +791,7 @@ public final class ActivityStackSupervisor implements DisplayListener { IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config, - Bundle options, int userId, IActivityContainer iContainer) { + Bundle options, int userId, IActivityContainer iContainer, TaskRecord inTask) { // Refuse possible leaked file descriptors if (intent != null && intent.hasFileDescriptors()) { throw new IllegalArgumentException("File descriptors passed in Intent"); @@ -899,7 +901,7 @@ public final class ActivityStackSupervisor implements DisplayListener { int res = startActivityLocked(caller, intent, resolvedType, aInfo, voiceSession, voiceInteractor, resultTo, resultWho, requestCode, callingPid, callingUid, callingPackage, startFlags, options, - componentSpecified, null, container); + componentSpecified, null, container, inTask); Binder.restoreCallingIdentity(origId); @@ -1014,7 +1016,7 @@ public final class ActivityStackSupervisor implements DisplayListener { } int res = startActivityLocked(caller, intent, resolvedTypes[i], aInfo, null, null, resultTo, null, -1, callingPid, callingUid, callingPackage, - 0, theseOptions, componentSpecified, outActivity, null); + 0, theseOptions, componentSpecified, outActivity, null, null); if (res < 0) { return res; } @@ -1241,7 +1243,8 @@ public final class ActivityStackSupervisor implements DisplayListener { IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options, - boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container) { + boolean componentSpecified, ActivityRecord[] outActivity, ActivityContainer container, + TaskRecord inTask) { int err = ActivityManager.START_SUCCESS; ProcessRecord callerApp = null; @@ -1453,7 +1456,7 @@ public final class ActivityStackSupervisor implements DisplayListener { doPendingActivityLaunchesLocked(false); err = startActivityUncheckedLocked(r, sourceRecord, voiceSession, voiceInteractor, - startFlags, true, options); + startFlags, true, options, inTask); if (err < 0) { // If someone asked to have the keyguard dismissed on the next @@ -1536,10 +1539,9 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - final int startActivityUncheckedLocked(ActivityRecord r, - ActivityRecord sourceRecord, + final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord, IVoiceInteractionSession voiceSession, IVoiceInteractor voiceInteractor, int startFlags, - boolean doResume, Bundle options) { + boolean doResume, Bundle options, TaskRecord inTask) { final Intent intent = r.intent; final int callingUid = r.launchedFromUid; @@ -1571,8 +1573,9 @@ public final class ActivityStackSupervisor implements DisplayListener { } } - final boolean launchTaskBehind = r.mLaunchTaskBehind && - (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0; + final boolean launchTaskBehind = r.mLaunchTaskBehind + && !launchSingleTask && !launchSingleInstance + && (launchFlags & Intent.FLAG_ACTIVITY_NEW_DOCUMENT) != 0; if (r.resultTo != null && (launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { // For whatever reason this activity is being launched into a new @@ -1591,6 +1594,15 @@ public final class ActivityStackSupervisor implements DisplayListener { launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; } + // If we are actually going to launch in to a new task, there are some cases where + // we further want to do multiple task. + if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + if (launchTaskBehind + || r.info.documentLaunchMode == ActivityInfo.DOCUMENT_LAUNCH_ALWAYS) { + launchFlags |= Intent.FLAG_ACTIVITY_MULTIPLE_TASK; + } + } + // We'll invoke onUserLeaving before onPause only if the launching // activity did not explicitly state that this is an automated launch. mUserLeaving = (launchFlags & Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; @@ -1624,7 +1636,7 @@ public final class ActivityStackSupervisor implements DisplayListener { if (sourceRecord == null) { // This activity is not being started from another... in this // case we -always- start a new task. - if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 && inTask == null) { Slog.w(TAG, "startActivity called from non-Activity context; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; @@ -1642,7 +1654,7 @@ public final class ActivityStackSupervisor implements DisplayListener { ActivityInfo newTaskInfo = null; Intent newTaskIntent = null; - final ActivityStack sourceStack; + ActivityStack sourceStack; if (sourceRecord != null) { if (sourceRecord.finishing) { // If the source is finishing, we can't further count it as our source. This @@ -1666,12 +1678,51 @@ public final class ActivityStackSupervisor implements DisplayListener { sourceStack = null; } - intent.setFlags(launchFlags); - boolean addingToTask = false; boolean movedHome = false; TaskRecord reuseTask = null; ActivityStack targetStack; + + intent.setFlags(launchFlags); + + // If the caller is not coming from another activity, but has given us an + // explicit task into which they would like us to launch the new activity, + // then let's see about doing that. + if (sourceRecord == null && inTask != null && inTask.stack != null) { + // If this task is empty, then we are adding the first activity -- it + // determines the root, and must be launching as a NEW_TASK. + if (inTask.getRootActivity() == null) { + if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) == 0 + && !launchSingleInstance && !launchSingleTask) { + throw new IllegalStateException("Caller has inTask " + inTask + + " but target is not a new task"); + } else if (inTask.getBaseIntent() == null || !intent.getComponent().equals( + inTask.getBaseIntent().getComponent())) { + throw new IllegalStateException("Caller requested " + inTask + " is component " + + inTask.getBaseIntent() + " but starting " + intent); + } + inTask.setIntent(r); + + // If the task is not empty, then we are going to add the new activity on top + // of the task, so it can not be launching as a new task. + } else { + if ((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 + || launchSingleInstance || launchSingleTask) { + throw new IllegalStateException("Caller has inTask " + inTask + + " but target is a new task"); + } + } + sourceStack = inTask.stack; + reuseTask = inTask; + } else { + inTask = null; + } + + // We may want to try to place the new activity in to an existing task. We always + // do this if the target activity is singleTask or singleInstance; we will also do + // this if NEW_TASK has been requested, and there is not an additional qualifier telling + // us to still place it in a new task: multi task, always doc mode, or being asked to + // launch this as a new task behind the current one. if (((launchFlags & Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && (launchFlags & Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) || launchSingleInstance || launchSingleTask) { @@ -1908,8 +1959,13 @@ public final class ActivityStackSupervisor implements DisplayListener { Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; } - newTask = true; - targetStack = adjustStackFocus(r, newTask); + if (inTask == null) { + // If we have an incoming task, we are just going to use that. + newTask = true; + targetStack = adjustStackFocus(r, newTask); + } else { + targetStack = inTask.stack; + } if (!launchTaskBehind) { targetStack.moveToFront(); } @@ -1986,6 +2042,20 @@ public final class ActivityStackSupervisor implements DisplayListener { if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in existing task " + r.task + " from source " + sourceRecord); + } else if (inTask != null) { + // The calling is asking that the new activity be started in an explicit + // task it has provided to us. + if (isLockTaskModeViolation(inTask)) { + Slog.e(TAG, "Attempted Lock Task Mode violation r=" + r); + return ActivityManager.START_RETURN_LOCK_TASK_MODE_VIOLATION; + } + targetStack = inTask.stack; + targetStack.moveToFront(); + mWindowManager.moveTaskToTop(targetStack.topTask().taskId); + r.setTask(inTask, null); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in explicit task " + r.task); + } else { // This not being started from an existing activity, and not part // of a new task... just put it in the top task, though these days @@ -2023,7 +2093,7 @@ public final class ActivityStackSupervisor implements DisplayListener { while (!mPendingActivityLaunches.isEmpty()) { PendingActivityLaunch pal = mPendingActivityLaunches.remove(0); startActivityUncheckedLocked(pal.r, pal.sourceRecord, null, null, pal.startFlags, - doResume && mPendingActivityLaunches.isEmpty(), null); + doResume && mPendingActivityLaunches.isEmpty(), null, null); } } @@ -2726,6 +2796,64 @@ public final class ActivityStackSupervisor implements DisplayListener { } } + void releaseSomeActivitiesLocked(ProcessRecord app, String reason) { + // Examine all activities currently running in the process. + TaskRecord firstTask = null; + // Tasks is non-null only if two or more tasks are found. + ArraySet<TaskRecord> tasks = null; + if (DEBUG_RELEASE) Slog.d(TAG, "Trying to release some activities in " + app); + for (int i=0; i<app.activities.size(); i++) { + ActivityRecord r = app.activities.get(i); + // First, if we find an activity that is in the process of being destroyed, + // then we just aren't going to do anything for now; we want things to settle + // down before we try to prune more activities. + if (r.finishing || r.state == ActivityState.DESTROYING + || r.state == ActivityState.DESTROYED) { + if (DEBUG_RELEASE) Slog.d(TAG, "Abort release; already destroying: " + r); + return; + } + // Don't consider any activies that are currently not in a state where they + // can be destroyed. + if (r.visible || !r.stopped || !r.haveState + || r.state == ActivityState.RESUMED || r.state == ActivityState.PAUSING + || r.state == ActivityState.PAUSED || r.state == ActivityState.STOPPING) { + if (DEBUG_RELEASE) Slog.d(TAG, "Not releasing in-use activity: " + r); + continue; + } + if (r.task != null) { + if (DEBUG_RELEASE) Slog.d(TAG, "Collecting release task " + r.task + + " from " + r); + if (firstTask == null) { + firstTask = r.task; + } else if (firstTask != r.task) { + if (tasks == null) { + tasks = new ArraySet<>(); + tasks.add(firstTask); + } + tasks.add(r.task); + } + } + } + if (tasks == null) { + if (DEBUG_RELEASE) Slog.d(TAG, "Didn't find two or more tasks to release"); + return; + } + // If we have activities in multiple tasks that are in a position to be destroyed, + // let's iterate through the tasks and release the oldest one. + final int numDisplays = mActivityDisplays.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks; + // Step through all stacks starting from behind, to hit the oldest things first. + for (int stackNdx = 0; stackNdx < stacks.size(); stackNdx++) { + final ActivityStack stack = stacks.get(stackNdx); + // Try to release activities in this stack; if we manage to, we are done. + if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) { + return; + } + } + } + } + boolean switchUserLocked(int userId, UserStartedState uss) { mUserStackInFront.put(mCurrentUser, getFocusedStack().getStackId()); final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID); @@ -3499,7 +3627,7 @@ public final class ActivityStackSupervisor implements DisplayListener { mimeType = mService.getProviderMimeType(intent.getData(), userId); } return startActivityMayWait(null, -1, null, intent, mimeType, null, null, null, null, 0, 0, null, - null, null, null, null, userId, this); + null, null, null, null, userId, this, null); } @Override diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java index 98999e9ba92b..433ab602e9dc 100644 --- a/services/core/java/com/android/server/am/PendingIntentRecord.java +++ b/services/core/java/com/android/server/am/PendingIntentRecord.java @@ -254,7 +254,7 @@ final class PendingIntentRecord extends IIntentSender.Stub { } else { owner.startActivityInPackage(uid, key.packageName, finalIntent, resolvedType, resultTo, resultWho, requestCode, 0, - options, userId, container); + options, userId, container, null); } } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java index f1bcb6051e4a..0817dd84b375 100644 --- a/services/core/java/com/android/server/am/ProcessRecord.java +++ b/services/core/java/com/android/server/am/ProcessRecord.java @@ -17,6 +17,8 @@ package com.android.server.am; import android.util.ArraySet; +import android.util.EventLog; +import android.util.Slog; import com.android.internal.app.ProcessStats; import com.android.internal.os.BatteryStatsImpl; @@ -502,6 +504,21 @@ final class ProcessRecord { return adj; } + void kill(String reason, boolean noisy) { + if (!killedByAm) { + if (noisy) { + Slog.i(ActivityManagerService.TAG, "Killing " + toShortString() + " (adj " + setAdj + + "): " + reason); + } + EventLog.writeEvent(EventLogTags.AM_KILL, userId, pid, processName, setAdj, reason); + Process.killProcessQuiet(pid); + Process.killProcessGroup(info.uid, pid); + if (!persistent) { + killedByAm = true; + } + } + } + public String toShortString() { if (shortStringName != null) { return shortStringName; diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml index c0898d30fbe8..3fb547dc4bf8 100644 --- a/tests/ActivityTests/AndroidManifest.xml +++ b/tests/ActivityTests/AndroidManifest.xml @@ -31,6 +31,11 @@ <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> + <activity android:name="SpamActivity" android:label="Spam!" + android:documentLaunchMode="always"> + </activity> + <activity android:name="DocActivity" android:label="Some doc"> + </activity> <service android:name="SingleUserService" android:singleUser="true" android:exported="true"> </service> diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java index 7f3aa7762804..ea0db5683028 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java @@ -21,6 +21,7 @@ import java.util.List; import android.app.Activity; import android.app.ActivityManager; +import android.app.ActivityOptions; import android.app.AlertDialog; import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; @@ -29,13 +30,15 @@ import android.content.ContentProviderClient; import android.content.Intent; import android.content.ServiceConnection; import android.graphics.BitmapFactory; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; +import android.os.Message; import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.graphics.Bitmap; -import android.view.WindowManager; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -60,6 +63,28 @@ public class ActivityTestMain extends Activity { ArrayList<ServiceConnection> mConnections = new ArrayList<ServiceConnection>(); + static final int MSG_SPAM = 1; + + final Handler mHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SPAM: { + boolean fg = msg.arg1 != 0; + Intent intent = new Intent(ActivityTestMain.this, SpamActivity.class); + Bundle options = null; + if (fg) { + ActivityOptions opts = ActivityOptions.makeLaunchTaskBehindAnimation(); + options = opts.toBundle(); + } + startActivity(intent, options); + scheduleSpam(!fg); + } break; + } + super.handleMessage(msg); + } + }; + class BroadcastResultReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { @@ -143,7 +168,8 @@ public class ActivityTestMain extends Activity { @Override public boolean onCreateOptionsMenu(Menu menu) { menu.add("Animate!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { - @Override public boolean onMenuItemClick(MenuItem item) { + @Override + public boolean onMenuItemClick(MenuItem item) { AlertDialog.Builder builder = new AlertDialog.Builder(ActivityTestMain.this, R.style.SlowDialog); builder.setTitle("This is a title"); @@ -316,6 +342,49 @@ public class ActivityTestMain extends Activity { return true; } }); + menu.add("Open Doc").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + ActivityManager.AppTask task = findDocTask(); + if (task == null) { + Intent intent = new Intent(ActivityTestMain.this, DocActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK + | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS); + startActivity(intent); + } else { + task.moveToFront(); + } + return true; + } + }); + menu.add("Stack Doc").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + ActivityManager.AppTask task = findDocTask(); + if (task != null) { + ActivityManager.RecentTaskInfo recent = task.getTaskInfo(); + Intent intent = new Intent(ActivityTestMain.this, DocActivity.class); + if (recent.id >= 0) { + // Stack on top. + intent.putExtra(DocActivity.LABEL, "Stacked"); + task.startActivity(ActivityTestMain.this, intent, null); + } else { + // Start root activity. + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT + | Intent.FLAG_ACTIVITY_MULTIPLE_TASK + | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS); + intent.putExtra(DocActivity.LABEL, "New Root"); + task.startActivity(ActivityTestMain.this, intent, null); + } + } + return true; + } + }); + menu.add("Spam!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + scheduleSpam(false); + return true; + } + }); return true; } @@ -342,6 +411,12 @@ public class ActivityTestMain extends Activity { mConnections.clear(); } + @Override + protected void onDestroy() { + super.onDestroy(); + mHandler.removeMessages(MSG_SPAM); + } + void addAppRecents(int count) { ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); Intent intent = new Intent(Intent.ACTION_MAIN); @@ -371,7 +446,31 @@ public class ActivityTestMain extends Activity { } } } - private View scrollWrap(View view) { + + ActivityManager.AppTask findDocTask() { + ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); + List<ActivityManager.AppTask> tasks = am.getAppTasks(); + if (tasks != null) { + for (int i=0; i<tasks.size(); i++) { + ActivityManager.AppTask task = tasks.get(i); + ActivityManager.RecentTaskInfo recent = task.getTaskInfo(); + if (recent.baseIntent != null + && recent.baseIntent.getComponent().getClassName().equals( + DocActivity.class.getCanonicalName())) { + return task; + } + } + } + return null; + } + + void scheduleSpam(boolean fg) { + mHandler.removeMessages(MSG_SPAM); + Message msg = mHandler.obtainMessage(MSG_SPAM, fg ? 1 : 0, 0); + mHandler.sendMessageDelayed(msg, 500); + } + + private View scrollWrap(View view) { ScrollView scroller = new ScrollView(this); scroller.addView(view, new ScrollView.LayoutParams(ScrollView.LayoutParams.MATCH_PARENT, ScrollView.LayoutParams.MATCH_PARENT)); diff --git a/tests/ActivityTests/src/com/google/android/test/activity/DocActivity.java b/tests/ActivityTests/src/com/google/android/test/activity/DocActivity.java new file mode 100644 index 000000000000..6330c7911662 --- /dev/null +++ b/tests/ActivityTests/src/com/google/android/test/activity/DocActivity.java @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2014 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.google.android.test.activity; + +import android.app.Activity; +import android.app.ActivityManager; +import android.os.Bundle; + +public class DocActivity extends Activity { + static final String LABEL = "label"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + String label = getIntent().getStringExtra(LABEL); + if (label != null) { + setTaskDescription(new ActivityManager.TaskDescription(label)); + setTitle(label); + } + } +} diff --git a/tests/ActivityTests/src/com/google/android/test/activity/SpamActivity.java b/tests/ActivityTests/src/com/google/android/test/activity/SpamActivity.java new file mode 100644 index 000000000000..e5de559e9533 --- /dev/null +++ b/tests/ActivityTests/src/com/google/android/test/activity/SpamActivity.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2014 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.google.android.test.activity; + +import android.app.Activity; +import android.os.Bundle; + +public class SpamActivity extends Activity { + byte[] mBytes; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // Chew up some RAM -- 8 megs worth. + mBytes = new byte[8*1024*1024]; + } +} |