Using ContentResolver instead of ContentProviderClient in SyncAdapter - android

As I understand from the docs, one SyncAdapter defined in a SyncService is limited to receive only one ContentProvider authority to work on.
But, at the same time, it has access to ContentResolver which it allows to run queries on other ContentProviders as well. I don't understand this particular design concept if a developer is needed to provide a single content authority to SyncAdapter and nonetheless she is able to do whatever she wants on whatever ContentProvider she has access to. My question is: What are the consequences of ignoring onPerformSync's parameters: String authority and ContentProviderClient provider and going with pure ContentResolver?
My application's (actually its SyncService) idea is simple: query a calendar server (OwnCloud in my case) to get not only events (synced with com.android.calendar) but also VTODOS, which are then distributed between various task management apps I can get source code and/or ContentProviderContract. I also thought of my own "Hub" ContentProvider, which has basic VTODO/Task structure, and is the only one compared to the server. It should be able to sync 2-way with different content providers of task management apps and then it syncs with the server.
I have read using ContentProviderClient vs ContentResolver to access content provider and I think I understand the difference. I'm now puzzled why there is so strong suggestion from android SDK to use a single ContentProvider in a single SyncAdapter and yet you are allowed to use ContentResolver to bypass that limitation.
I spent all day figuring this out and searched hundreds of SO/Google resources on the matter (some of them multiple times). I have also seen questions regarding using one SyncAdapter to sync multiple ContentProviders, but none of the answers were any close to suggesting using ContentResolver instead.

There is no special limitation on ContentResolver's API when used from the context of SyncAdapter. IMHO, the only reason why the framework passes ContentProviderClient and authority to onPerformSync() is convenience and kind of a hint to developers as to how SyncAdapter intended work.
This fact is easily seen in the source code for AbstractThreadedSyncAdapter.SyncThread - the ContentProviderClient passed to onPerformSync() is obtained in a standard fashion:
#Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
// Trace this sync instance. Note, conceptually this should be in
// SyncStorageEngine.insertStartSyncEvent(), but the trace functions require unique
// threads in order to track overlapping operations, so we'll do it here for now.
Trace.traceBegin(Trace.TRACE_TAG_SYNC_MANAGER, mAuthority);
SyncResult syncResult = new SyncResult();
ContentProviderClient provider = null;
try {
if (isCanceled()) {
return;
}
provider = mContext.getContentResolver().acquireContentProviderClient(mAuthority);
if (provider != null) {
AbstractThreadedSyncAdapter.this.onPerformSync(mAccount, mExtras,
mAuthority, provider, syncResult);
} else {
syncResult.databaseError = true;
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_SYNC_MANAGER);
if (provider != null) {
provider.release();
}
if (!isCanceled()) {
mSyncContext.onFinished(syncResult);
}
// synchronize so that the assignment will be seen by other threads
// that also synchronize accesses to mSyncThreads
synchronized (mSyncThreadLock) {
mSyncThreads.remove(mThreadsKey);
}
}
}
Therefore, the bootom line: you can use ContentResolver in your SyncAdapter as you wish - just call getContext().getContentResolver() and access any exported ContentProvider.

Related

How to test ContentProvider with ProviderTestRule in kotlin

I have implemented a ContentProvider that uses a Room database to store the data. The implementation is done in kotlin and it follows the same pattern shown in this Google example.
The ContentProvider works fine when used in an app. Now I want to write some tests and I am relying on ProviderTestRule for doing so. The configuration I have seems fine, but unfortunately I am getting the following exception, which looks like some initialisation is missing and then the context is not available.
java.lang.UnsupportedOperationException
at androidx.test.rule.provider.DelegatingContext.getSystemService(DelegatingContext.java:277)
at androidx.room.RoomDatabase$JournalMode.resolve(RoomDatabase.java:517)
at androidx.room.RoomDatabase$Builder.build(RoomDatabase.java:943)
I wasn't able to find any example of how to test this scenario. Any hint would be really helpful!
ProviderTestRule internally uses DelegatingContext, which is a wrapper around the application context that purposely limits its capabilities.
From the source code you can see that context.getSystemService is stubbed out, throwing UnsupportedOperationException most of the time:
/**
* This method only supports retrieving {#link android.app.AppOpsManager}, which is needed by
* {#link android.content.ContentProvider#attachInfo}.
*/
#Override
public Object getSystemService(#NonNull String name) {
checkArgument(!TextUtils.isEmpty(name), "name cannot be empty or null");
// getSystemService(Context.APP_OPS_SERVICE) is only used in ContentProvider#attachInfo for
// API level >= 19.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
&& Context.APP_OPS_SERVICE.equals(name)) {
return context.getSystemService(Context.APP_OPS_SERVICE);
}
throw new UnsupportedOperationException();
}
I have no clear explaination why they forbid access to system services for ProviderTestRule in the first place.
Unfortunately, it seems that Room requires access to the ActivityManager in order to find the most appropriate JournalMode.
What you can try to workaround the situation:
Force the JournalMode of you Room database to JournalMode.WRITE_AHEAD_LOGGING (or JournalMode.TRUNCATE), or
If it did not solve the situation, you'd have to write your own ProviderTestRule that uses the real application context to and allow access to the desired system service.

Android SyncAdapter logic for onPerformSync

I have a local SQLite DB, and a few ContentProviders for my tables.
What exactly should I code inside the onPerformSync in order to have the local tables synced with the remote tables (located on a remote server in a MySQL database) ?
I need bi-directional syncing. The app will be used on multiple devices, by multiple users at the same time, and all of the devices must sync with the same remote DB tables, in this way: SEND all that is local to remote, and GET the part that regards the specific device/user, from remote to local.
Can I use PHP on the server side? Because I assume I wont directly access the MySQL database from the device...? So I will probably send/receive some JSON through a webservice kind of approach?
Please show me an example of what I should perform in onPerformSync method.
Here is an example from a tutorial I found:
#Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
String authtoken = null;
try {
authtoken = mAccountManager.blockingGetAuthToken(account,
AuthenticatorActivity.PARAM_AUTHTOKEN_TYPE, true);
// Dummy sample. Do whatever you want in this method.
// Now I assume here I should do/call my methods that DO SOMETHING...
// What exactly does a method that does the syncing look like?
List data = fetchData(authtoken);
syncRemoteDeleted(data);
syncFromServerToLocalStorage(data);
syncDirtyToServer(authtoken, getDirtyList(mContentResolver));
} catch (Exception e) {
handleException(authtoken, e, syncResult);
}
}
So what should I do in "syncRemoteDeleted(data)" method for example?
Please share some code with me because I'm in the dark here.
Thank you

Call API in content provider for global search

We are attempting to hook up our AndroidTV app to append results into the global search. I'm running into an issue where I cannot make an api call to get the results because the system calls my content provider on the main thread.
#Override
public Cursor query(Uri uri, String[] projection, String search, String[] selectionArgs, String searchOrder) {
... Logic here that calls the API using RxJava / Retrofit
return cursor;
}
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:label="#string/foo"
android:searchSettingsDescription="#string/foo_results"
android:includeInGlobalSearch="true"
android:searchSuggestAuthority="com.foo.search.provider"
android:searchSuggestIntentAction="android.intent.action.VIEW" />
<provider
android:authorities="com.foo.search.provider"
android:name=".search.GlobalSearchProvider"
android:exported="true"/>
When i do a global search i can see that the ContentProvider#query is called. If i attempt to do an api call on the current thread i get an networkonmainthreadexception.
I have attempted to notifty the cursor that data has changed via but had no success either.
getContext().getContentResolver().notifyChange(Uri.parse("content://com.foo.test"), null);
...
cursor.setNotificationUri(getContext().getContentResolver(), Uri.parse("content://com.foo.test"));
Is there anyway i can force the O.S to call the content provider on a seperate thread or at least notify the search that the cursor has new content?
Thank You
One of the solutions can be to set the content provider process
android:process:":androidtv"
and set the ThreadPolicy to LAX just before making network call
ThreadPolicy tp = ThreadPolicy.LAX;
StrictMode.setThreadPolicy(tp);
By running contentprovider in a different process, even if the query runs on main thread, it will not affect your UI operations
I've also struggled with this as I didn't find the currently accepted answer of blocking the UI acceptable.
However, according to Marc Bächinger from the Google TV team, this is only a problem with the emulator. In more recent builds (such as those in the currently available hardware) the search providers are called in a background thread, which avoid the problem altogether.
I've been able to test it on the Nexus Player and can confirm it works properly.
Source: https://plus.google.com/+DanielCachapa/posts/dbNMoyoRGEi
EDITED ANSWER
I've experienced this issue myself and I had to rely on the accepted answer's proposed solution. However, I've noticed that there is a noticeable lag when typing in the 'Global search' box. This lag is:
Caused by the application, since its removal makes the lag disappear
Most likely due to a synchronous wait on the queried applications - since our app does two network requests, the query() method takes time to complete, resulting in this lag
I found out that the separate process (:androidtv) is not necessary. By setting the ThreadPolicy.LAX configuration, the network request will still execute without throwing a NetworkOnMainThreadException.
I still don't understand why the lag is there.
ORIGINAL ANSWER
I don't believe that the accepted answer, while it certainly works, is the right way do it.
Once the query() method is called, you should spawn a new thread/task/job to perform a network call (therefore, avoiding the NetworkOnMainThreadException), which will update the adapter once it obtains the desired data.
There are different ways of doing this. You can either use a callback or an event bus (e.g., Otto). This is the method that I call to update the adapter:
public void updateSearchResult(ArrayList<Data> result) {
mListRowAdapter.clear();
mListRowAdapter.addAll(0, result);
HeaderItem header = new HeaderItem(0, "Search results", null);
mRowsAdapter.add(new ListRow(header, mListRowAdapter));
}
In order to solve for displaying the result in the global search using API in query method what I basically did is introduce a delay between fetching of api result and querying the db for results to return a cursor.
You can do this via
private Cursor getSuggestions(final String query) {
Cursor cursor;
cursor = getCursor(query);
if (cursor==null || cursor.getCount() == 0) {
//apiCall
try {
Thread.sleep(X millis);
} catch (InterruptedException e) {
e.printStackTrace();
}
cursor = getCursor(query);
}
return cursor;
}
Will keep on looking to see if, we can get some kind of hook to reattach without using a delay.
Because I wasn't really familiar with the Android thread. For anyone who has the same problem as me. The main point is that the query() method in content provider is not running on the UI thread.
Instead of using the asynchronous function to do HTTP requests and then update the cursor, please simply use the synchronous function to do HTTP requests and then return the cursor with the data you want.

Android SyncAdapter Callback

I have implemented a SyncAdapter, AccountManager and private ContentProvider along the lines of the SimpleSyncAdapter sample project in the SDK. It is all working well.
Now I want to show a message to the user when new rows have been downloaded from the remote server that have a specific flag set. I need a callback from the SyncAdapter when a Sync has finished so I can do the query and display the message from an activity. I have seen a few questions on StackOverflow discussing this but none with a good answer.
How does one listen for progress from Android SyncAdapter? says that the SyncStatusObserver is useless. User mobibob suggests using a ResultReceiver to respond back to the UI from the sync thread.
How to know when sync is finished? suggests using an Intent in your SyncService.
How to signal sync-complete to the Android SyncManager? suggests using the SyncResult. The example code linked to by maxpower47 uses the SyncResult class to report exceptions but not to actually report if a sync was successfully completed.
I just don't know which is the best option and I have not seen any example projects where any of these solutions are used.
I know this is an old question, but I was asking the same thing myself.
What I found out as a good solution, specially because I'm dealing with local data as you are, is to use the following method from ContentResolver:
registerContentObserver(Uri uri, boolean notifyForDescendents, ContentObserver observer)
This register an observer class that get callback when data identified by a given content URI changes. But that can only happens if your ContentProvider send the notification. So for example, if you want to get notified on the ContentObserver above for all updates done on your database through a ContentProvider, your ContentProvider should implement update similar to this:
#Override
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
// code goes here
this.getContext().getContentResolver().notifyChange(uri, null);
return 0;
}
Using notifyForDescendents when you do a registerContentObserver can be very useful.
This is an old question but I did some research in the past days and there are not many exemples on syncAdapter handling network requests and notifying the UI.
First you should use Loaders with contentProvider to make your life easier. You don't need to register for content resolver anymore the Loader does it for you. So it means your UI gets notified for anything that goes into your content provider.
What if nothing changed ? everything was up to date or you had a network error.
You can listen to the status of your syncAdapter as the Google I/O
app does, search for mSyncStatusObserver in the BaseActivity
I had a look at the default android email app and they use a Singleton with callBacks.
You can BroadcastIntents or use an eventBus (square Otto for exemple) to notify your UI of any behaviour.
I like the last one better because it gives you more granularity on the events that happen in the syncAdapter.
We ran into a similar situation and wrote a static Listener interface to the SyncAdapter. The listener is the activity and performs necessary actions when the data is available (update UI). This also works when the sync-adapter is called by the system during autosync where this listener would be null and the sync process would mind its own business.
class SyncAdapter extends AbstractThreadedSyncAdapter {
protected static Listener uiListener = null;
public interface Listener {
public void onSync();
}
public static void setListener(Listener l) {
uiListener = l;
}
public static void clearListener() {
uiListener = null;
}
protected void broadcastSync() {
if (uiListener != null)
uiListener.onSync();
}
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
// call broadcastSync();
}
Then in the Activity, implement SyncAdapter.Listener interface.

SQLite transactions with Google IO REST pattern's ContentProvider?

I'm trying to implement the second REST client model presented by Virgil Dobjanschi on this video:
http://developer.android.com/videos/index.html#v=xHXn3Kg2IQE
This is the high level diagram for the model I'm talking about:
I implemented everything as suggested, but I have a complex SQLite database model with lots of tables and I need to use transactions to update my local data with brand new data retrieved from server (step 7 in the picture).
Are there any suggestions you could make to help me out implement a transactional ContentProvider for this case?
Some of you may suggest me to use raw SQLite instead, but this way I won't take the advantages of ContentObservers, managedQueries and database accesses synchronization provided by the ContentProvider.
Any help would be appreciated.
Since you don't have access to the the Level 11 API, you could do this instead. Lets say you want to do this transaction stuff in your update method:
final Cursor update(Uri uri, ContentValues values, String where, String[] selectionArgs)
{
if(uri == uri1){
//do stuff you normally do
}
//other uri stuff
...
else if(uri == special_uri){
//do your transaction stuff here
}
}
In this case, special_uri is a uri you use to indicate that you're going to need to do your special transaction stuff. In other words, we're using the URI here to indicate that a transaction must be done.
You can implement custom function in your ContentProvider that execute your necessary transactions. Then you can call those funcitons using the call() function in your Processor.

Categories

Resources