/*
* Kontalk Android client
* Copyright (C) 2017 Kontalk Devteam <devteam@kontalk.org>
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kontalk.sync;
import android.accounts.Account;
import android.accounts.OperationCanceledException;
import android.content.AbstractThreadedSyncAdapter;
import android.content.ContentProviderClient;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.SyncResult;
import android.os.Bundle;
import android.os.SystemClock;
import android.provider.ContactsContract;
import android.support.v4.content.LocalBroadcastManager;
import org.kontalk.Log;
import org.kontalk.authenticator.Authenticator;
import org.kontalk.provider.UsersProvider;
import org.kontalk.service.msgcenter.MessageCenterService;
import org.kontalk.util.Preferences;
/**
* The Sync Adapter.
* @author Daniele Ricci
*/
public class SyncAdapter extends AbstractThreadedSyncAdapter {
public static final String TAG = SyncAdapter.class.getSimpleName();
/** How many seconds between sync operations. */
private static final int MAX_SYNC_DELAY = 600;
/** Broadcast action: sync has started. */
public static final String ACTION_SYNC_START = "org.kontalk.sync.action.START";
/** Broadcast action: sync has finished. */
public static final String ACTION_SYNC_FINISH = "org.kontalk.sync.action.FINISH";
private final Context mContext;
private final LocalBroadcastManager mBroadcastManager;
private Syncer mSyncer;
public SyncAdapter(Context context, boolean autoInitialize) {
super(context, autoInitialize);
mContext = context;
mBroadcastManager = LocalBroadcastManager.getInstance(context);
}
@Override
public void onPerformSync(Account account, Bundle extras, String authority,
ContentProviderClient provider, SyncResult syncResult) {
try {
// broadcast sync start
mBroadcastManager.sendBroadcast(new Intent(ACTION_SYNC_START));
final long startTime = SystemClock.elapsedRealtime();
boolean force = extras.getBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, false);
// do not start if offline
if (Preferences.getOfflineMode()) {
Log.d(TAG, "not requesting sync - offline mode");
return;
}
// do not start if no server available (limbo state)
if (Preferences.getEndpointServer(mContext) == null) {
Log.d(TAG, "no server available - aborting");
return;
}
if (!force) {
if (isThrottling()) {
Log.d(TAG, "not starting sync - throttling");
// TEST do not delay - syncResult.delayUntil = (long) diff;
return;
}
}
Log.i(TAG, "sync started (authority=" + authority + ")");
// avoid other syncs to get scheduled in the meanwhile
Preferences.setLastSyncTimestamp(System.currentTimeMillis());
ContentProviderClient usersProvider = getContext().getContentResolver()
.acquireContentProviderClient(UsersProvider.AUTHORITY);
try {
// hold a reference to the message center while syncing
MessageCenterService.hold(mContext, true);
// start sync
mSyncer = new Syncer(mContext);
mSyncer.performSync(mContext, account, authority,
provider, usersProvider, syncResult);
}
catch (OperationCanceledException e) {
Log.w(TAG, "sync canceled!", e);
}
finally {
// release the message center
MessageCenterService.release(mContext);
// release user provider
usersProvider.release();
Preferences.setLastSyncTimestamp(System.currentTimeMillis());
// some stats :)
long endTime = SystemClock.elapsedRealtime();
Log.d(TAG, String.format("sync took %.5f seconds",
((float) (endTime - startTime)) / 1000));
}
}
finally {
// broadcast sync finish
mBroadcastManager.sendBroadcast(new Intent(ACTION_SYNC_FINISH));
}
}
@Override
public void onSyncCanceled() {
super.onSyncCanceled();
mSyncer.onSyncCanceled();
}
/**
* Requests a manual sync to the system.
* @return true if the sync has been actually requested to the system.
*/
public static boolean requestSync(Context context, boolean force) {
if (!force && isThrottling()) {
Log.d(TAG, "not requesting sync - throttling");
return false;
}
// do not start if offline
if (Preferences.getOfflineMode()) {
Log.d(TAG, "not requesting sync - offline mode");
return false;
}
Account acc = Authenticator.getDefaultAccount(context);
Bundle extra = new Bundle();
// override auto-sync and background data settings
extra.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
// put our sync ahead of other sync operations :)
extra.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
ContentResolver.requestSync(acc, ContactsContract.AUTHORITY, extra);
return true;
}
public static boolean isThrottling() {
long lastSync = Preferences.getLastSyncTimestamp();
float diff = (System.currentTimeMillis() - lastSync) / 1000;
return (lastSync >= 0 && diff < MAX_SYNC_DELAY);
}
public static boolean isPending(Context context) {
Account acc = Authenticator.getDefaultAccount(context);
return ContentResolver.isSyncPending(acc, ContactsContract.AUTHORITY);
}
public static boolean isActive(Context context) {
Account acc = Authenticator.getDefaultAccount(context);
return acc != null && ContentResolver.isSyncActive(acc, ContactsContract.AUTHORITY);
}
public static boolean isError(SyncResult syncResult) {
return syncResult.databaseError || syncResult.stats.numIoExceptions > 0;
}
public static String getIQPacketId() {
return Syncer.IQ_PACKET_ID;
}
}