diff options
23 files changed, 803 insertions, 169 deletions
diff --git a/api/current.txt b/api/current.txt index 6266a06..ba97e21 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 2e66a4c..9b7cc1c 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 2173647..586e7d4 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -2623,6 +2623,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 82af99b..36e8892 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 2136209..0356093 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 69e1710..57c4b71 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 4e38c36..37fead9 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 b28d7cc..bc71bad 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 2241716..95d1351 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 3b0f0f4..240d9df 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 8710aa2..0b36bdb 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java +++ b/packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java @@ -220,21 +220,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 1dd484b..47fda5b 100644 --- a/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java +++ b/packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java @@ -458,13 +458,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) { @@ -473,9 +466,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 1ce073a..89e3f49 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 f315b74..e2a6534 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 { @@ -7921,17 +7953,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); @@ -7975,7 +7996,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"; } @@ -8028,32 +8049,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 @@ -10151,7 +10176,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; } } @@ -10195,7 +10220,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; } } @@ -10314,8 +10339,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) { @@ -10784,7 +10809,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); } } } @@ -11351,8 +11376,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); } } @@ -13701,9 +13729,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 { @@ -16559,8 +16587,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) { @@ -16568,8 +16595,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; @@ -16604,7 +16630,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) { @@ -16984,19 +17010,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; @@ -17012,7 +17038,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 @@ -17240,11 +17266,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(); @@ -18308,14 +18330,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); } @@ -18330,17 +18353,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(); @@ -18348,14 +18418,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 39b6375..066ec68 100755 --- a/services/core/java/com/android/server/am/ActivityRecord.java +++ b/services/core/java/com/android/server/am/ActivityRecord.java @@ -1070,6 +1070,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 41ed4ce..fd1474f 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 bd8501c..a9898ee 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); } } @@ -2725,6 +2795,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); @@ -3498,7 +3626,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 98999e9..433ab60 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 f1bcb60..0817dd8 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 c0898d3..3fb547d 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 7f3aa77..ea0db56 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 0000000..6330c79 --- /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 0000000..e5de559 --- /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]; + } +} |