/*
* Copyright 2014 sonaive.com. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.sonaive.v2ex.sync;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SyncResult;
import android.net.ConnectivityManager;
import android.os.Bundle;
import com.sonaive.v2ex.R;
import com.sonaive.v2ex.provider.V2exContract;
import com.sonaive.v2ex.sync.api.Api;
import com.sonaive.v2ex.sync.api.UserIdentityApi;
import com.sonaive.v2ex.util.AccountUtils;
import java.io.IOException;
import de.greenrobot.event.EventBus;
import static com.sonaive.v2ex.util.LogUtils.LOGD;
import static com.sonaive.v2ex.util.LogUtils.LOGE;
import static com.sonaive.v2ex.util.LogUtils.makeLogTag;
/**
* A helper class for dealing with data synchronization.
* All operations occur on the thread they're called from, so it's best to wrap
* calls in an {@link android.os.AsyncTask}, or better yet, a
* {@link android.app.Service}.
*
* Created by liutao on 12/6/14.
*/
public class SyncHelper {
private static final String TAG = makeLogTag(SyncHelper.class);
private Context mContext;
private V2exDataHandler mDataHandler;
public SyncHelper(Context context) {
mContext = context;
mDataHandler = new V2exDataHandler(mContext);
}
public static void requestManualSync(Context context, Bundle args) {
Account account = AccountUtils.getActiveAccount(context);
if (account != null) {
LOGD(TAG, "Requesting manual sync for account " + account.name
+" args=" + args.toString());
args.putBoolean(ContentResolver.SYNC_EXTRAS_MANUAL, true);
args.putBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, true);
args.putBoolean(SyncAdapter.EXTRA_SYNC_REMOTE, false);
AccountManager accountManager = (AccountManager) context.getSystemService(Context.ACCOUNT_SERVICE);
accountManager.addAccountExplicitly(account, null, null);
// Inform the system that this account is eligible for auto sync when the network is up
ContentResolver.setSyncAutomatically(account, V2exContract.CONTENT_AUTHORITY, true);
// Inform the system that this account supports sync
ContentResolver.setIsSyncable(account, V2exContract.CONTENT_AUTHORITY, 1);
boolean pending = ContentResolver.isSyncPending(account,
V2exContract.CONTENT_AUTHORITY);
if (pending) {
LOGD(TAG, "Warning: sync is PENDING. Will cancel.");
}
boolean active = ContentResolver.isSyncActive(account,
V2exContract.CONTENT_AUTHORITY);
if (active) {
LOGD(TAG, "Warning: sync is ACTIVE. Will cancel.");
}
if (pending || active) {
LOGD(TAG, "Cancelling previously pending/active sync.");
ContentResolver.cancelSync(account, V2exContract.CONTENT_AUTHORITY);
}
LOGD(TAG, "Requesting sync now.");
ContentResolver.requestSync(account, V2exContract.CONTENT_AUTHORITY, args);
} else {
LOGD(TAG, "Can't request manual sync -- no chosen account.");
}
}
/**
* Attempts to perform data synchronization.
*
* @param syncResult (optional) the sync result object to update with statistics.
* @param account the account associated with this sync
* @return Whether or not the synchronization made any changes to the data.
*/
public boolean performSync(SyncResult syncResult, Account account, Bundle extras) {
final boolean remoteSync = extras.getBoolean(SyncAdapter.EXTRA_SYNC_REMOTE, true);
// remote sync consists of these operations, which we try one by one (and tolerate
// individual failures on each)
String[] apisToPerform = remoteSync ?
new String[] { Api.API_TOPICS_LATEST, Api.API_TOPICS_HOT} :
new String[] { extras.getString(Api.ARG_API_NAME)};
for (String api : apisToPerform) {
try {
sync(api, extras);
} catch (Throwable throwable) {
throwable.printStackTrace();
EventBus.getDefault().postSticky(new ExceptionEvent(mContext.getString(R.string.err_io)));
LOGE(TAG, "Error performing remote sync.");
increaseIoExceptions(syncResult);
}
}
int operations = mDataHandler.getContentProviderOperationsDone();
if (syncResult != null && syncResult.stats != null) {
syncResult.stats.numEntries += operations;
syncResult.stats.numUpdates += operations;
}
LOGD(TAG, "SYNC STATS:\n" +
" * Account synced: " + (account == null ? "null" : account.name) + "\n" +
" * Content provider operations: " + operations);
return true;
}
private void sync(String api, Bundle args) throws IOException {
if (args == null) {
return;
}
switch (api) {
case Api.API_MEMBER: {
UserIdentityApi userIdentityApi = new UserIdentityApi(mContext);
String accountName = args.getString("accountName");
userIdentityApi.verifyUserIdentity(accountName);
break;
}
case Api.API_TOPICS_LATEST:
case Api.API_TOPICS_HOT:
case Api.API_TOPICS_SPECIFIC: {
Api feedsApi = new Api(mContext, args, api, V2exDataHandler.DATA_KEY_FEEDS);
// save the remote data to the database
mDataHandler.applyData(new Bundle[] {feedsApi.sync(Api.HttpMethod.GET)});
break;
}
case Api.API_NODES_ALL: {
Api allNodesApi = new Api(mContext, args, api, V2exDataHandler.DATA_KEY_NODES);
mDataHandler.applyData(new Bundle[] {allNodesApi.sync(Api.HttpMethod.GET)});
break;
}
case Api.API_NODES_SPECIFIC: {
Api specificNodesApi = new Api(mContext, args, api, V2exDataHandler.DATA_KEY_NODES);
mDataHandler.applyData(new Bundle[] {specificNodesApi.sync(Api.HttpMethod.GET)});
break;
}
case Api.API_REVIEWS: {
Api reviewsApi = new Api(mContext, args, api, V2exDataHandler.DATA_KEY_REVIEWS);
mDataHandler.applyData(new Bundle[] {reviewsApi.sync(Api.HttpMethod.GET)});
break;
}
}
}
// Returns whether we are connected to the internet.
private boolean isOnline() {
ConnectivityManager cm = (ConnectivityManager) mContext.getSystemService(
Context.CONNECTIVITY_SERVICE);
return cm.getActiveNetworkInfo() != null &&
cm.getActiveNetworkInfo().isConnectedOrConnecting();
}
private void increaseIoExceptions(SyncResult syncResult) {
if (syncResult != null && syncResult.stats != null) {
++syncResult.stats.numIoExceptions;
}
}
private void increaseSuccesses(SyncResult syncResult) {
if (syncResult != null && syncResult.stats != null) {
++syncResult.stats.numEntries;
++syncResult.stats.numUpdates;
}
}
}