package com.orgzly.android.sync;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.AsyncTask;
import android.os.Binder;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.support.v4.content.LocalBroadcastManager;
import com.orgzly.BuildConfig;
import com.orgzly.R;
import com.orgzly.android.BookAction;
import com.orgzly.android.AppIntent;
import com.orgzly.android.Shelf;
import com.orgzly.android.prefs.AppPreferences;
import com.orgzly.android.repos.DirectoryRepo;
import com.orgzly.android.repos.Repo;
import com.orgzly.android.util.AppPermissions;
import com.orgzly.android.util.LogUtils;
import java.io.IOException;
import java.util.Collection;
import java.util.Map;
public class SyncService extends Service {
public static final String TAG = SyncService.class.getName();
private SyncStatus status = new SyncStatus();
private Shelf shelf;
private SyncTask syncTask;
// private NotificationManager notificationManager;
private final IBinder binder = new LocalBinder();
@Override
public void onCreate() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
shelf = new Shelf(this);
// notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// startForeground();
status.loadFromPreferences(this);
}
// private void startForeground() {
// startForeground(NOTIFICATION_ID, createNotification(getString(R.string.syncing_in_progress)));
// }
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG, intent);
if (intent != null && AppIntent.ACTION_SYNC_START.equals(intent.getAction())) {
if (!isRunning()) {
start();
}
} else if (intent != null && AppIntent.ACTION_SYNC_STOP.equals(intent.getAction())) {
if (isRunning()) {
stop();
}
} else {
if (isRunning()) {
stop();
} else {
start();
}
}
return Service.START_REDELIVER_INTENT;
}
private boolean isRunning() {
return syncTask != null;
}
private void start() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
Map<String, Repo> repos = shelf.getAllRepos();
/* There are no repositories configured. */
if (repos.size() == 0) {
status.set(SyncStatus.Type.FAILED, getString(R.string.no_repos_configured), 0, 0);
announceActiveSyncStatus();
stopSelf();
return;
}
/* If one of the repositories requires internet connection, check for it. */
if (reposRequireConnection(repos.values()) && !haveNetworkConnection()) {
status.set(SyncStatus.Type.FAILED, getString(R.string.no_connection), 0, 0);
announceActiveSyncStatus();
stopSelf();
return;
}
/* Make sure we have permission to access local storage,
* if there are repositories that would use it.
*/
if (reposRequireStoragePermission(repos.values())) {
if (AppPermissions.isNotGranted(this, AppPermissions.FOR_SYNC_START)) {
status.set(SyncStatus.Type.NO_STORAGE_PERMISSION, null, 0, 0);
announceActiveSyncStatus();
stopSelf();
return;
}
}
syncTask = new SyncTask();
syncTask.execute();
}
private void stop() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
status.set(SyncStatus.Type.CANCELING, null, status.currentBook, status.totalBooks);
announceActiveSyncStatus();
syncTask.cancel(false);
}
private boolean reposRequireConnection(Collection<Repo> repos) {
for (Repo repo: repos) {
if (repo.requiresConnection()) {
return true;
}
}
return false;
}
private boolean reposRequireStoragePermission(Collection<Repo> repos) {
for (Repo repo: repos) {
if (DirectoryRepo.SCHEME.equals(repo.getUri().getScheme())) {
return true;
}
}
return false;
}
/**
* Determines if there is internet connection available.
*/
private boolean haveNetworkConnection() {
ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo networkInfo = cm.getActiveNetworkInfo();
if (networkInfo != null) {
int type = networkInfo.getType();
if (type == ConnectivityManager.TYPE_WIFI || type == ConnectivityManager.TYPE_MOBILE) {
return true;
}
}
return false;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
return binder;
}
@Override
public boolean onUnbind(Intent intent) {
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
if (BuildConfig.LOG_DEBUG) LogUtils.d(TAG);
/* Kill the on-going notification (might not exist). */
// notificationManager.cancel(NOTIFICATION_ID);
super.onDestroy();
}
/**
* Announce current sync status.
*/
public void announceActiveSyncStatus() {
if (BuildConfig.LOG_DEBUG)
LogUtils.d(TAG, status.type, status.message, status.currentBook, status.totalBooks);
/* Broadcast the intent. */
LocalBroadcastManager.getInstance(this).sendBroadcast(status.intent());
status.saveToPreferences(this);
}
public SyncStatus getStatus() {
return status;
}
/**
* Main sync task.
*/
private class SyncTask extends AsyncTask<Void, Object, Exception> {
@Override
protected void onPreExecute() {
status.set(SyncStatus.Type.STARTING, null, 0, 0);
announceActiveSyncStatus();
}
@Override
protected Exception doInBackground(Void... params) { /* Executing on a different thread. */
/* Get the list of local and remote books from all repositories.
* Group them by name.
* Inserts dummy books if they don't exist in database.
*/
Map<String, BookNamesake> namesakes;
try {
namesakes = shelf.groupAllNotebooksByName();
} catch (Exception e) {
e.printStackTrace();
return e;
}
if (namesakes.size() == 0) {
return new IOException("No notebooks found");
}
status.set(SyncStatus.Type.BOOKS_COLLECTED, null, 0, namesakes.size());
announceActiveSyncStatus();
/*
* Update books' statuses, before starting to sync them.
*/
for (BookNamesake namesake : namesakes.values()) {
shelf.setBookStatus(namesake.getBook(), null, new BookAction(BookAction.Type.PROGRESS, getString(R.string.syncing_in_progress)));
}
/*
* Start syncing name by name.
*/
int curr = 0;
for (BookNamesake namesake : namesakes.values()) {
/* If task has been canceled, just mark the remaining books as such. */
if (isCancelled()) {
shelf.setBookStatus(namesake.getBook(), null,
new BookAction(BookAction.Type.INFO, getString(R.string.canceled)));
} else {
status.set(SyncStatus.Type.BOOK_STARTED, namesake.getName(), curr, namesakes.size());
announceActiveSyncStatus();
try {
BookAction action = shelf.syncNamesake(namesake);
shelf.setBookStatus(namesake.getBook(), namesake.getStatus().toString(), action);
} catch (Exception e) {
e.printStackTrace();
shelf.setBookStatus(namesake.getBook(), null, new BookAction(BookAction.Type.ERROR, e.getMessage()));
}
status.set(SyncStatus.Type.BOOK_ENDED, namesake.getName(), curr + 1, namesakes.size());
announceActiveSyncStatus();
}
curr++;
}
return null; /* Success. */
}
@Override
protected void onCancelled(Exception e) {
status.set(SyncStatus.Type.CANCELED, getString(R.string.canceled), 0, 0);
announceActiveSyncStatus();
syncTask = null;
stopSelf();
}
@Override
protected void onPostExecute(Exception exception) {
if (exception != null) {
String msg = (exception.getMessage() != null ?
exception.getMessage() : exception.toString());
status.set(SyncStatus.Type.FAILED, msg, 0, 0);
} else {
status.set(SyncStatus.Type.FINISHED, null, 0, 0);
/** Save last successful sync time to preferences. */
long time = System.currentTimeMillis();
AppPreferences.lastSuccessfulSyncTime(getApplicationContext(), time);
}
announceActiveSyncStatus();
syncTask = null;
stopSelf();
}
}
public class LocalBinder extends Binder {
public SyncService getService() {
return SyncService.this;
}
}
}