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.

+3


source share


1 answer


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.

+12


source







All Articles