/* * 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; } } }