Communication Between Two Android Apps Using ContentProvider Not Working Problem Statement I'm trying to share data between two separate Android applications using a ContentProvider. App A provides data via a custom ContentProvider, and I can access this data within App A. However, when I try to query the same ContentProvider from App B, the cursor is returning null. What I'm Trying to Achieve I want to access data from App A's ContentProvider in App B. Code Structure App A (Provider App) AndroidManifest.xml: xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.ContentProvider"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.ContentProvider">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<provider
android:name=".MyContentProvider"
android:authorities="com.example.app1.provider"
android:exported="true"
android:grantUriPermissions="true" />
</application>
<permission
android:name="com.example.app1.permission.READ_DATA"
android:protectionLevel="dangerous" />
</manifest>
MyContentProvider.java:
javapackage com.example.app1;
import android.content.ContentProvider;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.net.Uri;
import android.util.Log;
import android.content.UriMatcher;
public class MyContentProvider extends ContentProvider {
private static final int URI_CODE_SHARED_DATA = 1;
private static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
static {
// Register the URI pattern with the UriMatcher
URI_MATCHER.addURI("com.example.app1.provider", "shared_data", URI_CODE_SHARED_DATA);
}
@Override
public boolean onCreate() {
Log.d("MyContentProvider", "Provider initialized");
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Log.d("MyContentProvider", "Query received: " + uri);
switch (URI_MATCHER.match(uri)) {
case URI_CODE_SHARED_DATA:
MatrixCursor matrixCursor = new MatrixCursor(new String[]{"data"});
matrixCursor.addRow(new Object[]{"my content provider data"});
return matrixCursor;
default:
return null; // No match found for the URI
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
throw new UnsupportedOperationException("Insert not supported");
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Update not supported");
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
throw new UnsupportedOperationException("Delete not supported");
}
@Override
public String getType(Uri uri) {
return "vnd.android.cursor.dir/vnd.com.example.app1.provider.shared_data";
}
}
MainActivity of App A:
javapackage com.example.app1;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import androidx.activity.ComponentActivity;
public class MainActivity extends ComponentActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Test accessing our own content provider
getContentResolver().query(
Uri.parse("content://com.example.app1.provider/shared_data"),
null,
null,
null,
null
)?.use { cursor ->
if (cursor.moveToFirst()) {
int index = cursor.getColumnIndex("data");
if (index != -1) {
String value = cursor.getString(index);
Log.d("MyContentProvider", "Provider data: " + value);
} else {
Log.d("MyContentProvider", "No 'data' column found");
}
} else {
Log.d("MyContentProvider", "Cursor is empty");
}
}
// Rest of onCreate...
}
}
App B (Consumer App) AndroidManifest.xml: xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<!-- Added permission to read from App A -->
<uses-permission android:name="com.example.app1.permission.READ_DATA"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.App2"
tools:targetApi="31">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.App2">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
MainActivity of App B:
javapackage com.example.app2;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import androidx.activity.ComponentActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
public class MainActivity extends ComponentActivity {
private static final int REQUEST_PERMISSION_CODE = 1001;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Check permission
if (ContextCompat.checkSelfPermission(
this,
"com.example.app1.permission.READ_DATA"
) != PackageManager.PERMISSION_GRANTED
) {
// Request permission
ActivityCompat.requestPermissions(
this,
new String[]{"com.example.app1.permission.READ_DATA"},
REQUEST_PERMISSION_CODE
);
} else {
// Permission already granted
tryReadSharedData();
}
// Rest of onCreate...
}
@Override
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] grantResults
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_PERMISSION_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted
tryReadSharedData();
} else {
Toast.makeText(this, "Permission denied. Cannot fetch data.", Toast.LENGTH_SHORT).show();
}
}
}
private void tryReadSharedData() {
Log.i("SharedData", "Attempting to read from content provider");
try {
Uri uri = Uri.parse("content://com.example.app1.provider/shared_data");
Log.i("SharedData", "Querying with URI: " + uri);
// This is returning null
Cursor cursor = getContentResolver().query(uri, null, null, null, null);
if (cursor == null) {
Log.e("SharedData", "Cursor is null - failed to query content provider");
return;
}
if (cursor.moveToFirst()) {
int dataIndex = cursor.getColumnIndex("data");
if (dataIndex != -1) {
String data = cursor.getString(dataIndex);
Log.d("SharedData", "Successfully read data: " + data);
}
} else {
Log.d("SharedData", "Cursor is empty");
}
cursor.close();
} catch (Exception e) {
Log.e("SharedData", "Error when reading from Content Provider", e);
}
}
}
The Issue When I try to query the content provider from App B, the cursor is null. However, I can successfully query the same content provider from within App A. What I've Tried
Added the necessary permission in App B's manifest Set the content provider to be exported in App A Requested runtime permissions in App B Verified the URI pattern is identical in both apps
Debug Information
Logcat in App B shows: "Cursor is null - failed to query content provider" No other exceptions are thrown Both apps are installed on the same device
Questions
Why am I getting a null cursor when querying from App B? Is there something missing in my permission setup or content provider configuration? Are there any additional steps needed to enable cross-app communication via ContentProvider?
To connect to a non-system ContentProvider
, such as one provided by another app of yours, it appears that you need to add a <queries>
element to the client app's manifest, identifying the app with the provider.
The specific package approach should work:
<manifest package="com.example.game">
<queries>
<package android:name="com.whatever.the.application.id.is.of.the.app.with.the.provider" />
</queries>
...
</manifest>