Don't retain global ref to last inflated context.

Also rework URI permission granting to support upcoming
clipboard use.

Change-Id: I9842920350955531c5a511c2ecc5215e8c783343
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index 9a55a6f..1d1b4dc 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -1248,8 +1248,9 @@
         
         case IS_USER_A_MONKEY_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
-            reply.writeInt(isUserAMonkey() ? 1 : 0);
+            boolean areThey = isUserAMonkey();
             reply.writeNoException();
+            reply.writeInt(areThey ? 1 : 0);
             return true;
         }
         
@@ -1263,8 +1264,9 @@
         case IS_IMMERSIVE_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             IBinder token = data.readStrongBinder();
-            reply.writeInt(isImmersive(token) ? 1 : 0);
+            boolean isit = isImmersive(token);
             reply.writeNoException();
+            reply.writeInt(isit ? 1 : 0);
             return true;
         }
 
@@ -1279,8 +1281,9 @@
         
         case IS_TOP_ACTIVITY_IMMERSIVE_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
-            reply.writeInt(isTopActivityImmersive() ? 1 : 0);
+            boolean isit = isTopActivityImmersive();
             reply.writeNoException();
+            reply.writeInt(isit ? 1 : 0);
             return true;
         }
 
@@ -1295,6 +1298,40 @@
             return true;
         }
 
+        case NEW_URI_PERMISSION_OWNER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            String name = data.readString();
+            IBinder perm = newUriPermissionOwner(name);
+            reply.writeNoException();
+            reply.writeStrongBinder(perm);
+            return true;
+        }
+
+        case GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder owner = data.readStrongBinder();
+            int fromUid = data.readInt();
+            String targetPkg = data.readString();
+            Uri uri = Uri.CREATOR.createFromParcel(data);
+            int mode = data.readInt();
+            grantUriPermissionFromOwner(owner, fromUid, targetPkg, uri, mode);
+            reply.writeNoException();
+            return true;
+        }
+
+        case REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            IBinder owner = data.readStrongBinder();
+            Uri uri = null;
+            if (data.readInt() != 0) {
+                Uri.CREATOR.createFromParcel(data);
+            }
+            int mode = data.readInt();
+            revokeUriPermissionFromOwner(owner, uri, mode);
+            reply.writeNoException();
+            return true;
+        }
+
         }
         
         return super.onTransact(code, data, reply, flags);
@@ -2841,8 +2878,8 @@
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeStrongBinder(token);
         mRemote.transact(IS_IMMERSIVE_TRANSACTION, data, reply, 0);
-        boolean res = reply.readInt() == 1;
         reply.readException();
+        boolean res = reply.readInt() == 1;
         data.recycle();
         reply.recycle();
         return res;
@@ -2854,8 +2891,8 @@
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         mRemote.transact(IS_TOP_ACTIVITY_IMMERSIVE_TRANSACTION, data, reply, 0);
-        boolean res = reply.readInt() == 1;
         reply.readException();
+        boolean res = reply.readInt() == 1;
         data.recycle();
         reply.recycle();
         return res;
@@ -2875,6 +2912,55 @@
         data.recycle();
         reply.recycle();
     }
+
+    public IBinder newUriPermissionOwner(String name)
+            throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeString(name);
+        mRemote.transact(NEW_URI_PERMISSION_OWNER_TRANSACTION, data, reply, 0);
+        reply.readException();
+        IBinder res = reply.readStrongBinder();
+        data.recycle();
+        reply.recycle();
+        return res;
+    }
+
+    public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg,
+            Uri uri, int mode) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(owner);
+        data.writeInt(fromUid);
+        data.writeString(targetPkg);
+        uri.writeToParcel(data, 0);
+        data.writeInt(mode);
+        mRemote.transact(GRANT_URI_PERMISSION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
+
+    public void revokeUriPermissionFromOwner(IBinder owner, Uri uri,
+            int mode) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeStrongBinder(owner);
+        if (uri != null) {
+            data.writeInt(1);
+            uri.writeToParcel(data, 0);
+        } else {
+            data.writeInt(0);
+        }
+        data.writeInt(mode);
+        mRemote.transact(REVOKE_URI_PERMISSION_TRANSACTION, data, reply, 0);
+        reply.readException();
+        data.recycle();
+        reply.recycle();
+    }
     
     private IBinder mRemote;
 }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 416f289..664cf18 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -318,6 +318,12 @@
     public void crashApplication(int uid, int initialPid, String packageName,
             String message) throws RemoteException;
     
+    public IBinder newUriPermissionOwner(String name) throws RemoteException;
+    public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg,
+            Uri uri, int mode) throws RemoteException;
+    public void revokeUriPermissionFromOwner(IBinder owner, Uri uri,
+            int mode) throws RemoteException;
+
     /*
      * Private non-Binder interfaces
      */
@@ -524,4 +530,7 @@
     int SET_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+111;
     int IS_TOP_ACTIVITY_IMMERSIVE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+112;
     int CRASH_APPLICATION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+113;
+    int NEW_URI_PERMISSION_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+114;
+    int GRANT_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+115;
+    int REVOKE_URI_PERMISSION_FROM_OWNER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+116;
 }
diff --git a/core/java/android/view/LayoutInflater.java b/core/java/android/view/LayoutInflater.java
index e5985c1..3228127 100644
--- a/core/java/android/view/LayoutInflater.java
+++ b/core/java/android/view/LayoutInflater.java
@@ -434,6 +434,10 @@
                 throw ex;
             }
 
+            // Told retain static reference on context.
+            mConstructorArgs[0] = null;
+            mConstructorArgs[1] = null;
+
             return result;
         }
     }
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index c316074..d535343 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -4294,12 +4294,10 @@
                         + " when granting permission to uri " + uri);
             }
             if (targetPkg == null) {
-                Slog.w(TAG, "grantUriPermission: null target");
-                return;
+                throw new IllegalArgumentException("null target");
             }
             if (uri == null) {
-                Slog.w(TAG, "grantUriPermission: null uri");
-                return;
+                throw new IllegalArgumentException("null uri");
             }
 
             grantUriPermissionLocked(r.info.uid, targetPkg, uri, modeFlags,
@@ -4451,6 +4449,56 @@
         }
     }
 
+    @Override
+    public IBinder newUriPermissionOwner(String name) {
+        synchronized(this) {
+            UriPermissionOwner owner = new UriPermissionOwner(this, name);
+            return owner.getExternalTokenLocked();
+        }
+    }
+
+    @Override
+    public void grantUriPermissionFromOwner(IBinder token, int fromUid, String targetPkg,
+            Uri uri, int modeFlags) {
+        synchronized(this) {
+            UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
+            if (owner == null) {
+                throw new IllegalArgumentException("Unknown owner: " + token);
+            }
+            if (fromUid != Binder.getCallingUid()) {
+                if (Binder.getCallingUid() != Process.myUid()) {
+                    // Only system code can grant URI permissions on behalf
+                    // of other users.
+                    throw new SecurityException("nice try");
+                }
+            }
+            if (targetPkg == null) {
+                throw new IllegalArgumentException("null target");
+            }
+            if (uri == null) {
+                throw new IllegalArgumentException("null uri");
+            }
+
+            grantUriPermissionLocked(fromUid, targetPkg, uri, modeFlags, owner);
+        }
+    }
+
+    @Override
+    public void revokeUriPermissionFromOwner(IBinder token, Uri uri, int mode) {
+        synchronized(this) {
+            UriPermissionOwner owner = UriPermissionOwner.fromExternalToken(token);
+            if (owner == null) {
+                throw new IllegalArgumentException("Unknown owner: " + token);
+            }
+
+            if (uri == null) {
+                owner.removeUriPermissionsLocked(mode);
+            } else {
+                owner.removeUriPermissionLocked(uri, mode);
+            }
+        }
+    }
+
     public void showWaitingForDebugger(IApplicationThread who, boolean waiting) {
         synchronized (this) {
             ProcessRecord app =
@@ -8260,7 +8308,7 @@
                 si.deliveryCount++;
                 if (si.targetPermissionUid >= 0) {
                     grantUriPermissionUncheckedFromIntentLocked(si.targetPermissionUid,
-                            r.packageName, si.intent, si);
+                            r.packageName, si.intent, si.getUriPermissionsLocked());
                 }
                 if (DEBUG_SERVICE) Slog.v(TAG, ">>> EXECUTING start of " + r);
                 bumpServiceExecutingLocked(r);
diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java
index 80a41b7..62be918 100644
--- a/services/java/com/android/server/am/ActivityRecord.java
+++ b/services/java/com/android/server/am/ActivityRecord.java
@@ -44,7 +44,7 @@
 /**
  * An entry in the history stack, representing an activity.
  */
-class ActivityRecord extends IApplicationToken.Stub implements UriPermissionOwner {
+class ActivityRecord extends IApplicationToken.Stub {
     final ActivityManagerService service; // owner
     final ActivityStack stack; // owner
     final ActivityInfo info; // all about me
@@ -78,8 +78,7 @@
     HashSet<WeakReference<PendingIntentRecord>> pendingResults; // all pending intents for this act
     ArrayList newIntents;   // any pending new intents for single-top mode
     HashSet<ConnectionRecord> connections; // All ConnectionRecord we hold
-    HashSet<UriPermission> readUriPermissions; // special access to reading uris.
-    HashSet<UriPermission> writeUriPermissions; // special access to writing uris.
+    UriPermissionOwner uriPermissions; // current special URI access perms.
     ProcessRecord app;  // if non-null, hosting application
     Bitmap thumbnail;       // icon representation of paused screen
     CharSequence description; // textual description of paused screen
@@ -141,11 +140,15 @@
         if (pendingResults != null) {
             pw.print(prefix); pw.print("pendingResults="); pw.println(pendingResults);
         }
-        if (readUriPermissions != null) {
-            pw.print(prefix); pw.print("readUriPermissions="); pw.println(readUriPermissions);
-        }
-        if (writeUriPermissions != null) {
-            pw.print(prefix); pw.print("writeUriPermissions="); pw.println(writeUriPermissions);
+        if (uriPermissions != null) {
+            if (uriPermissions.readUriPermissions != null) {
+                pw.print(prefix); pw.print("readUriPermissions=");
+                        pw.println(uriPermissions.readUriPermissions);
+            }
+            if (uriPermissions.writeUriPermissions != null) {
+                pw.print(prefix); pw.print("writeUriPermissions=");
+                        pw.println(uriPermissions.writeUriPermissions);
+            }
         }
         pw.print(prefix); pw.print("launchFailed="); pw.print(launchFailed);
                 pw.print(" haveState="); pw.print(haveState);
@@ -301,6 +304,13 @@
         }
     }
 
+    UriPermissionOwner getUriPermissionsLocked() {
+        if (uriPermissions == null) {
+            uriPermissions = new UriPermissionOwner(service, this);
+        }
+        return uriPermissions;
+    }
+
     void addResultLocked(ActivityRecord from, String resultWho,
             int requestCode, int resultCode,
             Intent resultData) {
@@ -350,7 +360,7 @@
                 intent = new Intent(intent);
                 ar.add(intent);
                 service.grantUriPermissionFromIntentLocked(callingUid, packageName,
-                        intent, this);
+                        intent, getUriPermissionsLocked());
                 app.thread.scheduleNewIntent(ar, this);
                 sent = true;
             } catch (RemoteException e) {
@@ -367,27 +377,9 @@
     }
 
     void removeUriPermissionsLocked() {
-        if (readUriPermissions != null) {
-            for (UriPermission perm : readUriPermissions) {
-                perm.readOwners.remove(this);
-                if (perm.readOwners.size() == 0 && (perm.globalModeFlags
-                        &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) {
-                    perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
-                    service.removeUriPermissionIfNeededLocked(perm);
-                }
-            }
-            readUriPermissions = null;
-        }
-        if (writeUriPermissions != null) {
-            for (UriPermission perm : writeUriPermissions) {
-                perm.writeOwners.remove(this);
-                if (perm.writeOwners.size() == 0 && (perm.globalModeFlags
-                        &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) {
-                    perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
-                    service.removeUriPermissionIfNeededLocked(perm);
-                }
-            }
-            writeUriPermissions = null;
+        if (uriPermissions != null) {
+            uriPermissions.removeUriPermissionsLocked();
+            uriPermissions = null;
         }
     }
 
@@ -578,38 +570,6 @@
                 state == ActivityState.RESUMED;
      }
     
-    @Override
-    public void addReadPermission(UriPermission perm) {
-        if (readUriPermissions == null) {
-            readUriPermissions = new HashSet<UriPermission>();
-        }
-        readUriPermissions.add(perm);
-    }
-
-    @Override
-    public void addWritePermission(UriPermission perm) {
-        if (writeUriPermissions == null) {
-            writeUriPermissions = new HashSet<UriPermission>();
-        }
-        writeUriPermissions.add(perm);
-    }
-
-    @Override
-    public void removeReadPermission(UriPermission perm) {
-        readUriPermissions.remove(perm);
-        if (readUriPermissions.size() == 0) {
-            readUriPermissions = null;
-        }
-    }
-
-    @Override
-    public void removeWritePermission(UriPermission perm) {
-        writeUriPermissions.remove(perm);
-        if (writeUriPermissions.size() == 0) {
-            writeUriPermissions = null;
-        }
-    }
-    
     public String toString() {
         if (stringName != null) {
             return stringName;
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index a5f7e96..a99b48c 100644
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -2327,12 +2327,12 @@
         if (grantedUriPermissions != null && callingUid > 0) {
             for (int i=0; i<grantedUriPermissions.length; i++) {
                 mService.grantUriPermissionLocked(callingUid, r.packageName,
-                        grantedUriPermissions[i], grantedMode, r);
+                        grantedUriPermissions[i], grantedMode, r.getUriPermissionsLocked());
             }
         }
 
         mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
-                intent, r);
+                intent, r.getUriPermissionsLocked());
 
         if (newTask) {
             EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.task.taskId);
@@ -2557,7 +2557,7 @@
 
         if (callingUid > 0) {
             mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName,
-                    data, r);
+                    data, r.getUriPermissionsLocked());
         }
 
         if (DEBUG_RESULTS) Slog.v(TAG, "Send activity result to " + r
@@ -2885,7 +2885,7 @@
                     + " res=" + resultCode + " data=" + resultData);
             if (r.info.applicationInfo.uid > 0) {
                 mService.grantUriPermissionFromIntentLocked(r.info.applicationInfo.uid,
-                        r.packageName, resultData, r);
+                        r.packageName, resultData, r.getUriPermissionsLocked());
             }
             resultTo.addResultLocked(r, r.resultWho, r.requestCode, resultCode,
                                      resultData);
diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java
index d5b050b..f35a68e 100644
--- a/services/java/com/android/server/am/ServiceRecord.java
+++ b/services/java/com/android/server/am/ServiceRecord.java
@@ -96,7 +96,7 @@
 
     String stringName;      // caching of toString
     
-    static class StartItem implements UriPermissionOwner {
+    static class StartItem {
         final ServiceRecord sr;
         final int id;
         final Intent intent;
@@ -104,12 +104,10 @@
         long deliveredTime;
         int deliveryCount;
         int doneExecutingCount;
+        UriPermissionOwner uriPermissions;
 
         String stringName;      // caching of toString
 
-        HashSet<UriPermission> readUriPermissions; // special access to reading uris.
-        HashSet<UriPermission> writeUriPermissions; // special access to writing uris.
-
         StartItem(ServiceRecord _sr, int _id, Intent _intent, int _targetPermissionUid) {
             sr = _sr;
             id = _id;
@@ -117,60 +115,17 @@
             targetPermissionUid = _targetPermissionUid;
         }
 
+        UriPermissionOwner getUriPermissionsLocked() {
+            if (uriPermissions == null) {
+                uriPermissions = new UriPermissionOwner(sr.ams, this);
+            }
+            return uriPermissions;
+        }
+
         void removeUriPermissionsLocked() {
-            if (readUriPermissions != null) {
-                for (UriPermission perm : readUriPermissions) {
-                    perm.readOwners.remove(this);
-                    if (perm.readOwners.size() == 0 && (perm.globalModeFlags
-                            &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) {
-                        perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
-                        sr.ams.removeUriPermissionIfNeededLocked(perm);
-                    }
-                }
-                readUriPermissions = null;
-            }
-            if (writeUriPermissions != null) {
-                for (UriPermission perm : writeUriPermissions) {
-                    perm.writeOwners.remove(this);
-                    if (perm.writeOwners.size() == 0 && (perm.globalModeFlags
-                            &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) {
-                        perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
-                        sr.ams.removeUriPermissionIfNeededLocked(perm);
-                    }
-                }
-                writeUriPermissions = null;
-            }
-        }
-
-        @Override
-        public void addReadPermission(UriPermission perm) {
-            if (readUriPermissions == null) {
-                readUriPermissions = new HashSet<UriPermission>();
-            }
-            readUriPermissions.add(perm);
-        }
-
-        @Override
-        public void addWritePermission(UriPermission perm) {
-            if (writeUriPermissions == null) {
-                writeUriPermissions = new HashSet<UriPermission>();
-            }
-            writeUriPermissions.add(perm);
-        }
-
-        @Override
-        public void removeReadPermission(UriPermission perm) {
-            readUriPermissions.remove(perm);
-            if (readUriPermissions.size() == 0) {
-                readUriPermissions = null;
-            }
-        }
-
-        @Override
-        public void removeWritePermission(UriPermission perm) {
-            writeUriPermissions.remove(perm);
-            if (writeUriPermissions.size() == 0) {
-                writeUriPermissions = null;
+            if (uriPermissions != null) {
+                uriPermissions.removeUriPermissionsLocked();
+                uriPermissions = null;
             }
         }
 
@@ -218,13 +173,15 @@
                 pw.print(prefix); pw.print("  targetPermissionUid=");
                         pw.println(si.targetPermissionUid);
             }
-            if (si.readUriPermissions != null) {
-                pw.print(prefix); pw.print("  readUriPermissions=");
-                        pw.println(si.readUriPermissions);
-            }
-            if (si.writeUriPermissions != null) {
-                pw.print(prefix); pw.print("  writeUriPermissions=");
-                        pw.println(si.writeUriPermissions);
+            if (si.uriPermissions != null) {
+                if (si.uriPermissions.readUriPermissions != null) {
+                    pw.print(prefix); pw.print("  readUriPermissions=");
+                            pw.println(si.uriPermissions.readUriPermissions);
+                }
+                if (si.uriPermissions.writeUriPermissions != null) {
+                    pw.print(prefix); pw.print("  writeUriPermissions=");
+                            pw.println(si.uriPermissions.writeUriPermissions);
+                }
             }
         }
     }
diff --git a/services/java/com/android/server/am/UriPermission.java b/services/java/com/android/server/am/UriPermission.java
index 93c59cc..c95546e 100644
--- a/services/java/com/android/server/am/UriPermission.java
+++ b/services/java/com/android/server/am/UriPermission.java
@@ -22,13 +22,14 @@
 import java.io.PrintWriter;
 import java.util.HashSet;
 
-interface UriPermissionOwner {
-    void addReadPermission(UriPermission perm);
-    void addWritePermission(UriPermission perm);
-    void removeReadPermission(UriPermission perm);
-    void removeWritePermission(UriPermission perm);
-}
-
+/**
+ * Description of a permission granted to an app to access a particular URI.
+ *
+ * CTS tests for this functionality can be run with "runtest cts-appsecurity".
+ *
+ * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert
+ *      /src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+ */
 class UriPermission {
     final int uid;
     final Uri uri;
diff --git a/services/java/com/android/server/am/UriPermissionOwner.java b/services/java/com/android/server/am/UriPermissionOwner.java
new file mode 100644
index 0000000..99c82e6
--- /dev/null
+++ b/services/java/com/android/server/am/UriPermissionOwner.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright (C) 2010 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.android.server.am;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+
+import java.util.HashSet;
+import java.util.Iterator;
+
+class UriPermissionOwner {
+    final ActivityManagerService service;
+    final Object owner;
+
+    Binder externalToken;
+
+    HashSet<UriPermission> readUriPermissions; // special access to reading uris.
+    HashSet<UriPermission> writeUriPermissions; // special access to writing uris.
+
+    class ExternalToken extends Binder {
+        UriPermissionOwner getOwner() {
+            return UriPermissionOwner.this;
+        }
+    }
+
+    UriPermissionOwner(ActivityManagerService _service, Object _owner) {
+        service = _service;
+        owner = _owner;
+    }
+
+    Binder getExternalTokenLocked() {
+        if (externalToken != null) {
+            externalToken = new ExternalToken();
+        }
+        return externalToken;
+    }
+
+    static UriPermissionOwner fromExternalToken(IBinder token) {
+        if (token instanceof ExternalToken) {
+            return ((ExternalToken)token).getOwner();
+        }
+        return null;
+    }
+
+    void removeUriPermissionsLocked() {
+        removeUriPermissionsLocked(Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+    }
+
+    void removeUriPermissionsLocked(int mode) {
+        if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
+                && readUriPermissions != null) {
+            for (UriPermission perm : readUriPermissions) {
+                perm.readOwners.remove(this);
+                if (perm.readOwners.size() == 0 && (perm.globalModeFlags
+                        &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) {
+                    perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
+                    service.removeUriPermissionIfNeededLocked(perm);
+                }
+            }
+            readUriPermissions = null;
+        }
+        if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
+                && writeUriPermissions != null) {
+            for (UriPermission perm : writeUriPermissions) {
+                perm.writeOwners.remove(this);
+                if (perm.writeOwners.size() == 0 && (perm.globalModeFlags
+                        &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) {
+                    perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+                    service.removeUriPermissionIfNeededLocked(perm);
+                }
+            }
+            writeUriPermissions = null;
+        }
+    }
+
+    void removeUriPermissionLocked(Uri uri, int mode) {
+        if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0
+                && readUriPermissions != null) {
+            Iterator<UriPermission> it = readUriPermissions.iterator();
+            while (it.hasNext()) {
+                UriPermission perm = it.next();
+                if (uri.equals(perm.uri)) {
+                    perm.readOwners.remove(this);
+                    if (perm.readOwners.size() == 0 && (perm.globalModeFlags
+                            &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) {
+                        perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION;
+                        service.removeUriPermissionIfNeededLocked(perm);
+                    }
+                    it.remove();
+                }
+            }
+            if (readUriPermissions.size() == 0) {
+                readUriPermissions = null;
+            }
+        }
+        if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0
+                && writeUriPermissions != null) {
+            Iterator<UriPermission> it = writeUriPermissions.iterator();
+            while (it.hasNext()) {
+                UriPermission perm = it.next();
+                if (uri.equals(perm.uri)) {
+                    perm.writeOwners.remove(this);
+                    if (perm.writeOwners.size() == 0 && (perm.globalModeFlags
+                            &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) {
+                        perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
+                        service.removeUriPermissionIfNeededLocked(perm);
+                    }
+                    it.remove();
+                }
+            }
+            if (writeUriPermissions.size() == 0) {
+                writeUriPermissions = null;
+            }
+        }
+    }
+
+    public void addReadPermission(UriPermission perm) {
+        if (readUriPermissions == null) {
+            readUriPermissions = new HashSet<UriPermission>();
+        }
+        readUriPermissions.add(perm);
+    }
+
+    public void addWritePermission(UriPermission perm) {
+        if (writeUriPermissions == null) {
+            writeUriPermissions = new HashSet<UriPermission>();
+        }
+        writeUriPermissions.add(perm);
+    }
+
+    public void removeReadPermission(UriPermission perm) {
+        readUriPermissions.remove(perm);
+        if (readUriPermissions.size() == 0) {
+            readUriPermissions = null;
+        }
+    }
+
+    public void removeWritePermission(UriPermission perm) {
+        writeUriPermissions.remove(perm);
+        if (writeUriPermissions.size() == 0) {
+            writeUriPermissions = null;
+        }
+    }
+
+    @Override
+    public String toString() {
+        return owner.toString();
+    }
+}