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?
It seems that
FLAG_GRANT_READ_URI_PERMISSION
affects onlyUri Intent.mData
but not uris in extras.I found similar problems when playing with
ACTION_SEND
, which takes an uri inEXTRA_STREAM
. Providing the same uri insetData()
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 requiresrevokeUriPermission
. To do the same job asstartActivity(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:
ActivityView.java:
Sender app:
ActivitySend.java: