diff options
22 files changed, 819 insertions, 161 deletions
diff --git a/api/current.xml b/api/current.xml index 4025f3c..4400314 100644 --- a/api/current.xml +++ b/api/current.xml @@ -23927,6 +23927,21 @@ <parameter name="packageName" type="java.lang.String"> </parameter> </method> +<method name="moveTaskToFront" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="taskId" type="int"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> <method name="restartPackage" return="void" abstract="false" @@ -23940,6 +23955,17 @@ <parameter name="packageName" type="java.lang.String"> </parameter> </method> +<field name="MOVE_TASK_WITH_HOME" + type="int" + transient="false" + volatile="false" + value="1" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="RECENT_WITH_EXCLUDED" type="int" transient="false" @@ -33066,6 +33092,25 @@ visibility="public" > </method> +<method name="getActivities" + return="android.app.PendingIntent" + abstract="false" + native="false" + synchronized="false" + static="true" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="context" type="android.content.Context"> +</parameter> +<parameter name="requestCode" type="int"> +</parameter> +<parameter name="intents" type="android.content.Intent[]"> +</parameter> +<parameter name="flags" type="int"> +</parameter> +</method> <method name="getActivity" return="android.app.PendingIntent" abstract="false" @@ -47145,6 +47190,19 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="startActivities" + return="void" + abstract="true" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="intents" type="android.content.Intent[]"> +</parameter> +</method> <method name="startActivity" return="void" abstract="true" @@ -48602,6 +48660,19 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="startActivities" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="intents" type="android.content.Intent[]"> +</parameter> +</method> <method name="startActivity" return="void" abstract="false" @@ -52554,6 +52625,17 @@ visibility="public" > </field> +<field name="FLAG_ACTIVITY_CLEAR_TASK" + type="int" + transient="false" + volatile="false" + value="32768" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="FLAG_ACTIVITY_CLEAR_TOP" type="int" transient="false" @@ -52708,6 +52790,17 @@ visibility="public" > </field> +<field name="FLAG_ACTIVITY_TASK_ON_HOME" + type="int" + transient="false" + volatile="false" + value="16384" + static="true" + final="true" + deprecated="not deprecated" + visibility="public" +> +</field> <field name="FLAG_DEBUG_LOG_RESOLUTION" type="int" transient="false" @@ -174471,6 +174564,19 @@ <exception name="IOException" type="java.io.IOException"> </exception> </method> +<method name="startActivities" + return="void" + abstract="false" + native="false" + synchronized="false" + static="false" + final="false" + deprecated="not deprecated" + visibility="public" +> +<parameter name="intents" type="android.content.Intent[]"> +</parameter> +</method> <method name="startActivity" return="void" abstract="false" diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 5174f19..d69a179 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -3135,6 +3135,30 @@ public class Activity extends ContextThemeWrapper } /** + * Launch a new activity. You will not receive any information about when + * the activity exits. This implementation overrides the base version, + * providing information about + * the activity performing the launch. Because of this additional + * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not + * required; if not specified, the new activity will be added to the + * task of the caller. + * + * <p>This method throws {@link android.content.ActivityNotFoundException} + * if there was no Activity found to run the given Intent. + * + * @param intents The intents to start. + * + * @throws android.content.ActivityNotFoundException + * + * @see #startActivityForResult + */ + @Override + public void startActivities(Intent[] intents) { + mInstrumentation.execStartActivities(this, mMainThread.getApplicationThread(), + mToken, this, intents); + } + + /** * Like {@link #startActivity(Intent)}, but taking a IntentSender * to start; see * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} @@ -3616,7 +3640,7 @@ public class Activity extends ContextThemeWrapper ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName, mParent == null ? mToken : mParent.mToken, - mEmbeddedID, requestCode, data, null, flags); + mEmbeddedID, requestCode, new Intent[] { data }, null, flags); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { // Empty diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index fe1e7d7..e168034 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -334,6 +334,32 @@ public class ActivityManager { } /** + * Flag for {@link #moveTaskToFront(int, int)}: also move the "home" + * activity along with the task, so it is positioned immediately behind + * the task. + */ + public static final int MOVE_TASK_WITH_HOME = 0x00000001; + + /** + * Ask that the task associated with a given task ID be moved to the + * front of the stack, so it is now visible to the user. Requires that + * the caller hold permission {@link android.Manifest.permission#REORDER_TASKS} + * or a SecurityException will be thrown. + * + * @param taskId The identifier of the task to be moved, as found in + * {@link RunningTaskInfo} or {@link RecentTaskInfo}. + * @param flags Additional operational flags, 0 or more of + * {@link #MOVE_TASK_WITH_HOME}. + */ + public void moveTaskToFront(int taskId, int flags) { + try { + ActivityManagerNative.getDefault().moveTaskToFront(taskId, flags); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + } + } + + /** * Information you can retrieve about a particular Service that is * currently running in the system. */ diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 8cc6428..273e3c6 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -492,7 +492,8 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case MOVE_TASK_TO_FRONT_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); int task = data.readInt(); - moveTaskToFront(task); + int fl = data.readInt(); + moveTaskToFront(task, fl); reply.writeNoException(); return true; } @@ -791,13 +792,19 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM IBinder token = data.readStrongBinder(); String resultWho = data.readString(); int requestCode = data.readInt(); - Intent requestIntent = data.readInt() != 0 - ? Intent.CREATOR.createFromParcel(data) : null; - String requestResolvedType = data.readString(); + Intent[] requestIntents; + String[] requestResolvedTypes; + if (data.readInt() != 0) { + requestIntents = data.createTypedArray(Intent.CREATOR); + requestResolvedTypes = data.createStringArray(); + } else { + requestIntents = null; + requestResolvedTypes = null; + } int fl = data.readInt(); IIntentSender res = getIntentSender(type, packageName, token, - resultWho, requestCode, requestIntent, - requestResolvedType, fl); + resultWho, requestCode, requestIntents, + requestResolvedTypes, fl); reply.writeNoException(); reply.writeStrongBinder(res != null ? res.asBinder() : null); return true; @@ -1355,6 +1362,33 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case START_ACTIVITIES_IN_PACKAGE_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + int uid = data.readInt(); + Intent[] intents = data.createTypedArray(Intent.CREATOR); + String[] resolvedTypes = data.createStringArray(); + IBinder resultTo = data.readStrongBinder(); + int result = startActivitiesInPackage(uid, intents, resolvedTypes, resultTo); + reply.writeNoException(); + reply.writeInt(result); + return true; + } + + case START_ACTIVITIES_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + Intent[] intents = data.createTypedArray(Intent.CREATOR); + String[] resolvedTypes = data.createStringArray(); + IBinder resultTo = data.readStrongBinder(); + int result = startActivities(app, intents, resolvedTypes, resultTo); + reply.writeNoException(); + reply.writeInt(result); + return true; + } + } return super.onTransact(code, data, reply, flags); @@ -1829,12 +1863,13 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); return list; } - public void moveTaskToFront(int task) throws RemoteException + public void moveTaskToFront(int task, int flags) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeInt(task); + data.writeInt(flags); mRemote.transact(MOVE_TASK_TO_FRONT_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); @@ -2283,7 +2318,7 @@ class ActivityManagerProxy implements IActivityManager } public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) + int requestCode, Intent[] intents, String[] resolvedTypes, int flags) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2293,13 +2328,13 @@ class ActivityManagerProxy implements IActivityManager data.writeStrongBinder(token); data.writeString(resultWho); data.writeInt(requestCode); - if (intent != null) { + if (intents != null) { data.writeInt(1); - intent.writeToParcel(data, 0); + data.writeTypedArray(intents, 0); + data.writeStringArray(resolvedTypes); } else { data.writeInt(0); } - data.writeString(resolvedType); data.writeInt(flags); mRemote.transact(GET_INTENT_SENDER_TRANSACTION, data, reply, 0); reply.readException(); @@ -3026,5 +3061,39 @@ class ActivityManagerProxy implements IActivityManager return res; } + public int startActivities(IApplicationThread caller, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + data.writeTypedArray(intents, 0); + data.writeStringArray(resolvedTypes); + data.writeStrongBinder(resultTo); + mRemote.transact(START_ACTIVITIES_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } + + public int startActivitiesInPackage(int uid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(uid); + data.writeTypedArray(intents, 0); + data.writeStringArray(resolvedTypes); + data.writeStrongBinder(resultTo); + mRemote.transact(START_ACTIVITIES_IN_PACKAGE_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } + private IBinder mRemote; } diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 82b3f75..d2de382 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -844,6 +844,19 @@ class ContextImpl extends Context { } @Override + public void startActivities(Intent[] intents) { + if ((intents[0].getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + throw new AndroidRuntimeException( + "Calling startActivities() from outside of an Activity " + + " context requires the FLAG_ACTIVITY_NEW_TASK flag on first Intent." + + " Is this really what you want?"); + } + mMainThread.getInstrumentation().execStartActivities( + getOuterContext(), mMainThread.getApplicationThread(), null, + (Activity)null, intents); + } + + @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index c9d5448..dc18083 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -134,7 +134,7 @@ public interface IActivityManager extends IInterface { public List getServices(int maxNum, int flags) throws RemoteException; public List<ActivityManager.ProcessErrorStateInfo> getProcessesInErrorState() throws RemoteException; - public void moveTaskToFront(int task) throws RemoteException; + public void moveTaskToFront(int task, int flags) throws RemoteException; public void moveTaskToBack(int task) throws RemoteException; public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) throws RemoteException; public void moveTaskBackwards(int task) throws RemoteException; @@ -199,7 +199,8 @@ public interface IActivityManager extends IInterface { public static final int INTENT_SENDER_SERVICE = 4; public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) throws RemoteException; + int requestCode, Intent[] intents, String[] resolvedTypes, + int flags) throws RemoteException; public void cancelIntentSender(IIntentSender sender) throws RemoteException; public boolean clearApplicationUserData(final String packageName, final IPackageDataObserver observer) throws RemoteException; @@ -208,7 +209,8 @@ public interface IActivityManager extends IInterface { public void setProcessLimit(int max) throws RemoteException; public int getProcessLimit() throws RemoteException; - public void setProcessForeground(IBinder token, int pid, boolean isForeground) throws RemoteException; + public void setProcessForeground(IBinder token, int pid, + boolean isForeground) throws RemoteException; public int checkPermission(String permission, int pid, int uid) throws RemoteException; @@ -332,6 +334,11 @@ public interface IActivityManager extends IInterface { public boolean dumpHeap(String process, boolean managed, String path, ParcelFileDescriptor fd) throws RemoteException; + public int startActivities(IApplicationThread caller, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException; + public int startActivitiesInPackage(int uid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) throws RemoteException; + /* * Private non-Binder interfaces */ @@ -544,4 +551,6 @@ public interface IActivityManager extends IInterface { int REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+117; int CHECK_GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118; int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+119; + int START_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+120; + int START_ACTIVITIES_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+121; } diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index 1deb9fb..ad811d8 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -1354,8 +1354,8 @@ public class Instrumentation { * {@hide} */ public ActivityResult execStartActivity( - Context who, IBinder contextThread, IBinder token, Activity target, - Intent intent, int requestCode) { + Context who, IBinder contextThread, IBinder token, Activity target, + Intent intent, int requestCode) { IApplicationThread whoThread = (IApplicationThread) contextThread; if (mActivityMonitors != null) { synchronized (mSync) { @@ -1386,6 +1386,44 @@ public class Instrumentation { /** * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)}, + * but accepts an array of activities to be started. Note that active + * {@link ActivityMonitor} objects only match against the first activity in + * the array. + * + * {@hide} + */ + public void execStartActivities(Context who, IBinder contextThread, + IBinder token, Activity target, Intent[] intents) { + 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, intents[0])) { + am.mHits++; + if (am.isBlocking()) { + return; + } + break; + } + } + } + } + try { + String[] resolvedTypes = new String[intents.length]; + for (int i=0; i<intents.length; i++) { + resolvedTypes[i] = intents[i].resolveTypeIfNeeded(who.getContentResolver()); + } + int result = ActivityManagerNative.getDefault() + .startActivities(whoThread, intents, resolvedTypes, token); + checkStartActivityResult(result, intents[0]); + } catch (RemoteException e) { + } + } + + /** + * Like {@link #execStartActivity(Context, IBinder, IBinder, Activity, Intent, int)}, * but for calls from a {#link Fragment}. * * @param who The Context from which the activity is being started. diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index be1dc4a..5b43b65 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -195,7 +195,67 @@ public final class PendingIntent implements Parcelable { IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_ACTIVITY, packageName, - null, null, requestCode, intent, resolvedType, flags); + null, null, requestCode, new Intent[] { intent }, + resolvedType != null ? new String[] { resolvedType } : null, flags); + return target != null ? new PendingIntent(target) : null; + } catch (RemoteException e) { + } + return null; + } + + /** + * Like {@link #getActivity(Context, int, Intent, int)}, but allows an + * array of Intents to be supplied. The first Intent in the array is + * taken as the primary key for the PendingIntent, like the single Intent + * given to {@link #getActivity(Context, int, Intent, int)}. Upon sending + * the resulting PendingIntent, all of the Intents are started in the same + * way as they would be by passing them to {@link Context#startActivities(Intent[])}. + * + * <p class="note"> + * The <em>first</em> intent in the array will be started outside of the context of an + * existing activity, so you must use the {@link Intent#FLAG_ACTIVITY_NEW_TASK + * Intent.FLAG_ACTIVITY_NEW_TASK} launch flag in the Intent. (Activities after + * the first in the array are started in the context of the previous activity + * in the array, so FLAG_ACTIVITY_NEW_TASK is not needed nor desired for them.) + * </p> + * + * <p class="note"> + * The <em>last</em> intent in the array represents the key for the + * PendingIntent. In other words, it is the significant element for matching + * (as done with the single intent given to {@link #getActivity(Context, int, Intent, int)}, + * its content will be the subject of replacement by + * {@link #send(Context, int, Intent)} and {@link #FLAG_UPDATE_CURRENT}, etc. + * This is because it is the most specific of the supplied intents, and the + * UI the user actually sees when the intents are started. + * </p> + * + * @param context The Context in which this PendingIntent should start + * the activity. + * @param requestCode Private request code for the sender (currently + * not used). + * @param intents Array of Intents of the activities to be launched. + * @param flags May be {@link #FLAG_ONE_SHOT}, {@link #FLAG_NO_CREATE}, + * {@link #FLAG_CANCEL_CURRENT}, {@link #FLAG_UPDATE_CURRENT}, + * or any of the flags as supported by + * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts + * of the intent that can be supplied when the actual send happens. + * + * @return Returns an existing or new PendingIntent matching the given + * parameters. May return null only if {@link #FLAG_NO_CREATE} has been + * supplied. + */ + public static PendingIntent getActivities(Context context, int requestCode, + Intent[] intents, int flags) { + String packageName = context.getPackageName(); + String[] resolvedTypes = new String[intents.length]; + for (int i=0; i<intents.length; i++) { + resolvedTypes[i] = intents[i].resolveTypeIfNeeded(context.getContentResolver()); + } + try { + IIntentSender target = + ActivityManagerNative.getDefault().getIntentSender( + IActivityManager.INTENT_SENDER_ACTIVITY, packageName, + null, null, requestCode, intents, resolvedTypes, flags); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { } @@ -230,7 +290,8 @@ public final class PendingIntent implements Parcelable { IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_BROADCAST, packageName, - null, null, requestCode, intent, resolvedType, flags); + null, null, requestCode, new Intent[] { intent }, + resolvedType != null ? new String[] { resolvedType } : null, flags); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { } @@ -266,7 +327,8 @@ public final class PendingIntent implements Parcelable { IIntentSender target = ActivityManagerNative.getDefault().getIntentSender( IActivityManager.INTENT_SENDER_SERVICE, packageName, - null, null, requestCode, intent, resolvedType, flags); + null, null, requestCode, new Intent[] { intent }, + resolvedType != null ? new String[] { resolvedType } : null, flags); return target != null ? new PendingIntent(target) : null; } catch (RemoteException e) { } diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index a463afc..27be51a 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -725,6 +725,28 @@ public abstract class Context { public abstract void startActivity(Intent intent); /** + * Launch multiple new activities. This is generally the same as calling + * {@link #startActivity(Intent)} for the first Intent in the array, + * that activity during its creation calling {@link #startActivity(Intent)} + * for the second entry, etc. Note that unlike that approach, generally + * none of the activities except the last in the array will be created + * at this point, but rather will be created when the user first visits + * them (due to pressing back from the activity on top). + * + * <p>This method throws {@link ActivityNotFoundException} + * if there was no Activity found for <em>any</em> given Intent. In this + * case the state of the activity stack is undefined (some Intents in the + * list may be on it, some not), so you probably want to avoid such situations. + * + * @param intents An array of Intents to be started. + * + * @throws ActivityNotFoundException + * + * @see PackageManager#resolveActivity + */ + public abstract void startActivities(Intent[] intents); + + /** * Like {@link #startActivity(Intent)}, but taking a IntentSender * to start. If the IntentSender is for an activity, that activity will be started * as if you had called the regular {@link #startActivity(Intent)} diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 3f5d215..f8928e4 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -266,6 +266,11 @@ public class ContextWrapper extends Context { } @Override + public void startActivities(Intent[] intents) { + mBase.startActivities(intents); + } + + @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 4b6333e..84cb2fb 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -2614,13 +2614,29 @@ public class Intent implements Parcelable, Cloneable { * animation to go to the next activity state. This doesn't mean an * animation will never run -- if another activity change happens that doesn't * specify this flag before the activity started here is displayed, then - * that transition will be used. This this flag can be put to good use + * that transition will be used. This flag can be put to good use * when you are going to do a series of activity operations but the * animation seen by the user shouldn't be driven by the first activity * change but rather a later one. */ public static final int FLAG_ACTIVITY_NO_ANIMATION = 0X00010000; /** + * If set in an Intent passed to {@link Context#startActivity Context.startActivity()}, + * this flag will cause any existing task that would be associated with the + * activity to be cleared before the activity is started. That is, the activity + * becomes the new root of an otherwise empty task, and any old activities + * are finished. This can only be used in conjunction with {@link #FLAG_ACTIVITY_NEW_TASK}. + */ + public static final int FLAG_ACTIVITY_CLEAR_TASK = 0X00008000; + /** + * If set in an Intent passed to {@link Context#startActivity Context.startActivity()}, + * this flag will cause a newly launching task to be placed on top of the current + * home activity task (if there is one). That is, pressing back from the task + * will always return the user to home even if that was not the last activity they + * saw. This can only be used in conjunction with {@link #FLAG_ACTIVITY_NEW_TASK}. + */ + public static final int FLAG_ACTIVITY_TASK_ON_HOME = 0X00004000; + /** * If set, when sending a broadcast only registered receivers will be * called -- no BroadcastReceiver components will be launched. */ @@ -4871,18 +4887,22 @@ public class Intent implements Parcelable, Cloneable { * @see #FLAG_DEBUG_LOG_RESOLUTION * @see #FLAG_FROM_BACKGROUND * @see #FLAG_ACTIVITY_BROUGHT_TO_FRONT - * @see #FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET + * @see #FLAG_ACTIVITY_CLEAR_TASK * @see #FLAG_ACTIVITY_CLEAR_TOP + * @see #FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET * @see #FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS * @see #FLAG_ACTIVITY_FORWARD_RESULT * @see #FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY * @see #FLAG_ACTIVITY_MULTIPLE_TASK * @see #FLAG_ACTIVITY_NEW_TASK + * @see #FLAG_ACTIVITY_NO_ANIMATION * @see #FLAG_ACTIVITY_NO_HISTORY * @see #FLAG_ACTIVITY_NO_USER_ACTION * @see #FLAG_ACTIVITY_PREVIOUS_IS_TOP * @see #FLAG_ACTIVITY_RESET_TASK_IF_NEEDED + * @see #FLAG_ACTIVITY_REORDER_TO_FRONT * @see #FLAG_ACTIVITY_SINGLE_TOP + * @see #FLAG_ACTIVITY_TASK_ON_HOME * @see #FLAG_RECEIVER_REGISTERED_ONLY */ public Intent setFlags(int flags) { diff --git a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java index ada7f36..1984167 100644 --- a/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java +++ b/core/java/com/android/internal/app/HeavyWeightSwitcherActivity.java @@ -120,7 +120,7 @@ public class HeavyWeightSwitcherActivity extends Activity { private OnClickListener mSwitchOldListener = new OnClickListener() { public void onClick(View v) { try { - ActivityManagerNative.getDefault().moveTaskToFront(mCurTask); + ActivityManagerNative.getDefault().moveTaskToFront(mCurTask, 0); } catch (RemoteException e) { } finish(); diff --git a/docs/html/guide/topics/fundamentals.jd b/docs/html/guide/topics/fundamentals.jd index 84c2ed2..fffc1cd 100644 --- a/docs/html/guide/topics/fundamentals.jd +++ b/docs/html/guide/topics/fundamentals.jd @@ -19,6 +19,7 @@ page.title=Application Fundamentals <li><a href="#lmodes">Launch modes</a></li> <li><a href="#clearstack">Clearing the stack</a></li> <li><a href="#starttask">Starting tasks</a></li> + <li><a href="#commonpatterns">Common patterns</a></li> </ol></li> <li><a href="#procthread">Processes and Threads</a> <ol> @@ -892,6 +893,60 @@ See <a href="#clearstack">Clearing the stack</a>, earlier. </p> +<h3 id="commonpatterns">Common patterns</h3> + +<p> +In most cases an application won't use any flags or special features. +This gives the standard interaction the user expects: launching the application +brings any existing task to the foreground, or starts the main activity in +a new task if there isn't one. +</p> + +<p> +If an application posts notifications, it needs to decide how the user's +selection of a notification should impact any currently running task. The +current suggested behavior is that any current tasks be completely removed, +replaced with a new task containing a stack of activities representing where +the user is jumping in to the app. This can be accomplished with a combination +of the {@link android.app.PendingIntent#getActivities PendingIntent.getActivities()} +method and {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK Intent.FLAG_ACTIVITY_CLEAR_TASK}. +</p> + +<p> +For example, here is sample code to build an array of Intents to launch an +application into an activity three levels deep. The first Intent is always +the main Intent of the application as started by the launcher. The exact +details of the Intent data will of course depend on your application. +</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/StatusBarNotifications.java + intent_array} + +<p> +In some cases an application may not want to directly launch its application +from a notification, but instead go to a intermediate summary activity. To +accomplish this, the summary activity should be given a task affinity that +is different from the main application (one will typically give it no affinity, +that is "") so that it does not get launched into any existing application task. +</p> + +{@sample development/samples/ApiDemos/AndroidManifest.xml no_task_affinity} + +<p> +The PendingIntent to launch this then does not need to supply anything special: +</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/IncomingMessage.java + pending_intent} + +<p> +If an application implements an app widget, it should generally use the same +approach as the first one for notifications: when the user taps on the app +widget it should throw away any current task of the application and start a +new task with potentially multiple activities representing the state the +user is jumping in to. +</p> + <h2 id="procthread">Processes and Threads</h2> <p> diff --git a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java index ff2a4ed..a98ef0b 100644 --- a/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java +++ b/packages/SystemUI/src/com/android/systemui/recent/RecentApplicationsActivity.java @@ -193,14 +193,13 @@ public class RecentApplicationsActivity extends Activity { ActivityDescription item = mActivityDescriptions.get(n); if (item.id >= 0) { // This is an active task; it should just go to the foreground. - IActivityManager am = ActivityManagerNative.getDefault(); - try { - am.moveTaskToFront(item.id); - } catch (RemoteException e) { - } + final ActivityManager am = (ActivityManager) + getSystemService(Context.ACTIVITY_SERVICE); + am.moveTaskToFront(item.id, ActivityManager.MOVE_TASK_WITH_HOME); } else if (item.intent != null) { // prepare a launch intent and send it - item.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); + item.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY + | Intent.FLAG_ACTIVITY_TASK_ON_HOME); try { if (DBG) Log.v(TAG, "Starting intent " + item.intent); startActivity(item.intent); diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java index e0b05f9..0c31304 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/tablet/RecentAppsPanel.java @@ -252,13 +252,13 @@ public class RecentAppsPanel extends LinearLayout implements StatusBarPanel, OnC ActivityDescription ad = (ActivityDescription)v.getTag(); if (ad.id >= 0) { // This is an active task; it should just go to the foreground. - IActivityManager am = ActivityManagerNative.getDefault(); - try { - am.moveTaskToFront(ad.id); - } catch (RemoteException e) { - } + final ActivityManager am = (ActivityManager) + getContext().getSystemService(Context.ACTIVITY_SERVICE); + am.moveTaskToFront(ad.id, ActivityManager.MOVE_TASK_WITH_HOME); } else { Intent intent = ad.intent; + intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY + | Intent.FLAG_ACTIVITY_TASK_ON_HOME); if (DEBUG) Log.v(TAG, "Starting activity " + intent); getContext().startActivity(intent); } diff --git a/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java b/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java index d9e8c2b..f53092d 100644 --- a/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java +++ b/policy/src/com/android/internal/policy/impl/RecentApplicationsDialog.java @@ -138,13 +138,12 @@ public class RecentApplicationsDialog extends Dialog implements OnClickListener RecentTag tag = (RecentTag)b.getTag(); if (tag.info.id >= 0) { // This is an active task; it should just go to the foreground. - IActivityManager am = ActivityManagerNative.getDefault(); - try { - am.moveTaskToFront(tag.info.id); - } catch (RemoteException e) { - } + final ActivityManager am = (ActivityManager) + getContext().getSystemService(Context.ACTIVITY_SERVICE); + am.moveTaskToFront(tag.info.id, ActivityManager.MOVE_TASK_WITH_HOME); } else if (tag.intent != null) { - tag.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY); + tag.intent.addFlags(Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY + | Intent.FLAG_ACTIVITY_TASK_ON_HOME); try { getContext().startActivity(tag.intent); } catch (ActivityNotFoundException e) { diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index e815524..1773eca 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -2026,7 +2026,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); mMainStack.startActivityLocked(null, intent, null, null, 0, aInfo, - null, null, 0, 0, 0, false, false); + null, null, 0, 0, 0, false, false, null); } } @@ -2082,7 +2082,7 @@ public final class ActivityManagerService extends ActivityManagerNative intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); mMainStack.startActivityLocked(null, intent, null, null, 0, ri.activityInfo, - null, null, 0, 0, 0, false, false); + null, null, 0, 0, 0, false, false, null); } } } @@ -2121,13 +2121,13 @@ public final class ActivityManagerService extends ActivityManagerNative } mPendingActivityLaunches.clear(); } - + public final int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { - return mMainStack.startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, null); } @@ -2138,7 +2138,7 @@ public final class ActivityManagerService extends ActivityManagerNative String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) { WaitResult res = new WaitResult(); - mMainStack.startActivityMayWait(caller, intent, resolvedType, + mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, res, null); return res; @@ -2149,12 +2149,12 @@ public final class ActivityManagerService extends ActivityManagerNative int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug, Configuration config) { - return mMainStack.startActivityMayWait(caller, intent, resolvedType, + return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType, grantedUriPermissions, grantedMode, resultTo, resultWho, requestCode, onlyIfNeeded, debug, null, config); } - public int startActivityIntentSender(IApplicationThread caller, + public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flagsMask, int flagsValues) { @@ -2267,7 +2267,7 @@ public final class ActivityManagerService extends ActivityManagerNative // those are not yet exposed to user code, so there is no need. int res = mMainStack.startActivityLocked(r.app.thread, intent, r.resolvedType, null, 0, aInfo, resultTo, resultWho, - requestCode, -1, r.launchedFromUid, false, false); + requestCode, -1, r.launchedFromUid, false, false, null); Binder.restoreCallingIdentity(origId); r.finishing = wasFinishing; @@ -2289,38 +2289,28 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException( "startActivityInPackage only available to the system"); } - - final boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - // Collect information about the target of the Intent. - ActivityInfo aInfo; - try { - ResolveInfo rInfo = - AppGlobals.getPackageManager().resolveIntent( - intent, resolvedType, - PackageManager.MATCH_DEFAULT_ONLY | STOCK_PM_FLAGS); - aInfo = rInfo != null ? rInfo.activityInfo : null; - } catch (RemoteException e) { - aInfo = null; - } + return mMainStack.startActivityMayWait(null, uid, intent, resolvedType, + null, 0, resultTo, resultWho, requestCode, onlyIfNeeded, false, null, null); + } - if (aInfo != null) { - // Store the found target back into the intent, because now that - // we have it we never want to do this again. For example, if the - // user navigates back to this point in the history, we should - // always restart the exact same activity. - intent.setComponent(new ComponentName( - aInfo.applicationInfo.packageName, aInfo.name)); - } + public final int startActivities(IApplicationThread caller, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + return mMainStack.startActivities(caller, -1, intents, resolvedTypes, resultTo); + } - synchronized(this) { - return mMainStack.startActivityLocked(null, intent, resolvedType, - null, 0, aInfo, resultTo, resultWho, requestCode, -1, uid, - onlyIfNeeded, componentSpecified); + public final int startActivitiesInPackage(int uid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + + // This is so super not safe, that only the system (or okay root) + // can do it. + final int callingUid = Binder.getCallingUid(); + if (callingUid != 0 && callingUid != Process.myUid()) { + throw new SecurityException( + "startActivityInPackage only available to the system"); } + + return mMainStack.startActivities(null, uid, intents, resolvedTypes, resultTo); } final void addRecentTaskLocked(TaskRecord task) { @@ -3890,16 +3880,30 @@ public final class ActivityManagerService extends ActivityManagerNative public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) { + int requestCode, Intent[] intents, String[] resolvedTypes, int flags) { // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors() == true) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - if (type == INTENT_SENDER_BROADCAST) { - if ((intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + if (intents != null) { + if (intents.length < 1) { + throw new IllegalArgumentException("Intents array length must be >= 1"); + } + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + throw new IllegalArgumentException("Null intent at index " + i); + } + if (intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + if (type == INTENT_SENDER_BROADCAST && + (intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + throw new IllegalArgumentException( + "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + } + intents[i] = new Intent(intent); + } + if (resolvedTypes != null && resolvedTypes.length != intents.length) { throw new IllegalArgumentException( - "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + "Intent array length does not match resolvedTypes length"); } } @@ -3922,7 +3926,7 @@ public final class ActivityManagerService extends ActivityManagerNative } return getIntentSenderLocked(type, packageName, callingUid, - token, resultWho, requestCode, intent, resolvedType, flags); + token, resultWho, requestCode, intents, resolvedTypes, flags); } catch (RemoteException e) { throw new SecurityException(e); @@ -3932,7 +3936,7 @@ public final class ActivityManagerService extends ActivityManagerNative IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, IBinder token, String resultWho, - int requestCode, Intent intent, String resolvedType, int flags) { + int requestCode, Intent[] intents, String[] resolvedTypes, int flags) { ActivityRecord activity = null; if (type == INTENT_SENDER_ACTIVITY_RESULT) { int index = mMainStack.indexOfTokenLocked(token); @@ -3953,14 +3957,24 @@ public final class ActivityManagerService extends ActivityManagerNative PendingIntentRecord.Key key = new PendingIntentRecord.Key( type, packageName, activity, resultWho, - requestCode, intent, resolvedType, flags); + requestCode, intents, resolvedTypes, flags); WeakReference<PendingIntentRecord> ref; ref = mIntentSenderRecords.get(key); PendingIntentRecord rec = ref != null ? ref.get() : null; if (rec != null) { if (!cancelCurrent) { if (updateCurrent) { - rec.key.requestIntent.replaceExtras(intent); + if (rec.key.requestIntent != null) { + rec.key.requestIntent.replaceExtras(intents != null ? intents[0] : null); + } + if (intents != null) { + intents[intents.length-1] = rec.key.requestIntent; + rec.key.allIntents = intents; + rec.key.allResolvedTypes = resolvedTypes; + } else { + rec.key.allIntents = null; + rec.key.allResolvedTypes = null; + } } return rec; } @@ -5006,7 +5020,7 @@ public final class ActivityManagerService extends ActivityManagerNative /** * TODO: Add mController hook */ - public void moveTaskToFront(int task) { + public void moveTaskToFront(int task, int flags) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToFront()"); @@ -5021,6 +5035,11 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); if (tr.taskId == task) { + if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just move the home task to the top first. + mMainStack.moveHomeToFrontLocked(); + } mMainStack.moveTaskToFrontLocked(tr, null); return; } @@ -5028,6 +5047,11 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i); if (hr.task.taskId == task) { + if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just move the home task to the top first. + mMainStack.moveHomeToFrontLocked(); + } mMainStack.moveTaskToFrontLocked(hr.task, null); return; } diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index 51dc84e..b4ea036 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -99,8 +99,8 @@ public class ActivityStack { static final int DESTROY_TIMEOUT = 10*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; + // disabled. + static final long ACTIVITY_INACTIVE_RESET_TIME = 0; // How long between activity launches that we consider safe to not warn // the user about an unexpected activity being launched on top. @@ -1487,7 +1487,8 @@ public class ActivityStack { ActivityRecord newActivity) { boolean forceReset = (newActivity.info.flags &ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; - if (taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { + if (ACTIVITY_INACTIVE_RESET_TIME > 0 + && taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { if ((newActivity.info.flags &ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) { forceReset = true; @@ -1573,8 +1574,7 @@ public class ActivityStack { if (mService.mCurTask <= 0) { mService.mCurTask = 1; } - target.task = new TaskRecord(mService.mCurTask, target.info, null, - (target.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); + target.task = new TaskRecord(mService.mCurTask, target.info, null); target.task.affinityIntent = target.intent; if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + " out to new task " + target.task); @@ -1776,11 +1776,11 @@ public class ActivityStack { * activities on top of it and return the instance. * * @param newR Description of the new activity being started. - * @return Returns the old activity that should be continue to be used, + * @return Returns the old activity that should be continued to be used, * or null if none was found. */ private final ActivityRecord performClearTaskLocked(int taskId, - ActivityRecord newR, int launchFlags, boolean doClear) { + ActivityRecord newR, int launchFlags) { int i = mHistory.size(); // First find the requested task. @@ -1806,17 +1806,18 @@ public class ActivityStack { if (r.realActivity.equals(newR.realActivity)) { // Here it is! Now finish everything in front... ActivityRecord ret = r; - if (doClear) { - while (i < (mHistory.size()-1)) { - i++; - r = (ActivityRecord)mHistory.get(i); - if (r.finishing) { - continue; - } - if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear")) { - i--; - } + while (i < (mHistory.size()-1)) { + i++; + r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId != taskId) { + break; + } + if (r.finishing) { + continue; + } + if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "clear")) { + i--; } } @@ -1843,6 +1844,51 @@ public class ActivityStack { } /** + * Completely remove all activities associated with an existing task. + */ + private final void performClearTaskLocked(int taskId) { + int i = mHistory.size(); + + // First find the requested task. + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId == taskId) { + i++; + break; + } + } + + // Now clear it. + while (i > 0) { + i--; + ActivityRecord r = (ActivityRecord)mHistory.get(i); + if (r.finishing) { + continue; + } + if (r.task.taskId != taskId) { + // We hit the bottom. Now finish it all... + while (i < (mHistory.size()-1)) { + i++; + r = (ActivityRecord)mHistory.get(i); + if (r.task.taskId != taskId) { + // Whoops hit the end. + return; + } + if (r.finishing) { + continue; + } + if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, + null, "clear")) { + i--; + } + } + return; + } + } + } + + /** * Find the activity in the history stack within the given task. Returns * the index within the history at which it's found, or < 0 if not found. */ @@ -1882,7 +1928,7 @@ public class ActivityStack { int grantedMode, ActivityInfo aInfo, IBinder resultTo, String resultWho, int requestCode, int callingPid, int callingUid, boolean onlyIfNeeded, - boolean componentSpecified) { + boolean componentSpecified, ActivityRecord[] outActivity) { int err = START_SUCCESS; @@ -2004,6 +2050,9 @@ public class ActivityStack { ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, intent, resolvedType, aInfo, mService.mConfiguration, resultRecord, resultWho, requestCode, componentSpecified); + if (outActivity != null) { + outActivity[0] = r; + } if (mMainStack) { if (mResumedActivity == null @@ -2038,6 +2087,16 @@ public class ActivityStack { grantedUriPermissions, grantedMode, onlyIfNeeded, true); } + final void moveHomeToFrontFromLaunchLocked(int launchFlags) { + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) + == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) { + // Caller wants to appear on home activity, so before starting + // their own activity we will bring home to the front. + moveHomeToFrontLocked(); + } + } + final int startActivityUncheckedLocked(ActivityRecord r, ActivityRecord sourceRecord, Uri[] grantedUriPermissions, int grantedMode, boolean onlyIfNeeded, boolean doResume) { @@ -2111,6 +2170,7 @@ public class ActivityStack { } boolean addingToTask = false; + TaskRecord reuseTask = null; if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK @@ -2148,6 +2208,7 @@ public class ActivityStack { if (callerAtFront) { // We really do want to push this one into the // user's face, right now. + moveHomeToFrontFromLaunchLocked(launchFlags); moveTaskToFrontLocked(taskTop.task, r); } } @@ -2166,7 +2227,16 @@ public class ActivityStack { } return START_RETURN_INTENT_TO_CALLER; } - if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) + == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) { + // The caller has requested to completely replace any + // exising task with its new activity. Well that should + // not be too hard... + reuseTask = taskTop.task; + performClearTaskLocked(taskTop.task.taskId); + reuseTask.setIntent(r.intent, r.info); + } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { // In this situation we want to remove all activities @@ -2174,7 +2244,7 @@ public class ActivityStack { // cases this means we are resetting the task to its // initial state. ActivityRecord top = performClearTaskLocked( - taskTop.task.taskId, r, launchFlags, true); + taskTop.task.taskId, r, launchFlags); if (top != null) { if (top.frontOfTask) { // Activity aliases may mean we use different @@ -2235,7 +2305,7 @@ public class ActivityStack { // for now we'll just drop it. taskTop.task.setIntent(r.intent, r.info); } - if (!addingToTask) { + if (!addingToTask && reuseTask == null) { // 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. @@ -2298,19 +2368,23 @@ public class ActivityStack { // Should this be considered a new task? if (r.resultTo == null && !addingToTask && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { - // todo: should do better management of integers. - mService.mCurTask++; - if (mService.mCurTask <= 0) { - mService.mCurTask = 1; + if (reuseTask == null) { + // todo: should do better management of integers. + mService.mCurTask++; + if (mService.mCurTask <= 0) { + mService.mCurTask = 1; + } + r.task = new TaskRecord(mService.mCurTask, r.info, intent); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in new task " + r.task); + } else { + r.task = reuseTask; } - r.task = new TaskRecord(mService.mCurTask, r.info, intent, - (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r - + " in new task " + r.task); newTask = true; if (mMainStack) { mService.addRecentTaskLocked(r.task); } + moveHomeToFrontFromLaunchLocked(launchFlags); } else if (sourceRecord != null) { if (!addingToTask && @@ -2319,7 +2393,7 @@ public class ActivityStack { // task, but the caller has asked to clear that task if the // activity is already running. ActivityRecord top = performClearTaskLocked( - sourceRecord.task.taskId, r, launchFlags, true); + sourceRecord.task.taskId, r, launchFlags); if (top != null) { logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); top.deliverNewIntentLocked(callingUid, r.intent); @@ -2361,9 +2435,8 @@ public class ActivityStack { ActivityRecord prev = N > 0 ? (ActivityRecord)mHistory.get(N-1) : null; r.task = prev != null - ? prev.task - : new TaskRecord(mService.mCurTask, r.info, intent, - (r.info.flags&ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0); + ? prev.task + : new TaskRecord(mService.mCurTask, r.info, intent); if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new guessed " + r.task); } @@ -2386,21 +2459,7 @@ public class ActivityStack { return START_SUCCESS; } - final int startActivityMayWait(IApplicationThread caller, - Intent intent, String resolvedType, Uri[] grantedUriPermissions, - int grantedMode, IBinder resultTo, - String resultWho, int requestCode, boolean onlyIfNeeded, - boolean debug, WaitResult outResult, Configuration config) { - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - + ActivityInfo resolveActivity(Intent intent, String resolvedType, boolean debug) { // Collect information about the target of the Intent. ActivityInfo aInfo; try { @@ -2429,11 +2488,32 @@ public class ActivityStack { } } } + return aInfo; + } + + final int startActivityMayWait(IApplicationThread caller, int callingUid, + Intent intent, String resolvedType, Uri[] grantedUriPermissions, + int grantedMode, IBinder resultTo, + String resultWho, int requestCode, boolean onlyIfNeeded, + boolean debug, WaitResult outResult, Configuration config) { + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedType, debug); synchronized (mService) { int callingPid; - int callingUid; - if (caller == null) { + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { callingPid = Binder.getCallingPid(); callingUid = Binder.getCallingUid(); } else { @@ -2472,8 +2552,8 @@ public class ActivityStack { IIntentSender target = mService.getIntentSenderLocked( IActivityManager.INTENT_SENDER_ACTIVITY, "android", - realCallingUid, null, null, 0, intent, - resolvedType, PendingIntent.FLAG_CANCEL_CURRENT + realCallingUid, null, null, 0, new Intent[] { intent }, + new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT); Intent newIntent = new Intent(); @@ -2518,7 +2598,7 @@ public class ActivityStack { int res = startActivityLocked(caller, intent, resolvedType, grantedUriPermissions, grantedMode, aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, - onlyIfNeeded, componentSpecified); + onlyIfNeeded, componentSpecified, null); if (mConfigWillChange && mMainStack) { // If the caller also wants to switch to a new configuration, @@ -2569,6 +2649,75 @@ public class ActivityStack { } } + final int startActivities(IApplicationThread caller, int callingUid, + Intent[] intents, String[] resolvedTypes, IBinder resultTo) { + if (intents == null) { + throw new NullPointerException("intents is null"); + } + if (resolvedTypes == null) { + throw new NullPointerException("resolvedTypes is null"); + } + if (intents.length != resolvedTypes.length) { + throw new IllegalArgumentException("intents are length different than resolvedTypes"); + } + + ActivityRecord[] outActivity = new ActivityRecord[1]; + + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mService) { + + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + continue; + } + + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], false); + + if (mMainStack && aInfo != null && (aInfo.applicationInfo.flags + & ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { + throw new IllegalArgumentException( + "FLAG_CANT_SAVE_STATE not supported here"); + } + + int res = startActivityLocked(caller, intent, resolvedTypes[i], + null, 0, aInfo, resultTo, null, -1, callingPid, callingUid, + false, componentSpecified, outActivity); + if (res < 0) { + return res; + } + + resultTo = outActivity[0]; + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return IActivityManager.START_SUCCESS; + } + void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, long thisTime, long totalTime) { for (int i=mWaitingActivityLaunched.size()-1; i>=0; i--) { @@ -3252,6 +3401,24 @@ public class ActivityStack { removeHistoryRecordsForAppLocked(mFinishingActivities, app); } + /** + * Move the current home activity's task (if one exists) to the front + * of the stack. + */ + final void moveHomeToFrontLocked() { + TaskRecord homeTask = null; + for (int i=mHistory.size()-1; i>=0; i--) { + ActivityRecord hr = (ActivityRecord)mHistory.get(i); + if (hr.isHomeActivity) { + homeTask = hr.task; + } + } + if (homeTask != null) { + moveTaskToFrontLocked(homeTask, null); + } + } + + final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason) { if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index 7a85eb8..ee6e420 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -47,20 +47,24 @@ class PendingIntentRecord extends IIntentSender.Stub { final int requestCode; final Intent requestIntent; final String requestResolvedType; + Intent[] allIntents; + String[] allResolvedTypes; final int flags; final int hashCode; private static final int ODD_PRIME_NUMBER = 37; Key(int _t, String _p, ActivityRecord _a, String _w, - int _r, Intent _i, String _it, int _f) { + int _r, Intent[] _i, String[] _it, int _f) { type = _t; packageName = _p; activity = _a; who = _w; requestCode = _r; - requestIntent = _i; - requestResolvedType = _it; + requestIntent = _i != null ? _i[_i.length-1] : null; + requestResolvedType = _it != null ? _it[_it.length-1] : null; + allIntents = _i; + allResolvedTypes = _it; flags = _f; int hash = 23; @@ -72,11 +76,11 @@ class PendingIntentRecord extends IIntentSender.Stub { if (_a != null) { hash = (ODD_PRIME_NUMBER*hash) + _a.hashCode(); } - if (_i != null) { - hash = (ODD_PRIME_NUMBER*hash) + _i.filterHashCode(); + if (requestIntent != null) { + hash = (ODD_PRIME_NUMBER*hash) + requestIntent.filterHashCode(); } - if (_it != null) { - hash = (ODD_PRIME_NUMBER*hash) + _it.hashCode(); + if (requestResolvedType != null) { + hash = (ODD_PRIME_NUMBER*hash) + requestResolvedType.hashCode(); } hash = (ODD_PRIME_NUMBER*hash) + _p.hashCode(); hash = (ODD_PRIME_NUMBER*hash) + _t; @@ -209,9 +213,24 @@ class PendingIntentRecord extends IIntentSender.Stub { switch (key.type) { case IActivityManager.INTENT_SENDER_ACTIVITY: try { - owner.startActivityInPackage(uid, - finalIntent, resolvedType, - resultTo, resultWho, requestCode, false); + if (key.allIntents != null && key.allIntents.length > 1) { + Intent[] allIntents = new Intent[key.allIntents.length]; + String[] allResolvedTypes = new String[key.allIntents.length]; + System.arraycopy(key.allIntents, 0, allIntents, 0, + key.allIntents.length); + if (key.allResolvedTypes != null) { + System.arraycopy(key.allResolvedTypes, 0, allResolvedTypes, 0, + key.allResolvedTypes.length); + } + allIntents[allIntents.length-1] = finalIntent; + allResolvedTypes[allResolvedTypes.length-1] = resolvedType; + owner.startActivitiesInPackage(uid, allIntents, + allResolvedTypes, resultTo); + } else { + owner.startActivityInPackage(uid, + finalIntent, resolvedType, + resultTo, resultWho, requestCode, false); + } } catch (RuntimeException e) { Slog.w(ActivityManagerService.TAG, "Unable to send startActivity intent", e); diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index bcb8f54..09d9c3b6 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -26,7 +26,6 @@ import java.io.PrintWriter; class TaskRecord { final int taskId; // Unique identifier for this task. final String affinity; // The affinity name for this task, or null. - final boolean clearOnBackground; // As per the original activity. Intent intent; // The original intent that started the task. Intent affinityIntent; // Intent of affinity-moved activity that started this task. ComponentName origActivity; // The non-alias activity component of the intent. @@ -38,11 +37,9 @@ class TaskRecord { String stringName; // caching of toString() result. - TaskRecord(int _taskId, ActivityInfo info, Intent _intent, - boolean _clearOnBackground) { + TaskRecord(int _taskId, ActivityInfo info, Intent _intent) { taskId = _taskId; affinity = info.taskAffinity; - clearOnBackground = _clearOnBackground; setIntent(_intent, info); } @@ -86,9 +83,8 @@ class TaskRecord { } void dump(PrintWriter pw, String prefix) { - if (clearOnBackground || numActivities != 0 || rootWasReset) { - pw.print(prefix); pw.print("clearOnBackground="); pw.print(clearOnBackground); - pw.print(" numActivities="); pw.print(numActivities); + if (numActivities != 0 || rootWasReset) { + pw.print(prefix); pw.print("numActivities="); pw.print(numActivities); pw.print(" rootWasReset="); pw.println(rootWasReset); } if (affinity != null) { diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index c31c9cc..3b52252 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -246,6 +246,11 @@ public class MockContext extends Context { } @Override + public void startActivities(Intent[] intents) { + throw new UnsupportedOperationException(); + } + + @Override public void startIntentSender(IntentSender intent, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) throws IntentSender.SendIntentException { diff --git a/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java b/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java index fec3671..4cacbc4 100644 --- a/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java +++ b/tests/permission/src/com/android/framework/permission/tests/ActivityManagerPermissionTests.java @@ -39,7 +39,7 @@ public class ActivityManagerPermissionTests extends TestCase { @SmallTest public void testREORDER_TASKS() { try { - mAm.moveTaskToFront(-1); + mAm.moveTaskToFront(0, 0); fail("IActivityManager.moveTaskToFront did not throw SecurityException as" + " expected"); } catch (SecurityException e) { |