I've been trying to get data from another app's custom ContentProvider class but I keep getting this error: Failed to find provider info for com.example.serialprovider.provider.SampleProvider..
I searched a lot for similar issues online but still didn't know what's wrong, I checked the manifest multiple times, and I even took a copy of the authorities attribute to use it in the receiver app but still, the receiver app can't find the provider.
Here's the declaration in the manifest:
<provider
android:name=".provider.SampleProvider"
android:authorities="com.example.serialprovider.provider.SampleProvider"
android:enabled="true"
android:exported="true" />
and here's the implementation of onCreate and query methods in the Provider class (I'm using RoomDatabase):
public class SampleProvider extends ContentProvider {
public SampleProvider() {
}
private static final String AUTHORITY = "com.example.serialprovider.provider.SampleProvider";
private static final String TABLE_NAME = "devicepin";
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(AUTHORITY, TABLE_NAME, 1);
}
#Override
public boolean onCreate() {
return true;
}
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
if (sURIMatcher.match(uri) == 1) {
final Context context = getContext();
AppDao dao = DatabaseClient.getInstance(context).getAppDatabase().appDao();
final Cursor cursor = dao.get();
cursor.setNotificationUri(getContext().getContentResolver(), uri);
cursor.close();
return cursor;
} else {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
}
and here's how I try to get the cursor in the other app "receiver":
private void getPin(){
new Thread(() -> {
ContentResolver resolver = getContentResolver();
try{
Cursor cursor = resolver.query(Uri.parse("content://com.example.serialprovider.provider.SampleProvider/devciepin"), null, null, null, null);
cursor.close();
}
catch (Exception e){
e.printStackTrace();
}
}).start();
}
cursor is always null, when I surround it with try and catch blocks, the "failed to find provider info" is what I get as an exception.
Turns out the code is alright, but there's some new restrictions that were introduced in Android 11 (API 30) when accessing the ContentProvider from another app.
Quoting the Documentation on Android 11 behavior changes:
If your app shares a content URI with another app, the intent must grant URI access permissions by setting at least one of the following intent flags: FLAG_GRANT_READ_URI_PERMISSION and FLAG_GRANT_WRITE_URI_PERMISSION. That way, if the other app targets Android 11, it can still access the content URI. Your app must include the intent flags even when the content URI is associated with a content provider that your app doesn't own.
If your app owns the content provider that's associated with the content URI, verify that the content provider isn't exported. We already recommend this security best practice.
Related
Since the changes related to the authorizations of access to the shared storage, it does not seem any more possible to search all the documents of the type pdf by this approach (with requestLegacyExternalStorage = "false"):
ContentResolver cr = context.getContentResolver();
Uri uri = MediaStore.Files.getContentUri("external");
String[] projection = null;
String selection = MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_NONE;
String[] selectionArgs = null;
String sortOrder = null;
Cursor allNonMediaFiles = cr.query(uri, projection, selection, selectionArgs, sortOrder);
Check this link : Media data restrictions
The only solution I see is to scan in a recurcive way all the tree of the shared storage with SAF, which seems to me very expensive in resources and ridiculous.
Does anyone have another idea?
The basic idea of scoped storage is exactly to avoid this kind of behavior, you can't know if there are or not some files somewhere in the user phone. You can just ask for permission to access to the storage tree and scan everything as you said. Even in this case the user could select a folder different from the root so your app will be limited to that folder. The idea could be perform a scan and then update your database keeping in sync using a job (job service) scheduled on the modification of tree URI and descendants.
if your project targeted to Sdk level 29 you should add to your AndroidManifest.xml the flag android:requestLegacyExternalStorage="true" under your <application> tag.
public List<String> queryFilesFromDevice(Uri uri, String[] projection, String selection, final Context context) {
final List<String> tempList = new ArrayList<>();
Cursor c = context.getContentResolver().query(uri, projection,
selection,
null,
null);
if (c != null) {
while (c.moveToNext()) {
String path = c.getString(0);
long size = c.getLong(1);
// your code logic should be here
}
c.close();
}
tempList.add(path);
return tempList;
}
you need to call the function like this:
String pdfExt = "_data LIKE '%.pdf'";
Uri ducumentsUri = MediaStore.Files.getContentUri("external");
String[] docsProjection ={MediaStore.Files.FileColumns.DATA,MediaStore.Images.Media.SIZE,MediaStore.Files.FileColumns.MIME_TYPE,};
queryFilesFromDevice(ducumentsUri, docsProjection, pdfExt, this);
I have read many answers regarding content providers, but I can't understand why my client isn't recieving any data stored in the database.
-I'm using the same uri everywhere.
-I have added the read and write permissions in my provider in the manifest.
-I have exported the provider and set the multiprocess to "true" too.
-My client in app has the same read and write permissions.
-I created a jar containing the resolver for my client in app. I have added it as a library.
-I know that the parameters got into the database because I can see all the info inside the database.
-My client wasn't able to connect to the provider until I have done all mentioned above, because I fixed exception regarding it.
Now everything should work and doesn't crash any more, but all the strings I get from the resolver are empty. I don't know if it really conects to the provider or if something else is incorrect.
Please help me!
Q: Do the permission string and the authorities string in the provider declaration have to match the package structure?
Here are some pieces of code:
Provider declaration in manifest:
<provider
android:name=".lessons.LessonsProvider"
android:authorities="com.scheduler.lessons"
android:readPermission="com.lessons.READ_DATABASE"
android:writePermission="com.lessons.WRITE_DATABASE"
android:multiprocess="true"
android:exported="true" />
Resolver code:
public class LessonsResolver {
private static final String TAG = "LessonProvider";
private final String PROVIDER_NAME = "com.scheduler.lessons";
private Uri uri;
private Context mContext;
public LessonsResolver(Context context) {
super();
mContext = context;
uri = Uri.parse("content://"+ PROVIDER_NAME+"/parameters_table");
}
public String getString(String parameterName, String defaultValue) {
Cursor cursor = mContext.getContentResolver().query(uri, null, "parameter_name='"+parameterName+"'", null, null);
if (cursor == null || cursor.getCount() == 0) {
Log.e(TAG,"getString: for parameterName: " + parameterName + ", cursor from the content provider "+ (cursor == null ? "is NULL" : "is empty" ));
cursor.close();
return defaultValue;
}
cursor.moveToFirst();
String s = cursor.getString(2);
cursor.close();
return s;
}
Permissions in client app manifest:
<uses-permission android:name="com.lessons.READ_DATABASE"/>
<uses-permission android:name="com.lessons.WRITE_DATABASE"/>
My provider:
public class LessonsProvider extends ContentProvider {
public static final String PROVIDER_NAME = "com.scheduler.lessons";
private static final String TABLE_NAME = "lessons";
private static final String TAG = "LessonsProvider";
private static final Uri CONTENT_URI = Uri.parse("content://"+PROVIDER_NAME + "/parameters_table");
...
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteQueryBuilder sqlBuilder = new SQLiteQueryBuilder();
sqlBuilder.setTables(TABLE_NAME);
Cursor c = sqlBuilder.query(mSqlDB, projection, selection, selectionArgs, null, null, null);
return c;
}
I am trying to create an app which simply offers an edittext and imagebutton. If the butten gets clicked, the idea is that an album is added to the Playlist, named in the edittext box. Albums should be selected randomly. Goes without saying that the album tracks should be in the correct order.
I can add more functionality later eg. save, overwrite, delete etc.
I have the interface but am struggling with the code. I sort of get the concept of ContentProviders.
so the code needs to:
access the Playlists, which I believe is achieved by using MediaStore.Audio.Playlists
access the Albums, which I believe is achieved by using MediaStore.Audio.Albums
add to the Playlist
I have the following code (most bits obtained from this site. Thanks btw) to access the Playlist but it crashes with a Null Exception error.
public void checkforplaylists()
{
//Get a cursor over all playlists.
final ContentResolver resolver= MediaProvider.mContentResolver;
final Uri uri=MediaStore.Audio.Playlists.INTERNAL_CONTENT_URI;
final String id=MediaStore.Audio.Playlists._ID;
final String name=MediaStore.Audio.Playlists.NAME;
final String[]columns={id,name};
final Cursor playlists= resolver.query(uri, columns, null, null, null);
if(playlists==null)
{
Log.e(TAG,"Found no playlists.");
return;
}
return;
}
Anyone who can help?
I think you mean NullPointerException, which means one of your assignments is null and then you try to access a member of the object you intended it to be. Most likely it is resolver, but to be sure you need the line number reported and/or to step through that with a debugger.
This works. When using the ContentResolver, the Context (this) is required.
public void checkforplaylists(Context context)
{
ContentResolver cr = context.getContentResolver();
final Uri uri=MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
final String id=MediaStore.Audio.Playlists._ID;
final String name=MediaStore.Audio.Playlists.NAME;
final String[]columns={id,name};
final Cursor playlists= cr.query(uri, columns, null, null, null);
if(playlists==null)
{
Log.e(TAG,"Found no playlists.");
return;
}
Log.e(TAG,"Found playlists.");
return;
}
use this code, will work
public boolean addPlaylist(String pname) {
Uri playlists = MediaStore.Audio.Playlists.EXTERNAL_CONTENT_URI;
Cursor c = resolver.query(playlists, new String[] { "*" }, null, null,
null);
long playlistId = 0;
c.moveToFirst();
do {
String plname = c.getString(c
.getColumnIndex(MediaStore.Audio.Playlists.NAME));
if (plname.equalsIgnoreCase(pname)) {
playlistId = c.getLong(c
.getColumnIndex(MediaStore.Audio.Playlists._ID));
break;
}
} while (c.moveToNext());
c.close();
if (playlistId != 0) {
Uri deleteUri = ContentUris.withAppendedId(playlists, playlistId);
Log.d(TAG, "REMOVING Existing Playlist: " + playlistId);
// delete the playlist
resolver.delete(deleteUri, null, null);
}
Log.d(TAG, "CREATING PLAYLIST: " + pname);
ContentValues v1 = new ContentValues();
v1.put(MediaStore.Audio.Playlists.NAME, pname);
v1.put(MediaStore.Audio.Playlists.DATE_MODIFIED,
System.currentTimeMillis());
Uri newpl = resolver.insert(playlists, v1);
Log.d(TAG, "Added PlayLIst: " + newpl);
flag=true;
return flag;
}
This is what I have, but its failing saying that the table does not exists. I am positive it does incase anyone asks that. But this is some of the code that does that:
What gets called
/*Constants*/
public static final String AUTHORITY = "content://com.smartcal.eventprovider";
private static final int EVENTS_INFO = 1;
baseUri = Uri.withAppendedPath(baseUri, "events_info");
return new CursorLoader(this, baseUri, args.getStringArray("projection"),
args.getString("selection"), args.getStringArray("selectionArgs"), args.getBoolean("sortOrder") ? args.getString("sortOrder") : null );
What matches is
private String getTable(Uri uri) {
String table = "";
switch(sUriMatcher.match(uri)){
case EVENTS_INFO: table = "events_info";
}
return table;
}
private static final UriMatcher sUriMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static
{
sUriMatcher.addURI(AUTHORITY, "events_info", 1);
}
And my query() method in custom ContentProvider
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
database = openHelper.getWritableDatabase();
return database.query(getTable(uri), projection, selection, selectionArgs, null, null, null);
}
Error:
06-19 17:34:14.366: E/AndroidRuntime(686): Caused by:
java.lang.IllegalStateException: Invalid tables
EDIT:
When using Alex's IllegalArgumentException, this is what I got back from the error:
06-19 19:17:42.277: E/AndroidRuntime(1134): Caused by:
java.lang.IllegalArgumentException: Unknown URI
content://com.smartcal.eventprovider/events_info
As you can tell, it should match, assuming the sUriMatcher.addURI() method is working correctly.
Your problem might stem from the fact that your code isn't very well organized...
First thing,
private String getTable(Uri uri) {
switch(sUriMatcher.match(uri)) {
case EVENTS_INFO:
return "events_info"; // return
/** PROVIDE A DEFAULT CASE HERE **/
default:
// If the URI doesn't match any of the known patterns, throw an exception.
throw new IllegalArgumentException("Unknown URI " + uri);
}
}
Second thing,
/** Use constants, not raw ints **/
sUriMatcher.addURI(AUTHORITY, "events_info", EVENT_INFO);
Other than that, you might want to provide some more info.
Have you already installed the app and is the table new? If so, and you are not updating the schema in your sql helper class, you need to uninstall the app first. Then install the app in your emulator device. The db is only created once, and the create method in the sql helper will not be called after that first time, even if you are installing or running a new build/apk.
I need third party applications ("Foo") to get information from my application ("Bar"), but my solution so far seems cumbersome:
Application Foo needs information from Bar and sends a broadcast ("bar.POLL").
Application Bar listens for this broadcast, and replies with another broadcast ("bar.PUSH");
Foo listens for bar.PUSH and reads the contents of the included Bundle.
Is there a more direct way to do this?
EDIT: I solved it with an extremely simplistic ContentProvider as Guido suggested:
public class MyProvider extends ContentProvider {
private String state = "";
#Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
MatrixCursor cursor = new MatrixCursor(new String[]{"state"});
cursor.addRow(new Object[]{state});
return cursor;
}
#Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
state = (String) values.get("state");
return 1;
}
#Override
public boolean onCreate() {
return true;
}
#Override
public String getType(Uri uri) {
return null;
}
#Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
#Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
}
Remember to add the provider to the manifest:
<provider android:name=".MyProvider" android:authorities="com.example.hello" />
Update the state from an Activity like this:
ContentValues cv = new ContentValues();
cv.put("state", "myNewState");
getContext().getContentResolver().update(Uri.parse("content://com.example.hello"), cv, null, null);
Get content from the provider in the external app:
Cursor cur = managedQuery(Uri.parse("content://com.example.hello"), null, null, null, null);
if (cur.moveToFirst()) {
String myContent = cur.getString(0);
}
You should expose a ContentProvider.
"Content providers store and retrieve data and make it accessible to all applications. They're the only way to share data across applications; there's no common storage area that all Android packages can access."
Content providers implement a common interface for querying the provider and returning results. It is not hard to implement, but maybe the official documentation is not the best to get started with it. You can find other examples at:
http://thinkandroid.wordpress.com/2010/01/13/writing-your-own-contentprovider/
google