summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--api/current.txt3
-rw-r--r--core/java/android/app/Activity.java20
-rw-r--r--core/java/android/app/ActivityManager.java34
-rw-r--r--core/java/android/app/ActivityManagerNative.java39
-rw-r--r--core/java/android/app/ActivityThread.java38
-rw-r--r--core/java/android/app/IActivityManager.java4
-rw-r--r--core/java/android/app/IAppTask.aidl5
-rw-r--r--core/java/android/app/Instrumentation.java34
-rw-r--r--core/java/android/provider/Settings.java3
-rw-r--r--core/java/com/android/internal/os/BinderInternal.java31
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/AlternateRecentsComponent.java12
-rw-r--r--packages/SystemUI/src/com/android/systemui/recents/views/RecentsView.java10
-rw-r--r--services/core/java/com/android/server/Watchdog.java16
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityManagerService.java275
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityRecord.java18
-rwxr-xr-xservices/core/java/com/android/server/am/ActivityStack.java67
-rw-r--r--services/core/java/com/android/server/am/ActivityStackSupervisor.java168
-rw-r--r--services/core/java/com/android/server/am/PendingIntentRecord.java2
-rw-r--r--services/core/java/com/android/server/am/ProcessRecord.java17
-rw-r--r--tests/ActivityTests/AndroidManifest.xml5
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java105
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/DocActivity.java35
-rw-r--r--tests/ActivityTests/src/com/google/android/test/activity/SpamActivity.java31
23 files changed, 803 insertions, 169 deletions
diff --git a/api/current.txt b/api/current.txt
index 4d8d635..6909af8 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 2667bcd..58790f6 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 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 354eb55..c229930 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 07a7e74..0379a22 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 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 e794b83..296b0e2 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 fcbe71e..694142a 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 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 8aec392..8058c05 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 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];
+ }
+}