Tests for persistable Uri permission grants.

Verify that persistable permissions can be taken, enumerated, and
relinquished.  Verify both sides of taken grants can enumerate them.

Verify that touching grant updates timestamp.  Clean up launching
of GrantUriPermission receiver.

Bug: 11080911
Change-Id: I0a5779ad97136444d1b3e5eec70c746ae37deb2e
diff --git a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
index 31a3f47..46e1dff 100644
--- a/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
+++ b/hostsidetests/appsecurity/test-apps/PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
@@ -19,27 +19,82 @@
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.UriPermission;
+import android.net.Uri;
 import android.util.Log;
 
+import java.util.List;
+
 public class GrantUriPermission extends BroadcastReceiver {
+    public static final String ACTION_GRANT_URI = "grantUri";
+    public static final String ACTION_REVOKE_URI = "revokeUri";
+    public static final String ACTION_START_ACTIVITY = "startActivity";
+    public static final String ACTION_START_SERVICE = "startService";
+    public static final String ACTION_VERIFY_OUTGOING_PERSISTED = "verifyOutgoingPersisted";
+
+    public static final String EXTRA_PACKAGE_NAME = "packageName";
+    public static final String EXTRA_INTENT = Intent.EXTRA_INTENT;
+    public static final String EXTRA_URI = "uri";
+    public static final String EXTRA_MODE = "mode";
+
+    public static final int SUCCESS = 101;
+    public static final int FAILURE = 100;
+
     @Override
     public void onReceive(Context context, Intent intent) {
-        Intent newIntent = (Intent)intent.getParcelableExtra("intent");
-        boolean service = intent.getBooleanExtra("service", false);
         try {
-            if (!service) {
+            final String action = intent.getAction();
+            if (ACTION_GRANT_URI.equals(action)) {
+                final Uri uri = intent.getParcelableExtra(EXTRA_URI);
+                context.grantUriPermission(intent.getStringExtra(EXTRA_PACKAGE_NAME), uri,
+                        intent.getIntExtra(EXTRA_MODE, 0));
+
+            } else if (ACTION_REVOKE_URI.equals(action)) {
+                final Uri uri = intent.getParcelableExtra(EXTRA_URI);
+                context.revokeUriPermission(uri, intent.getIntExtra(EXTRA_MODE, 0));
+
+            } else if (ACTION_START_ACTIVITY.equals(action)) {
+                final Intent newIntent = intent.getParcelableExtra(EXTRA_INTENT);
                 newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                 context.startActivity(newIntent);
-            } else {
+
+            } else if (ACTION_START_SERVICE.equals(action)) {
+                final Intent newIntent = intent.getParcelableExtra(EXTRA_INTENT);
                 context.startService(newIntent);
+
+            } else if (ACTION_VERIFY_OUTGOING_PERSISTED.equals(action)) {
+                verifyOutgoingPersisted(context, intent);
             }
+
             if (isOrderedBroadcast()) {
-                setResultCode(101);
+                setResultCode(SUCCESS);
             }
         } catch (SecurityException e) {
             Log.i("GrantUriPermission", "Security exception", e);
             if (isOrderedBroadcast()) {
-                setResultCode(100);
+                setResultCode(FAILURE);
+            }
+        }
+    }
+
+    private void verifyOutgoingPersisted(Context context, Intent intent) {
+        final Uri uri = intent.getParcelableExtra(EXTRA_URI);
+        final List<UriPermission> perms = context.getContentResolver()
+                .getOutgoingPersistedUriPermissions();
+        if (uri != null) {
+            // Should have a single persisted perm
+            if (perms.size() != 1) {
+                throw new SecurityException("Missing grant");
+            }
+            final UriPermission perm = perms.get(0);
+            if (!perm.getUri().equals(uri)) {
+                throw new SecurityException(
+                        "Expected " + uri + " but found " + perm.getUri());
+            }
+        } else {
+            // Should have zero persisted perms
+            if (perms.size() != 0) {
+                throw new SecurityException("Unexpected grant");
             }
         }
     }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
index acf9f6f..d836042 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/Android.mk
@@ -18,7 +18,8 @@
 
 LOCAL_MODULE_TAGS := tests
 
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_SRC_FILES := $(call all-java-files-under, src) \
+    ../PermissionDeclareApp/src/com/android/cts/permissiondeclareapp/GrantUriPermission.java
 
 LOCAL_SDK_VERSION := current
 
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
index 8f40cf1..db26eec 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
@@ -23,13 +23,17 @@
 import android.content.ContentValues;
 import android.content.Context;
 import android.content.Intent;
+import android.content.UriPermission;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.SystemClock;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
+import com.android.cts.permissiondeclareapp.GrantUriPermission;
+
 import java.io.IOException;
+import java.util.List;
 
 /**
  * Tests that signature-enforced permissions cannot be accessed by apps signed
@@ -54,6 +58,14 @@
     private static final String EXPECTED_MIME_TYPE_AMBIGUOUS = "got/theUnspecifiedMIME";
     private static final Uri AMBIGUOUS_URI = Uri.parse("content://ctsambiguousprovider");
 
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+
+        // Always dispose, usually to clean up from failed tests
+        ReceiveUriActivity.finishCurInstanceSync();
+    }
+
     private void assertReadingContentUriNotAllowed(Uri uri, String msg) {
         try {
             getContext().getContentResolver().query(uri, null, null, null, null);
@@ -423,11 +435,11 @@
             synchronized (this) {
                 mHaveResult = true;
                 switch (getResultCode()) {
-                    case 100:
+                    case GrantUriPermission.FAILURE:
                         mGoodResult = true;
                         mSucceeded = false;
                         break;
-                    case 101:
+                    case GrantUriPermission.SUCCESS:
                         mGoodResult = true;
                         mSucceeded = true;
                         break;
@@ -491,8 +503,9 @@
                 service ? ReceiveUriService.class : ReceiveUriActivity.class);
         Intent intent = new Intent();
         intent.setComponent(GRANT_URI_PERM_COMP);
-        intent.putExtra("intent", grantIntent);
-        intent.putExtra("service", service);
+        intent.setAction(service ? GrantUriPermission.ACTION_START_SERVICE
+                : GrantUriPermission.ACTION_START_ACTIVITY);
+        intent.putExtra(GrantUriPermission.EXTRA_INTENT, grantIntent);
         GrantResultReceiver receiver = new GrantResultReceiver();
         getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
         receiver.assertFailure("Able to grant URI permission to " + grantDataUri + " when should not");
@@ -502,8 +515,9 @@
                 service ? ReceiveUriService.class : ReceiveUriActivity.class);
         intent = new Intent();
         intent.setComponent(GRANT_URI_PERM_COMP);
-        intent.putExtra("intent", grantIntent);
-        intent.putExtra("service", service);
+        intent.setAction(service ? GrantUriPermission.ACTION_START_SERVICE
+                : GrantUriPermission.ACTION_START_ACTIVITY);
+        intent.putExtra(GrantUriPermission.EXTRA_INTENT, grantIntent);
         receiver = new GrantResultReceiver();
         getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
         receiver.assertFailure("Able to grant URI permission to " + grantIntent.getClipData()
@@ -578,8 +592,9 @@
                 service ? ReceiveUriService.class : ReceiveUriActivity.class);
         Intent intent = new Intent();
         intent.setComponent(GRANT_URI_PERM_COMP);
-        intent.putExtra("intent", grantIntent);
-        intent.putExtra("service", service);
+        intent.setAction(service ? GrantUriPermission.ACTION_START_SERVICE
+                : GrantUriPermission.ACTION_START_ACTIVITY);
+        intent.putExtra(GrantUriPermission.EXTRA_INTENT, grantIntent);
         getContext().sendBroadcast(intent);
     }
 
@@ -843,7 +858,7 @@
         doTestGrantActivityUriWritePermission(PRIV_URI_GRANTING, false);
     }
 
-    void doTestGrantServiceUriReadPermission(Uri uri, boolean useClip) {
+    private void doTestGrantServiceUriReadPermission(Uri uri, boolean useClip) {
         final Uri subUri = Uri.withAppendedPath(uri, "foo");
         final Uri subSubUri = Uri.withAppendedPath(subUri, "bar");
         final Uri sub2Uri = Uri.withAppendedPath(uri, "yes");
@@ -1207,4 +1222,137 @@
         assertEquals(EXPECTED_MIME_TYPE_AMBIGUOUS,
                 getContext().getContentResolver().getType(AMBIGUOUS_URI_COMPAT));
     }
+
+    /**
+     * Validate behavior of persistable permission grants.
+     */
+    public void testGrantPersistableUriPermission() {
+        final ContentResolver resolver = getContext().getContentResolver();
+
+        final Uri target = Uri.withAppendedPath(PERM_URI_GRANTING, "foo");
+        final ClipData clip = makeSingleClipData(target);
+
+        // Make sure we can't see the target
+        assertReadingClipNotAllowed(clip, "reading should have failed");
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+
+        // Make sure we can't take a grant we don't have
+        try {
+            resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            fail("taking read should have failed");
+        } catch (SecurityException expected) {
+        }
+
+        // And since we were just installed, no persisted grants yet
+        assertNoPersistedUriPermission();
+
+        // Now, let's grant ourselves some access
+        ReceiveUriActivity.clearStarted();
+        grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
+        ReceiveUriActivity.waitForStart();
+
+        // We should now have reading access, even before taking the persistable
+        // grant. Persisted grants should still be empty.
+        assertReadingClipAllowed(clip);
+        assertWritingClipNotAllowed(clip, "writing should have failed");
+        assertNoPersistedUriPermission();
+
+        // Take the read grant and verify we have it!
+        long before = System.currentTimeMillis();
+        resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        long after = System.currentTimeMillis();
+        assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+        // Make sure we can't take a grant we don't have
+        try {
+            resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+            fail("taking write should have failed");
+        } catch (SecurityException expected) {
+        }
+
+        // Launch again giving ourselves persistable read and write access
+        ReceiveUriActivity.clearNewIntent();
+        grantClipUriPermission(clip, Intent.FLAG_GRANT_READ_URI_PERMISSION
+                | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION, false);
+        ReceiveUriActivity.waitForNewIntent();
+
+        // Previous persisted grant should be unchanged
+        assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+        // We should have both read and write; read is persisted, and write
+        // isn't persisted yet.
+        assertReadingClipAllowed(clip);
+        assertWritingClipAllowed(clip);
+
+        // Take again, but still only read; should just update timestamp
+        before = System.currentTimeMillis();
+        resolver.takePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        after = System.currentTimeMillis();
+        assertPersistedUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION, before, after);
+
+        // And take yet again, both read and write
+        before = System.currentTimeMillis();
+        resolver.takePersistableUriPermission(target,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        after = System.currentTimeMillis();
+        assertPersistedUriPermission(target,
+                Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                before, after);
+
+        // Now drop the persisted grant; write first, then read
+        resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_READ_URI_PERMISSION);
+        assertPersistedUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION, before, after);
+        resolver.releasePersistableUriPermission(target, Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+        assertNoPersistedUriPermission();
+
+        // And even though we dropped the persistable grants, our activity is
+        // still running with the global grants (until reboot).
+        assertReadingClipAllowed(clip);
+        assertWritingClipAllowed(clip);
+
+        ReceiveUriActivity.finishCurInstanceSync();
+    }
+
+    private void assertNoPersistedUriPermission() {
+        assertPersistedUriPermission(null, 0, -1, -1);
+    }
+
+    private void assertPersistedUriPermission(Uri uri, int flags, long before, long after) {
+        // Assert local
+        final List<UriPermission> perms = getContext()
+                .getContentResolver().getPersistedUriPermissions();
+        if (uri != null) {
+            assertEquals("expected exactly one permission", 1, perms.size());
+
+            final UriPermission perm = perms.get(0);
+            assertEquals("unexpected uri", uri, perm.getUri());
+
+            final long actual = perm.getPersistedTime();
+            if (before != -1) {
+                assertTrue("found " + actual + " before " + before, actual >= before);
+            }
+            if (after != -1) {
+                assertTrue("found " + actual + " after " + after, actual <= after);
+            }
+
+            final boolean expectedRead = (flags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0;
+            final boolean expectedWrite = (flags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0;
+            assertEquals("unexpected read status", expectedRead, perm.isReadPermission());
+            assertEquals("unexpected write status", expectedWrite, perm.isWritePermission());
+
+        } else {
+            assertEquals("expected zero permissions", 0, perms.size());
+        }
+
+        // And assert remote
+        Intent intent = new Intent();
+        intent.setComponent(GRANT_URI_PERM_COMP);
+        intent.setAction(GrantUriPermission.ACTION_VERIFY_OUTGOING_PERSISTED);
+        intent.putExtra(GrantUriPermission.EXTRA_URI, uri);
+        GrantResultReceiver receiver = new GrantResultReceiver();
+        getContext().sendOrderedBroadcast(intent, null, receiver, null, 0, null, null);
+        receiver.assertSuccess("unexpected outgoing persisted Uri status");
+    }
 }
diff --git a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriActivity.java b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriActivity.java
index 851d8da..dd9a5c5 100644
--- a/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriActivity.java
+++ b/hostsidetests/appsecurity/test-apps/UsePermissionDiffCert/src/com/android/cts/usespermissiondiffcertapp/ReceiveUriActivity.java
@@ -32,7 +32,7 @@
     private static final Object sLock = new Object();
     private static boolean sStarted;
     private static boolean sNewIntent;
-    private static boolean sDestroyed;
+    private static boolean sDestroyed = true;
     private static ReceiveUriActivity sCurInstance;
 
     private static final long TIMEOUT_MILLIS = 30 * SECOND_IN_MILLIS;