How to grant temporary access to custom content provider using FLAG_GRANT_READ_URI_PERMISSION
It seems that FLAG_GRANT_READ_URI_PERMISSION
affects only Uri Intent.mData
but not uris in extras.
I found similar problems when playing with ACTION_SEND
, which takes an uri in EXTRA_STREAM
. Providing the same uri in setData()
will work, but doesn't conform to the rules and leads to unexpected behavior (e.g. Gmail recipient).
As of Jelly Bean intents can contain ClipData, which should solve the problem. For ACTION_SEND
it's generated automatically from extras.
grantUriPermission
will work, but requires revokeUriPermission
. To do the same job as startActivity(Intent.createChooser(intent, title))
you will have to pick the target (ACTION_PICK_ACTIVITY
), grant permissions to its package and revoke them when not needed anymore (onActivityResult?).
Here's some code:
Receiver app:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.handler" >
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" >
…
<activity
android:name=".ActivityView"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="android.intent.action.SEND"/>
<category android:name="android.intent.category.DEFAULT"/>
<data android:mimeType="*/*"/>
</intent-filter>
</activity>
</application>
</manifest>
ActivityView.java:
protected void onCreate(Bundle savedInstanceState) {
Intent intent = getIntent();
if ((intent != null)) {
procUri(intent.getData());
procUri(intent.<Uri>getParcelableExtra(Intent.EXTRA_STREAM));
}
}
private void procUri(Uri uri) {
if (uri != null) {
InputStream i;
try {
i = getContentResolver().openInputStream(uri);
byte[] b = new byte[i.available()];
i.read(b);
…
} catch (Throwable e) {
…
}
}
}
Sender app:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.sender" >
<application android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" >
…
<activity
android:name=".ActivitySend"
android:label="@string/app_name" >
</activity>
<provider
android:name=".MyContentProvider"
android:authorities="com.example.sender.content"
android:enabled="true"
android:exported="false"
android:grantUriPermissions="true" >
</provider>
</application>
</manifest>
ActivitySend.java:
// OK!
private void doTestView(Uri uri, String type) {
startActivity(
new Intent(Intent.ACTION_VIEW)
.setDataAndType(uri, type)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
);
}
// error prior to JellyBean
// ok on JellyBean, even without explicit FLAG_GRANT_READ_URI_PERMISSION (due to generated ClipData)
private void doTestSend(Uri uri, String type, CharSequence title) {
startActivity(
Intent.createChooser(
new Intent(Intent.ACTION_SEND)
.setType(type)
.putExtra(Intent.EXTRA_STREAM, uri)
.putExtra(Intent.EXTRA_SUBJECT, title)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
,
title
)
);
}
// working but not ok, unexpected results!
private void doTestSend2(Uri uri, String type, CharSequence title) {
startActivity(
Intent.createChooser(
new Intent(Intent.ACTION_SEND)
.setDataAndType(uri, type)
.putExtra(Intent.EXTRA_STREAM, uri)
.putExtra(Intent.EXTRA_SUBJECT, title)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
,
title
)
);
}
Jin
Updated on July 09, 2022Comments
-
Jin almost 2 years
I am trying to query custom content provider(App A) from another app(App B).
I can do that when there is no permission protection for content provider. Specifically, I build custom content provider on App A and sent an intent containing the URI to App B. Here is intent-sending part in App A.
class InsertOnClickListener implements OnClickListener{ public void onClick(View v) { ContentValues values = new ContentValues(); values.put(DataBaseConfiguation.TableConfiguation.USER_NAME, "Jack"); Uri uri = getContentResolver().insert(DataBaseConfiguation.TableConfiguation.CONTENT_URI, values); System.out.println("uri------------------->" + uri); // the uri above should be like "content://com.catking.contentprovider.MyContentProvider/user" Uri uri2 = Uri.parse("content://com.catking.contentprovider.MyContentProvider/user"); Cursor c = managedQuery(uri2, null, null, null, null); String sendvalue = null; if (c.moveToFirst()) { do{ System.out.println("User name:"+c.getString(c.getColumnIndex(DataBaseConfiguation.TableConfiguation.USER_NAME)).toString()); sendvalue = c.getString(c.getColumnIndex(DataBaseConfiguation.TableConfiguation.USER_NAME)).toString(); } while (c.moveToNext()); } Intent sendIntent = new Intent(); sendIntent.setClassName("com.android.web", "com.android.web.Provid"); sendIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); sendIntent.putExtra("name", uri2.toString()); sendIntent.setType("text/plain"); startActivity(sendIntent); } }
followed by manifest file of App A.
<application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name=".ContentProviderTestActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <provider android:authorities="com.catking.contentprovider.MyContentProvider" android:exported="true" android:grantUriPermissions="true" android:name="com.catking.contentprovider.MyContentProvider" android:readPermission="android.permission.permRead" android:writePermission="android.permission.permWrite" > </provider> </application>
Then App B(class Provid) get the URI and query the corresponding data in content provider(using following code).
public class Provid extends Activity { public void onCreate(Bundle savedInstanceState) { Bundle extras = getIntent().getExtras(); String userNameuri; if (extras != null) { userNameuri = extras.getString("name"); Uri allTitles = Uri.parse(userNameuri); Cursor c = managedQuery(allTitles, null, null, null, null); if (c.moveToFirst()) { do{ System.out.println("Name is"+c.getString(c.getColumnIndex(DataBaseConfiguation.TableConfiguation.USER_NAME)).toString()); } while (c.moveToNext()); } } }
}
Here's App B's manifest file.
<uses-permission android:name="android.permission.INTERNET" /> <application android:icon="@drawable/ic_launcher" android:label="@string/app_name" > <activity android:name="._GetWebResoureActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> <intent-filter> <action android:name="android.intent.action.SEND" > </action> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="*/*" /> </intent-filter> </activity> <receiver android:name="StaticReceiver11" > <intent-filter> <action android:name="android.intent.action.MYSEND" /> <category android:name="android.intent.category.DEFAULT" /> </intent-filter> </receiver> <activity android:name="Provid" android:label="@string/title_activity_provid" > <intent-filter> <action android:name="android.intent.action.VIEW" /> <category android:name="android.intent.category.DEFAULT" /> <data android:mimeType="*/*" /> </intent-filter> </activity> </application>
However, when I query content provider from App B, errors occur:
java.lang.RuntimeException: Unable to start activity ComponentInfo {com.android.web/com.android.web.Provid}: java.lang.SecurityException: Permission Denial: opening provider com.ck.contentprovider.MyContentProvider from ProcessRecord{426c6ea8 17032:com.android.web/u0a95} (pid=17032, uid=10095) requires android.permission.permRead or android.permission.permWrite
It seems that App B did not make use of the temporary permission to access. In other word, how to utilize FLAG_GRANT_READ_URI_PERMISSION from App B?
I have also tried directly adding Uri to intent(using setData()), instead of Uri.toString()(using putExtra()).
sendIntent.setData(uri2);
and
Uri userNameuri = getIntent().getData();
But the "userNameuri" got in App B is null.
I am totally confused...
updated
I tried "grantUriPermission("com.android.getCPaccess", uri2, Intent.FLAG_GRANT_READ_URI_PERMISSION)" according to a previous post
What is the correct permission handling when sending sensitive app data as email attachment?And it works indeed. It can work without using FLAG_GRANT_READ_URI_PERMISSION. But the permission is not "temporary". It have to be ended manually by revokeUriPermission().
So, I am wondering if there is a way to grant temporary permission as introduced in FLAG_GRANT_READ_URI_PERMISSION, or it's a bug at all?
-
Jens over 11 yearsIs there a <uses-permission> for android.permission.permRead in your app that implements the provider & picker activity?
-
Jin over 11 yearsHi, Jens. I have also tried that. When both of apps have <uses-permission android:name="android.permission.permRead" /> in manifest file, it works fine. But this is the kind of permanent permission. I want to work out temporary access permission with FLAG_GRANT_READ_URI_PERMISSION(as described by google android developer). So the App B won't have android.permission.permRead, instead, it receive the intent with FLAG_GRANT_READ_URI_PERMISSION flag "true" from App A. But this fails.
-
Jens over 11 yearsYou should only need one
uses-permission
, declared in the app that declares the picker activity that calls#addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
. You should probably include more of your manifest & code in the question. -
Jin over 11 yearsSure. I edited the question. Yes App A doesn't need uses-permission. But App B shouldn't have <uses-permission android:name="android.permission.permRead" />either, because if it has, there's no need to addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION).
-
Jens over 11 yearsNo - application A, which defines the provider, should also define the permission(s) you use for read/write. It must also declare a uses-permission for both the read (& write, if you in the future wish to grant write permission also) - you can't grant a permission you yourself do not hold.
-
Jin over 11 yearsIt still cannot work. The weird thing is: if I use putExtra("name", uri2.toString()) to transfer the URI, it may fail because the system may not bundle temporary acecss grant to a string. So I use setData(uri2), but when I use getIntent().getData() on the other side, it shows uri is null. Other part of intent can all be successfully received.
-