diff --git a/api/src/com/todoroo/andlib/utility/AndroidUtilities.java b/api/src/com/todoroo/andlib/utility/AndroidUtilities.java index 81cf3def7..53d708de8 100644 --- a/api/src/com/todoroo/andlib/utility/AndroidUtilities.java +++ b/api/src/com/todoroo/andlib/utility/AndroidUtilities.java @@ -510,16 +510,55 @@ public class AndroidUtilities { * @param args arguments * @return method return value, or null if nothing was called or exception */ - @SuppressWarnings("nls") public static Object callApiMethod(int minSdk, Object receiver, String methodName, Class[] params, Object... args) { if(getSdkVersion() < minSdk) return null; - Method method; + return AndroidUtilities.callMethod(receiver.getClass(), + receiver, methodName, params, args); + } + + /** + * Call a static method via reflection if API level is at least minSdk + * @param minSdk minimum sdk number (i.e. 8) + * @param className fully qualified class to call method on + * @param methodName method name to call + * @param params method parameter types + * @param args arguments + * @return method return value, or null if nothing was called or exception + */ + @SuppressWarnings("nls") + public static Object callApiStaticMethod(int minSdk, String className, + String methodName, Class[] params, Object... args) { + if(getSdkVersion() < minSdk) + return null; + + try { + return AndroidUtilities.callMethod(Class.forName(className), + null, methodName, params, args); + } catch (ClassNotFoundException e) { + getExceptionService().reportError("call-method", e); + return null; + } + } + + /** + * Call a method via reflection + * @param class class to call method on + * @param receiver object to call method on (can be null) + * @param methodName method name to call + * @param params method parameter types + * @param args arguments + * @return method return value, or null if nothing was called or exception + */ + @SuppressWarnings("nls") + public static Object callMethod(Class cls, Object receiver, + String methodName, Class[] params, Object... args) { try { - method = receiver.getClass().getMethod(methodName, params); - return method.invoke(receiver, args); + Method method = cls.getMethod(methodName, params); + Object result = method.invoke(receiver, args); + return result; } catch (SecurityException e) { getExceptionService().reportError("call-method", e); } catch (NoSuchMethodException e) { diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index 6fb0366cc..e13742870 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -331,12 +331,6 @@ - - - - - - diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/TagUpdatesActivity.java b/astrid/plugin-src/com/todoroo/astrid/actfm/TagUpdatesActivity.java index 22155a939..8cd793a1a 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/TagUpdatesActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/TagUpdatesActivity.java @@ -1,7 +1,6 @@ package com.todoroo.astrid.actfm; import android.app.ListActivity; -import android.app.ProgressDialog; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; @@ -28,7 +27,6 @@ import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.DateUtilities; -import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.astrid.actfm.ActFmCameraModule.CameraResultCallback; import com.todoroo.astrid.actfm.ActFmCameraModule.ClearImageCallback; import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; @@ -38,6 +36,7 @@ import com.todoroo.astrid.dao.UpdateDao; import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Update; +import com.todoroo.astrid.helper.ProgressBarSyncResultCallback; import com.todoroo.astrid.service.StatisticsConstants; import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.service.TagDataService; @@ -141,7 +140,7 @@ public class TagUpdatesActivity extends ListActivity { }); refreshUpdatesList(); - refreshActivity(null); // start a pull in the background + refreshActivity(false); // start a pull in the background } private void refreshUpdatesList() { @@ -193,8 +192,7 @@ public class TagUpdatesActivity extends ListActivity { case MENU_REFRESH_ID: { - final ProgressDialog progressDialog = DialogUtilities.progressDialog(this, getString(R.string.DLG_please_wait)); - refreshActivity(progressDialog); + refreshActivity(true); return true; } @@ -202,20 +200,25 @@ public class TagUpdatesActivity extends ListActivity { } } - private void refreshActivity(final ProgressDialog progressDialog) { - actFmSyncService.fetchUpdatesForTag(tagData, true, new Runnable() { + private void refreshActivity(boolean manual) { + final ProgressBarSyncResultCallback callback = new ProgressBarSyncResultCallback( + this, R.id.progressBar, new Runnable() { + @Override + public void run() { + refreshUpdatesList(); + } + }); + + callback.started(); + callback.incrementMax(100); + actFmSyncService.fetchUpdatesForTag(tagData, manual, new Runnable() { @Override public void run() { - runOnUiThread(new Runnable() { - @Override - public void run() { - refreshUpdatesList(); - if (progressDialog != null) - DialogUtilities.dismissDialog(TagUpdatesActivity.this, progressDialog); - } - }); + callback.incrementProgress(50); + callback.finished(); } }); + callback.incrementProgress(50); } @SuppressWarnings("nls") diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/TagViewActivity.java b/astrid/plugin-src/com/todoroo/astrid/actfm/TagViewActivity.java index 19d5a88f4..7f23d8bd1 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/TagViewActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/TagViewActivity.java @@ -2,13 +2,10 @@ package com.todoroo.astrid.actfm; import greendroid.widget.AsyncImageView; -import java.io.IOException; - import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import android.app.ProgressDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; @@ -16,7 +13,6 @@ import android.content.IntentFilter; import android.os.Bundle; import android.text.TextUtils; import android.util.DisplayMetrics; -import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.MotionEvent; @@ -50,9 +46,9 @@ import com.todoroo.astrid.core.SortHelper; import com.todoroo.astrid.dao.TaskDao.TaskCriteria; import com.todoroo.astrid.data.TagData; import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.helper.ProgressBarSyncResultCallback; import com.todoroo.astrid.service.TagDataService; import com.todoroo.astrid.tags.TagFilterExposer; -import com.todoroo.astrid.tags.TagService; import com.todoroo.astrid.tags.TagService.Tag; import com.todoroo.astrid.welcome.HelpInfoPopover; @@ -71,7 +67,7 @@ public class TagViewActivity extends TaskListActivity { private static final int REQUEST_CODE_SETTINGS = 0; - public static final String TOKEN_START_ACTIVITY = "startActivity"; + public static final String TOKEN_START_ACTIVITY = "startActivity"; //$NON-NLS-1$ private TagData tagData; @@ -214,17 +210,6 @@ public class TagViewActivity extends TaskListActivity { cursor.close(); } - if(tagData.getValue(TagData.REMOTE_ID) > 0) { - String fetchKey = LAST_FETCH_KEY + tagData.getId(); - long lastFetchDate = Preferences.getLong(fetchKey, 0); - if(DateUtilities.now() > lastFetchDate + 300000L) { - refreshData(false, false); - Preferences.setLong(fetchKey, DateUtilities.now()); - } - } else { - ((TextView)taskListView.findViewById(android.R.id.empty)).setText(R.string.TLA_no_items); - } - setUpMembersGallery(); super.onNewIntent(intent); @@ -258,81 +243,33 @@ public class TagViewActivity extends TaskListActivity { // --------------------------------------------------------- refresh data - /** refresh the list with latest data from the web */ - private void refreshData(final boolean manual, boolean bypassTagShow) { - final boolean noRemoteId = tagData.getValue(TagData.REMOTE_ID) == 0; - - final ProgressDialog progressDialog; - if(manual && !noRemoteId) - progressDialog = DialogUtilities.progressDialog(this, getString(R.string.DLG_please_wait)); - else - progressDialog = null; - Thread tagShowThread = new Thread(new Runnable() { - @SuppressWarnings("nls") - @Override - public void run() { - try { - String oldName = tagData.getValue(TagData.NAME); - actFmSyncService.fetchTag(tagData); - - DialogUtilities.dismissDialog(TagViewActivity.this, progressDialog); - runOnUiThread(new Runnable() { - @Override - public void run() { - if(noRemoteId && tagData.getValue(TagData.REMOTE_ID) > 0) - refreshData(manual, true); - } - }); - - if(!oldName.equals(tagData.getValue(TagData.NAME))) { - TagService.getInstance().rename(oldName, - tagData.getValue(TagData.NAME)); - } - - } catch (IOException e) { - Log.e("tag-view-activity", "error-fetching-task-io", e); - } catch (JSONException e) { - Log.e("tag-view-activity", "error-fetching-task", e); - } - } - }); - if(!bypassTagShow) - tagShowThread.start(); + @Override + protected void initiateAutomaticSync() { + long lastAutoSync = Preferences.getLong(LAST_FETCH_KEY + tagData.getId(), 0); + if(DateUtilities.now() - lastAutoSync > DateUtilities.ONE_HOUR) + refreshData(false); + } - if(noRemoteId) { - ((TextView)taskListView.findViewById(android.R.id.empty)).setText(R.string.TLA_no_items); - return; - } + /** refresh the list with latest data from the web */ + private void refreshData(final boolean manual) { + ((TextView)taskListView.findViewById(android.R.id.empty)).setText(R.string.DLG_loading); - setUpMembersGallery(); - actFmSyncService.fetchTasksForTag(tagData, manual, new Runnable() { + syncService.synchronizeList(tagData, manual, new ProgressBarSyncResultCallback(this, + R.id.progressBar, new Runnable() { @Override public void run() { - runOnUiThread(new Runnable() { - @Override - public void run() { - loadTaskListContent(true); - ((TextView)taskListView.findViewById(android.R.id.empty)).setText(R.string.TLA_no_items); - DialogUtilities.dismissDialog(TagViewActivity.this, progressDialog); - } - }); + setUpMembersGallery(); + loadTaskListContent(true); + ((TextView)taskListView.findViewById(android.R.id.empty)).setText(R.string.TLA_no_items); } - }); + })); + Preferences.setLong(LAST_FETCH_KEY + tagData.getId(), DateUtilities.now()); - actFmSyncService.fetchUpdatesForTag(tagData, manual, new Runnable() { - @Override - public void run() { - runOnUiThread(new Runnable() { - @Override - public void run() { - //refreshUpdatesList(); - DialogUtilities.dismissDialog(TagViewActivity.this, progressDialog); - } - }); - } - }); + final boolean noRemoteId = tagData.getValue(TagData.REMOTE_ID) == 0; + if(noRemoteId && !manual) + ((TextView)taskListView.findViewById(android.R.id.empty)).setText(R.string.TLA_no_items); } private void setUpMembersGallery() { @@ -479,7 +416,7 @@ public class TagViewActivity extends TaskListActivity { //refreshUpdatesList(); } }); - refreshData(false, true); + refreshData(false); NotificationManager nm = new AndroidNotificationManager(ContextManager.getContext()); nm.cancel(tagData.getValue(TagData.REMOTE_ID).intValue()); @@ -528,7 +465,7 @@ public class TagViewActivity extends TaskListActivity { // handle my own menus switch (item.getItemId()) { case MENU_REFRESH_ID: - refreshData(true, false); + refreshData(true); return true; } diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncProvider.java index f54a91b8c..89bfc7b88 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncProvider.java @@ -133,15 +133,16 @@ public class ActFmSyncProvider extends SyncProvider { try { int serverTime = Preferences.getInt(ActFmPreferenceService.PREF_SERVER_TIME, 0); + ArrayList remoteTasks = new ArrayList(); - int newServerTime = fetchRemoteTasks(serverTime, remoteTasks); + // int newServerTime = fetchRemoteTasks(serverTime, remoteTasks); if (serverTime == 0) { // If we've never synced, we may lose some empty tags pushUnsavedTagData(); } - fetchRemoteTagData(serverTime); + // fetchRemoteTagData(serverTime); - SyncData syncData = populateSyncData(remoteTasks); + /* SyncData syncData = populateSyncData(remoteTasks); try { synchronizeTasks(syncData); @@ -150,7 +151,8 @@ public class ActFmSyncProvider extends SyncProvider { syncData.localUpdated.close(); } - Preferences.setInt(ActFmPreferenceService.PREF_SERVER_TIME, newServerTime); + Preferences.setInt(ActFmPreferenceService.PREF_SERVER_TIME, newServerTime); */ + actFmPreferenceService.recordSuccessfulSync(); syncSuccess = getFinalSyncStatus(); @@ -321,6 +323,7 @@ public class ActFmSyncProvider extends SyncProvider { } else { // Set default reminders for remotely created tasks TaskDao.setDefaultReminders(task.task); } + task.task.setValue(Task.LAST_SYNC, DateUtilities.now() + 1000); actFmDataService.saveTaskAndMetadata(task); } diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java index 4382389d3..2e517038c 100644 --- a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncService.java @@ -395,15 +395,17 @@ public final class ActFmSyncService { JSONObject result = actFmInvoker.invoke("task_save", params.toArray(new Object[params.size()])); ArrayList metadata = new ArrayList(); JsonHelper.taskFromJson(result, task, metadata); - Flags.set(Flags.ACTFM_SUPPRESS_SYNC); - taskDao.saveExisting(task); } catch (JSONException e) { handleException("task-save-json", e); } catch (IOException e) { if (notPermanentError(e)) addFailedPush(new FailedPush(PUSH_TYPE_TASK, task.getId())); handleException("task-save-io", e); + task.setValue(Task.LAST_SYNC, DateUtilities.now() + 1000L); } + + Flags.set(Flags.ACTFM_SUPPRESS_SYNC); + taskDao.saveExisting(task); } /** @@ -647,10 +649,11 @@ public final class ActFmSyncService { /** * Fetch all tags * @param serverTime + * @return new serverTime */ - public void fetchTags(int serverTime) throws JSONException, IOException { + public int fetchTags(int serverTime) throws JSONException, IOException { if(!checkForToken()) - return; + return 0; JSONObject result = actFmInvoker.invoke("tag_list", "token", token, "modified_after", serverTime); @@ -666,6 +669,53 @@ public final class ActFmSyncService { Long[] remoteIdArray = remoteIds.toArray(new Long[remoteIds.size()]); tagDataService.deleteWhere(Criterion.not(TagData.REMOTE_ID.in(remoteIdArray))); } + + return result.optInt("time", 0); + } + + /** + * Fetch active tasks asynchronously + * @param manual + * @param done + */ + public void fetchActiveTasks(final boolean manual, Runnable done) { + invokeFetchList("task", manual, new ListItemProcessor() { + @Override + protected void mergeAndSave(JSONArray list, HashMap locals) throws JSONException { + Task remote = new Task(); + + ArrayList metadata = new ArrayList(); + for(int i = 0; i < list.length(); i++) { + JSONObject item = list.getJSONObject(i); + readIds(locals, item, remote); + JsonHelper.taskFromJson(item, remote, metadata); + + if(remote.getValue(Task.USER_ID) == 0) { + if(!remote.isSaved()) + StatisticsService.reportEvent(StatisticsConstants.ACTFM_TASK_CREATED); + else if(remote.isCompleted()) + StatisticsService.reportEvent(StatisticsConstants.ACTFM_TASK_COMPLETED); + } + + if(!remote.isSaved() && remote.hasDueDate() && + remote.getValue(Task.DUE_DATE) < DateUtilities.now()) + remote.setFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE, false); + + Flags.set(Flags.ACTFM_SUPPRESS_SYNC); + taskService.save(remote); + metadataService.synchronizeMetadata(remote.getId(), metadata, MetadataCriteria.withKey(TagService.KEY)); + remote.clear(); + } + } + + @Override + protected HashMap getLocalModels() { + TodorooCursor cursor = taskService.query(Query.select(Task.ID, + Task.REMOTE_ID).where(Task.REMOTE_ID.in(remoteIds)).orderBy( + Order.asc(Task.REMOTE_ID))); + return cursorToMap(cursor, taskDao, Task.REMOTE_ID, Task.ID); + } + }, done, "active_tasks"); } /** diff --git a/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java new file mode 100644 index 000000000..78dc16837 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/actfm/sync/ActFmSyncV2Provider.java @@ -0,0 +1,234 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.actfm.sync; + +import java.io.IOException; +import java.util.concurrent.atomic.AtomicInteger; + +import org.json.JSONException; + +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.service.ExceptionService; +import com.todoroo.andlib.sql.Criterion; +import com.todoroo.andlib.sql.Query; +import com.todoroo.andlib.utility.Preferences; +import com.todoroo.astrid.dao.TaskDao.TaskCriteria; +import com.todoroo.astrid.data.TagData; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.service.AstridDependencyInjector; +import com.todoroo.astrid.service.StartupService; +import com.todoroo.astrid.service.SyncV2Service.SyncResultCallback; +import com.todoroo.astrid.service.SyncV2Service.SyncV2Provider; +import com.todoroo.astrid.service.TaskService; +import com.todoroo.astrid.tags.TagService; + +/** + * Exposes sync action + * + */ +public class ActFmSyncV2Provider implements SyncV2Provider { + + @Autowired ActFmPreferenceService actFmPreferenceService; + + @Autowired ActFmSyncService actFmSyncService; + + @Autowired ExceptionService exceptionService; + + @Autowired TaskService taskService; + + static { + AstridDependencyInjector.initialize(); + } + + public ActFmSyncV2Provider() { + DependencyInjectionService.getInstance().inject(this); + } + + @Override + public boolean isActive() { + return actFmPreferenceService.isLoggedIn(); + } + + private static final String LAST_TAG_FETCH_TIME = "actfm_lastTag"; //$NON-NLS-1$ + + // --- synchronize active tasks + + @Override + public void synchronizeActiveTasks(boolean manual, + final SyncResultCallback callback) { + + callback.started(); + callback.incrementMax(100); + + final AtomicInteger finisher = new AtomicInteger(2); + + startTagFetcher(callback, finisher); + + startTaskFetcher(manual, callback, finisher); + + pushQueued(callback, finisher); + + callback.incrementProgress(50); + } + + /** fetch changes to tags */ + private void startTagFetcher(final SyncResultCallback callback, + final AtomicInteger finisher) { + new Thread(new Runnable() { + @Override + public void run() { + int time = Preferences.getInt(LAST_TAG_FETCH_TIME, 0); + try { + time = actFmSyncService.fetchTags(time); + Preferences.setInt(LAST_TAG_FETCH_TIME, time); + } catch (JSONException e) { + exceptionService.reportError("actfm-sync", e); //$NON-NLS-1$ + } catch (IOException e) { + exceptionService.reportError("actfm-sync", e); //$NON-NLS-1$ + } finally { + callback.incrementProgress(20); + if(finisher.decrementAndGet() == 0) + callback.finished(); + } + } + }).start(); + } + + /** @return runnable to fetch changes to tags */ + private void startTaskFetcher(final boolean manual, final SyncResultCallback callback, + final AtomicInteger finisher) { + actFmSyncService.fetchActiveTasks(manual, new Runnable() { + @Override + public void run() { + callback.incrementProgress(30); + if(finisher.decrementAndGet() == 0) + callback.finished(); + } + }); + } + + private void pushQueued(final SyncResultCallback callback, + final AtomicInteger finisher) { + TodorooCursor cursor = taskService.query(Query.select(Task.PROPERTIES). + where(Criterion.or( + Criterion.and(TaskCriteria.isActive(), + Task.ID.gt(StartupService.INTRO_TASK_SIZE), + Task.REMOTE_ID.eq(0)), + Criterion.and(Task.REMOTE_ID.gt(0), + Task.MODIFICATION_DATE.gt(Task.LAST_SYNC))))); + + try { + callback.incrementMax(cursor.getCount() * 20); + finisher.addAndGet(cursor.getCount()); + + for(int i = 0; i < cursor.getCount(); i++) { + cursor.moveToNext(); + final Task task = new Task(cursor); + + new Thread(new Runnable() { + public void run() { + try { + actFmSyncService.pushTaskOnSave(task, task.getMergedValues()); + } finally { + callback.incrementProgress(20); + if(finisher.decrementAndGet() == 0) + callback.finished(); + } + } + }).start(); + } + } finally { + cursor.close(); + } + } + + // --- synchronize list + + @Override + public void synchronizeList(Object list, boolean manual, + final SyncResultCallback callback) { + + if(!(list instanceof TagData)) + return; + + TagData tagData = (TagData) list; + final boolean noRemoteId = tagData.getValue(TagData.REMOTE_ID) == 0; + + if(noRemoteId && !manual) + return; + + callback.started(); + callback.incrementMax(100); + + final AtomicInteger finisher = new AtomicInteger(3); + + fetchTagData(tagData, noRemoteId, manual, callback, finisher); + + if(!noRemoteId) { + fetchTasksForTag(tagData, manual, callback, finisher); + fetchUpdatesForTag(tagData, manual, callback, finisher); + } + + callback.incrementProgress(50); + } + + private void fetchTagData(final TagData tagData, final boolean noRemoteId, + final boolean manual, final SyncResultCallback callback, + final AtomicInteger finisher) { + new Thread(new Runnable() { + @Override + public void run() { + String oldName = tagData.getValue(TagData.NAME); + try { + actFmSyncService.fetchTag(tagData); + + if(noRemoteId) { + fetchTasksForTag(tagData, manual, callback, finisher); + fetchUpdatesForTag(tagData, manual, callback, finisher); + } + + if(!oldName.equals(tagData.getValue(TagData.NAME))) { + TagService.getInstance().rename(oldName, + tagData.getValue(TagData.NAME)); + } + } catch (IOException e) { + exceptionService.reportError("sync-io", e); //$NON-NLS-1$ + } catch (JSONException e) { + exceptionService.reportError("sync-json", e); //$NON-NLS-1$ + } finally { + callback.incrementProgress(20); + if(finisher.decrementAndGet() == 0) + callback.finished(); + } + } + }).start(); + } + + private void fetchUpdatesForTag(TagData tagData, boolean manual, final SyncResultCallback callback, + final AtomicInteger finisher) { + actFmSyncService.fetchUpdatesForTag(tagData, manual, new Runnable() { + @Override + public void run() { + callback.incrementProgress(20); + if(finisher.decrementAndGet() == 0) + callback.finished(); + } + }); + } + + private void fetchTasksForTag(TagData tagData, boolean manual, final SyncResultCallback callback, + final AtomicInteger finisher) { + actFmSyncService.fetchTasksForTag(tagData, manual, new Runnable() { + @Override + public void run() { + callback.incrementProgress(30); + if(finisher.decrementAndGet() == 0) + callback.finished(); + } + }); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java b/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java index 86e4f2b0d..8850a9082 100644 --- a/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/notes/EditNoteActivity.java @@ -10,7 +10,6 @@ import java.util.List; import org.json.JSONObject; import android.app.ListActivity; -import android.app.ProgressDialog; import android.content.Context; import android.graphics.Color; import android.os.Bundle; @@ -37,7 +36,6 @@ import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Query; import com.todoroo.andlib.utility.DateUtilities; -import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; import com.todoroo.astrid.actfm.sync.ActFmSyncService; @@ -47,9 +45,11 @@ import com.todoroo.astrid.dao.UpdateDao; import com.todoroo.astrid.data.Metadata; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.data.Update; +import com.todoroo.astrid.helper.ProgressBarSyncResultCallback; import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.StatisticsConstants; import com.todoroo.astrid.service.StatisticsService; +import com.todoroo.astrid.service.SyncV2Service.SyncResultCallback; import com.todoroo.astrid.utility.Flags; public class EditNoteActivity extends ListActivity { @@ -95,12 +95,12 @@ public class EditNoteActivity extends ListActivity { findViewById(R.id.add_comment).setVisibility(View.VISIBLE); if(task.getValue(Task.REMOTE_ID) == 0) - refreshData(true); + refreshData(true, null); else { String fetchKey = LAST_FETCH_KEY + task.getId(); long lastFetchDate = Preferences.getLong(fetchKey, 0); if(DateUtilities.now() > lastFetchDate + 300000L) { - refreshData(false); + refreshData(false, null); Preferences.setLong(fetchKey, DateUtilities.now()); } else { loadingText.setText(R.string.ENA_no_comments); @@ -223,21 +223,32 @@ public class EditNoteActivity extends ListActivity { // --- events - private void refreshData(boolean manual) { - final ProgressDialog progressDialog; - if(manual) - progressDialog = DialogUtilities.progressDialog(this, getString(R.string.DLG_please_wait)); - else - progressDialog = null; + private void refreshData(boolean manual, SyncResultCallback existingCallback) { + final SyncResultCallback callback; + if(existingCallback != null) + callback = existingCallback; + else { + callback = new ProgressBarSyncResultCallback( + this, R.id.progressBar, new Runnable() { + @Override + public void run() { + setUpListAdapter(); + loadingText.setText(R.string.ENA_no_comments); + loadingText.setVisibility(items.size() == 0 ? View.VISIBLE : View.GONE); + } + }); + + callback.started(); + callback.incrementMax(100); + } + // push task if it hasn't been pushed if(task.getValue(Task.REMOTE_ID) == 0) { - // push task if it hasn't been pushed new Thread(new Runnable() { @Override public void run() { actFmSyncService.pushTask(task.getId()); - refreshData(false); - DialogUtilities.dismissDialog(EditNoteActivity.this, progressDialog); + refreshData(false, callback); } }).start(); return; @@ -246,17 +257,11 @@ public class EditNoteActivity extends ListActivity { actFmSyncService.fetchUpdatesForTask(task, manual, new Runnable() { @Override public void run() { - runOnUiThread(new Runnable() { - @Override - public void run() { - setUpListAdapter(); - loadingText.setText(R.string.ENA_no_comments); - loadingText.setVisibility(items.size() == 0 ? View.VISIBLE : View.GONE); - DialogUtilities.dismissDialog(EditNoteActivity.this, progressDialog); - } - }); + callback.incrementProgress(50); + callback.finished(); } }); + callback.incrementProgress(50); } private void addComment() { @@ -288,7 +293,7 @@ public class EditNoteActivity extends ListActivity { switch (item.getItemId()) { case MENU_REFRESH_ID: { - refreshData(true); + refreshData(true, null); return true; } diff --git a/astrid/res/layout/edit_note_activity.xml b/astrid/res/layout/edit_note_activity.xml index b371ae39f..951355cf1 100644 --- a/astrid/res/layout/edit_note_activity.xml +++ b/astrid/res/layout/edit_note_activity.xml @@ -5,6 +5,15 @@ android:layout_height="fill_parent" android:orientation="vertical" android:background="@drawable/background_gradient"> + + + + + diff --git a/astrid/res/layout/task_list_activity.xml b/astrid/res/layout/task_list_activity.xml index fd58f4c46..27d9c841f 100644 --- a/astrid/res/layout/task_list_activity.xml +++ b/astrid/res/layout/task_list_activity.xml @@ -86,6 +86,14 @@ + + diff --git a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java index 0b22c0356..63920c95c 100644 --- a/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/FilterListActivity.java @@ -222,10 +222,6 @@ public class FilterListActivity extends ExpandableListActivity { R.string.FLA_menu_search); item.setIcon(android.R.drawable.ic_menu_search); - item = menu.add(Menu.NONE, MENU_REFRESH_ID, Menu.NONE, - R.string.TLA_menu_sync); - item.setIcon(R.drawable.ic_menu_refresh); - item = menu.add(Menu.NONE, MENU_HELP_ID, Menu.NONE, R.string.FLA_menu_help); item.setIcon(android.R.drawable.ic_menu_help); diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index e0b254b88..0a86a410c 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -74,8 +74,6 @@ import com.todoroo.andlib.utility.Preferences; import com.todoroo.andlib.widget.GestureService; import com.todoroo.andlib.widget.GestureService.GestureInterface; import com.todoroo.astrid.actfm.ActFmLoginActivity; -import com.todoroo.astrid.actfm.sync.ActFmPreferenceService; -import com.todoroo.astrid.actfm.sync.ActFmSyncProvider; import com.todoroo.astrid.activity.SortSelectionActivity.OnSortSelectedListener; import com.todoroo.astrid.adapter.TaskAdapter; import com.todoroo.astrid.adapter.TaskAdapter.OnCompletedTaskListener; @@ -96,6 +94,7 @@ import com.todoroo.astrid.data.Metadata; import com.todoroo.astrid.data.Task; import com.todoroo.astrid.gcal.GCalHelper; import com.todoroo.astrid.helper.MetadataHelper; +import com.todoroo.astrid.helper.ProgressBarSyncResultCallback; import com.todoroo.astrid.helper.TaskListContextMenuExtensionLoader; import com.todoroo.astrid.helper.TaskListContextMenuExtensionLoader.ContextMenuItem; import com.todoroo.astrid.reminders.ReminderDebugContextActions; @@ -105,6 +104,7 @@ import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.service.StartupService; import com.todoroo.astrid.service.StatisticsConstants; import com.todoroo.astrid.service.StatisticsService; +import com.todoroo.astrid.service.SyncV2Service; import com.todoroo.astrid.service.TagDataService; import com.todoroo.astrid.service.TaskService; import com.todoroo.astrid.service.ThemeService; @@ -163,8 +163,6 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, public static final String TOKEN_OVERRIDE_ANIM = "finishAnim"; //$NON-NLS-1$ - private static final String LAST_AUTOSYNC_ATTEMPT = "last-autosync"; //$NON-NLS-1$ - // --- instance variables @Autowired ExceptionService exceptionService; @@ -179,7 +177,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, @Autowired UpgradeService upgradeService; - @Autowired ActFmPreferenceService actFmPreferenceService; + @Autowired protected SyncV2Service syncService; @Autowired TagDataService tagDataService; @@ -239,7 +237,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, new StartupService().onStartupApplication(this); ThemeService.applyTheme(this); ViewGroup parent = (ViewGroup) getLayoutInflater().inflate(R.layout.task_list_activity, null); - parent.addView(getListBody(parent), 1); + parent.addView(getListBody(parent), 2); setContentView(parent); if(database == null) @@ -502,23 +500,6 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, getWindow().addFlags(WindowManager.LayoutParams.FLAG_DITHER); } - private void initiateAutomaticSync() { - if (!actFmPreferenceService.isLoggedIn()) return; - long lastFetchDate = actFmPreferenceService.getLastSyncDate(); - long lastAutosyncAttempt = Preferences.getLong(LAST_AUTOSYNC_ATTEMPT, 0); - - long lastTry = Math.max(lastFetchDate, lastAutosyncAttempt); - if(DateUtilities.now() < lastTry + 300000L) - return; - new Thread() { - @Override - public void run() { - Preferences.setLong(LAST_AUTOSYNC_ATTEMPT, DateUtilities.now()); - new ActFmSyncProvider().synchronize(TaskListActivity.this, false); - } - }.start(); - } - // Subclasses can override these to customize extras in quickadd intent protected Intent getOnClickQuickAddIntent(Task t) { Intent intent = new Intent(TaskListActivity.this, TaskEditActivity.class); @@ -616,9 +597,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, Preferences.setBoolean(R.string.p_showed_lists_help, true); } - if (filter.title != null && filter.title.equals(getString(R.string.BFE_Active))) { - initiateAutomaticSync(); - } + initiateAutomaticSync(); } @Override @@ -1136,8 +1115,31 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, } } - private void performSyncAction() { - if (syncActions.size() == 0) { + private static final String PREF_LAST_AUTO_SYNC = "taskListLastAutoSync"; //$NON-NLS-1$ + + protected void initiateAutomaticSync() { + if (filter.title == null || !filter.title.equals(getString(R.string.BFE_Active))) + return; + + long lastAutoSync = Preferences.getLong(PREF_LAST_AUTO_SYNC, 0); + if(DateUtilities.now() - lastAutoSync > DateUtilities.ONE_HOUR) { + performSyncServiceV2Sync(false); + } + } + + protected void performSyncServiceV2Sync(boolean manual) { + syncService.synchronizeActiveTasks(manual, new ProgressBarSyncResultCallback(this, + R.id.progressBar, new Runnable() { + @Override + public void run() { + loadTaskListContent(true); + } + })); + Preferences.setLong(PREF_LAST_AUTO_SYNC, DateUtilities.now()); + } + + protected void performSyncAction() { + if (syncActions.size() == 0 && !syncService.isActive()) { String desiredCategory = getString(R.string.SyP_label); // Get a list of all sync plugins and bring user to the prefs pane @@ -1178,32 +1180,20 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, showSyncOptionMenu(actions, listener); } - else if(syncActions.size() == 1) { - SyncAction syncAction = syncActions.iterator().next(); - try { - syncAction.intent.send(); - Toast.makeText(this, R.string.SyP_progress_toast, - Toast.LENGTH_LONG).show(); - } catch (CanceledException e) { - // - } - } else { - // We have >1 sync actions, pop up a dialogue so the user can - // select just one of them (only sync one at a time) - final SyncAction[] actions = syncActions.toArray(new SyncAction[syncActions.size()]); - DialogInterface.OnClickListener listener = new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface click, int which) { + else { + performSyncServiceV2Sync(true); + + if(syncActions.size() > 0) { + for(SyncAction syncAction : syncActions) { try { - actions[which].intent.send(); - Toast.makeText(TaskListActivity.this, R.string.SyP_progress_toast, - Toast.LENGTH_LONG).show(); + syncAction.intent.send(); } catch (CanceledException e) { // } } - }; - showSyncOptionMenu(actions, listener); + Toast.makeText(TaskListActivity.this, R.string.SyP_progress_toast, + Toast.LENGTH_LONG).show(); + } } } diff --git a/astrid/src/com/todoroo/astrid/helper/ProgressBarSyncResultCallback.java b/astrid/src/com/todoroo/astrid/helper/ProgressBarSyncResultCallback.java new file mode 100644 index 000000000..eda27d80b --- /dev/null +++ b/astrid/src/com/todoroo/astrid/helper/ProgressBarSyncResultCallback.java @@ -0,0 +1,96 @@ +package com.todoroo.astrid.helper; + +import java.util.concurrent.atomic.AtomicInteger; + +import android.app.Activity; +import android.view.View; +import android.view.animation.AlphaAnimation; +import android.widget.ProgressBar; + +import com.todoroo.andlib.utility.AndroidUtilities; +import com.todoroo.astrid.service.SyncV2Service.SyncResultCallback; + +public class ProgressBarSyncResultCallback implements SyncResultCallback { + + private final ProgressBar progressBar; + private final Activity activity; + private final Runnable onFinished; + + private final AtomicInteger providers = new AtomicInteger(0); + + public ProgressBarSyncResultCallback(Activity activity, + int progressBarId, Runnable onFinished) { + this.progressBar = (ProgressBar) activity.findViewById(progressBarId); + this.activity = activity; + this.onFinished = onFinished; + progressBar.setProgress(0); + progressBar.setMax(0); + } + + @Override + public void finished() { + if(providers.decrementAndGet() == 0) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + progressBar.setMax(100); + progressBar.setProgress(100); + AlphaAnimation animation = new AlphaAnimation(1, 0); + animation.setFillAfter(true); + animation.setDuration(1000L); + progressBar.startAnimation(animation); + + onFinished.run(); + } + }); + new Thread() { + @Override + public void run() { + AndroidUtilities.sleepDeep(1000); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.GONE); + } + }); + } + }.start(); + } + } + + @Override + public void incrementMax(final int incrementBy) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + progressBar.setMax(progressBar.getMax() + incrementBy); + } + }); + } + + @Override + public void incrementProgress(final int incrementBy) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + progressBar.incrementProgressBy(incrementBy); + } + }); + } + + @Override + public void started() { + if(providers.incrementAndGet() == 1) { + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + progressBar.setVisibility(View.VISIBLE); + AlphaAnimation animation = new AlphaAnimation(0, 1); + animation.setFillAfter(true); + animation.setDuration(1000L); + progressBar.startAnimation(animation); + } + }); + } + } +} \ No newline at end of file diff --git a/astrid/src/com/todoroo/astrid/service/AstridDependencyInjector.java b/astrid/src/com/todoroo/astrid/service/AstridDependencyInjector.java index bc355cebf..b849a3783 100644 --- a/astrid/src/com/todoroo/astrid/service/AstridDependencyInjector.java +++ b/astrid/src/com/todoroo/astrid/service/AstridDependencyInjector.java @@ -73,6 +73,7 @@ public class AstridDependencyInjector extends AbstractDependencyInjector { injectables.put("tagDataService", TagDataService.class); injectables.put("upgradeService", UpgradeService.class); injectables.put("addOnService", AddOnService.class); + injectables.put("syncService", SyncV2Service.class); // com.timsu.astrid.data injectables.put("tasksTable", "tasks"); diff --git a/astrid/src/com/todoroo/astrid/service/SyncV2Service.java b/astrid/src/com/todoroo/astrid/service/SyncV2Service.java new file mode 100644 index 000000000..b6545e8d0 --- /dev/null +++ b/astrid/src/com/todoroo/astrid/service/SyncV2Service.java @@ -0,0 +1,93 @@ +package com.todoroo.astrid.service; + +import com.todoroo.astrid.actfm.sync.ActFmSyncV2Provider; + +/** + * SyncV2Service is a simplified synchronization interface for supporting + * next-generation sync interfaces such as Google Tasks and Astrid.com + * + * @author Tim Su + * + */ +public class SyncV2Service { + + public interface SyncResultCallback { + /** + * Increment max sync progress + * @param incrementBy + */ + public void incrementMax(int incrementBy); + + /** + * Increment current sync progress + * @param incrementBy + */ + public void incrementProgress(int incrementBy); + + /** + * Provider started sync + */ + public void started(); + + /** + * Provider finished sync + */ + public void finished(); + } + + public interface SyncV2Provider { + public boolean isActive(); + public void synchronizeActiveTasks(boolean manual, SyncResultCallback callback); + public void synchronizeList(Object list, boolean manual, SyncResultCallback callback); + } + + /* + * At present, sync provider interactions are handled through code. If + * there is enough interest, the Astrid team could create an interface + * for responding to sync requests through this new API. + */ + private final SyncV2Provider[] providers = new SyncV2Provider[] { + new ActFmSyncV2Provider() + }; + + /** + * Determine if synchronization is available + * + * @param callback + */ + public boolean isActive() { + for(SyncV2Provider provider : providers) { + if(provider.isActive()) + return true; + } + return false; + } + + /** + * Initiate synchronization of active tasks + * + * @param manual if manual sync + * @param callback result callback + */ + public void synchronizeActiveTasks(boolean manual, SyncResultCallback callback) { + for(SyncV2Provider provider : providers) { + if(provider.isActive()) + provider.synchronizeActiveTasks(manual, callback); + } + } + + /** + * Initiate synchronization of task list + * + * @param list list object + * @param manual if manual sync + * @param callback result callback + */ + public void synchronizeList(Object list, boolean manual, SyncResultCallback callback) { + for(SyncV2Provider provider : providers) { + if(provider.isActive()) + provider.synchronizeList(list, manual, callback); + } + } + +}