summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDianne Hackborn <hackbod@google.com>2009-05-20 08:54:33 -0700
committerThe Android Open Source Project <initial-contribution@android.com>2009-05-20 08:54:33 -0700
commit0bcad89a58ad49a0a836902ad41584777ca1f3f3 (patch)
tree312ced3cce63f7f998c0e8bb1ada55727398d2c4
parentf0c6ab843e7261755483d1c48e4c03f630cc79b5 (diff)
parent95fc68f24a2a93e5664e2234abcfe479d385cc05 (diff)
downloadframeworks_base-0bcad89a58ad49a0a836902ad41584777ca1f3f3.zip
frameworks_base-0bcad89a58ad49a0a836902ad41584777ca1f3f3.tar.gz
frameworks_base-0bcad89a58ad49a0a836902ad41584777ca1f3f3.tar.bz2
am 95fc68f2: Fix issue where apps could prevent the user from going home.
Merge commit '95fc68f24a2a93e5664e2234abcfe479d385cc05' * commit '95fc68f24a2a93e5664e2234abcfe479d385cc05': Fix issue where apps could prevent the user from going home.
-rw-r--r--cmds/am/src/com/android/commands/am/Am.java40
-rw-r--r--core/java/android/app/ActivityManagerNative.java34
-rw-r--r--core/java/android/app/IActivityManager.java26
-rw-r--r--core/res/AndroidManifest.xml14
-rw-r--r--core/res/res/values/strings.xml6
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java270
-rw-r--r--services/java/com/android/server/am/HistoryRecord.java3
-rw-r--r--services/java/com/android/server/status/StatusBarService.java9
8 files changed, 356 insertions, 46 deletions
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index 6d4b455..88ad265 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -194,18 +194,17 @@ public class Am {
if (intent != null) {
System.out.println("Starting: " + intent);
try {
- intent.addFlags(intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
// XXX should do something to determine the MIME type.
int res = mAm.startActivity(null, intent, intent.getType(),
null, 0, null, null, 0, false, mDebugOption);
switch (res) {
case IActivityManager.START_SUCCESS:
break;
- case IActivityManager.START_CLASS_NOT_FOUND:
- System.err.println("Error type 3");
- System.err.println("Error: Activity class " +
- intent.getComponent().toShortString()
- + " does not exist.");
+ case IActivityManager.START_SWITCHES_CANCELED:
+ System.err.println(
+ "Warning: Activity not started because the "
+ + " current activity is being kept for the user.");
break;
case IActivityManager.START_DELIVERED_TO_TOP:
System.err.println(
@@ -213,25 +212,36 @@ public class Am {
+ "been delivered to currently running "
+ "top-most instance.");
break;
- case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
+ case IActivityManager.START_RETURN_INTENT_TO_CALLER:
System.err.println(
- "Error: Activity not started, you requested to "
- + "both forward and receive its result");
+ "Warning: Activity not started because intent "
+ + "should be handled by the caller");
+ break;
+ case IActivityManager.START_TASK_TO_FRONT:
+ System.err.println(
+ "Warning: Activity not started, its current "
+ + "task has been brought to the front");
break;
case IActivityManager.START_INTENT_NOT_RESOLVED:
System.err.println(
"Error: Activity not started, unable to "
+ "resolve " + intent.toString());
break;
- case IActivityManager.START_RETURN_INTENT_TO_CALLER:
+ case IActivityManager.START_CLASS_NOT_FOUND:
+ System.err.println("Error type 3");
+ System.err.println("Error: Activity class " +
+ intent.getComponent().toShortString()
+ + " does not exist.");
+ break;
+ case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
System.err.println(
- "Warning: Activity not started because intent "
- + "should be handled by the caller");
+ "Error: Activity not started, you requested to "
+ + "both forward and receive its result");
break;
- case IActivityManager.START_TASK_TO_FRONT:
+ case IActivityManager.START_PERMISSION_DENIED:
System.err.println(
- "Warning: Activity not started, its current "
- + "task has been brought to the front");
+ "Error: Activity not started, you do not "
+ + "have permission to access it.");
break;
default:
System.err.println(
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 541f413..16f0a30 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -998,6 +998,20 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM
return true;
}
+ case STOP_APP_SWITCHES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ stopAppSwitches();
+ reply.writeNoException();
+ return true;
+ }
+
+ case RESUME_APP_SWITCHES_TRANSACTION: {
+ data.enforceInterface(IActivityManager.descriptor);
+ resumeAppSwitches();
+ reply.writeNoException();
+ return true;
+ }
+
case PEEK_SERVICE_TRANSACTION: {
data.enforceInterface(IActivityManager.descriptor);
Intent service = Intent.CREATOR.createFromParcel(data);
@@ -2182,5 +2196,25 @@ class ActivityManagerProxy implements IActivityManager
return res;
}
+ public void stopAppSwitches() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(STOP_APP_SWITCHES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
+ public void resumeAppSwitches() throws RemoteException {
+ Parcel data = Parcel.obtain();
+ Parcel reply = Parcel.obtain();
+ data.writeInterfaceToken(IActivityManager.descriptor);
+ mRemote.transact(RESUME_APP_SWITCHES_TRANSACTION, data, reply, 0);
+ reply.readException();
+ reply.recycle();
+ data.recycle();
+ }
+
private IBinder mRemote;
}
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 56b29c1..d15a154 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -44,9 +44,30 @@ import java.util.List;
* {@hide}
*/
public interface IActivityManager extends IInterface {
+ /**
+ * Returned by startActivity() if the start request was canceled because
+ * app switches are temporarily canceled to ensure the user's last request
+ * (such as pressing home) is performed.
+ */
+ public static final int START_SWITCHES_CANCELED = 4;
+ /**
+ * Returned by startActivity() if an activity wasn't really started, but
+ * the given Intent was given to the existing top activity.
+ */
public static final int START_DELIVERED_TO_TOP = 3;
+ /**
+ * Returned by startActivity() if an activity wasn't really started, but
+ * a task was simply brought to the foreground.
+ */
public static final int START_TASK_TO_FRONT = 2;
+ /**
+ * Returned by startActivity() if the caller asked that the Intent not
+ * be executed if it is the recipient, and that is indeed the case.
+ */
public static final int START_RETURN_INTENT_TO_CALLER = 1;
+ /**
+ * Activity was started successfully as normal.
+ */
public static final int START_SUCCESS = 0;
public static final int START_INTENT_NOT_RESOLVED = -1;
public static final int START_CLASS_NOT_FOUND = -2;
@@ -225,6 +246,9 @@ public interface IActivityManager extends IInterface {
public boolean shutdown(int timeout) throws RemoteException;
+ public void stopAppSwitches() throws RemoteException;
+ public void resumeAppSwitches() throws RemoteException;
+
/*
* Private non-Binder interfaces
*/
@@ -371,4 +395,6 @@ public interface IActivityManager extends IInterface {
int PEEK_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+84;
int PROFILE_CONTROL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+85;
int SHUTDOWN_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+86;
+ int STOP_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+87;
+ int RESUME_APP_SWITCHES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+88;
}
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 523a2c8..8b7ec90 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -803,14 +803,22 @@
android:description="@string/permdesc_runSetActivityWatcher"
android:protectionLevel="signature" />
- <!-- Allows an application to watch and control how activities are
- started globally in the system. Only for is in debugging
- (usually the monkey command). -->
+ <!-- Allows an application to call the activity manager shutdown() API
+ to put the higher-level system there into a shutdown state. -->
<permission android:name="android.permission.SHUTDOWN"
android:label="@string/permlab_shutdown"
android:description="@string/permdesc_shutdown"
android:protectionLevel="signature" />
+ <!-- Allows an application to tell the activity manager to temporarily
+ stop application switches, putting it into a special mode that
+ prevents applications from immediately switching away from some
+ critical UI such as the home screen. -->
+ <permission android:name="android.permission.STOP_APP_SWITCHES"
+ android:label="@string/permlab_stopAppSwitches"
+ android:description="@string/permdesc_stopAppSwitches"
+ android:protectionLevel="signature" />
+
<!-- Allows an application to retrieve the current state of keys and
switches. This is only for use by the system.-->
<permission android:name="android.permission.READ_INPUT_STATE"
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index ce716b1..855089d 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -480,6 +480,12 @@
state. Does not perform a complete shutdown.</string>
<!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permlab_stopAppSwitches">prevent app switches</string>
+ <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
+ <string name="permdesc_stopAppSwitches">Prevents the user from switching to
+ another application.</string>
+
+ <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permlab_runSetActivityWatcher">monitor and control all application launching</string>
<!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
<string name="permdesc_runSetActivityWatcher">Allows an application to
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index c4dbd32..fd37cc2 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -191,6 +191,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
// Maximum number of recent tasks that we can remember.
static final int MAX_RECENT_TASKS = 20;
+ // Amount of time after a call to stopAppSwitches() during which we will
+ // prevent further untrusted switches from happening.
+ static final long APP_SWITCH_DELAY_TIME = 5*1000;
+
// How long until we reset a task when the user returns to it. Currently
// 30 minutes.
static final long ACTIVITY_INACTIVE_RESET_TIME = 1000*60*30;
@@ -328,6 +332,21 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
final ArrayList mHistory = new ArrayList();
/**
+ * Description of a request to start a new activity, which has been held
+ * due to app switches being disabled.
+ */
+ class PendingActivityLaunch {
+ HistoryRecord r;
+ HistoryRecord sourceRecord;
+ Uri[] grantedUriPermissions;
+ int grantedMode;
+ boolean onlyIfNeeded;
+ }
+
+ final ArrayList<PendingActivityLaunch> mPendingActivityLaunches
+ = new ArrayList<PendingActivityLaunch>();
+
+ /**
* List of all active broadcasts that are to be executed immediately
* (without waiting for another broadcast to finish). Currently this only
* contains broadcasts to registered receivers, to avoid spinning up
@@ -705,6 +724,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
int mFactoryTest;
/**
+ * The time at which we will allow normal application switches again,
+ * after a call to {@link #stopAppSwitches()}.
+ */
+ long mAppSwitchesAllowedTime;
+
+ /**
+ * This is set to true after the first switch after mAppSwitchesAllowedTime
+ * is set; any switches after that will clear the time.
+ */
+ boolean mDidAppSwitch;
+
+ /**
* Set while we are wanting to sleep, to prevent any
* activities from being started/resumed.
*/
@@ -852,6 +883,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
static final int SERVICE_ERROR_MSG = 18;
static final int RESUME_TOP_ACTIVITY_MSG = 19;
static final int PROC_START_TIMEOUT_MSG = 20;
+ static final int DO_PENDING_ACTIVITY_LAUNCHES_MSG = 21;
AlertDialog mUidAlert;
@@ -910,6 +942,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
d.show();
proc.anrDialog = d;
}
+
+ ensureScreenEnabled();
} break;
case SHOW_FACTORY_ERROR_MSG: {
Dialog d = new FactoryErrorDialog(
@@ -1041,6 +1075,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
processStartTimedOutLocked(app);
}
}
+ case DO_PENDING_ACTIVITY_LAUNCHES_MSG: {
+ synchronized (ActivityManagerService.this) {
+ doPendingActivityLaunchesLocked(true);
+ }
+ }
}
}
};
@@ -1495,6 +1534,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
return null;
}
+ private final HistoryRecord topRunningNonDelayedActivityLocked(HistoryRecord notTop) {
+ int i = mHistory.size()-1;
+ while (i >= 0) {
+ HistoryRecord r = (HistoryRecord)mHistory.get(i);
+ if (!r.finishing && !r.delayedResume && r != notTop) {
+ return r;
+ }
+ i--;
+ }
+ return null;
+ }
+
/**
* This is a simplified version of topRunningActivityLocked that provides a number of
* optional skip-over modes. It is intended for use with the ActivityWatcher hook only.
@@ -2245,6 +2296,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
return true;
}
+ next.delayedResume = false;
+
// If the top activity is the resumed one, nothing to do.
if (mResumedActivity == next && next.state == ActivityState.RESUMED) {
// Make sure we have executed any pending transitions, since there
@@ -2471,7 +2524,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
return true;
}
- private final void startActivityLocked(HistoryRecord r, boolean newTask) {
+ private final void startActivityLocked(HistoryRecord r, boolean newTask,
+ boolean doResume) {
final int NH = mHistory.size();
int addPos = -1;
@@ -2558,7 +2612,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
if ((r.intent.getFlags()
&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) {
resetTaskIfNeededLocked(r, r);
- doShow = topRunningActivityLocked(null) == r;
+ doShow = topRunningNonDelayedActivityLocked(null) == r;
}
}
if (SHOW_APP_STARTING_ICON && doShow) {
@@ -2588,13 +2642,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
mWindowManager.validateAppTokens(mHistory);
}
- resumeTopActivityLocked(null);
+ if (doResume) {
+ resumeTopActivityLocked(null);
+ }
}
/**
* Perform clear operation as requested by
- * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: assuming the top task on the
- * stack is the one that the new activity is being launched in, look for
+ * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: search from the top of the
+ * stack to the given task, then look for
* an instance of that activity in the stack and, if found, finish all
* activities on top of it and return the instance.
*
@@ -2602,9 +2658,21 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
* @return Returns the old activity that should be continue to be used,
* or null if none was found.
*/
- private final HistoryRecord performClearTopTaskLocked(int taskId,
+ private final HistoryRecord performClearTaskLocked(int taskId,
HistoryRecord newR, boolean doClear) {
int i = mHistory.size();
+
+ // First find the requested task.
+ while (i > 0) {
+ i--;
+ HistoryRecord r = (HistoryRecord)mHistory.get(i);
+ if (r.task.taskId == taskId) {
+ i++;
+ break;
+ }
+ }
+
+ // Now clear it.
while (i > 0) {
i--;
HistoryRecord r = (HistoryRecord)mHistory.get(i);
@@ -2840,15 +2908,75 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
intent, resolvedType, aInfo, mConfiguration,
resultRecord, resultWho, requestCode, componentSpecified);
- HistoryRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
- != 0 ? r : null;
-
+ if (mResumedActivity == null
+ || mResumedActivity.info.applicationInfo.uid != callingUid) {
+ if (!checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) {
+ PendingActivityLaunch pal = new PendingActivityLaunch();
+ pal.r = r;
+ pal.sourceRecord = sourceRecord;
+ pal.grantedUriPermissions = grantedUriPermissions;
+ pal.grantedMode = grantedMode;
+ pal.onlyIfNeeded = onlyIfNeeded;
+ mPendingActivityLaunches.add(pal);
+ return START_SWITCHES_CANCELED;
+ }
+ }
+
+ if (mDidAppSwitch) {
+ // This is the second allowed switch since we stopped switches,
+ // so now just generally allow switches. Use case: user presses
+ // home (switches disabled, switch to home, mDidAppSwitch now true);
+ // user taps a home icon (coming from home so allowed, we hit here
+ // and now allow anyone to switch again).
+ mAppSwitchesAllowedTime = 0;
+ } else {
+ mDidAppSwitch = true;
+ }
+
+ doPendingActivityLaunchesLocked(false);
+
+ return startActivityUncheckedLocked(r, sourceRecord,
+ grantedUriPermissions, grantedMode, onlyIfNeeded, true);
+ }
+
+ private final void doPendingActivityLaunchesLocked(boolean doResume) {
+ final int N = mPendingActivityLaunches.size();
+ if (N <= 0) {
+ return;
+ }
+ for (int i=0; i<N; i++) {
+ PendingActivityLaunch pal = mPendingActivityLaunches.get(i);
+ startActivityUncheckedLocked(pal.r, pal.sourceRecord,
+ pal.grantedUriPermissions, pal.grantedMode, pal.onlyIfNeeded,
+ doResume && i == (N-1));
+ }
+ mPendingActivityLaunches.clear();
+ }
+
+ private final int startActivityUncheckedLocked(HistoryRecord r,
+ HistoryRecord sourceRecord, Uri[] grantedUriPermissions,
+ int grantedMode, boolean onlyIfNeeded, boolean doResume) {
+ final Intent intent = r.intent;
+ final int callingUid = r.launchedFromUid;
+
+ int launchFlags = intent.getFlags();
+
// 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;
if (DEBUG_USER_LEAVING) Log.v(TAG,
"startActivity() => mUserLeaving=" + mUserLeaving);
+ // If the caller has asked not to resume at this point, we make note
+ // of this in the record so that we can skip it when trying to find
+ // the top running activity.
+ if (!doResume) {
+ r.delayedResume = true;
+ }
+
+ HistoryRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP)
+ != 0 ? r : null;
+
// If the onlyIfNeeded flag is set, then we can do this if the activity
// being launched is the same as the one making the call... or, as
// a special case, if we do not know the caller then we count the
@@ -2856,7 +2984,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
if (onlyIfNeeded) {
HistoryRecord checkedCaller = sourceRecord;
if (checkedCaller == null) {
- checkedCaller = topRunningActivityLocked(notTop);
+ checkedCaller = topRunningNonDelayedActivityLocked(notTop);
}
if (!checkedCaller.realActivity.equals(r.realActivity)) {
// Caller is not the same as launcher, so always needed.
@@ -2894,7 +3022,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK;
}
- if (resultRecord != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
+ if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
// For whatever reason this activity is being launched into a new
// task... yet the caller has requested a result back. Well, that
// is pretty messed up, so instead immediately send back a cancel
@@ -2902,10 +3030,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
// dependency on its originator.
Log.w(TAG, "Activity is launching as a new task, so cancelling activity result.");
sendActivityResultLocked(-1,
- resultRecord, resultWho, requestCode,
+ r.resultTo, r.resultWho, r.requestCode,
Activity.RESULT_CANCELED, null);
r.resultTo = null;
- resultRecord = null;
}
boolean addingToTask = false;
@@ -2916,7 +3043,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
// If bring to front is requested, and no result is requested, and
// we can find a task that was started with this same
// component, then instead of launching bring that one to the front.
- if (resultRecord == null) {
+ if (r.resultTo == null) {
// See if there is a task to bring to the front. If this is
// a SINGLE_INSTANCE activity, there can be one and only one
// instance of it in the history, and it is always in its own
@@ -2938,7 +3065,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
// to have the same behavior as if a new instance was
// being started, which means not bringing it to the front
// if the caller is not itself in the front.
- HistoryRecord curTop = topRunningActivityLocked(notTop);
+ HistoryRecord curTop = topRunningNonDelayedActivityLocked(notTop);
if (curTop.task != taskTop.task) {
r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT);
boolean callerAtFront = sourceRecord == null
@@ -2959,7 +3086,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
// the client said not to do anything if that
// is the case, so this is it! And for paranoia, make
// sure we have correctly resumed the top activity.
- resumeTopActivityLocked(null);
+ if (doResume) {
+ resumeTopActivityLocked(null);
+ }
return START_RETURN_INTENT_TO_CALLER;
}
if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0
@@ -2969,7 +3098,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
// from the task up to the one being started. In most
// cases this means we are resetting the task to its
// initial state.
- HistoryRecord top = performClearTopTaskLocked(
+ HistoryRecord top = performClearTaskLocked(
taskTop.task.taskId, r, true);
if (top != null) {
if (top.frontOfTask) {
@@ -3035,7 +3164,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
// We didn't do anything... but it was needed (a.k.a., client
// don't use that intent!) And for paranoia, make
// sure we have correctly resumed the top activity.
- resumeTopActivityLocked(null);
+ if (doResume) {
+ resumeTopActivityLocked(null);
+ }
return START_TASK_TO_FRONT;
}
}
@@ -3052,8 +3183,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
// If the activity being launched is the same as the one currently
// at the top, then we need to check if it should only be launched
// once.
- HistoryRecord top = topRunningActivityLocked(notTop);
- if (top != null && resultRecord == null) {
+ HistoryRecord top = topRunningNonDelayedActivityLocked(notTop);
+ if (top != null && r.resultTo == null) {
if (top.realActivity.equals(r.realActivity)) {
if (top.app != null && top.app.thread != null) {
if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0
@@ -3062,7 +3193,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
logStartActivity(LOG_AM_NEW_INTENT, top, top.task);
// For paranoia, make sure we have correctly
// resumed the top activity.
- resumeTopActivityLocked(null);
+ if (doResume) {
+ resumeTopActivityLocked(null);
+ }
if (onlyIfNeeded) {
// We don't need to start a new activity, and
// the client said not to do anything if that
@@ -3077,9 +3210,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
}
} else {
- if (resultRecord != null) {
+ if (r.resultTo != null) {
sendActivityResultLocked(-1,
- resultRecord, resultWho, requestCode,
+ r.resultTo, r.resultWho, r.requestCode,
Activity.RESULT_CANCELED, null);
}
return START_CLASS_NOT_FOUND;
@@ -3088,7 +3221,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
boolean newTask = false;
// Should this be considered a new task?
- if (resultRecord == null && !addingToTask
+ if (r.resultTo == null && !addingToTask
&& (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) {
// todo: should do better management of integers.
mCurTask++;
@@ -3108,14 +3241,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
// In this case, we are adding the activity to an existing
// task, but the caller has asked to clear that task if the
// activity is already running.
- HistoryRecord top = performClearTopTaskLocked(
+ HistoryRecord top = performClearTaskLocked(
sourceRecord.task.taskId, r, true);
if (top != null) {
logStartActivity(LOG_AM_NEW_INTENT, r, top.task);
deliverNewIntentLocked(top, r.intent);
// For paranoia, make sure we have correctly
// resumed the top activity.
- resumeTopActivityLocked(null);
+ if (doResume) {
+ resumeTopActivityLocked(null);
+ }
return START_DELIVERED_TO_TOP;
}
} else if (!addingToTask &&
@@ -3128,7 +3263,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
HistoryRecord top = moveActivityToFrontLocked(where);
logStartActivity(LOG_AM_NEW_INTENT, r, top.task);
deliverNewIntentLocked(top, r.intent);
- resumeTopActivityLocked(null);
+ if (doResume) {
+ resumeTopActivityLocked(null);
+ }
return START_DELIVERED_TO_TOP;
}
}
@@ -3157,7 +3294,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
EventLog.writeEvent(LOG_AM_CREATE_TASK, r.task.taskId);
}
logStartActivity(LOG_AM_CREATE_ACTIVITY, r, r.task);
- startActivityLocked(r, newTask);
+ startActivityLocked(r, newTask, doResume);
return START_SUCCESS;
}
@@ -4911,6 +5048,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
}
}
+ final void ensureScreenEnabled() {
+ boolean enableScreen;
+ synchronized (this) {
+ enableScreen = !mBooted;
+ mBooted = true;
+ }
+
+ if (enableScreen) {
+ EventLog.writeEvent(LOG_BOOT_PROGRESS_ENABLE_SCREEN,
+ SystemClock.uptimeMillis());
+ enableScreenAfterBoot();
+ }
+ }
+
public final void activityPaused(IBinder token, Bundle icicle) {
// Refuse possible leaked file descriptors
if (icicle != null && icicle.hasFileDescriptors()) {
@@ -6251,6 +6402,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
"moveTaskToFront()");
synchronized(this) {
+ if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
+ Binder.getCallingUid(), "Task to front")) {
+ return;
+ }
final long origId = Binder.clearCallingIdentity();
try {
int N = mRecentTasks.size();
@@ -6335,6 +6490,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
"moveTaskToBack()");
synchronized(this) {
+ if (mResumedActivity != null && mResumedActivity.task.taskId == task) {
+ if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
+ Binder.getCallingUid(), "Task to back")) {
+ return;
+ }
+ }
final long origId = Binder.clearCallingIdentity();
moveTaskToBackLocked(task);
Binder.restoreCallingIdentity(origId);
@@ -6438,6 +6599,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
"moveTaskBackwards()");
synchronized(this) {
+ if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(),
+ Binder.getCallingUid(), "Task backwards")) {
+ return;
+ }
final long origId = Binder.clearCallingIdentity();
moveTaskBackwardsLocked(task);
Binder.restoreCallingIdentity(origId);
@@ -7179,6 +7344,55 @@ public final class ActivityManagerService extends ActivityManagerNative implemen
}
}
+ public void stopAppSwitches() {
+ if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires permission "
+ + android.Manifest.permission.STOP_APP_SWITCHES);
+ }
+
+ synchronized(this) {
+ mAppSwitchesAllowedTime = SystemClock.uptimeMillis()
+ + APP_SWITCH_DELAY_TIME;
+ mDidAppSwitch = false;
+ mHandler.removeMessages(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
+ Message msg = mHandler.obtainMessage(DO_PENDING_ACTIVITY_LAUNCHES_MSG);
+ mHandler.sendMessageDelayed(msg, APP_SWITCH_DELAY_TIME);
+ }
+ }
+
+ public void resumeAppSwitches() {
+ if (checkCallingPermission(android.Manifest.permission.STOP_APP_SWITCHES)
+ != PackageManager.PERMISSION_GRANTED) {
+ throw new SecurityException("Requires permission "
+ + android.Manifest.permission.STOP_APP_SWITCHES);
+ }
+
+ synchronized(this) {
+ // Note that we don't execute any pending app switches... we will
+ // let those wait until either the timeout, or the next start
+ // activity request.
+ mAppSwitchesAllowedTime = 0;
+ }
+ }
+
+ boolean checkAppSwitchAllowedLocked(int callingPid, int callingUid,
+ String name) {
+ if (mAppSwitchesAllowedTime < SystemClock.uptimeMillis()) {
+ return true;
+ }
+
+ final int perm = checkComponentPermission(
+ android.Manifest.permission.STOP_APP_SWITCHES, callingPid,
+ callingUid, -1);
+ if (perm == PackageManager.PERMISSION_GRANTED) {
+ return true;
+ }
+
+ Log.w(TAG, name + " request from " + callingUid + " stopped");
+ return false;
+ }
+
public void setDebugApp(String packageName, boolean waitForDebugger,
boolean persistent) {
enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP,
diff --git a/services/java/com/android/server/am/HistoryRecord.java b/services/java/com/android/server/am/HistoryRecord.java
index 1488791..1789687 100644
--- a/services/java/com/android/server/am/HistoryRecord.java
+++ b/services/java/com/android/server/am/HistoryRecord.java
@@ -85,6 +85,7 @@ class HistoryRecord extends IApplicationToken.Stub {
boolean launchFailed; // set if a launched failed, to abort on 2nd try
boolean haveState; // have we gotten the last activity state?
boolean stopped; // is activity pause finished?
+ boolean delayedResume; // not yet resumed because of stopped app switches?
boolean finishing; // activity in pending finish list?
boolean configDestroy; // need to destroy due to config change?
int configChangeFlags; // which config values have changed
@@ -146,6 +147,7 @@ class HistoryRecord extends IApplicationToken.Stub {
pw.print(" icicle="); pw.println(icicle);
pw.print(prefix); pw.print("state="); pw.print(state);
pw.print(" stopped="); pw.print(stopped);
+ pw.print(" delayedResume="); pw.print(delayedResume);
pw.print(" finishing="); pw.println(finishing);
pw.print(prefix); pw.print("keysPaused="); pw.print(keysPaused);
pw.print(" inHistory="); pw.print(inHistory);
@@ -191,6 +193,7 @@ class HistoryRecord extends IApplicationToken.Stub {
launchFailed = false;
haveState = false;
stopped = false;
+ delayedResume = false;
finishing = false;
configDestroy = false;
keysPaused = false;
diff --git a/services/java/com/android/server/status/StatusBarService.java b/services/java/com/android/server/status/StatusBarService.java
index 5336e27..48cbace 100644
--- a/services/java/com/android/server/status/StatusBarService.java
+++ b/services/java/com/android/server/status/StatusBarService.java
@@ -19,6 +19,7 @@ package com.android.server.status;
import com.android.internal.R;
import com.android.internal.util.CharSequences;
+import android.app.ActivityManagerNative;
import android.app.Dialog;
import android.app.IStatusBar;
import android.app.PendingIntent;
@@ -1254,6 +1255,14 @@ public class StatusBarService extends IStatusBar.Stub
public void onClick(View v) {
try {
+ // The intent we are sending is for the application, which
+ // won't have permission to immediately start an activity after
+ // the user switches to home. We know it is safe to do at this
+ // point, so make sure new activity switches are now allowed.
+ ActivityManagerNative.getDefault().resumeAppSwitches();
+ } catch (RemoteException e) {
+ }
+ try {
mIntent.send();
mNotificationCallbacks.onNotificationClick(mPkg, mId);
} catch (PendingIntent.CanceledException e) {