Implement permission granting in clipboard.

Change-Id: I9a7a949d1aaf4b3beabceaf807fb7d3b040e4ea8
diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java
index 308c9c0..bdf313c 100644
--- a/services/java/com/android/server/ClipboardService.java
+++ b/services/java/com/android/server/ClipboardService.java
@@ -16,32 +16,81 @@
 
 package com.android.server;
 
+import android.app.ActivityManagerNative;
+import android.app.IActivityManager;
 import android.content.ClipData;
 import android.content.ClipDescription;
 import android.content.IClipboard;
 import android.content.IOnPrimaryClipChangedListener;
 import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
+import android.util.Pair;
+import android.util.Slog;
+
+import java.util.HashSet;
 
 /**
  * Implementation of the clipboard for copy and paste.
  */
 public class ClipboardService extends IClipboard.Stub {
-    private ClipData mPrimaryClip;
+    private final Context mContext;
+    private final IActivityManager mAm;
+    private final PackageManager mPm;
+    private final IBinder mPermissionOwner;
+
     private final RemoteCallbackList<IOnPrimaryClipChangedListener> mPrimaryClipListeners
             = new RemoteCallbackList<IOnPrimaryClipChangedListener>();
 
+    private ClipData mPrimaryClip;
+
+    private final HashSet<String> mActivePermissionOwners
+            = new HashSet<String>();
+
     /**
      * Instantiates the clipboard.
      */
-    public ClipboardService(Context context) { }
+    public ClipboardService(Context context) {
+        mContext = context;
+        mAm = ActivityManagerNative.getDefault();
+        mPm = context.getPackageManager();
+        IBinder permOwner = null;
+        try {
+            permOwner = mAm.newUriPermissionOwner("clipboard");
+        } catch (RemoteException e) {
+            Slog.w("clipboard", "AM dead", e);
+        }
+        mPermissionOwner = permOwner;
+    }
+
+    @Override
+    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+            throws RemoteException {
+        try {
+            return super.onTransact(code, data, reply, flags);
+        } catch (RuntimeException e) {
+            Slog.w("clipboard", "Exception: ", e);
+            throw e;
+        }
+        
+    }
 
     public void setPrimaryClip(ClipData clip) {
         synchronized (this) {
             if (clip != null && clip.getItemCount() <= 0) {
                 throw new IllegalArgumentException("No items");
             }
+            checkDataOwnerLocked(clip, Binder.getCallingUid());
+            clearActiveOwnersLocked();
             mPrimaryClip = clip;
             final int n = mPrimaryClipListeners.beginBroadcast();
             for (int i = 0; i < n; i++) {
@@ -57,8 +106,9 @@
         }
     }
     
-    public ClipData getPrimaryClip() {
+    public ClipData getPrimaryClip(String pkg) {
         synchronized (this) {
+            addActiveOwnerLocked(Binder.getCallingUid(), pkg);
             return mPrimaryClip;
         }
     }
@@ -96,4 +146,110 @@
             return false;
         }
     }
+
+    private final void checkUriOwnerLocked(Uri uri, int uid) {
+        if (!"content".equals(uri.getScheme())) {
+            return;
+        }
+        long ident = Binder.clearCallingIdentity();
+        boolean allowed = false;
+        try {
+            // This will throw SecurityException for us.
+            mAm.checkGrantUriPermission(uid, null, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void checkItemOwnerLocked(ClipData.Item item, int uid) {
+        if (item.getUri() != null) {
+            checkUriOwnerLocked(item.getUri(), uid);
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            checkUriOwnerLocked(intent.getData(), uid);
+        }
+    }
+
+    private final void checkDataOwnerLocked(ClipData data, int uid) {
+        final int N = data.getItemCount();
+        for (int i=0; i<N; i++) {
+            checkItemOwnerLocked(data.getItem(i), uid);
+        }
+    }
+
+    private final void grantUriLocked(Uri uri, String pkg) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mAm.grantUriPermissionFromOwner(mPermissionOwner, Process.myUid(), pkg, uri,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void grantItemLocked(ClipData.Item item, String pkg) {
+        if (item.getUri() != null) {
+            grantUriLocked(item.getUri(), pkg);
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            grantUriLocked(intent.getData(), pkg);
+        }
+    }
+
+    private final void addActiveOwnerLocked(int uid, String pkg) {
+        PackageInfo pi;
+        try {
+            pi = mPm.getPackageInfo(pkg, 0);
+            if (pi.applicationInfo.uid != uid) {
+                throw new SecurityException("Calling uid " + uid
+                        + " does not own package " + pkg);
+            }
+        } catch (NameNotFoundException e) {
+            throw new IllegalArgumentException("Unknown package " + pkg, e);
+        }
+        if (!mActivePermissionOwners.contains(pkg)) {
+            final int N = mPrimaryClip.getItemCount();
+            for (int i=0; i<N; i++) {
+                grantItemLocked(mPrimaryClip.getItem(i), pkg);
+            }
+            mActivePermissionOwners.add(pkg);
+        }
+    }
+
+    private final void revokeUriLocked(Uri uri) {
+        long ident = Binder.clearCallingIdentity();
+        try {
+            mAm.revokeUriPermissionFromOwner(mPermissionOwner, uri,
+                    Intent.FLAG_GRANT_READ_URI_PERMISSION
+                    | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        } catch (RemoteException e) {
+        } finally {
+            Binder.restoreCallingIdentity(ident);
+        }
+    }
+
+    private final void revokeItemLocked(ClipData.Item item) {
+        if (item.getUri() != null) {
+            revokeUriLocked(item.getUri());
+        }
+        Intent intent = item.getIntent();
+        if (intent != null && intent.getData() != null) {
+            revokeUriLocked(intent.getData());
+        }
+    }
+
+    private final void clearActiveOwnersLocked() {
+        mActivePermissionOwners.clear();
+        if (mPrimaryClip == null) {
+            return;
+        }
+        final int N = mPrimaryClip.getItemCount();
+        for (int i=0; i<N; i++) {
+            revokeItemLocked(mPrimaryClip.getItem(i));
+        }
+    }
 }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 1eab7fc7..d008c90 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -4350,8 +4350,10 @@
             return -1;
         }
 
-        if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
-                "Checking grant " + targetPkg + " permission to " + uri);
+        if (targetPkg != null) {
+            if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                    "Checking grant " + targetPkg + " permission to " + uri);
+        }
         
         final IPackageManager pm = AppGlobals.getPackageManager();
 
@@ -4380,23 +4382,45 @@
         }
 
         int targetUid;
-        try {
-            targetUid = pm.getPackageUid(targetPkg);
-            if (targetUid < 0) {
-                if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
-                        "Can't grant URI permission no uid for: " + targetPkg);
+        if (targetPkg != null) {
+            try {
+                targetUid = pm.getPackageUid(targetPkg);
+                if (targetUid < 0) {
+                    if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                            "Can't grant URI permission no uid for: " + targetPkg);
+                    return -1;
+                }
+            } catch (RemoteException ex) {
                 return -1;
             }
-        } catch (RemoteException ex) {
-            return -1;
+        } else {
+            targetUid = -1;
         }
 
-        // First...  does the target actually need this permission?
-        if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
-            // No need to grant the target this permission.
-            if (DEBUG_URI_PERMISSION) Slog.v(TAG, 
-                    "Target " + targetPkg + " already has full permission to " + uri);
-            return -1;
+        if (targetUid >= 0) {
+            // First...  does the target actually need this permission?
+            if (checkHoldingPermissionsLocked(pm, pi, uri, targetUid, modeFlags)) {
+                // No need to grant the target this permission.
+                if (DEBUG_URI_PERMISSION) Slog.v(TAG,
+                        "Target " + targetPkg + " already has full permission to " + uri);
+                return -1;
+            }
+        } else {
+            // First...  there is no target package, so can anyone access it?
+            boolean allowed = pi.exported;
+            if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) {
+                if (pi.readPermission != null) {
+                    allowed = false;
+                }
+            }
+            if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) {
+                if (pi.writePermission != null) {
+                    allowed = false;
+                }
+            }
+            if (allowed) {
+                return -1;
+            }
         }
 
         // Second...  is the provider allowing granting of URI permissions?
@@ -4426,16 +4450,25 @@
 
         // Third...  does the caller itself have permission to access
         // this uri?
-        if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
-            if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
-                throw new SecurityException("Uid " + callingUid
-                        + " does not have permission to uri " + uri);
+        if (callingUid != Process.myUid()) {
+            if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) {
+                if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) {
+                    throw new SecurityException("Uid " + callingUid
+                            + " does not have permission to uri " + uri);
+                }
             }
         }
 
         return targetUid;
     }
 
+    public int checkGrantUriPermission(int callingUid, String targetPkg,
+            Uri uri, int modeFlags) {
+        synchronized(this) {
+            return checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
+        }
+    }
+
     void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg,
             Uri uri, int modeFlags, UriPermissionOwner owner) {
         modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION
@@ -4478,6 +4511,10 @@
 
     void grantUriPermissionLocked(int callingUid,
             String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) {
+        if (targetPkg == null) {
+            throw new NullPointerException("targetPkg");
+        }
+
         int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, modeFlags);
         if (targetUid < 0) {
             return;
@@ -4496,6 +4533,10 @@
                 + " from " + intent + "; flags=0x"
                 + Integer.toHexString(intent != null ? intent.getFlags() : 0));
 
+        if (targetPkg == null) {
+            throw new NullPointerException("targetPkg");
+        }
+
         if (intent == null) {
             return -1;
         }
diff --git a/services/java/com/android/server/am/UriPermissionOwner.java b/services/java/com/android/server/am/UriPermissionOwner.java
index 99c82e6..68a2e0f 100644
--- a/services/java/com/android/server/am/UriPermissionOwner.java
+++ b/services/java/com/android/server/am/UriPermissionOwner.java
@@ -45,7 +45,7 @@
     }
 
     Binder getExternalTokenLocked() {
-        if (externalToken != null) {
+        if (externalToken == null) {
             externalToken = new ExternalToken();
         }
         return externalToken;