Android SyncAdapter not syncing on my Samsung S6 Edge Plus Nougat device (syncing periodically with API <= 23)
I have a problem using my SyncAdapter. The weirdest thing about this: it used to work, but now sync only works if I call it manually.
It also doesn't work on the emulator (API 24)
Here is my sync adapter code:
public class SmogAppSyncAdapter extends AbstractThreadedSyncAdapter { private static final String LOG_TAG = SmogAppSyncAdapter.class.getSimpleName(); public static final int SYNC_INTERVAL = 60; // 60 * 60 = 1h to the nearest 20min. public static final int SYNC_FLEXTIME = SYNC_INTERVAL / 3; public static final int POLLUTION_DISTANCE = 10000; //preferred distance between prefs location and nearest measurement point private static final int POLLUTION_NOTIFICATION_ID = 0; private ContentResolver mContentResolver; private SharedPreferences prefs; private Context syncContext; private int prefsPollutionLevel; private double prefsHomeLocationLatitude; private double prefsHomeLocationLongitude; private boolean prefsNewMessageNotification; private int currentApiPollutionLevel; private Float currentApiPollutionLevelLatitude; private Float currentApiPollutionLevelLongitude; /** * Set up the sync adapter */ SmogAppSyncAdapter(Context context, boolean autoInitialize) { super(context, autoInitialize); /* * If your app uses a content resolver, get an instance of it * from the incoming Context */ mContentResolver = context.getContentResolver(); prefs = PreferenceManager.getDefaultSharedPreferences(context); syncContext = context; prefsHomeLocationLatitude = prefs.getFloat(syncContext.getResources().getString(R.string.pref_key_home_latitude), 0f); prefsHomeLocationLongitude = prefs.getFloat(syncContext.getResources().getString(R.string.pref_key_home_longitude), 0f); prefsNewMessageNotification = prefs.getBoolean(syncContext.getResources().getString(R.string.pref_key_notification_new_message), true); prefsPollutionLevel = Integer.valueOf(prefs.getString(syncContext.getResources().getString(R.string.pref_key_pollution_level_list), "0")); } @Override public void onPerformSync(Account account, Bundle extras, String authority, ContentProviderClient provider, SyncResult syncResult) { // fetching remote data and insert some stuff Log.d(LOG_TAG, "onPerformSync was called"); } /** * Helper method to schedule the sync adapter periodic execution */ private static void configurePeriodicSync(Context context, int syncInterval, int flexTime) { Account account = getSyncAccount(context); String authority = context.getString(R.string.content_authority); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { // we can enable inexact timers in our periodic sync SyncRequest request = new SyncRequest.Builder(). syncPeriodic(syncInterval, flexTime). setSyncAdapter(account, authority). setExtras(new Bundle()).build(); ContentResolver.requestSync(request); } else { ContentResolver.addPeriodicSync(account, authority, new Bundle(), syncInterval); } } /** * Helper method to have the sync adapter sync immediately * * @param context The context used to access the account service */ private static void syncImmediately(Context context) { Bundle bundle = new Bundle(); bundle.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true); bundle.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true); ContentResolver.requestSync(getSyncAccount(context), context.getString(R.string.content_authority), bundle); } /** * Helper method to get the fake account to be used with SyncAdapter, or make a new one * if the fake account doesn't exist yet. If we make a new account, we call the * onAccountCreated method so we can initialize things. * * @param context The context used to access the account service * @return a fake account. */ public static Account getSyncAccount(Context context) { Log.d(LOG_TAG, "getSyncAccount"); // Get an instance of the Android account manager AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE); // Create the account type and default account Account newAccount = new Account( context.getString(R.string.app_name), context.getString(R.string.sync_account_type)); // If the password doesn't exist, the account doesn't exist if (null == accountManager.getPassword(newAccount)) { /* * Add the account and account type, no password or user data * If successful, return the Account object, otherwise report an error. */ if (!accountManager.addAccountExplicitly(newAccount, "", null)) { Log.d(LOG_TAG, "return null"); return null; } /* * If you don't set android:syncable="true" in * in your <provider> element in the manifest, * then call ContentResolver.setIsSyncable(account, AUTHORITY, 1) * here. */ onAccountCreated(newAccount, context); } else { Log.d(LOG_TAG, "If the password doesn't exist, the account doesn't exist"); } Log.d(LOG_TAG, "Account name: " + newAccount.name); return newAccount; } private static void onAccountCreated(Account newAccount, Context context) { Log.d(LOG_TAG, "onAccountCreated"); /* * Since we've created an account */ SmogAppSyncAdapter.configurePeriodicSync(context, SYNC_INTERVAL, SYNC_FLEXTIME); /* * Without calling setSyncAutomatically, our periodic sync will not be enabled. */ ContentResolver.setIsSyncable(newAccount, context.getString(R.string.content_authority), 1); ContentResolver.setSyncAutomatically(newAccount, context.getString(R.string.content_authority), true); /* * Finally, let do a sync to get things started */ // syncImmediately(context); } public static void initializeSyncAdapter(Context context) { Log.d(LOG_TAG, "inside initializeSyncAdapter"); getSyncAccount(context); }
}
My service:
public class SmogAppSyncService extends Service { private static SmogAppSyncAdapter sSyncAdapter = null; private static final Object sSyncAdapterLock = new Object(); @Override public void onCreate() { synchronized (sSyncAdapterLock) { sSyncAdapter = new SmogAppSyncAdapter(getApplicationContext(), true); } } @Nullable @Override public IBinder onBind(Intent intent) { return sSyncAdapter.getSyncAdapterBinder(); }
}
In my manifest, I added:
<service android:name=".services.sync.SmogAppAuthenticatorService"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/authenticator" /> </service> <service android:name=".services.sync.SmogAppSyncService" android:exported="true" android:process=":sync"> <intent-filter> <action android:name="android.content.SyncAdapter" /> </intent-filter> <meta-data android:name="android.content.SyncAdapter" android:resource="@xml/syncadapter" /> </service>
and permissions:
<uses-permission android:name="android.permission.READ_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.WRITE_SYNC_SETTINGS" /> <uses-permission android:name="android.permission.AUTHENTICATE_ACCOUNTS" />
Here are my other xml files:
<?xml version="1.0" encoding="utf-8"?> <sync-adapter xmlns:android="http://schemas.android.com/apk/res/android" android:contentAuthority="@string/content_authority" android:accountType="@string/sync_account_type" android:userVisible="false" android:supportsUploading="false" android:allowParallelSyncs="false" android:isAlwaysSyncable="true" />
Authetnicator.xml
<?xml version="1.0" encoding="utf-8"?> <account-authenticator xmlns:android="http://schemas.android.com/apk/res/android" android:accountType="@string/sync_account_type" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:smallIcon="@mipmap/ic_launcher" />
I can provide more details if helpful. I am really stuck with this issue and have checked some of the answers on stackoverflow. It didn't really work for me. Is there a way to get this to work? Periodic sync works on the emulator, but not on a real device.
UPDATE: I read about Doze mode which might be the cause, but this is probably not my case, or I just configured something wrong. In principle, Dose mode with optimal battery settings can disable some background tasks on the device.
source share
From the SyncRequest.Builder#syncPeriodic(long, long)
javadoc:
/** * Build a periodic sync. ... * @param pollFrequency the amount of time in seconds that you wish * to elapse between periodic syncs. A minimum period of 1 hour is enforced. ... */ public Builder syncPeriodic(long pollFrequency, long beforeSeconds) { ... }
Note that it states that the minimum periodic sync timeout is 1 hour. This is probably your problem.
But since when? I have not heard such a long timeout before. Let's put it in.
I ran the following commands:
$ cd ~/aosp/frameworks/base $ find ./ -name SyncRequest.java | xargs git blame | grep "A minimum period of 1 hour is enforced"
And get this result:
e96c3b7eff52 (Shreyas Basarge 2016-01-29 19:25:51 +0000 310) * to elapse between periodic syncs. A minimum period of 1 hour is enforced.
It looks like a commit from Jan 2016. This explains why it works on API 19 but not 25.
I also confirmed that this post added code that increased the minimum timeout from 60 seconds to 1 hour.
And then you might ask, why would Google possibly change the semantics of a working API that many applications rely on without proper notice to developers? And the answer is as usual - because they can.
source share