diff options
author | Dianne Hackborn <hackbod@google.com> | 2012-08-07 19:12:33 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2012-08-07 19:19:22 -0700 |
commit | 7d19e0242faac8017033dabb872cdf1542fa184c (patch) | |
tree | ab12d3c6597bc42bc7c0dca10ded546192c5c249 | |
parent | 03ad783c5078c7bd487e47bb2a2af67dfbe89f1a (diff) | |
download | frameworks_base-7d19e0242faac8017033dabb872cdf1542fa184c.zip frameworks_base-7d19e0242faac8017033dabb872cdf1542fa184c.tar.gz frameworks_base-7d19e0242faac8017033dabb872cdf1542fa184c.tar.bz2 |
More mult-user API work.
- You can now use android:singleUser with receivers and providers.
- New API to send ordered broadcasts as a user.
- New Process.myUserHandle() API.
For now I am trying out "user handle" as the name for the numbers
representing users.
Change-Id: I754c713ab172494bb4251bc7a37a17324a2e235e
25 files changed, 531 insertions, 163 deletions
diff --git a/api/current.txt b/api/current.txt index f594205..c5768db 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5075,6 +5075,7 @@ package android.content { public class ContentProviderClient { method public android.content.ContentProviderResult[] applyBatch(java.util.ArrayList<android.content.ContentProviderOperation>) throws android.content.OperationApplicationException, android.os.RemoteException; method public int bulkInsert(android.net.Uri, android.content.ContentValues[]) throws android.os.RemoteException; + method public android.os.Bundle call(java.lang.String, java.lang.String, android.os.Bundle) throws android.os.RemoteException; method public int delete(android.net.Uri, java.lang.String, java.lang.String[]) throws android.os.RemoteException; method public android.content.ContentProvider getLocalContentProvider(); method public java.lang.String[] getStreamTypes(android.net.Uri, java.lang.String) throws android.os.RemoteException; @@ -5319,9 +5320,10 @@ package android.content { method public abstract void revokeUriPermission(android.net.Uri, int); method public abstract void sendBroadcast(android.content.Intent); method public abstract void sendBroadcast(android.content.Intent, java.lang.String); - method public void sendBroadcastToUser(android.content.Intent, int); + method public abstract void sendBroadcastToUser(android.content.Intent, int); method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String); method public abstract void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle); + method public abstract void sendOrderedBroadcastToUser(android.content.Intent, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle); method public abstract void sendStickyBroadcast(android.content.Intent); method public abstract void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle); method public abstract void setTheme(int); @@ -5453,8 +5455,10 @@ package android.content { method public void revokeUriPermission(android.net.Uri, int); method public void sendBroadcast(android.content.Intent); method public void sendBroadcast(android.content.Intent, java.lang.String); + method public void sendBroadcastToUser(android.content.Intent, int); method public void sendOrderedBroadcast(android.content.Intent, java.lang.String); method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle); + method public void sendOrderedBroadcastToUser(android.content.Intent, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle); method public void sendStickyBroadcast(android.content.Intent); method public void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle); method public void setTheme(int); @@ -6235,6 +6239,7 @@ package android.content.pm { field public static final int FLAG_HARDWARE_ACCELERATED = 512; // 0x200 field public static final int FLAG_MULTIPROCESS = 1; // 0x1 field public static final int FLAG_NO_HISTORY = 128; // 0x80 + field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000 field public static final int FLAG_STATE_NOT_NEEDED = 16; // 0x10 field public static final int LAUNCH_MULTIPLE = 0; // 0x0 field public static final int LAUNCH_SINGLE_INSTANCE = 3; // 0x3 @@ -6657,7 +6662,9 @@ package android.content.pm { ctor public ProviderInfo(android.content.pm.ProviderInfo); method public int describeContents(); field public static final android.os.Parcelable.Creator CREATOR; + field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000 field public java.lang.String authority; + field public int flags; field public boolean grantUriPermissions; field public int initOrder; field public deprecated boolean isSyncable; @@ -6703,7 +6710,7 @@ package android.content.pm { method public void dump(android.util.Printer, java.lang.String); field public static final android.os.Parcelable.Creator CREATOR; field public static final int FLAG_ISOLATED_PROCESS = 2; // 0x2 - field public static final int FLAG_SINGLE_USER = 4; // 0x4 + field public static final int FLAG_SINGLE_USER = 1073741824; // 0x40000000 field public static final int FLAG_STOP_WITH_TASK = 1; // 0x1 field public int flags; field public java.lang.String permission; @@ -16248,6 +16255,7 @@ package android.os { method public static final int myPid(); method public static final int myTid(); method public static final int myUid(); + method public static final int myUserHandle(); method public static final void sendSignal(int, int); method public static final void setThreadPriority(int, int) throws java.lang.IllegalArgumentException, java.lang.SecurityException; method public static final void setThreadPriority(int) throws java.lang.IllegalArgumentException, java.lang.SecurityException; @@ -21143,8 +21151,10 @@ package android.test.mock { method public void revokeUriPermission(android.net.Uri, int); method public void sendBroadcast(android.content.Intent); method public void sendBroadcast(android.content.Intent, java.lang.String); + method public void sendBroadcastToUser(android.content.Intent, int); method public void sendOrderedBroadcast(android.content.Intent, java.lang.String); method public void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle); + method public void sendOrderedBroadcastToUser(android.content.Intent, int, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle); method public void sendStickyBroadcast(android.content.Intent); method public void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle); method public void setTheme(int); diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 92b6f72..1bbc49a 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -1840,6 +1840,18 @@ public class ActivityManager { return PackageManager.PERMISSION_DENIED; } + /** @hide */ + public static int checkUidPermission(String permission, int uid) { + try { + return AppGlobals.getPackageManager() + .checkUidPermission(permission, uid); + } catch (RemoteException e) { + // Should never happen, but if it does... deny! + Slog.e(TAG, "PackageManager is dead?!?", e); + } + return PackageManager.PERMISSION_DENIED; + } + /** * Returns the usage statistics of each installed package. * diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java index 3c8a290..f46388f 100644 --- a/core/java/android/app/ContextImpl.java +++ b/core/java/android/app/ContextImpl.java @@ -967,18 +967,6 @@ class ContextImpl extends Context { } @Override - public void sendBroadcastToUser(Intent intent, int userId) { - String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); - try { - intent.setAllowFds(false); - ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(), - intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false, - userId); - } catch (RemoteException e) { - } - } - - @Override public void sendBroadcast(Intent intent, String receiverPermission) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { @@ -1039,6 +1027,50 @@ class ContextImpl extends Context { } @Override + public void sendBroadcastToUser(Intent intent, int userHandle) { + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.setAllowFds(false); + ActivityManagerNative.getDefault().broadcastIntent(mMainThread.getApplicationThread(), + intent, resolvedType, null, Activity.RESULT_OK, null, null, null, false, false, + userHandle); + } catch (RemoteException e) { + } + } + + @Override + public void sendOrderedBroadcastToUser(Intent intent, int userHandle, + BroadcastReceiver resultReceiver, Handler scheduler, + int initialCode, String initialData, Bundle initialExtras) { + IIntentReceiver rd = null; + if (resultReceiver != null) { + if (mPackageInfo != null) { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = mPackageInfo.getReceiverDispatcher( + resultReceiver, getOuterContext(), scheduler, + mMainThread.getInstrumentation(), false); + } else { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = new LoadedApk.ReceiverDispatcher( + resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver(); + } + } + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + intent.setAllowFds(false); + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, rd, + initialCode, initialData, initialExtras, null, + true, false, userHandle); + } catch (RemoteException e) { + } + } + + @Override public void sendStickyBroadcast(Intent intent) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { @@ -1197,7 +1229,7 @@ class ContextImpl extends Context { /** @hide */ @Override - public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) { + public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) { IServiceConnection sd; if (conn == null) { throw new IllegalArgumentException("connection is null"); @@ -1219,7 +1251,7 @@ class ContextImpl extends Context { int res = ActivityManagerNative.getDefault().bindService( mMainThread.getApplicationThread(), getActivityToken(), service, service.resolveTypeIfNeeded(getContentResolver()), - sd, flags, userId); + sd, flags, userHandle); if (res < 0) { throw new SecurityException( "Not allowed to bind to service " + service); diff --git a/core/java/android/content/ContentProviderClient.java b/core/java/android/content/ContentProviderClient.java index 5c315ce..204f963 100644 --- a/core/java/android/content/ContentProviderClient.java +++ b/core/java/android/content/ContentProviderClient.java @@ -231,6 +231,19 @@ public class ContentProviderClient { } } + /** See {@link ContentProvider#call(String, String, Bundle)} */ + public Bundle call(String method, String arg, Bundle extras) + throws RemoteException { + try { + return mContentProvider.call(method, arg, extras); + } catch (DeadObjectException e) { + if (!mStable) { + mContentResolver.unstableProviderDied(mContentProvider); + } + throw e; + } + } + /** * Call this to indicate to the system that the associated {@link ContentProvider} is no * longer needed by this {@link ContentProviderClient}. diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index af8b213..fca1d55 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -988,18 +988,6 @@ public abstract class Context { public abstract void sendBroadcast(Intent intent); /** - * Same as #sendBroadcast(Intent intent), but for a specific user. This broadcast - * can only be sent to receivers that are part of the calling application. It - * requires holding the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} - * permission. - * @param intent the intent to broadcast - * @param userId user to send the intent to - */ - public void sendBroadcastToUser(Intent intent, int userId) { - throw new RuntimeException("Not implemented. Must override in a subclass."); - } - - /** * Broadcast the given intent to all interested BroadcastReceivers, allowing * an optional required permission to be enforced. This * call is asynchronous; it returns immediately, and you will continue @@ -1097,6 +1085,48 @@ public abstract class Context { Bundle initialExtras); /** + * Same as {@link #sendBroadcast(Intent)}, but for a specific user. This broadcast + * can only be sent to receivers that are part of the calling application. It + * requires holding the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} + * permission. + * @param intent The intent to broadcast + * @param userHandle User to send the intent to. + * @see #sendBroadcast(Intent) + */ + public abstract void sendBroadcastToUser(Intent intent, int userHandle); + + /** + * Same as + * {@link #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle)}, + * but for a specific user. This broadcast + * can only be sent to receivers that are part of the calling application. It + * requires holding the {@link android.Manifest.permission#INTERACT_ACROSS_USERS} + * permission. + * + * <p>See {@link BroadcastReceiver} for more information on Intent broadcasts. + * + * @param intent The Intent to broadcast; all receivers matching this + * Intent will receive the broadcast. + * @param userHandle User to send the intent to. + * @param resultReceiver Your own BroadcastReceiver to treat as the final + * receiver of the broadcast. + * @param scheduler A custom Handler with which to schedule the + * resultReceiver callback; if null it will be + * scheduled in the Context's main thread. + * @param initialCode An initial value for the result code. Often + * Activity.RESULT_OK. + * @param initialData An initial value for the result data. Often + * null. + * @param initialExtras An initial value for the result extras. Often + * null. + * + * @see #sendOrderedBroadcast(Intent, String, BroadcastReceiver, Handler, int, String, Bundle) + */ + public abstract void sendOrderedBroadcastToUser(Intent intent, int userHandle, + BroadcastReceiver resultReceiver, Handler scheduler, + int initialCode, String initialData, Bundle initialExtras); + + /** * Perform a {@link #sendBroadcast(Intent)} that is "sticky," meaning the * Intent you are sending stays around after the broadcast is complete, * so that others can quickly retrieve that data through the return @@ -1403,11 +1433,11 @@ public abstract class Context { int flags); /** - * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userId + * Same as {@link #bindService(Intent, ServiceConnection, int)}, but with an explicit userHandle * argument for use by system server and other multi-user aware code. * @hide */ - public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) { + public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) { throw new RuntimeException("Not implemented. Must override in a subclass."); } diff --git a/core/java/android/content/ContextWrapper.java b/core/java/android/content/ContextWrapper.java index 7738132..af6bc11 100644 --- a/core/java/android/content/ContextWrapper.java +++ b/core/java/android/content/ContextWrapper.java @@ -313,11 +313,6 @@ public class ContextWrapper extends Context { } @Override - public void sendBroadcastToUser(Intent intent, int userId) { - mBase.sendBroadcastToUser(intent, userId); - } - - @Override public void sendBroadcast(Intent intent, String receiverPermission) { mBase.sendBroadcast(intent, receiverPermission); } @@ -339,6 +334,19 @@ public class ContextWrapper extends Context { } @Override + public void sendBroadcastToUser(Intent intent, int userHandle) { + mBase.sendBroadcastToUser(intent, userHandle); + } + + @Override + public void sendOrderedBroadcastToUser(Intent intent, int userHandle, + BroadcastReceiver resultReceiver, Handler scheduler, + int initialCode, String initialData, Bundle initialExtras) { + mBase.sendOrderedBroadcastToUser(intent, userHandle, resultReceiver, + scheduler, initialCode, initialData, initialExtras); + } + + @Override public void sendStickyBroadcast(Intent intent) { mBase.sendStickyBroadcast(intent); } @@ -395,8 +403,8 @@ public class ContextWrapper extends Context { /** @hide */ @Override - public boolean bindService(Intent service, ServiceConnection conn, int flags, int userId) { - return mBase.bindService(service, conn, flags, userId); + public boolean bindService(Intent service, ServiceConnection conn, int flags, int userHandle) { + return mBase.bindService(service, conn, flags, userHandle); } @Override diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java index 35e5610..3035729 100644 --- a/core/java/android/content/pm/ActivityInfo.java +++ b/core/java/android/content/pm/ActivityInfo.java @@ -172,6 +172,14 @@ public class ActivityInfo extends ComponentInfo */ public static final int FLAG_IMMERSIVE = 0x0400; /** + * Bit in {@link #flags}: If set, a single instance of the receiver will + * run for all users on the device. Set from the + * {@link android.R.attr#singleUser} attribute. Note that this flag is + * only relevent for ActivityInfo structures that are describiner receiver + * components; it is not applied to activities. + */ + public static final int FLAG_SINGLE_USER = 0x40000000; + /** * Options that have been set in the activity declaration in the * manifest. * These include: @@ -181,7 +189,7 @@ public class ActivityInfo extends ComponentInfo * {@link #FLAG_STATE_NOT_NEEDED}, {@link #FLAG_EXCLUDE_FROM_RECENTS}, * {@link #FLAG_ALLOW_TASK_REPARENTING}, {@link #FLAG_NO_HISTORY}, * {@link #FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS}, - * {@link #FLAG_HARDWARE_ACCELERATED} + * {@link #FLAG_HARDWARE_ACCELERATED}, {@link #FLAG_SINGLE_USER}. */ public int flags; diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 3ce7c78..faeb082 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -2040,7 +2040,7 @@ public class PackageParser { return null; } - final boolean setExported = sa.hasValue( + boolean setExported = sa.hasValue( com.android.internal.R.styleable.AndroidManifestActivity_exported); if (setExported) { a.info.exported = sa.getBoolean( @@ -2166,6 +2166,21 @@ public class PackageParser { a.info.configChanges = 0; } + if (receiver) { + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestActivity_singleUser, + false)) { + a.info.flags |= ServiceInfo.FLAG_SINGLE_USER; + if (a.info.exported) { + Slog.w(TAG, "Activity exported request ignored due to singleUser: " + + a.className + " at " + mArchiveSourcePath + " " + + parser.getPositionDescription()); + a.info.exported = false; + } + setExported = true; + } + } + sa.recycle(); if (receiver && (owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { @@ -2475,6 +2490,20 @@ public class PackageParser { com.android.internal.R.styleable.AndroidManifestProvider_initOrder, 0); + p.info.flags = 0; + + if (sa.getBoolean( + com.android.internal.R.styleable.AndroidManifestProvider_singleUser, + false)) { + p.info.flags |= ProviderInfo.FLAG_SINGLE_USER; + if (p.info.exported) { + Slog.w(TAG, "Provider exported request ignored due to singleUser: " + + p.className + " at " + mArchiveSourcePath + " " + + parser.getPositionDescription()); + p.info.exported = false; + } + } + sa.recycle(); if ((owner.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java index ec01775..a534176 100644 --- a/core/java/android/content/pm/ProviderInfo.java +++ b/core/java/android/content/pm/ProviderInfo.java @@ -73,7 +73,21 @@ public final class ProviderInfo extends ComponentInfo /** Used to control initialization order of single-process providers * running in the same process. Higher goes first. */ public int initOrder = 0; - + + /** + * Bit in {@link #flags}: If set, a single instance of the provider will + * run for all users on the device. Set from the + * {@link android.R.attr#singleUser} attribute. + */ + public static final int FLAG_SINGLE_USER = 0x40000000; + + /** + * Options that have been set in the provider declaration in the + * manifest. + * These include: {@link #FLAG_SINGLE_USER}. + */ + public int flags = 0; + /** * Whether or not this provider is syncable. * @deprecated This flag is now being ignored. The current way to make a provider @@ -95,6 +109,7 @@ public final class ProviderInfo extends ComponentInfo pathPermissions = orig.pathPermissions; multiprocess = orig.multiprocess; initOrder = orig.initOrder; + flags = orig.flags; isSyncable = orig.isSyncable; } @@ -112,6 +127,7 @@ public final class ProviderInfo extends ComponentInfo out.writeTypedArray(pathPermissions, parcelableFlags); out.writeInt(multiprocess ? 1 : 0); out.writeInt(initOrder); + out.writeInt(flags); out.writeInt(isSyncable ? 1 : 0); } @@ -127,8 +143,7 @@ public final class ProviderInfo extends ComponentInfo }; public String toString() { - return "ContentProviderInfo{name=" + authority + " className=" + name - + " isSyncable=" + (isSyncable ? "true" : "false") + "}"; + return "ContentProviderInfo{name=" + authority + " className=" + name + "}"; } private ProviderInfo(Parcel in) { @@ -141,6 +156,7 @@ public final class ProviderInfo extends ComponentInfo pathPermissions = in.createTypedArray(PathPermission.CREATOR); multiprocess = in.readInt() != 0; initOrder = in.readInt(); + flags = in.readInt(); isSyncable = in.readInt() != 0; } } diff --git a/core/java/android/content/pm/ServiceInfo.java b/core/java/android/content/pm/ServiceInfo.java index 1aaceb4..796c2a4 100644 --- a/core/java/android/content/pm/ServiceInfo.java +++ b/core/java/android/content/pm/ServiceInfo.java @@ -53,13 +53,14 @@ public class ServiceInfo extends ComponentInfo * run for all users on the device. Set from the * {@link android.R.attr#singleUser} attribute. */ - public static final int FLAG_SINGLE_USER = 0x0004; + public static final int FLAG_SINGLE_USER = 0x40000000; /** * Options that have been set in the service declaration in the * manifest. * These include: - * {@link #FLAG_STOP_WITH_TASK}, {@link #FLAG_ISOLATED_PROCESS}. + * {@link #FLAG_STOP_WITH_TASK}, {@link #FLAG_ISOLATED_PROCESS}, + * {@link #FLAG_SINGLE_USER}. */ public int flags; diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index a04ad93..93860aa 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -640,11 +640,24 @@ public class Process { public static final native int myTid(); /** - * Returns the identifier of this process's user. + * Returns the identifier of this process's uid. This is the kernel uid + * that the process is running under, which is the identity of its + * app-specific sandbox. It is different from {@link #myUserHandle} in that + * a uid identifies a specific app sandbox in a specific user. */ public static final native int myUid(); /** + * Returns the identifier of this process's user handle. This is the + * user the process is running under. It is distinct from + * {@link #myUid()} in that a particular user will have multiple + * distinct apps running under it each with their own uid. + */ + public static final int myUserHandle() { + return UserId.getUserId(myUid()); + } + + /** * Returns whether the current process is in an isolated sandbox. * @hide */ diff --git a/core/res/res/values/attrs_manifest.xml b/core/res/res/values/attrs_manifest.xml index 8bc1e79..1c3318d 100644 --- a/core/res/res/values/attrs_manifest.xml +++ b/core/res/res/values/attrs_manifest.xml @@ -284,7 +284,19 @@ who do not know their particular component name) and for all content providers. --> <attr name="exported" format="boolean" /> - + + <!-- If set to true, a single instance of this component will run for + all users. That instance will run as user 0, the default/primary + user. When the app running is in processes for other users and interacts + with this component (by binding to a service for example) those processes will + always interact with the instance running for user 0. Enabling + single user mode forces "exported" of the component to be false, to + help avoid introducing multi-user security bugs. You must hold the + permission {@link android.Manifest.permission#INTERACT_ACROSS_USERS} in order + to use this feature. This flag can only be used with services, + receivers, and providers; it can not be used with activities. --> + <attr name="singleUser" format="boolean" /> + <!-- Specify a specific process that the associated code is to run in. Use with the application tag (to supply a default process for all application components), or with the activity, receiver, service, @@ -1194,6 +1206,7 @@ component specific values). --> <attr name="enabled" /> <attr name="exported" /> + <attr name="singleUser" /> </declare-styleable> <!-- Attributes that can be supplied in an AndroidManifest.xml @@ -1275,16 +1288,7 @@ that is isolated from the rest of the system. The only communication with it is through the Service API (binding and starting). --> <attr name="isolatedProcess" format="boolean" /> - <!-- If set to true, a single instance of this service will run for - all users. That instance will run as user 0, the default/primary - user. When the app running in processes for other users interacts - with this service (by binding to it, starting it, etc) they will - always interact with the instance running for user 0. Enabling - single user mode forces "exported" of the service to be false, to - avoid introducing multi-user security bugs. You must hold the - permission {@link android.Manifest.permission#INTERACT_ACROSS_USERS} in order - to use this feature. --> - <attr name="singleUser" format="boolean" /> + <attr name="singleUser" /> </declare-styleable> <!-- The <code>receiver</code> tag declares an @@ -1318,8 +1322,9 @@ component specific values). --> <attr name="enabled" /> <attr name="exported" /> + <attr name="singleUser" /> </declare-styleable> - + <!-- The <code>activity</code> tag declares an {@link android.app.Activity} class that is available as part of the package's application components, implementing @@ -1372,6 +1377,7 @@ <attr name="hardwareAccelerated" /> <attr name="uiOptions" /> <attr name="parentActivityName" /> + <attr name="singleUser" /> </declare-styleable> <!-- The <code>activity-alias</code> tag declares a new diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java index fbbff4c..df6c51e 100644 --- a/services/java/com/android/server/am/ActiveServices.java +++ b/services/java/com/android/server/am/ActiveServices.java @@ -478,7 +478,8 @@ public class ActiveServices { if (res.record == null) { return -1; } - if (mAm.isSingleton(res.record.processName, res.record.appInfo)) { + if (mAm.isSingleton(res.record.processName, res.record.appInfo, + res.record.serviceInfo.name, res.record.serviceInfo.flags)) { userId = 0; res = retrieveServiceLocked(service, resolvedType, Binder.getCallingPid(), Binder.getCallingUid(), 0); @@ -787,22 +788,9 @@ public class ActiveServices { ComponentName name = new ComponentName( sInfo.applicationInfo.packageName, sInfo.name); if (userId > 0) { - if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo) - || (sInfo.flags&ServiceInfo.FLAG_SINGLE_USER) != 0) { + if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo, + sInfo.name, sInfo.flags)) { userId = 0; - } else if ((sInfo.flags&ServiceInfo.FLAG_SINGLE_USER) != 0) { - if (mAm.checkComponentPermission( - android.Manifest.permission.INTERACT_ACROSS_USERS, - callingPid, callingUid, -1, true) - == PackageManager.PERMISSION_GRANTED) { - userId = 0; - } else { - String msg = "Permission Denial: Service " + name - + " requests FLAG_SINGLE_USER, but app does not hold " - + android.Manifest.permission.INTERACT_ACROSS_USERS; - Slog.w(TAG, msg); - throw new SecurityException(msg); - } } sInfo = new ServiceInfo(sInfo); sInfo.applicationInfo = mAm.getAppInfoForUser(sInfo.applicationInfo, userId); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 98d3482..05f38a5 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -5936,15 +5936,26 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.v(TAG_MU, "generateApplicationProvidersLocked, app.info.uid = " + app.uid); int userId = app.userId; if (providers != null) { - final int N = providers.size(); + int N = providers.size(); for (int i=0; i<N; i++) { ProviderInfo cpi = (ProviderInfo)providers.get(i); + boolean singleton = isSingleton(cpi.processName, cpi.applicationInfo, + cpi.name, cpi.flags); + if (singleton && UserId.getUserId(app.uid) != 0) { + // This is a singleton provider, but a user besides the + // default user is asking to initialize a process it runs + // in... well, no, it doesn't actually run in this process, + // it runs in the process of the default user. Get rid of it. + providers.remove(i); + N--; + continue; + } ComponentName comp = new ComponentName(cpi.packageName, cpi.name); ContentProviderRecord cpr = mProviderMap.getProviderByClass(comp, userId); if (cpr == null) { - cpr = new ContentProviderRecord(this, cpi, app.info, comp); + cpr = new ContentProviderRecord(this, cpi, app.info, comp, singleton); mProviderMap.putProviderByClass(comp, cpr); } if (DEBUG_MU) @@ -6177,6 +6188,7 @@ public final class ActivityManagerService extends ActivityManagerNative Binder.restoreCallingIdentity(origId); } + boolean singleton; if (!providerRunning) { try { cpi = AppGlobals.getPackageManager(). @@ -6187,7 +6199,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (cpi == null) { return null; } - if (isSingleton(cpi.processName, cpi.applicationInfo)) { + singleton = isSingleton(cpi.processName, cpi.applicationInfo, + cpi.name, cpi.flags); + if (singleton) { userId = 0; } cpi.applicationInfo = getAppInfoForUser(cpi.applicationInfo, userId); @@ -6222,7 +6236,7 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } ai = getAppInfoForUser(ai, userId); - cpr = new ContentProviderRecord(this, cpi, ai, comp); + cpr = new ContentProviderRecord(this, cpi, ai, comp, singleton); } catch (RemoteException ex) { // pm is in same process, this will never happen. } @@ -10515,17 +10529,31 @@ public final class ActivityManagerService extends ActivityManagerNative } } - boolean isSingleton(String componentProcessName, ApplicationInfo aInfo) { + boolean isSingleton(String componentProcessName, ApplicationInfo aInfo, + String className, int flags) { boolean result = false; if (UserId.getAppId(aInfo.uid) >= Process.FIRST_APPLICATION_UID) { - result = false; + if ((flags&ServiceInfo.FLAG_SINGLE_USER) != 0) { + if (ActivityManager.checkUidPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, + aInfo.uid) != PackageManager.PERMISSION_GRANTED) { + ComponentName comp = new ComponentName(aInfo.packageName, className); + String msg = "Permission Denial: Component " + comp.flattenToShortString() + + " requests FLAG_SINGLE_USER, but app does not hold " + + android.Manifest.permission.INTERACT_ACROSS_USERS; + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + result = true; + } } else if (componentProcessName == aInfo.packageName) { result = (aInfo.flags & ApplicationInfo.FLAG_PERSISTENT) != 0; } else if ("system".equals(componentProcessName)) { result = true; } if (DEBUG_MU) { - Slog.v(TAG, "isSingleton(" + componentProcessName + ", " + aInfo + ") = " + result); + Slog.v(TAG, "isSingleton(" + componentProcessName + ", " + aInfo + + ", " + className + ", 0x" + Integer.toHexString(flags) + ") = " + result); } return result; } @@ -11131,30 +11159,15 @@ public final class ActivityManagerService extends ActivityManagerNative List receivers = null; List<BroadcastFilter> registeredReceivers = null; try { - if (intent.getComponent() != null) { - // Broadcast is going to one specific receiver class... - ActivityInfo ai = AppGlobals.getPackageManager(). - getReceiverInfo(intent.getComponent(), STOCK_PM_FLAGS, userId); - if (ai != null) { - receivers = new ArrayList(); - ResolveInfo ri = new ResolveInfo(); - if (isSingleton(ai.processName, ai.applicationInfo)) { - ri.activityInfo = getActivityInfoForUser(ai, 0); - } else { - ri.activityInfo = getActivityInfoForUser(ai, userId); - } - receivers.add(ri); - } - } else { - // Need to resolve the intent to interested receivers... - if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) - == 0) { - receivers = - AppGlobals.getPackageManager().queryIntentReceivers( - intent, resolvedType, STOCK_PM_FLAGS, userId); - } - registeredReceivers = mReceiverResolver.queryIntent(intent, resolvedType, false, - userId); + // Need to resolve the intent to interested receivers... + if ((intent.getFlags()&Intent.FLAG_RECEIVER_REGISTERED_ONLY) + == 0) { + receivers = AppGlobals.getPackageManager().queryIntentReceivers( + intent, resolvedType, STOCK_PM_FLAGS, userId); + } + if (intent.getComponent() == null) { + registeredReceivers = mReceiverResolver.queryIntent(intent, + resolvedType, false, userId); } } catch (RemoteException ex) { // pm is in same process, this will never happen. diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index c9a633e..196a259 100755 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -3014,7 +3014,8 @@ final class ActivityStack { // Collect information about the target of the Intent. ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, profileFile, profileFd, userId); - if (aInfo != null && mService.isSingleton(aInfo.processName, aInfo.applicationInfo)) { + if (aInfo != null && mService.isSingleton(aInfo.processName, aInfo.applicationInfo, + null, 0)) { userId = 0; } aInfo = mService.getActivityInfoForUser(aInfo, userId); diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java index c6d46fc..76ddb96 100644 --- a/services/java/com/android/server/am/BroadcastQueue.java +++ b/services/java/com/android/server/am/BroadcastQueue.java @@ -20,12 +20,15 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import android.app.ActivityManager; import android.app.AppGlobals; import android.content.ComponentName; import android.content.IIntentReceiver; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -249,7 +252,7 @@ public class BroadcastQueue { finishReceiverLocked(br, br.resultCode, br.resultData, br.resultExtras, br.resultAbort, true); scheduleBroadcastsLocked(); - // We need to reset the state if we fails to start the receiver. + // We need to reset the state if we failed to start the receiver. br.state = BroadcastRecord.IDLE; throw new RuntimeException(e.getMessage()); } @@ -659,6 +662,9 @@ public class BroadcastQueue { ResolveInfo info = (ResolveInfo)nextReceiver; + ComponentName component = new ComponentName( + info.activityInfo.applicationInfo.packageName, + info.activityInfo.name); boolean skip = false; if (r.onlySendToCaller) { @@ -667,6 +673,7 @@ public class BroadcastQueue { + r.intent.toString() + " from " + r.callerPackage + " (pid=" + r.callingPid + ", uid=" + r.callingUid + ")" + + " to " + component.flattenToShortString() + " not allowed to go to different app " + info.activityInfo.applicationInfo.uid); skip = true; @@ -682,16 +689,14 @@ public class BroadcastQueue { + " from " + r.callerPackage + " (pid=" + r.callingPid + ", uid=" + r.callingUid + ")" + " is not exported from uid " + info.activityInfo.applicationInfo.uid - + " due to receiver " + info.activityInfo.packageName - + "/" + info.activityInfo.name); + + " due to receiver " + component.flattenToShortString()); } else { Slog.w(TAG, "Permission Denial: broadcasting " + r.intent.toString() + " from " + r.callerPackage + " (pid=" + r.callingPid + ", uid=" + r.callingUid + ")" + " requires " + info.activityInfo.permission - + " due to receiver " + info.activityInfo.packageName - + "/" + info.activityInfo.name); + + " due to receiver " + component.flattenToShortString()); } skip = true; } @@ -707,13 +712,33 @@ public class BroadcastQueue { if (perm != PackageManager.PERMISSION_GRANTED) { Slog.w(TAG, "Permission Denial: receiving " + r.intent + " to " - + info.activityInfo.applicationInfo.packageName + + component.flattenToShortString() + " requires " + r.requiredPermission + " due to sender " + r.callerPackage + " (uid " + r.callingUid + ")"); skip = true; } } + boolean isSingleton = false; + try { + isSingleton = mService.isSingleton(info.activityInfo.processName, + info.activityInfo.applicationInfo, + info.activityInfo.name, info.activityInfo.flags); + } catch (SecurityException e) { + Slog.w(TAG, e.getMessage()); + skip = true; + } + if ((info.activityInfo.flags&ActivityInfo.FLAG_SINGLE_USER) != 0) { + if (ActivityManager.checkUidPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, + info.activityInfo.applicationInfo.uid) + != PackageManager.PERMISSION_GRANTED) { + Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString() + + " requests FLAG_SINGLE_USER, but app does not hold " + + android.Manifest.permission.INTERACT_ACROSS_USERS); + skip = true; + } + } if (r.curApp != null && r.curApp.crashing) { // If the target process is crashing, just skip it. if (DEBUG_BROADCAST) Slog.v(TAG, @@ -736,14 +761,9 @@ public class BroadcastQueue { r.state = BroadcastRecord.APP_RECEIVE; String targetProcess = info.activityInfo.processName; - r.curComponent = new ComponentName( - info.activityInfo.applicationInfo.packageName, - info.activityInfo.name); - if (r.callingUid != Process.SYSTEM_UID) { - boolean isSingleton = mService.isSingleton(info.activityInfo.processName, - info.activityInfo.applicationInfo); - int targetUserId = isSingleton ? 0 : UserId.getUserId(r.callingUid); - info.activityInfo = mService.getActivityInfoForUser(info.activityInfo,targetUserId); + r.curComponent = component; + if (r.callingUid != Process.SYSTEM_UID && isSingleton) { + info.activityInfo = mService.getActivityInfoForUser(info.activityInfo, 0); } r.curReceiver = info.activityInfo; if (DEBUG_MU && r.callingUid > UserId.PER_USER_RANGE) { diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java index fb21b06..c80d63a 100644 --- a/services/java/com/android/server/am/ContentProviderRecord.java +++ b/services/java/com/android/server/am/ContentProviderRecord.java @@ -38,6 +38,7 @@ class ContentProviderRecord { final int uid; final ApplicationInfo appInfo; final ComponentName name; + final boolean singleton; public IContentProvider provider; public boolean noReleaseNeeded; // All attached clients @@ -54,12 +55,13 @@ class ContentProviderRecord { String shortStringName; public ContentProviderRecord(ActivityManagerService _service, ProviderInfo _info, - ApplicationInfo ai, ComponentName _name) { + ApplicationInfo ai, ComponentName _name, boolean _singleton) { service = _service; info = _info; uid = ai.uid; appInfo = ai; name = _name; + singleton = _singleton; noReleaseNeeded = uid == 0 || uid == Process.SYSTEM_UID; } @@ -69,6 +71,7 @@ class ContentProviderRecord { uid = cpr.uid; appInfo = cpr.appInfo; name = cpr.name; + singleton = cpr.singleton; noReleaseNeeded = cpr.noReleaseNeeded; } @@ -150,6 +153,9 @@ class ContentProviderRecord { pw.print(prefix); pw.print("uid="); pw.print(uid); pw.print(" provider="); pw.println(provider); } + if (singleton) { + pw.print(prefix); pw.print("singleton="); pw.println(singleton); + } pw.print(prefix); pw.print("authority="); pw.println(info.authority); if (full) { if (info.isSyncable || info.multiprocess || info.initOrder != 0) { diff --git a/services/java/com/android/server/am/ProviderMap.java b/services/java/com/android/server/am/ProviderMap.java index d148ec3..f4d0f9b 100644 --- a/services/java/com/android/server/am/ProviderMap.java +++ b/services/java/com/android/server/am/ProviderMap.java @@ -44,9 +44,9 @@ public class ProviderMap { private static final boolean DBG = false; - private final HashMap<String, ContentProviderRecord> mGlobalByName + private final HashMap<String, ContentProviderRecord> mSingletonByName = new HashMap<String, ContentProviderRecord>(); - private final HashMap<ComponentName, ContentProviderRecord> mGlobalByClass + private final HashMap<ComponentName, ContentProviderRecord> mSingletonByClass = new HashMap<ComponentName, ContentProviderRecord>(); private final SparseArray<HashMap<String, ContentProviderRecord>> mProvidersByNamePerUser @@ -63,7 +63,7 @@ public class ProviderMap { Slog.i(TAG, "getProviderByName: " + name + " , callingUid = " + Binder.getCallingUid()); } // Try to find it in the global list - ContentProviderRecord record = mGlobalByName.get(name); + ContentProviderRecord record = mSingletonByName.get(name); if (record != null) { return record; } @@ -81,7 +81,7 @@ public class ProviderMap { Slog.i(TAG, "getProviderByClass: " + name + ", callingUid = " + Binder.getCallingUid()); } // Try to find it in the global list - ContentProviderRecord record = mGlobalByClass.get(name); + ContentProviderRecord record = mSingletonByClass.get(name); if (record != null) { return record; } @@ -95,8 +95,8 @@ public class ProviderMap { Slog.i(TAG, "putProviderByName: " + name + " , callingUid = " + Binder.getCallingUid() + ", record uid = " + record.appInfo.uid); } - if (record.appInfo.uid < Process.FIRST_APPLICATION_UID) { - mGlobalByName.put(name, record); + if (record.singleton) { + mSingletonByName.put(name, record); } else { final int userId = UserId.getUserId(record.appInfo.uid); getProvidersByName(userId).put(name, record); @@ -108,8 +108,8 @@ public class ProviderMap { Slog.i(TAG, "putProviderByClass: " + name + " , callingUid = " + Binder.getCallingUid() + ", record uid = " + record.appInfo.uid); } - if (record.appInfo.uid < Process.FIRST_APPLICATION_UID) { - mGlobalByClass.put(name, record); + if (record.singleton) { + mSingletonByClass.put(name, record); } else { final int userId = UserId.getUserId(record.appInfo.uid); getProvidersByClass(userId).put(name, record); @@ -117,10 +117,10 @@ public class ProviderMap { } void removeProviderByName(String name, int optionalUserId) { - if (mGlobalByName.containsKey(name)) { + if (mSingletonByName.containsKey(name)) { if (DBG) Slog.i(TAG, "Removing from globalByName name=" + name); - mGlobalByName.remove(name); + mSingletonByName.remove(name); } else { // TODO: Verify this works, i.e., the caller happens to be from the correct user if (DBG) @@ -132,10 +132,10 @@ public class ProviderMap { } void removeProviderByClass(ComponentName name, int optionalUserId) { - if (mGlobalByClass.containsKey(name)) { + if (mSingletonByClass.containsKey(name)) { if (DBG) Slog.i(TAG, "Removing from globalByClass name=" + name); - mGlobalByClass.remove(name); + mSingletonByClass.remove(name); } else { if (DBG) Slog.i(TAG, @@ -197,37 +197,30 @@ public class ProviderMap { } void dumpProvidersLocked(PrintWriter pw, boolean dumpAll) { - boolean needSep = false; - if (mGlobalByClass.size() > 0) { - if (needSep) - pw.println(" "); - pw.println(" Published content providers (by class):"); - dumpProvidersByClassLocked(pw, dumpAll, mGlobalByClass); + if (mSingletonByClass.size() > 0) { + pw.println(""); + pw.println(" Published single-user content providers (by class):"); + dumpProvidersByClassLocked(pw, dumpAll, mSingletonByClass); } - if (mProvidersByClassPerUser.size() > 1) { - pw.println(""); - for (int i = 0; i < mProvidersByClassPerUser.size(); i++) { - HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i); - pw.println(" User " + mProvidersByClassPerUser.keyAt(i) + ":"); - dumpProvidersByClassLocked(pw, dumpAll, map); - pw.println(" "); - } - } else if (mProvidersByClassPerUser.size() == 1) { - HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(0); + pw.println(""); + for (int i = 0; i < mProvidersByClassPerUser.size(); i++) { + HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i); + pw.println(" Published user " + mProvidersByClassPerUser.keyAt(i) + + " content providers (by class):"); dumpProvidersByClassLocked(pw, dumpAll, map); + pw.println(" "); } - needSep = true; if (dumpAll) { - pw.println(" "); - pw.println(" Authority to provider mappings:"); - dumpProvidersByNameLocked(pw, mGlobalByName); + pw.println(""); + pw.println(" Single-user authority to provider mappings:"); + dumpProvidersByNameLocked(pw, mSingletonByName); for (int i = 0; i < mProvidersByNamePerUser.size(); i++) { - if (i > 0) { - pw.println(" User " + mProvidersByNamePerUser.keyAt(i) + ":"); - } + pw.println(""); + pw.println(" User " + mProvidersByNamePerUser.keyAt(i) + + " authority to provider mappings:"); dumpProvidersByNameLocked(pw, mProvidersByNamePerUser.valueAt(i)); } } @@ -328,6 +321,4 @@ public class ProviderMap { } } } - - } diff --git a/test-runner/src/android/test/mock/MockContext.java b/test-runner/src/android/test/mock/MockContext.java index 92c6676..9acffa3 100644 --- a/test-runner/src/android/test/mock/MockContext.java +++ b/test-runner/src/android/test/mock/MockContext.java @@ -285,11 +285,6 @@ public class MockContext extends Context { } @Override - public void sendBroadcastToUser(Intent intent, int userId) { - throw new UnsupportedOperationException(); - } - - @Override public void sendBroadcast(Intent intent, String receiverPermission) { throw new UnsupportedOperationException(); } @@ -308,6 +303,18 @@ public class MockContext extends Context { } @Override + public void sendBroadcastToUser(Intent intent, int userId) { + throw new UnsupportedOperationException(); + } + + @Override + public void sendOrderedBroadcastToUser(Intent intent, int userId, + BroadcastReceiver resultReceiver, Handler scheduler, + int initialCode, String initialData, Bundle initialExtras) { + throw new UnsupportedOperationException(); + } + + @Override public void sendStickyBroadcast(Intent intent) { throw new UnsupportedOperationException(); } diff --git a/tests/ActivityTests/AndroidManifest.xml b/tests/ActivityTests/AndroidManifest.xml index 6f00095..9dfe4a1 100644 --- a/tests/ActivityTests/AndroidManifest.xml +++ b/tests/ActivityTests/AndroidManifest.xml @@ -33,5 +33,11 @@ </service> <receiver android:name="UserTarget"> </receiver> + <receiver android:name="SingleUserReceiver" + android:singleUser="true" android:exported="true" > + </receiver> + <provider android:name="SingleUserProvider" + android:authorities="com.google.android.test.activity.single_user" + android:singleUser="true" android:exported="true" /> </application> </manifest> diff --git a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java index bcef2d9..0ec1f13 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/ActivityTestMain.java @@ -22,16 +22,20 @@ import android.app.Activity; import android.app.ActivityManager; import android.app.AlertDialog; import android.content.ActivityNotFoundException; +import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentProviderClient; import android.content.Intent; import android.content.ServiceConnection; import android.os.Bundle; import android.os.IBinder; +import android.os.RemoteException; import android.graphics.Bitmap; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.ScrollView; +import android.widget.Toast; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -43,6 +47,18 @@ public class ActivityTestMain extends Activity { ActivityManager mAm; + class BroadcastResultReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Bundle res = getResultExtras(true); + int user = res.getInt("user", -1); + Toast.makeText(ActivityTestMain.this, + "Receiver executed as user " + + (user >= 0 ? Integer.toString(user) : "unknown"), + Toast.LENGTH_LONG).show(); + } + } + private void addThumbnail(LinearLayout container, Bitmap bm, final ActivityManager.RecentTaskInfo task, final ActivityManager.TaskThumbnails thumbs, final int subIndex) { @@ -134,8 +150,35 @@ public class ActivityTestMain extends Activity { }); menu.add("Send!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { + Intent intent = new Intent(ActivityTestMain.this, SingleUserReceiver.class); + sendOrderedBroadcast(intent, null, new BroadcastResultReceiver(), + null, Activity.RESULT_OK, null, null); + return true; + } + }); + menu.add("Call!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { + ContentProviderClient cpl = getContentResolver().acquireContentProviderClient( + SingleUserProvider.AUTHORITY); + Bundle res = null; + try { + res = cpl.call("getuser", null, null); + } catch (RemoteException e) { + } + int user = res != null ? res.getInt("user", -1) : -1; + Toast.makeText(ActivityTestMain.this, + "Provider executed as user " + + (user >= 0 ? Integer.toString(user) : "unknown"), + Toast.LENGTH_LONG).show(); + cpl.release(); + return true; + } + }); + menu.add("Send to user 1!").setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { + @Override public boolean onMenuItemClick(MenuItem item) { Intent intent = new Intent(ActivityTestMain.this, UserTarget.class); - sendBroadcastToUser(intent, 1); + sendOrderedBroadcastToUser(intent, 1, new BroadcastResultReceiver(), + null, Activity.RESULT_OK, null, null); return true; } }); diff --git a/tests/ActivityTests/src/com/google/android/test/activity/SingleUserProvider.java b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserProvider.java new file mode 100644 index 0000000..83785e4 --- /dev/null +++ b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserProvider.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.test.activity; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.os.Process; + +public class SingleUserProvider extends ContentProvider { + static final String AUTHORITY = "com.google.android.test.activity.single_user"; + + @Override + public boolean onCreate() { + return true; + } + + @Override + public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, + String sortOrder) { + return null; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public Bundle call(String method, String arg, Bundle extras) { + Bundle res = new Bundle(); + res.putInt("user", Process.myUserHandle()); + return res; + } +} diff --git a/tests/ActivityTests/src/com/google/android/test/activity/SingleUserReceiver.java b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserReceiver.java new file mode 100644 index 0000000..9295cf4 --- /dev/null +++ b/tests/ActivityTests/src/com/google/android/test/activity/SingleUserReceiver.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2012 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.android.test.activity; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.os.Process; + +public class SingleUserReceiver extends BroadcastReceiver { + @Override + public void onReceive(Context context, Intent intent) { + Bundle res = getResultExtras(true); + res.putInt("user", Process.myUserHandle()); + setResultExtras(res); + } +} diff --git a/tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java b/tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java index 9890483..9c6a9f1 100644 --- a/tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java +++ b/tests/ActivityTests/src/com/google/android/test/activity/UserTarget.java @@ -19,11 +19,16 @@ package com.google.android.test.activity; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; +import android.os.Bundle; +import android.os.Process; import android.util.Log; public class UserTarget extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Log.i("ActivityTest", "Received: " + intent); + Bundle res = getResultExtras(true); + res.putInt("user", Process.myUserHandle()); + setResultExtras(res); } } diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java index 3ae660d..c4a6906 100644 --- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java +++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/android/BridgeContext.java @@ -1187,6 +1187,18 @@ public final class BridgeContext extends Context { } @Override + public void sendBroadcastToUser(Intent intent, int userId) { + // pass + } + + @Override + public void sendOrderedBroadcastToUser(Intent intent, int userId, + BroadcastReceiver resultReceiver, Handler scheduler, + int initialCode, String initialData, Bundle initialExtras) { + // pass + } + + @Override public void sendStickyBroadcast(Intent arg0) { // pass |