First time sync loops indefinitely - android

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);

Related

How whatsapp is syncing contacts instantly after updating

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);
}

onPerformSync called on emulator but not physical device

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.

Account.setPassword causing SyncAdapter infinite loop

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.

SyncAdapter not getting called on "Network tickle"

Overview
I follwed Google's tutorial on using SyncAdapter without using ContentProvider, Authenticator..etc. It works perfectly when I call onPerformSync(...) when I need to do an "upload" to the server via de SyncAdapter.
Now, as you can imagine, I need to do downloads from the server as well (yes I understand that it would be better to use Google's Cloud Messaing system, but this is the set up I was given, and I can't change that). For that, instead of doing periodical syncs, I want to make use of the "Network tickle" Android system carries out when there is a network available. For that I state the following:
ContentResolver.setIsSyncable(accounts[0], AUTHORITY, 1);
ContentResolver.setSyncAutomatically(accounts[0], AUTHORITY, true);
But my SyncAdapter is just not getting called. Looking into other stackOverFlow questions, there seem to be a problem if targetting API 10 or below with SyncAdapter and that you must add an account explicitly before calling the before methods. So I ended up with this:
AccountManager accountManager = (AccountManager) context.getSystemService(ACCOUNT_SERVICE);
Account[] accounts = accountManager.getAccounts();
if(accounts.length == 0){ //ADD DUMMY ACCOUNT
Account newAccount = new Account(ACCOUNT, ACCOUNT_TYPE);
ContentResolver.setIsSyncable(accounts[0], AUTHORITY, 1);
ContentResolver.setSyncAutomatically(accounts[0], AUTHORITY, true);
accountManager.addAccountExplicitly(newAccount, null, null);
}else{
accounts = accountManager.getAccounts();
ContentResolver.setIsSyncable(accounts[0], AUTHORITY, 1);
ContentResolver.setSyncAutomatically(accounts[0], AUTHORITY, true);
}
Now this code gets executed when the user signs in, or if the application was killed and is started up again. I am wondering, should I call setIsSyncable and setSyncAutomatically only when I add the dummyAccount the very first time?
Also, part of the "goodiness" of the SyncAdapter is that it will keep on making the calls in case of an exception. But I don't quite understand how this goes about, so instead I have this:
private void profileUpdate(){
TableAccounts db = TableAccounts.getInstance(getContext());
boolean isRecordDirty = db.isRecordDirty(signedInUser);
if(isRecordDirty){
if(server.upDateUserProfile(signedInUser)){
db.cleanDirtyRecord(signedInUser);
turnOffPeriodicSync();
}else{
this.turnOnPeriodicSync(this.sync_bundle);
}
}else
turnOffPeriodicSync();
}
As you can see, depending on the result of my upload to the server, I turn on or off a periodic sync.
Since the accountManager.getAccounts[] return every account on the device, I think nothing guarantee that the account[0] is your app's account (aka, has the ACCOUNT_TYPE of your package name).
-- You could call addAccountExplicitly() in any case, if it is existed, then nothing happens.
Account account = new Account(ACCOUNT, ACCOUNT_TYPE);
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
accountManager.addAccountExplicitly(account, null, null)
context.getContentResolver().setSyncAutomatically(account, AUTHORITY, true);
Disclaimer: I might be mistaken.
On top everything you did, you also have to call ContentResolver.requestSync() from within your app every time you need a sync to run. The sync will not run immediately though, because Android is trying to cluster network activity.
Or you can use Googles Cloud Messaging API to request a sync, but I don't know a whole lot about that.

Running My Android SyncAdapter Immediately.

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

Categories

Resources