Making the clipboard work across users. When copying from the parent: the ClipData can be pasted in the managed profile. When copying from a managed profile: it can be pasted in the parent, unless the policies says it's disabled. In which case, the clipboard of the parent becomes empty. Supporting content uris. BUG: 15186236 Change-Id: I522564a7c07ff21df137adcda980bb52e5739964
diff --git a/api/current.txt b/api/current.txt index e0006f6..5d9d337 100644 --- a/api/current.txt +++ b/api/current.txt
@@ -22731,6 +22731,7 @@ field public static final java.lang.String DISALLOW_CONFIG_VPN = "no_config_vpn"; field public static final java.lang.String DISALLOW_CONFIG_WIFI = "no_config_wifi"; field public static final java.lang.String DISALLOW_CREATE_WINDOWS = "no_create_windows"; + field public static final java.lang.String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste"; field public static final java.lang.String DISALLOW_DEBUGGING_FEATURES = "no_debugging_features"; field public static final java.lang.String DISALLOW_FACTORY_RESET = "no_factory_reset"; field public static final java.lang.String DISALLOW_INSTALL_APPS = "no_install_apps";
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 342155d..318a520 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java
@@ -1661,8 +1661,10 @@ String targetPkg = data.readString(); Uri uri = Uri.CREATOR.createFromParcel(data); int mode = data.readInt(); - int userId = data.readInt(); - grantUriPermissionFromOwner(owner, fromUid, targetPkg, uri, mode, userId); + int sourceUserId = data.readInt(); + int targetUserId = data.readInt(); + grantUriPermissionFromOwner(owner, fromUid, targetPkg, uri, mode, sourceUserId, + targetUserId); reply.writeNoException(); return true; } @@ -4343,7 +4345,7 @@ } public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg, - Uri uri, int mode, int userId) throws RemoteException { + Uri uri, int mode, int sourceUserId, int targetUserId) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); @@ -4352,7 +4354,8 @@ data.writeString(targetPkg); uri.writeToParcel(data, 0); data.writeInt(mode); - data.writeInt(userId); + data.writeInt(sourceUserId); + data.writeInt(targetUserId); mRemote.transact(GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0); reply.readException(); data.recycle();
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index cc13a3b..53c1408 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java
@@ -332,7 +332,7 @@ public IBinder newUriPermissionOwner(String name) throws RemoteException; public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg, - Uri uri, int mode, int userId) throws RemoteException; + Uri uri, int mode, int sourceUserId, int targetUserId) throws RemoteException; public void revokeUriPermissionFromOwner(IBinder owner, Uri uri, int mode, int userId) throws RemoteException;
diff --git a/core/java/android/content/ClipData.java b/core/java/android/content/ClipData.java index b44abf9..7a16ef8 100644 --- a/core/java/android/content/ClipData.java +++ b/core/java/android/content/ClipData.java
@@ -828,6 +828,26 @@ } } + /** + * Only fixing the data field of the intents + * @hide + */ + public void fixUrisLight(int contentUserHint) { + final int size = mItems.size(); + for (int i = 0; i < size; i++) { + final Item item = mItems.get(i); + if (item.mIntent != null) { + Uri data = item.mIntent.getData(); + if (data != null) { + item.mIntent.setData(maybeAddUserId(data, contentUserHint)); + } + } + if (item.mUri != null) { + item.mUri = maybeAddUserId(item.mUri, contentUserHint); + } + } + } + @Override public String toString() { StringBuilder b = new StringBuilder(128);
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java index d3aee50..45edf28 100644 --- a/core/java/android/os/UserManager.java +++ b/core/java/android/os/UserManager.java
@@ -310,6 +310,18 @@ */ public static final String DISALLOW_CREATE_WINDOWS = "no_create_windows"; + /** + * Key for user restrictions. Specifies if what is copied in the clipboard of this profile can + * be pasted in related profiles. Does not restrict if the clipboard of related profiles can be + * pasted in this profile. + * The default value is <code>false</code>. + * <p/> + * Type: Boolean + * @see #setUserRestrictions(Bundle) + * @see #getUserRestrictions() + */ + public static final String DISALLOW_CROSS_PROFILE_COPY_PASTE = "no_cross_profile_copy_paste"; + /** @hide */ public static final int PIN_VERIFICATION_FAILED_INCORRECT = -3; /** @hide */
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java index ab8f2f5..c0ac023 100755 --- a/services/core/java/com/android/server/am/ActivityManagerService.java +++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -6563,13 +6563,20 @@ } void grantUriPermissionLocked(int callingUid, String targetPkg, GrantUri grantUri, - final int modeFlags, UriPermissionOwner owner) { + final int modeFlags, UriPermissionOwner owner, int targetUserId) { if (targetPkg == null) { throw new NullPointerException("targetPkg"); } + int targetUid; + final IPackageManager pm = AppGlobals.getPackageManager(); + try { + targetUid = pm.getPackageUid(targetPkg, targetUserId); + } catch (RemoteException ex) { + return; + } - int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, grantUri, modeFlags, - -1); + targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, grantUri, modeFlags, + targetUid); if (targetUid < 0) { return; } @@ -6720,7 +6727,8 @@ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); - grantUriPermissionLocked(r.uid, targetPkg, grantUri, modeFlags, null); + grantUriPermissionLocked(r.uid, targetPkg, grantUri, modeFlags, null, + UserHandle.getUserId(r.uid)); } } @@ -6897,7 +6905,7 @@ @Override public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg, Uri uri, - final int modeFlags, int userId) { + final int modeFlags, int sourceUserId, int targetUserId) { synchronized(this) { UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token); if (owner == null) { @@ -6917,8 +6925,8 @@ throw new IllegalArgumentException("null uri"); } - grantUriPermissionLocked(fromUid, targetPkg, new GrantUri(userId, uri, false), - modeFlags, owner); + grantUriPermissionLocked(fromUid, targetPkg, new GrantUri(sourceUserId, uri, false), + modeFlags, owner, targetUserId); } }
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java index 15e3e89..1c26846 100644 --- a/services/core/java/com/android/server/clipboard/ClipboardService.java +++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -32,18 +32,23 @@ import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.UserInfo; import android.net.Uri; import android.os.Binder; import android.os.IBinder; +import android.os.IUserManager; import android.os.Parcel; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.UserHandle; +import android.os.UserManager; import android.util.Slog; import android.util.SparseArray; import java.util.HashSet; +import java.util.List; /** * Implementation of the clipboard for copy and paste. @@ -54,6 +59,7 @@ private final Context mContext; private final IActivityManager mAm; + private final IUserManager mUm; private final PackageManager mPm; private final AppOpsManager mAppOps; private final IBinder mPermissionOwner; @@ -92,6 +98,7 @@ mContext = context; mAm = ActivityManagerNative.getDefault(); mPm = context.getPackageManager(); + mUm = (IUserManager) ServiceManager.getService(Context.USER_SERVICE); mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); IBinder permOwner = null; try { @@ -161,32 +168,81 @@ return; } checkDataOwnerLocked(clip, callingUid); - clearActiveOwnersLocked(); - PerUserClipboard clipboard = getClipboard(); - clipboard.primaryClip = clip; - final long ident = Binder.clearCallingIdentity(); - final int n = clipboard.primaryClipListeners.beginBroadcast(); - try { - for (int i = 0; i < n; i++) { + final int userId = UserHandle.getUserId(callingUid); + PerUserClipboard clipboard = getClipboard(userId); + revokeUris(clipboard); + setPrimaryClipInternal(clipboard, clip); + List<UserInfo> related = getRelatedProfiles(userId); + if (related != null) { + int size = related.size(); + if (size > 1) { // Related profiles list include the current profile. + boolean canCopy = false; try { - ListenerInfo li = (ListenerInfo) - clipboard.primaryClipListeners.getBroadcastCookie(i); - if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid, - li.mPackageName) == AppOpsManager.MODE_ALLOWED) { - clipboard.primaryClipListeners.getBroadcastItem(i) - .dispatchPrimaryClipChanged(); - } + canCopy = !mUm.getUserRestrictions(userId).getBoolean( + UserManager.DISALLOW_CROSS_PROFILE_COPY_PASTE); } catch (RemoteException e) { - // The RemoteCallbackList will take care of removing - // the dead object for us. + Slog.e(TAG, "Remote Exception calling UserManager: " + e); + } + // Copy clip data to related users if allowed. If disallowed, then remove + // primary clip in related users to prevent pasting stale content. + if (!canCopy) { + clip = null; + } else { + clip.fixUrisLight(userId); + } + for (int i = 0; i < size; i++) { + int id = related.get(i).id; + if (id != userId) { + setPrimaryClipInternal(getClipboard(id), clip); + } } } - } finally { - clipboard.primaryClipListeners.finishBroadcast(); - Binder.restoreCallingIdentity(ident); } } } + + List<UserInfo> getRelatedProfiles(int userId) { + final List<UserInfo> related; + final long origId = Binder.clearCallingIdentity(); + try { + related = mUm.getProfiles(userId, true); + } catch (RemoteException e) { + Slog.e(TAG, "Remote Exception calling UserManager: " + e); + return null; + } finally{ + Binder.restoreCallingIdentity(origId); + } + return related; + } + + void setPrimaryClipInternal(PerUserClipboard clipboard, ClipData clip) { + clipboard.activePermissionOwners.clear(); + if (clip == null && clipboard.primaryClip == null) { + return; + } + clipboard.primaryClip = clip; + final long ident = Binder.clearCallingIdentity(); + final int n = clipboard.primaryClipListeners.beginBroadcast(); + try { + for (int i = 0; i < n; i++) { + try { + ListenerInfo li = (ListenerInfo) + clipboard.primaryClipListeners.getBroadcastCookie(i); + if (mAppOps.checkOpNoThrow(AppOpsManager.OP_READ_CLIPBOARD, li.mUid, + li.mPackageName) == AppOpsManager.MODE_ALLOWED) { + clipboard.primaryClipListeners.getBroadcastItem(i) + .dispatchPrimaryClipChanged(); + } + } catch (RemoteException e) { + // The RemoteCallbackList will take care of removing + // the dead object for us. + } + } + } finally { + clipboard.primaryClipListeners.finishBroadcast(); + Binder.restoreCallingIdentity(ident); + } + } public ClipData getPrimaryClip(String pkg) { synchronized (this) { @@ -257,7 +313,8 @@ try { // This will throw SecurityException for us. mAm.checkGrantUriPermission(uid, null, ContentProvider.getUriWithoutUserId(uri), - Intent.FLAG_GRANT_READ_URI_PERMISSION, resolveUserId(uri, uid)); + Intent.FLAG_GRANT_READ_URI_PERMISSION, + ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(uid))); } catch (RemoteException e) { } finally { Binder.restoreCallingIdentity(ident); @@ -281,26 +338,26 @@ } } - private final void grantUriLocked(Uri uri, String pkg) { + private final void grantUriLocked(Uri uri, String pkg, int userId) { long ident = Binder.clearCallingIdentity(); try { + int sourceUserId = ContentProvider.getUserIdFromUri(uri, userId); + uri = ContentProvider.getUriWithoutUserId(uri); mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, - ContentProvider.getUriWithoutUserId(uri), - Intent.FLAG_GRANT_READ_URI_PERMISSION, - resolveUserId(uri, Process.myUid())); + uri, Intent.FLAG_GRANT_READ_URI_PERMISSION, sourceUserId, userId); } catch (RemoteException e) { } finally { Binder.restoreCallingIdentity(ident); } } - private final void grantItemLocked(ClipData.Item item, String pkg) { + private final void grantItemLocked(ClipData.Item item, String pkg, int userId) { if (item.getUri() != null) { - grantUriLocked(item.getUri(), pkg); + grantUriLocked(item.getUri(), pkg, userId); } Intent intent = item.getIntent(); if (intent != null && intent.getData() != null) { - grantUriLocked(intent.getData(), pkg); + grantUriLocked(intent.getData(), pkg, userId); } } @@ -326,19 +383,21 @@ if (clipboard.primaryClip != null && !clipboard.activePermissionOwners.contains(pkg)) { final int N = clipboard.primaryClip.getItemCount(); for (int i=0; i<N; i++) { - grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg); + grantItemLocked(clipboard.primaryClip.getItemAt(i), pkg, UserHandle.getUserId(uid)); } clipboard.activePermissionOwners.add(pkg); } } private final void revokeUriLocked(Uri uri) { + int userId = ContentProvider.getUserIdFromUri(uri, + UserHandle.getUserId(Binder.getCallingUid())); long ident = Binder.clearCallingIdentity(); try { - mAm.revokeUriPermissionFromOwner(mPermissionOwner, - ContentProvider.getUriWithoutUserId(uri), + uri = ContentProvider.getUriWithoutUserId(uri); + mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION, - resolveUserId(uri, Process.myUid())); + userId); } catch (RemoteException e) { } finally { Binder.restoreCallingIdentity(ident); @@ -355,9 +414,7 @@ } } - private final void clearActiveOwnersLocked() { - PerUserClipboard clipboard = getClipboard(); - clipboard.activePermissionOwners.clear(); + private final void revokeUris(PerUserClipboard clipboard) { if (clipboard.primaryClip == null) { return; } @@ -366,8 +423,4 @@ revokeItemLocked(clipboard.primaryClip.getItemAt(i)); } } - - private final int resolveUserId(Uri uri, int uid) { - return ContentProvider.getUserIdFromUri(uri, UserHandle.getUserId(uid)); - } }