Can anyone help me out in understanding, How WhatsApp and imo apps are syncing contacts immediately after add/delete/updating contact.
I have tried following method:
Tried to register ContentObserver in service so that we can get contact which is updated.
If we able to deploy a ContentObserver to contacts database, how do we differentiate manual and programatic updating of a contact.
Note: I am using Sync adaptor to sync the contacts with server but not able to get trigger points for sync process.
Please help me.
Sync adapter has an option to force sync or immediate sync . From documentation the method is similar to the snippet below
public void onRefreshButtonClick(View v) {
...
// Pass the settings flags by inserting them in a bundle
Bundle settingsBundle = new Bundle();
settingsBundle.putBoolean(
ContentResolver.SYNC_EXTRAS_MANUAL, true);
settingsBundle.putBoolean(
ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
/*
* Request the sync for the default account, authority, and
* manual sync settings
*/
ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
}
Related
I am making an app in which I have a local database and I'm using a SyncAdapter to sync this local database with the server, I don't have much experience with a SyncAdapter and I cannot seem to figure something out. So far I've implemented the "Run the sync adapter when content provider data changes" section from the Android documentation (https://developer.android.com/training/sync-adapters/running-sync-adapter), and initially it worked great but I started to notice something. When calling requestSync from inside the ContentObserver a new SyncRequest is queued by Android (I guess?) and then a little later executed but when my onPerformSync method from my SyncAdapter is executing, I find that if I make a new SyncRequest, this SyncRequest is completely ignored and not even executed later on. This is kinda annoying because when I update my database while my database is being synced, then it could happen that my updates do not reach my server (because the updates occured while the old data was being synced already). I cannot find much information about this behaviour, is this normal behaviour and if so how could I avoid this (without needing to write an entire queing system by myself)?
Here is the code from my ContentObserver (AppLogger is some custom logging system I made):
ContentObserver observer = new ContentObserver(null) {
#Override
public void onChange(boolean selfChange) {
super.onChange(selfChange, null);
}
#Override
public void onChange(boolean selfChange, #Nullable Uri uri) {
AppLogger.log(context, "AppBroadCastReceiver", "Requesting a sync for the Datamanager from the observer...");
ContentResolver.requestSync(mAccount, ApplicationProvider.AUTHORITY, new Bundle());
}
};
The onPerformSync method I used to test this behaviour:
#Override
public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) {
AppLogger.log(context, "DataManagerSyncAdapter", "Starting a sync attempt");
try{
Timer timer = new Timer();
CountDownLatch latch = new CountDownLatch(1);
timer.schedule(new TimerTask() {
#Override
public void run() {
latch.countDown();
}
}, 10000);
latch.await();
} catch(InterruptedException e){}
AppLogger.log(context, "DataManagerSyncAdapter", "Finishing a sync attempt");
}
And then the syncadapter xml:
<?xml version="1.0" encoding="utf-8"?>
<sync-adapter
xmlns:android="http://schemas.android.com/apk/res/android"
android:contentAuthority="com.example.getrequest.Providers.application_provider"
android:accountType="example.com"
android:userVisible="true"
android:supportsUploading="false"
android:allowParallelSyncs="false"
android:isAlwaysSyncable="true"/>
And I got as result in the log (when trying to test this behaviour):
AppBroadCastReceiver: Requesting a sync for the Datamanager from the observer...
DataManagerSyncAdapter: Starting a sync attempt
AppBroadCastReceiver: Requesting a sync for the Datamanager from the observer...
DataManagerSyncAdapter: Finishing a sync attempt
And then onPerformSync is never run again (atleast not in the next 20 minutes, after that I lost my patience). I also noticed setting android:supportsUploading="true" kinda solved my problem but then a ton of useless SyncRequests are handled which I don't even ask for (like one every minute almost).
I've also thought about maybe blocking access to the database until my SyncRequest is completely done but is this common practice? If I want to update multiple tables in the server based on multiple tables in my local database then isn't it better to only lock the database per table instead of locking everything until the SyncRequest is completed and added to this, does this really solve anything? At which point in the onPerformSync should I then unlock my database again? It looks to me that unlocking the database in the onPerformSync could always result in a database call being executed while the onPerformSync is still busy (even if the possibility is very small)? Any help or information about this would be greatly appreciated!
Edit:
When digging through the source code of the SyncManager (https://android.googlesource.com/platform/frameworks/base/+/master/services/core/java/com/android/server/content/SyncManager.java) I came across this:
// Check currently running syncs
for (ActiveSyncContext asc: mActiveSyncContexts) {
if (asc.mSyncOperation.key.equals(syncOperation.key)) {
if (isLoggable) {
Log.v(TAG, "Duplicate sync is already running. Not scheduling "
+ syncOperation);
}
return;
}
}
So I guess this is expected behaviour, which from my point of view does not make any sense at all but maybe I don't have enough experience with these sort of things. So how should I ensure that data updated during a onPerformSync is still getting updated (without writing tons of code myself) or how can I ensure that data is not being updated while onPerformSync is busy?
I've built an Account sync adapter to show contacts from the app in local contact book.
There is a fake authonticator that provides account. Also account is syncable
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
This account is displayed in all accounts on the device:
I've tried to trigger onPerformSync by system - from settings in menu press 'sync now' and programatically:
public static void triggerRefresh() {
Bundle b = new Bundle();
b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
Account account = AccountAuthenticatorService.GetAccount();
if (ContentResolver.isSyncPending(account, ContactsContract.AUTHORITY) ||
ContentResolver.isSyncActive(account, ContactsContract.AUTHORITY)) {
ContentResolver.cancelSync(account, ContactsContract.AUTHORITY);
}
ContentResolver.setIsSyncable(account, ContactsContract.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, ContactsContract.AUTHORITY, true);
ContentResolver.requestSync(
account, // Sync account
ContactsContract.AUTHORITY,authority
b);
}
It works fine on emulator, but on several devices (sony, samsung) it not triggered at all (I've tried to log smth in onPerformSync method but never see this log).
I've tried to find such problem, but nothing helps, I can't make onPerformSync force to be called.
What the main difference between emulator and device according to syncAdapter?
Finally I've found the reason of this issue:
onPerformSync method will never be called until you don't have internet connection. On several devices and emulators there was an internet connection but on others you can't trigger this method.
There are quite a few questions considering infinite loop of android's SyncAdapter: [1]
[2]
[3], but none described the problem I encountered.
I am setting up my sync as:
ContentResolver.setIsSyncable(account, AppConstants.AUTHORITY, 1);
ContentResolver.setSyncAutomatically(account, AppConstants.AUTHORITY, true);
ContentResolver.addPeriodicSync(account, AppConstants.AUTHORITY, Bundle.EMPTY, 60);
My sync adapter supports uploading (android:supportsUploading="true"), which means that in my ContentProvider I have to check whether the data change comes from my SyncAdapter, and if it does, then I notify change without requesting sync to network.
boolean syncToNetwork = false;
getContext().getContentResolver().notifyChange(uri, null, syncToNetwork);
Still my sync adapter runs in a constant loop, what another reason could there be for triggering another sync?
In each sync I request the server for data. For each request I get an access token from my custom Account Authenticator. Instead of saving a password in my account, I decided to save the Oauth2 refresh token, which can then be use to refresh the access token. With each refreshed access token the server also send a new refresh token, which I then update to my account:
accountManager.setPassword(account, refreshToken);
And THAT was the problem. Going through the AOSP codes I discovered the following BroadcastReceiver in the SyncManager:
private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
updateRunningAccounts();
// Kick off sync for everyone, since this was a radical account change
scheduleSync(null, UserHandle.USER_ALL, null, null, 0 /* no delay */, false);
}
};
So what it does, on each account change (adding, deleting, setting password) a broadcast in send to trigger sync for all SyncAdapters, not just your own!
I honestly don't know what what the reasoning for that, but I can see it as exploitable - I let my phone (with my app stuck in infinite loop) run over night, in the morning the battery was drained, but also my FUP - only the Google's Docs, Slides and Sheets apps consumed 143MB each.
I've implemented a SyncAdapter pretty much as outlined here (with a dummy account) and using one of my content providers (I have one per table). As far as I can tell the sync code is fine. If I go into "Settings -> Accounts" select the dummy account I've created and then the "Sync Now" menu item my adapter is run by the system. All it does ATM is log the fact that it was called.
But trying to run it from my own "Sync Now" menu option (see below on my main activity) doesn't. Is there a way of getting the sync adapter to run immediately given that we have a WiFi connection?
private boolean actionSynchronize() {
if (mLogger.isTraceEnabled())
mLogger.trace("Synchronising now!");
// Pass the settings flags by inserting them in a bundle
Bundle settingsBundle = new Bundle();
settingsBundle.putBoolean(
ContentResolver.SYNC_EXTRAS_MANUAL, true);
settingsBundle.putBoolean(
ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
/*
* Request the sync for the default account, authority, and
* manual sync settings
*/
ContentResolver.requestSync(mAccount, AUTHORITY, settingsBundle);
return true;
}
Please try with this.
Bundle b = new Bundle();
// Disable sync backoff and ignore sync preferences. In other words...perform sync NOW!
b.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
b.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(
GenericAccountService.GetAccount(), // Sync account
FeedContract.CONTENT_AUTHORITY, // Content authority
b); // Extras
I'm having a situation with SyncAdapter I don't know how to fix.
I'm using periodic syncs. The onPerformSync method just logs some info for me to know that the process is working (no calls to notifyChanges in content providers or anything else).
The project consists of two apps: The first one creates a user account (for testing purposes only). The second holds the sync adapter. Note that this is perfectly legal for the scope of the project.
I first install the app with the account.
I can see the account has been created.
Then I install the app with the sync adapter and the first time it runs the synchronization hangs. Seeing the account sync settings, the spinner icon is continuously running and no log messages are registered (meaning it does not reach onPerformSync).
However, I can cancel the sync in the Settings and then the sync process starts working normally. This means the wiring between Account, Content Provider and SyncService is properly set.
I'm aware that adding/removing an account triggers other sync processes so I let a good lapse of time to go before installing the app with the sync adapter.
Any hints on why this is happening?
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAccountManager = AccountManager.get(this);
// No worries here. The account exists and it's the one I want
Account[] accounts = mAccountManager.getAccountsByType(Constants.ACCOUNT_TYPE);
// Just first account for TESTING purposes
if (accounts != null && accounts.length > 0)
account = accounts[0];
else {
Log.e(TAG, "No accounts set!!");
return;
}
// Set sync for this account.
Bundle extras = new Bundle();
extras.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY, false);
extras.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
ContentResolver.setIsSyncable(account, authority, 1); // Mandatory since 3.1
// Allows the provider to sync when internet connection is back
ContentResolver.setSyncAutomatically(account, authority, true);
// Add a periodic synchronization
ContentResolver.addPeriodicSync(account, authority, extras, POLL_FREQUENCY);
}
EDIT
I found out that calling cancel on the sync, makes it work. Not the best solution but it fixes the problem by now. I put this line combined with a "isFirstUse" flag.
ContentResolver.cancelSync(account, authority);