/* * Copyright (c) 2015 Jonas Kalderstam. * * 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 com.nononsenseapps.notepad.data.service; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Context; import android.content.Intent; import android.database.ContentObserver; import android.net.Uri; import android.os.Handler; import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.Process; import android.preference.PreferenceManager; import android.support.v4.app.NotificationCompat; import com.nononsenseapps.notepad.data.remote.orgmodedropbox.DropboxSynchronizer; import com.nononsenseapps.notepad.data.local.orgmode.Monitor; import com.nononsenseapps.notepad.data.local.orgmode.SDSynchronizer; import com.nononsenseapps.notepad.data.local.orgmode.SynchronizerInterface; import com.nononsenseapps.notepad.util.Log; import com.nononsenseapps.notepad.BuildConfig; import com.nononsenseapps.notepad.data.model.sql.Task; import com.nononsenseapps.notepad.data.model.sql.TaskList; import com.nononsenseapps.notepad.ui.settings.PrefsActivity; import com.nononsenseapps.notepad.ui.settings.SyncPrefs; import com.nononsenseapps.notepad.data.service.gtasks.SyncAdapter; import com.nononsenseapps.notepad.util.SharedPreferencesHelper; import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; public class OrgSyncService extends Service { private static final String TAG = "OrgSyncService"; public static final String ACTION_START = "com.nononsenseapps.notepad" + ".sync.START"; public static final String ACTION_PAUSE = "com.nononsenseapps.notepad" + ".sync.PAUSE"; // Msg arguments public static final int TWO_WAY_SYNC = 1; public static final int SYNC_QUEUE = 2; public static final int SYNC_RUN = 3; private static final int DELAY_MSECS = 30000; private boolean firstStart = true; private Looper serviceLooper; private SyncHandler serviceHandler; // private FileWatcher fileWatcher; private DBWatcher dbWatcher; private final ArrayList<Monitor> monitors; private final ArrayList<SynchronizerInterface> synchronizers; public static void start(Context context) { context.startService(new Intent(context, OrgSyncService.class) .setAction(ACTION_START)); } public static void pause(Context context) { context.startService(new Intent(context, OrgSyncService.class) .setAction(ACTION_PAUSE)); } public static void stop(Context context) { context.stopService(new Intent(context, OrgSyncService.class)); } public static boolean areAnyEnabled(Context context) { return SharedPreferencesHelper.isSdSyncEnabled(context) || (BuildConfig.DROPBOX_ENABLED && SharedPreferencesHelper.isDropboxSyncEnabled(context)); } public OrgSyncService() { monitors = new ArrayList<Monitor>(); synchronizers = new ArrayList<SynchronizerInterface>(); } /** * Will only return Synchronizers which have been configured. * @return configured Synchronizers */ public ArrayList<SynchronizerInterface> getSynchronizers() { ArrayList<SynchronizerInterface> syncers = new ArrayList<SynchronizerInterface>(); // Try SD SynchronizerInterface sd = new SDSynchronizer(this); if (sd.isConfigured()) { syncers.add(sd); } // Try Dropbox if (BuildConfig.DROPBOX_ENABLED) { SynchronizerInterface db = new DropboxSynchronizer(this); if (db.isConfigured()) { syncers.add(db); } } return syncers; } @Override public void onCreate() { // Start up the thread running the service. Note that we create a // separate thread because the service normally runs in the process's // main thread, which we don't want to block. We also make it // background priority so CPU-intensive work will not disrupt our UI. HandlerThread thread = new HandlerThread("ServiceStartArguments", Process.THREAD_PRIORITY_BACKGROUND); thread.start(); // Get the HandlerThread's Looper and use it for our Handler serviceLooper = thread.getLooper(); serviceHandler = new SyncHandler(serviceLooper); } @Override public int onStartCommand(Intent intent, int flags, int startId) { if (intent != null && intent.getAction() != null && ACTION_PAUSE.equals(intent.getAction())) { pause(); } else { final Message msg = serviceHandler.obtainMessage(); msg.arg1 = TWO_WAY_SYNC; serviceHandler.sendMessage(msg); } // If we get killed, after returning from here, restart return START_STICKY; } private void pause() { // Pause monitors for (Monitor monitor: monitors) { monitor.pauseMonitor(); } } private void notifyError() { NotificationCompat.Builder notBuilder = new NotificationCompat.Builder( this) // TODO hardcoded .setContentTitle("Could not access files") .setContentText("Please change directory") .setContentIntent( PendingIntent.getActivity(this, 0, new Intent(this, PrefsActivity.class), PendingIntent.FLAG_UPDATE_CURRENT)); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); notificationManager.notify(237388, notBuilder.build()); } @Override public void onDestroy() { // Unregister observers for (Monitor monitor: monitors) { monitor.terminate(); } } @Override public IBinder onBind(Intent intent) { return null; } // Handler that receives messages from the thread public final class SyncHandler extends Handler { private int changeId = 0; private int lastChangeId; public SyncHandler(Looper looper) { super(looper); } public void onMonitorChange() { Log.d(TAG, "OnMonitorChange"); // Increment the changeId changeId++; // First queue the operation final Message q = obtainMessage(); q.arg1 = SYNC_QUEUE; q.arg2 = changeId; sendMessage(q); // Next, schedule a run in a short delay. // Only the run number matching a queue number will run (last one) final Message r =obtainMessage(); r.arg1 = SYNC_RUN; r.arg2 = changeId; sendMessageDelayed(r, DELAY_MSECS); } @Override public void handleMessage(Message msg) { if (synchronizers.isEmpty()) { synchronizers.addAll(getSynchronizers()); } // Get monitors if empty if (monitors.isEmpty()) { // First db watcher monitors.add(new DBWatcher(this)); // Then remote sources for (final SynchronizerInterface syncer: synchronizers) { final Monitor monitor = syncer.getMonitor(); if (monitor != null) { monitors.add(monitor); } } } try { /* * Queues are used to delay operations until subsequent updates * are complete. */ switch (msg.arg1) { case SYNC_QUEUE: Log.d(TAG, "Sync-Queue: " + msg.arg2); lastChangeId = msg.arg2; break; case SYNC_RUN: Log.d(TAG, "Sync-Run: " + msg.arg2); if (msg.arg2 != lastChangeId) { // Wait... return; } // Falling through case TWO_WAY_SYNC: Log.d(TAG, "Sync-Two-Way: " + msg.arg2); // Pause monitors for (final Monitor monitor: monitors) { monitor.pauseMonitor(); } // Sync each for (final SynchronizerInterface syncer : synchronizers) { sendBroadcast(new Intent(SyncAdapter.SYNC_STARTED)); syncer.fullSync(); syncer.postSynchronize(); } sendBroadcast(new Intent(SyncAdapter.SYNC_FINISHED)); // Restart monitors for (final Monitor monitor: monitors) { monitor.startMonitor(this); } // Save last sync time PreferenceManager .getDefaultSharedPreferences(OrgSyncService.this) .edit().putLong(SyncPrefs.KEY_LAST_SYNC, Calendar.getInstance() .getTimeInMillis()) .commit(); break; } } catch (IOException ignored) { Log.e(TAG, ignored.getMessage()); } catch (ParseException ignored) { } } } private final class DBWatcher extends ContentObserver implements Monitor { private final SyncHandler handler; // Giving it the service handler, onChange will run on that thread public DBWatcher(SyncHandler handler) { super(handler); this.handler = handler; } public boolean deliverSelfNotifications() { return true; } @Override public void onChange(boolean selfChange) { onChange(selfChange, null); } @Override public void onChange(boolean selfChange, Uri uri) { handler.onMonitorChange(); } @Override public void startMonitor(final SyncHandler handler) { // Monitor both lists and tasks getContentResolver().registerContentObserver(TaskList.URI, true, this); getContentResolver().registerContentObserver(Task.URI, true, this); } @Override public void pauseMonitor() { getContentResolver().unregisterContentObserver(this); } @Override public void terminate() { pauseMonitor(); } } }