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
Related
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.
I am trying to achieve the following things.
1.Activity via cursor loader will query to the content provider by query() method.
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader cursorLoader = new CursorLoader(getActivity(), MyProvider.toUri(MyApis.FILTER), new String[]{baseUrl, categoryCode}, MyApis.XYZ, null, null);
return cursorLoader;
}
2.Content Provider in its query() will get some data from network.
3.We will create the Custom Matrix Cursor from response and return the cursor to the loader with initial set of data.
public Cursor query(Uri uri, String[] serviceParams, String serviceApiName, String[] selectionArgs, String sortOrder) {
RestAdapter retrofit = new RestAdapter.Builder().setEndpoint(serviceParams[0]).setLogLevel(RestAdapter.LogLevel.FULL).setConverter(new SimpleXMLConverter()).build();
MyResponse response = retrofit.create(MyApis.class).filter(getFilterParams(serviceParams[1]));
MyMatrixCursor cursor = new MyMatrixCursor (new String[]{"_id", "cat", "name", "imageURI", "free", "year", "runtime", "stars"}, getContext(), uri, serviceApiName, serviceParams);
List<Thumbnails> thumbnailsList = response.getThumbnails();
for (Thumbnails thumbnails : thumbnailsList) {
cursor.addRow(new Object[]{thumbnails.getId(), thumbnails.getCat(), thumbnails.getName(), thumbnails.getImageURI(), thumbnails.getFree(), thumbnails.getYear(), thumbnails.getRuntime(), thumbnails.getStars()});
}
return cursor;
}
4.While movement of the cursor (Custom Cursor which has override the onMove and hit the network again while newPosition reaches to a certain fixed value to get additional data while user scrolls)we update the cursor by adding some rows into it.
5.Notify the resolver by notifyChange() API to requery it.
public class MyCursor extends MatrixCursor {
public MyCursor (String[] columnNames, Context mContext, Uri uri,
String serviceApiName, String[] serviceParams) {
super(columnNames);
this.mContext = mContext;
this.uri = uri;
this.serviceApiName = serviceApiName;
this.serviceParams = serviceParams;
setNotificationUri(mContext.getContentResolver(), uri);
}
#Override
public boolean onMove(int oldPosition, int newPosition) {
Log.d(TAG, "Old Position : " + oldPosition + " New Position : " + newPosition + " Count " + getCount());
if(newPosition == getCount()-1){
//Suppose this data comes from network asynchronously
addRow(new Object[]{1010, "Category", "Name", "ImageUrl", true,"2012","Android","5"});
mContext.getContentResolver().notifyChange(uri, null);
}
return super.onMove(oldPosition, newPosition);
}
}
Problems :
1.Is this the right way of doing the things if not suggest the best optimized approach for large set real time data.
2.Calling the notify calls the query method of provider again which results to return with the initial set of data instead of getting the additional data with initial set of data which I added in onMove.
I think i have made the things clear.Please ask if any doubt in use case
I'm developing Custom content provider in my app. And face issuel when get list from custom content provider. Detail, my custom content provider contain a Table. I just want get all object in this table. But it not working. This is my code, please show me what thing i wrong ?
public void onAdd(View v) {
String name = edtname.getText().toString();
Uri uri = Uri.parse("content://homework.iuh.hh.customcontentprovider.AccountProvider/accounts");
ContentValues values = new ContentValues();
values.put("NAME", name);
getContentResolver().insert(uri, values);
}
public void getList(View v) {
Uri uri = Uri.parse("content://homework.iuh.hh.customcontentprovider.AccountProvider/accounts");
Cursor c = getContentResolver().query(uri,null,null,null,null);
c.moveToFirst();
String res= "";
while(!c.isAfterLast())
{
res += c.getString(0);
c.moveToNext();
}
c.close();
Log.i(TAG,res);
And it is query method in content provider
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
Log.i(TAG,"query()");
Cursor c = getContext().getContentResolver().query(uri,projection,selection,selectionArgs,sortOrder);
c.setNotificationUri(getContext().getContentResolver(),uri);
return c;
}
In method onAdd(), it working fine. But with method getList, it show log, query function is called very time. and crash app with message
E/JavaBinder: !!! FAILED BINDER TRANSACTION !!!
05-21 11:07:43.661 5876-5876/? E/AndroidRuntime: Error reporting crash
android.os.TransactionTooLargeException
Try changing your code to be as follows:
Cursor c = getContentResolver().query(uri,null,null,null,null);
String res= "";
while(c.moveToNext()){
res += c.getString(0);
}
c.close();
Log.i(TAG,res);
The real problem is that your Content Provider is just endlessly looping in your query method. You need to actually execute the SQL here, not call the content resolver. Take a look at this class
App A with the following AndroidManifest.xml content (name and authority redacted)
<provider
android:name="test.provider"
android:authorities="test.content_authority"
android:exported="true" />
With the following ContentProvider class:
public class Provider extends ContentProvider {
#Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// [...]
}
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// [...]
}
}
From another App B I have no problem querying this provider:
String URL = "content://test.content_authority/data";
Uri data = Uri.parse(URL);
Cursor c = getContentResolver().query(data, null, null, null, null);
But when I try to update an entry:
String URL = "content://test.content_authority/data";
Uri favarticles = Uri.parse(URL);
ContentValues values = new ContentValues();
values.put("key", "value");
getContentResolver().update(favarticles, values, null, null);
I get the following exception:
java.lang.UnsupportedOperationException: Unknown Uri: content://test.content_authority/data
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:169)
at android.database.DatabaseUtils.readExceptionFromParcel(DatabaseUtils.java:137)
at android.content.ContentProviderProxy.update(ContentProviderNative.java:560)
at android.content.ContentResolver.update(ContentResolver.java:1316)
at com.example.test_contentprovider.MainActivity.updateEntryTest(MainActivity.java:119)
Is there anything obviously wrong? Do I not have the rights to access the Provider? Why do I get an Unknown Uri Exception when I can access this URI with a query?
thank you very much.
I have an app that stores user search history. I am now adding Custom Suggestions as well. This required me to add one more method, query() and override it in my MyCustomSuggestionProvider class.
However, doing this prevents Recent Suggestions from showing. They are still being stored in the suggestions.db. The moment I delete the query() method to test, recent suggestions come back.
Does anyone know how to make these two things work together?
The problem was not overiding query(), it was how I was handling it. Here is my solution:
#Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
Cursor recentCursor = super.query(uri, projection, selection,
selectionArgs, sortOrder);
query = selectionArgs[0];
if (query == null || query.length() < 3) {
return recentCursor;
} else {
query = selectionArgs[0];
MatrixCursor customCursor = new MatrixCursor(COLUMN_NAMES);
SearchSuggestionObject[] suggestions = getSuggestionsFromWebFeed(query);
for (SearchSuggestionObject suggestion : suggestions) {
customCursor.addRow(new Object[] { suggestion.getId(),
suggestion.getItem(), suggestion.getCat(),
suggestion.getItem(), suggestion.getItem(),
suggestion.getCat(), "android.intent.action.SEARCH" });
}
return customCursor;
}
}