diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index 69f5c00ed..45c08743b 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -1,8 +1,8 @@ + android:versionName="3.2.5 (build your own filters, easy sorting, customizable widget, ui improvements)" + android:versionCode="152"> @@ -343,6 +343,12 @@ + + + + + + diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java b/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java index 205b8936d..9d61282d0 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/BackupService.java @@ -15,6 +15,7 @@ import android.util.Log; import com.timsu.astrid.R; import com.todoroo.andlib.service.ContextManager; +import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.utility.Preferences; @@ -92,7 +93,7 @@ public class BackupService extends Service { if (!Preferences.getBoolean(R.string.backup_BPr_auto_key, true)) { return; } - am.setInexactRepeating(AlarmManager.RTC, System.currentTimeMillis() + BACKUP_OFFSET, + am.setInexactRepeating(AlarmManager.RTC, DateUtilities.now() + BACKUP_OFFSET, BACKUP_INTERVAL, pendingIntent); } diff --git a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java index e56407cc5..8c3cd18bc 100644 --- a/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java +++ b/astrid/plugin-src/com/todoroo/astrid/backup/TasksXmlImporter.java @@ -248,7 +248,7 @@ public class TasksXmlImporter { currentTask.setId(Task.NO_ID); // Save the task to the database. - taskService.save(currentTask, false); + taskService.save(currentTask); importCount++; } @@ -460,7 +460,7 @@ public class TasksXmlImporter { } // Save the task to the database. - taskService.save(task, false); + taskService.save(task); importCount++; return task; } diff --git a/astrid/plugin-src/com/todoroo/astrid/common/SyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/common/SyncProvider.java index 859809b97..11477a6b0 100644 --- a/astrid/plugin-src/com/todoroo/astrid/common/SyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/common/SyncProvider.java @@ -15,8 +15,8 @@ import android.content.Context; import android.widget.Toast; import com.timsu.astrid.R; -import com.todoroo.andlib.data.Property.LongProperty; import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.data.Property.LongProperty; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; @@ -72,9 +72,11 @@ public abstract class SyncProvider { /** * Create a task on the remote server. * - * @return task to create + * @param task + * task to create + * @return task created on remote server */ - abstract protected void create(TYPE task) throws IOException; + abstract protected TYPE create(TYPE task) throws IOException; /** * Push variables from given task to the remote server. @@ -231,10 +233,6 @@ public abstract class SyncProvider { remote.task.setId(local.task.getId()); data.remoteUpdated.set(remoteIndex, remote); - // if remote is deleted, undelete it, since we just created - if(remote.task.isDeleted()) - remote.task.setValue(Task.DELETION_DATE, 0L); - } else { create(local); } diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevBackgroundService.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevBackgroundService.java index 667d425d2..0d1a64bad 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevBackgroundService.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevBackgroundService.java @@ -8,11 +8,13 @@ import android.content.Intent; import android.os.IBinder; import android.util.Log; +import com.flurry.android.FlurryAgent; import com.timsu.astrid.R; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.utility.DateUtilities; import com.todoroo.astrid.core.PluginServices; import com.todoroo.astrid.producteev.sync.ProducteevSyncProvider; +import com.todoroo.astrid.utility.Constants; import com.todoroo.astrid.utility.Preferences; /** @@ -54,7 +56,9 @@ public class ProducteevBackgroundService extends Service { return; PluginServices.getTaskService(); + FlurryAgent.onStartSession(context, Constants.FLURRY_KEY); new ProducteevSyncProvider().synchronize(context); + FlurryAgent.onEndSession(context); } // --- alarm management diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java index 2cc21669c..dc7cbd2a9 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevControlSet.java @@ -87,7 +87,7 @@ public class ProducteevControlSet implements TaskEditControlSet { private void refreshResponsibleSpinner(ArrayList newUsers) { Metadata metadata = ProducteevDataService.getInstance().getTaskMetadata(myTask.getId()); long responsibleId = -1; - if(metadata.containsNonNullValue(ProducteevTask.RESPONSIBLE_ID)) + if(metadata != null && metadata.containsNonNullValue(ProducteevTask.RESPONSIBLE_ID)) responsibleId = metadata.getValue(ProducteevTask.RESPONSIBLE_ID); refreshResponsibleSpinner(newUsers, responsibleId); } @@ -188,7 +188,7 @@ public class ProducteevControlSet implements TaskEditControlSet { else metadata.setValue(ProducteevTask.RESPONSIBLE_ID, responsibleUser.getId()); - if(metadata.getSetValues().size() > 0 ) { + if(metadata.getSetValues().size() > 0) { metadataService.save(metadata); task.setValue(Task.MODIFICATION_DATE, DateUtilities.now()); } diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevStartupReceiver.java b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevStartupReceiver.java new file mode 100644 index 000000000..0225a6b9b --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/ProducteevStartupReceiver.java @@ -0,0 +1,26 @@ +/** + * See the file "LICENSE" for the full license governing this code. + */ +package com.todoroo.astrid.producteev; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; + +import com.todoroo.andlib.service.ContextManager; +import com.todoroo.astrid.service.AstridDependencyInjector; + +public class ProducteevStartupReceiver extends BroadcastReceiver { + + static { + AstridDependencyInjector.initialize(); + } + + @Override + /** Called when device is restarted */ + public void onReceive(final Context context, Intent intent) { + ContextManager.setContext(context); + ProducteevBackgroundService.scheduleService(); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java index 8f6a459b5..d7d8899bd 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/api/ProducteevInvoker.java @@ -2,13 +2,12 @@ package com.todoroo.astrid.producteev.api; import java.io.IOException; import java.io.UnsupportedEncodingException; -import java.math.BigInteger; import java.net.URLEncoder; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Map; import java.util.TreeMap; +import org.apache.commons.codec.digest.DigestUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -509,8 +508,7 @@ public class ProducteevInvoker { } sigBuilder.append(apiSecret); - byte[] digest = MessageDigest.getInstance("MD5").digest(sigBuilder.toString().getBytes("UTF-8")); - String signature = new BigInteger(1, digest).toString(16); + String signature = DigestUtils.md5Hex(sigBuilder.toString()); requestBuilder.append("api_sig").append('=').append(signature); return requestBuilder.toString(); } diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java index 52fbfd0b5..e25efb5a7 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevDataService.java @@ -20,7 +20,6 @@ import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Join; import com.todoroo.andlib.sql.Query; -import com.todoroo.astrid.dao.MetadataDao; import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; import com.todoroo.astrid.dao.StoreObjectDao; import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria; @@ -30,6 +29,7 @@ import com.todoroo.astrid.model.Metadata; import com.todoroo.astrid.model.StoreObject; import com.todoroo.astrid.model.Task; import com.todoroo.astrid.producteev.ProducteevUtilities; +import com.todoroo.astrid.service.MetadataService; import com.todoroo.astrid.tags.TagService; public final class ProducteevDataService { @@ -59,7 +59,7 @@ public final class ProducteevDataService { private TaskDao taskDao; @Autowired - private MetadataDao metadataDao; + private MetadataService metadataService; @Autowired private StoreObjectDao storeObjectDao; @@ -76,11 +76,11 @@ public final class ProducteevDataService { // --- task and metadata methods /** - * Clears RTM metadata information. Used when user logs out of RTM + * Clears metadata information. Used when user logs out of service */ public void clearMetadata() { - metadataDao.deleteWhere(Metadata.KEY.eq(ProducteevTask.METADATA_KEY)); - metadataDao.deleteWhere(Metadata.KEY.eq(ProducteevNote.METADATA_KEY)); + metadataService.deleteWhere(Metadata.KEY.eq(ProducteevTask.METADATA_KEY)); + metadataService.deleteWhere(Metadata.KEY.eq(ProducteevNote.METADATA_KEY)); storeObjectDao.deleteWhere(StoreObject.TYPE.eq(ProducteevDashboard.TYPE)); } @@ -139,18 +139,13 @@ public final class ProducteevDataService { * @param task */ public void saveTaskAndMetadata(ProducteevTaskContainer task) { - taskDao.save(task.task, true); + taskDao.save(task.task); - metadataDao.deleteWhere(Criterion.and(MetadataCriteria.byTask(task.task.getId()), + task.metadata.add(task.pdvTask); + metadataService.synchronizeMetadata(task.task.getId(), task.metadata, Criterion.or(MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), MetadataCriteria.withKey(ProducteevNote.METADATA_KEY), - MetadataCriteria.withKey(TagService.KEY)))); - task.metadata.add(task.pdvTask); - task.pdvTask.setValue(Metadata.KEY, ProducteevTask.METADATA_KEY); - for(Metadata metadata : task.metadata) { - metadata.setValue(Metadata.TASK, task.task.getId()); - metadataDao.persist(metadata); - } + MetadataCriteria.withKey(TagService.KEY))); } /** @@ -163,7 +158,7 @@ public final class ProducteevDataService { // read tags, notes, etc ArrayList metadata = new ArrayList(); - TodorooCursor metadataCursor = metadataDao.query(Query.select(Metadata.PROPERTIES). + TodorooCursor metadataCursor = metadataService.query(Query.select(Metadata.PROPERTIES). where(Criterion.and(MetadataCriteria.byTask(task.getId()), Criterion.or(MetadataCriteria.withKey(TagService.KEY), MetadataCriteria.withKey(ProducteevTask.METADATA_KEY), @@ -185,7 +180,7 @@ public final class ProducteevDataService { * @return null if no metadata found */ public Metadata getTaskMetadata(long taskId) { - TodorooCursor cursor = metadataDao.query(Query.select( + TodorooCursor cursor = metadataService.query(Query.select( Metadata.PROPERTIES).where( MetadataCriteria.byTaskAndwithKey(taskId, ProducteevTask.METADATA_KEY))); try { @@ -202,7 +197,7 @@ public final class ProducteevDataService { * Reads task notes out of a task */ public TodorooCursor getTaskNotesCursor(long taskId) { - TodorooCursor cursor = metadataDao.query(Query.select(Metadata.PROPERTIES). + TodorooCursor cursor = metadataService.query(Query.select(Metadata.PROPERTIES). where(MetadataCriteria.byTaskAndwithKey(taskId, ProducteevNote.METADATA_KEY))); return cursor; } @@ -286,4 +281,4 @@ public final class ProducteevDataService { // clear dashboard cache dashboards = null; } -} \ No newline at end of file +} diff --git a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java index 7f08f6d80..97e43deec 100644 --- a/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/producteev/sync/ProducteevSyncProvider.java @@ -50,6 +50,7 @@ import com.todoroo.astrid.utility.Preferences; @SuppressWarnings("nls") public class ProducteevSyncProvider extends SyncProvider { + private static final long TASK_ID_UNSYNCED = 1L; private ProducteevDataService dataService = null; private ProducteevInvoker invoker = null; private final ProducteevUtilities preferences = ProducteevUtilities.INSTANCE; @@ -201,8 +202,6 @@ public class ProducteevSyncProvider extends SyncProvider 0) invoker.tasksDelete(idTask); - else - create(local); + else { + // if we create, we transfer identifiers to old remote + // in case it is used by caller for other purposes + ProducteevTaskContainer newRemote = create(local); + transferIdentifiers(newRemote, remote); + remote = newRemote; + } } // dashboard if(remote != null && idDashboard != remote.pdvTask.getValue(ProducteevTask.DASHBOARD_ID)) { invoker.tasksSetWorkspace(idTask, idDashboard); remote = pull(local); + } else if(remote == null && idTask == TASK_ID_UNSYNCED) { + // was un-synced, create remote + remote = create(local); } // core properties diff --git a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java index 4742a493a..3d9248a57 100644 --- a/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java +++ b/astrid/plugin-src/com/todoroo/astrid/repeats/RepeatTaskCompleteListener.java @@ -58,11 +58,11 @@ public class RepeatTaskCompleteListener extends BroadcastReceiver { clone.setValue(Task.COMPLETION_DATE, 0L); clone.setValue(Task.TIMER_START, 0L); clone.setValue(Task.ELAPSED_SECONDS, 0); - PluginServices.getTaskService().save(clone, false); + PluginServices.getTaskService().save(clone); // clear recurrence from completed task so it can be re-completed task.setValue(Task.RECURRENCE, ""); //$NON-NLS-1$ - PluginServices.getTaskService().save(task, false); + PluginServices.getTaskService().save(task); // send a broadcast Intent broadcastIntent = new Intent(AstridApiConstants.BROADCAST_EVENT_TASK_REPEATED); diff --git a/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java index d42be060c..110a7a1fc 100644 --- a/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java +++ b/astrid/plugin-src/com/todoroo/astrid/tags/TagFilterExposer.java @@ -111,7 +111,7 @@ public class TagFilterExposer extends BroadcastReceiver { Filter[] filters = new Filter[sortedTagSet.size()]; int index = 0; for(Tag tag : sortedTagSet) { - filters[index++] = filterFromTag(context, tag, TaskCriteria.isActive()); + filters[index++] = filterFromTag(context, tag, TaskCriteria.activeAndVisible()); } FilterCategory tagsFilter = new FilterCategory(context.getString(R.string.tag_FEx_by_size), filters); list[2] = tagsFilter; diff --git a/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java b/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java index 461c38216..9a437ee2d 100644 --- a/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java +++ b/astrid/plugin-src/com/todoroo/astrid/timers/TimerPlugin.java @@ -52,7 +52,7 @@ public class TimerPlugin extends BroadcastReceiver { task.getValue(Task.ELAPSED_SECONDS) + newElapsed); } } - PluginServices.getTaskService().save(task, true); + PluginServices.getTaskService().save(task); TimerDecorationExposer.removeFromCache(task.getId()); // transmit new intents diff --git a/astrid/src/com/todoroo/astrid/activity/EditPreferences.java b/astrid/src/com/todoroo/astrid/activity/EditPreferences.java index eafe8f226..824216e24 100644 --- a/astrid/src/com/todoroo/astrid/activity/EditPreferences.java +++ b/astrid/src/com/todoroo/astrid/activity/EditPreferences.java @@ -130,7 +130,7 @@ public class EditPreferences extends TodorooPreferences { for(int i = 0; i < 100; i++) { task.clear(); task.setValue(Task.TITLE, Integer.toString(i)); - taskService.save(task, false); + taskService.save(task); } dialogUtilities.okDialog(EditPreferences.this, "done", null); return false; diff --git a/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java b/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java index 25fcf42cc..aad0405de 100644 --- a/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/ShortcutActivity.java @@ -26,10 +26,12 @@ import android.content.ContentValues; import android.content.Intent; import android.os.Bundle; +import com.timsu.astrid.R; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.sql.QueryTemplate; import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.astrid.api.Filter; +import com.todoroo.astrid.model.Task; /** * This activity is launched when a user opens up a notification from the @@ -42,6 +44,9 @@ public class ShortcutActivity extends Activity { // --- constants + /** token for passing a task id through extras for viewing a single task */ + public static final String TOKEN_SINGLE_TASK = "id"; //$NON-NLS-1$ + /** token for passing a {@link Filter}'s title through extras */ public static final String TOKEN_FILTER_TITLE = "title"; //$NON-NLS-1$ @@ -105,13 +110,20 @@ public class ShortcutActivity extends Activity { } } - Filter filter = new Filter("", title, new QueryTemplate(), values); //$NON-NLS-1$ - filter.sqlQuery = sql; + Filter filter = new Filter("", title, sql, values); //$NON-NLS-1$ + Intent taskListIntent = new Intent(this, TaskListActivity.class); + taskListIntent.putExtra(TaskListActivity.TOKEN_FILTER, filter); + startActivity(taskListIntent); + } else if(extras != null && extras.containsKey(TOKEN_SINGLE_TASK)) { + Filter filter = new Filter("", getString(R.string.TLA_custom), //$NON-NLS-1$ + new QueryTemplate().where(Task.ID.eq(extras.getLong(TOKEN_SINGLE_TASK, -1))), null); Intent taskListIntent = new Intent(this, TaskListActivity.class); taskListIntent.putExtra(TaskListActivity.TOKEN_FILTER, filter); startActivity(taskListIntent); } + + finish(); } diff --git a/astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java index 9fdff2af8..f52d5020b 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskEditActivity.java @@ -104,13 +104,18 @@ public final class TaskEditActivity extends TabActivity { /** * Task ID */ - public static final String TOKEN_ID = "i"; //$NON-NLS-1$ + public static final String TOKEN_ID = "id"; //$NON-NLS-1$ /** * Content Values to set */ public static final String TOKEN_VALUES = "v"; //$NON-NLS-1$ + /** + * Task in progress (during orientation change) + */ + private static final String TASK_IN_PROGRESS = "task_in_progress"; //$NON-NLS-1$ + // --- request codes @SuppressWarnings("unused") @@ -185,6 +190,16 @@ public final class TaskEditActivity extends TabActivity { // disable keyboard until user requests it AndroidUtilities.suppressVirtualKeyboard(title); + + // if we were editing a task already, restore it + if(savedInstanceState != null && savedInstanceState.containsKey(TASK_IN_PROGRESS)) { + Task task = savedInstanceState.getParcelable(TASK_IN_PROGRESS); + if(task != null) { + model = task; + } + } + + setResult(RESULT_OK); } /* ====================================================================== @@ -304,6 +319,12 @@ public final class TaskEditActivity extends TabActivity { */ @SuppressWarnings("nls") protected void loadItem(Intent intent) { + if(model != null) { + // came from bundle + isNewTask = (model.getValue(Task.TITLE).length() == 0); + return; + } + long idParam = intent.getLongExtra(TOKEN_ID, -1L); database.openForReading(); @@ -323,6 +344,9 @@ public final class TaskEditActivity extends TabActivity { if(model.getValue(Task.TITLE).length() == 0) { FlurryAgent.onEvent("create-task"); isNewTask = true; + + // set deletion date until task gets a title + model.setValue(Task.DELETION_DATE, DateUtilities.now()); } else { FlurryAgent.onEvent("edit-task"); } @@ -354,7 +378,10 @@ public final class TaskEditActivity extends TabActivity { for(TaskEditControlSet controlSet : controls) controlSet.writeToModel(model); - if(taskService.save(model, false)) + if(title.getText().length() > 0) + model.setValue(Task.DELETION_DATE, 0L); + + if(taskService.save(model) && title.getText().length() > 0) showSaveToast(); } @@ -362,11 +389,11 @@ public final class TaskEditActivity extends TabActivity { public void finish() { super.finish(); - // abandon editing in this case - if(title.getText().length() == 0 && isNewTask) { + // abandon editing and delete the newly created task if + // no title was entered + + if(title.getText().length() == 0 && isNewTask && model.isSaved()) { taskService.delete(model); - showCancelToast(); - setResult(RESULT_CANCELED); } } @@ -418,10 +445,6 @@ public final class TaskEditActivity extends TabActivity { * precision */ private void showSaveToast() { - // if we have no title, or nothing's changed, don't show toast - if(isNewTask) - return; - int stringResource; long due = model.getValue(Task.DUE_DATE); @@ -492,7 +515,6 @@ public final class TaskEditActivity extends TabActivity { Toast.LENGTH_SHORT).show(); } - @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { switch(item.getItemId()) { @@ -532,10 +554,11 @@ public final class TaskEditActivity extends TabActivity { @Override protected void onPause() { - if(shouldSaveState) - save(); super.onPause(); unregisterReceiver(controlReceiver); + + if(shouldSaveState) + save(); } @Override @@ -546,6 +569,14 @@ public final class TaskEditActivity extends TabActivity { populateFields(); } + @Override + protected void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + + // stick our task into the outState + outState.putParcelable(TASK_IN_PROGRESS, model); + } + @Override protected void onStart() { super.onStart(); diff --git a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java index 082901abe..f01c0340d 100644 --- a/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java +++ b/astrid/src/com/todoroo/astrid/activity/TaskListActivity.java @@ -286,7 +286,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, int importance = event.getNumber() - '1'; Task task = ((ViewHolder)selected.getTag()).task; task.setValue(Task.IMPORTANCE, importance); - taskService.save(task, false); + taskService.save(task); taskAdapter.setFieldContentsAndVisibility(selected); } // filter @@ -683,7 +683,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, } task.mergeWith(forTask); } - taskService.save(task, false); + taskService.save(task); if(forMetadata != null && forMetadata.size() > 0) { Metadata metadata = new Metadata(); metadata.setValue(Metadata.TASK, task.getId()); @@ -827,7 +827,7 @@ public class TaskListActivity extends ListActivity implements OnScrollListener, Task task = new Task(); task.setId(itemId); task.setValue(Task.DELETION_DATE, 0L); - taskService.save(task, false); + taskService.save(task); loadTaskListContent(true); return true; } diff --git a/astrid/src/com/todoroo/astrid/dao/TaskDao.java b/astrid/src/com/todoroo/astrid/dao/TaskDao.java index 5ef9e3c65..6298f9af6 100644 --- a/astrid/src/com/todoroo/astrid/dao/TaskDao.java +++ b/astrid/src/com/todoroo/astrid/dao/TaskDao.java @@ -146,12 +146,9 @@ public class TaskDao extends GenericDao { * exist. Returns true on success. * * @param task - * @param skipHooks - * Whether pre and post hooks should run. This should be set - * to true if tasks are created as part of synchronization * @return true if save occurred, false otherwise (i.e. nothing changed) */ - public boolean save(Task task, boolean skipHooks) { + public boolean save(Task task) { boolean saveSuccessful; ContentValues values = task.getSetValues(); diff --git a/astrid/src/com/todoroo/astrid/service/MetadataService.java b/astrid/src/com/todoroo/astrid/service/MetadataService.java index 1400a7860..40fc63b99 100644 --- a/astrid/src/com/todoroo/astrid/service/MetadataService.java +++ b/astrid/src/com/todoroo/astrid/service/MetadataService.java @@ -1,11 +1,17 @@ package com.todoroo.astrid.service; +import java.util.ArrayList; +import java.util.HashSet; + +import android.content.ContentValues; + import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.sql.Criterion; import com.todoroo.andlib.sql.Query; import com.todoroo.astrid.dao.MetadataDao; +import com.todoroo.astrid.dao.MetadataDao.MetadataCriteria; import com.todoroo.astrid.model.Metadata; /** @@ -67,4 +73,51 @@ public class MetadataService { public void save(Metadata metadata) { metadataDao.persist(metadata); } + + /** + * Synchronize metadata for given task id + * @param id + * @param metadata + * @param metadataKeys + */ + public void synchronizeMetadata(long taskId, ArrayList metadata, + Criterion metadataCriterion) { + HashSet newMetadataValues = new HashSet(); + for(Metadata metadatum : metadata) { + metadatum.setValue(Metadata.TASK, taskId); + metadatum.clearValue(Metadata.ID); + newMetadataValues.add(metadatum.getMergedValues()); + } + + Metadata item = new Metadata(); + TodorooCursor cursor = metadataDao.query(Query.select(Metadata.PROPERTIES).where(Criterion.and(MetadataCriteria.byTask(taskId), + metadataCriterion))); + try { + // try to find matches within our metadata list + for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + item.readFromCursor(cursor); + long id = item.getId(); + + // clear item id when matching with incoming values + item.clearValue(Metadata.ID); + ContentValues itemMergedValues = item.getMergedValues(); + if(newMetadataValues.contains(itemMergedValues)) { + newMetadataValues.remove(itemMergedValues); + continue; + } + + // not matched. cut it + metadataDao.delete(id); + } + } finally { + cursor.close(); + } + + // everything that remains shall be written + for(ContentValues values : newMetadataValues) { + item.clear(); + item.mergeWith(values); + metadataDao.persist(item); + } + } } diff --git a/astrid/src/com/todoroo/astrid/service/StartupService.java b/astrid/src/com/todoroo/astrid/service/StartupService.java index 1949b7cad..95495cdc6 100644 --- a/astrid/src/com/todoroo/astrid/service/StartupService.java +++ b/astrid/src/com/todoroo/astrid/service/StartupService.java @@ -20,7 +20,9 @@ import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.service.ExceptionService; import com.todoroo.andlib.service.ExceptionService.TodorooUncaughtExceptionHandler; +import com.todoroo.astrid.backup.BackupService; import com.todoroo.astrid.dao.Database; +import com.todoroo.astrid.producteev.ProducteevBackgroundService; import com.todoroo.astrid.producteev.ProducteevUtilities; import com.todoroo.astrid.utility.Constants; import com.todoroo.astrid.utility.Preferences; @@ -94,6 +96,8 @@ public class StartupService { Preferences.setCurrentVersion(version); } + upgradeService.performSecondaryUpgrade(context); + // perform startup activities in a background thread new Thread(new Runnable() { public void run() { @@ -115,6 +119,10 @@ public class StartupService { // if sync ongoing flag was set, clear it ProducteevUtilities.INSTANCE.stopOngoing(); + ProducteevBackgroundService.scheduleService(); + BackupService.scheduleService(context); + MilkBackgroundService.scheduleService(); + // check for task killers if(!Constants.OEM) showTaskKillerHelp(context); diff --git a/astrid/src/com/todoroo/astrid/service/TaskService.java b/astrid/src/com/todoroo/astrid/service/TaskService.java index 9237e3bb7..96ef546a9 100644 --- a/astrid/src/com/todoroo/astrid/service/TaskService.java +++ b/astrid/src/com/todoroo/astrid/service/TaskService.java @@ -67,7 +67,7 @@ public class TaskService { item.setValue(Task.COMPLETION_DATE, DateUtilities.now()); else item.setValue(Task.COMPLETION_DATE, 0L); - taskDao.save(item, false); + taskDao.save(item); } /** @@ -78,8 +78,8 @@ public class TaskService { * Whether pre and post hooks should run. This should be set * to true if tasks are created as part of synchronization */ - public boolean save(Task item, boolean runHooks) { - return taskDao.save(item, runHooks); + public boolean save(Task item) { + return taskDao.save(item); } /** @@ -122,12 +122,13 @@ public class TaskService { return; else if(item.containsValue(Task.TITLE) && item.getValue(Task.TITLE).length() == 0) { taskDao.delete(item.getId()); + item.setId(Task.NO_ID); } else { long id = item.getId(); item.clear(); item.setId(id); item.setValue(Task.DELETION_DATE, DateUtilities.now()); - taskDao.save(item, false); + taskDao.save(item); } } @@ -224,7 +225,7 @@ public class TaskService { try { for(cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { taskValues.setValue(Task.ID, cursor.get(Task.ID)); - taskDao.save(taskValues, false); + taskDao.save(taskValues); } return cursor.getCount(); } finally { @@ -233,7 +234,9 @@ public class TaskService { } public int countTasks(Filter filter) { - TodorooCursor cursor = query(Query.select(Task.ID).withQueryTemplate(filter.sqlQuery)); + String queryTemplate = PermaSql.replacePlaceholders(filter.sqlQuery); + TodorooCursor cursor = query(Query.select(Task.ID).withQueryTemplate( + queryTemplate)); try { return cursor.getCount(); } finally { diff --git a/astrid/src/com/todoroo/astrid/service/UpgradeService.java b/astrid/src/com/todoroo/astrid/service/UpgradeService.java index de20889cd..10103bb00 100644 --- a/astrid/src/com/todoroo/astrid/service/UpgradeService.java +++ b/astrid/src/com/todoroo/astrid/service/UpgradeService.java @@ -11,18 +11,25 @@ import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.astrid.activity.TaskListActivity; +import com.todoroo.astrid.dao.Database; public final class UpgradeService { + private static final int V3_2_4 = 151; + private static final int V3_2_3 = 150; private static final int V3_1_0 = 146; private static final int V3_0_6 = 145; private static final int V3_0_5 = 144; private static final int V3_0_0 = 136; private static final int V2_14_4 = 135; + @Autowired private DialogUtilities dialogUtilities; + @Autowired + private Database database; + public UpgradeService() { DependencyInjectionService.getInstance().inject(this); } @@ -102,6 +109,15 @@ public final class UpgradeService { "If you liked the old version, you can also go back by " + "clicking here", }); + if(from > V2_14_4 && from <= V3_2_4) + newVersionString(changeLog, "3.2.5 (8/18/10)", new String[] { + "Fix for duplicated tasks created in RTM", + }); + if(from > V2_14_4 && from <= V3_2_3) + newVersionString(changeLog, "3.2.5 (8/18/10)", new String[] { + "Fix for duplicated tasks created in Producteev", + "Fix for being able to create tasks without title", + }); if(from > V2_14_4 && from <= V3_1_0) newVersionString(changeLog, "3.2.0 (8/16/10)", new String[] { "Build your own custom filters from the Filter page", @@ -110,7 +126,6 @@ public final class UpgradeService { "Synchronize with Producteev! (producteev.com)", "Select tags by drop-down box", "Cosmetic improvements, calendar & sync bug fixes", - "... enjoy! - we <3 astrid team", }); if(from > V2_14_4 && from <= V3_0_6) newVersionString(changeLog, "3.1.0 (8/9/10)", new String[] { @@ -121,7 +136,6 @@ public final class UpgradeService { "Restored tag hiding when tag begins with underscore (_)", "FROYO: disabled moving app to SD card, it would break alarms and widget", "Also gone: a couple force closes, bugs with repeating tasks", - "... enjoy! - we <3 astrid team", }); if(from > V2_14_4 && from <= V3_0_5) newVersionString(changeLog, "3.0.6 (8/4/10)", new String[] { @@ -134,7 +148,7 @@ public final class UpgradeService { if(changeLog.length() == 0) return; - changeLog.append(""); + changeLog.append("Enjoy!"); String changeLogHtml = "" + changeLog; WebView webView = new WebView(context); @@ -163,6 +177,18 @@ public final class UpgradeService { changeLog.append(""); } - // --- database upgrade logic + // --- secondary upgrade + + /** + * If primary upgrade doesn't work for some reason (corrupt SharedPreferences, + * for example), this will catch some cases + */ + public void performSecondaryUpgrade(Context context) { + if(!context.getDatabasePath(database.getName()).exists() && + context.getDatabasePath("tasks").exists()) { //$NON-NLS-1$ + new Astrid2To3UpgradeHelper().upgrade2To3(context, 1); + } + } + } diff --git a/astrid/src/com/todoroo/astrid/widget/TasksWidget.java b/astrid/src/com/todoroo/astrid/widget/TasksWidget.java index 2622c7e00..388f10984 100644 --- a/astrid/src/com/todoroo/astrid/widget/TasksWidget.java +++ b/astrid/src/com/todoroo/astrid/widget/TasksWidget.java @@ -188,7 +188,7 @@ public class TasksWidget extends AppWidgetProvider { views.setOnClickPendingIntent(R.id.taskbody, pendingIntent); Intent editIntent = new Intent(context, TaskEditActivity.class); - editIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + editIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); if(filter != null && filter.valuesForNewTasks != null) { String values = AndroidUtilities.contentValuesToSerializedString(filter.valuesForNewTasks); editIntent.putExtra(TaskEditActivity.TOKEN_VALUES, values); diff --git a/tests/src/com/todoroo/astrid/dao/TaskDaoTests.java b/tests/src/com/todoroo/astrid/dao/TaskDaoTests.java index a9bcb45ea..6478fb845 100644 --- a/tests/src/com/todoroo/astrid/dao/TaskDaoTests.java +++ b/tests/src/com/todoroo/astrid/dao/TaskDaoTests.java @@ -32,7 +32,7 @@ public class TaskDaoTests extends DatabaseTestCase { // create task "happy" Task task = new Task(); task.setValue(Task.TITLE, "happy"); - assertTrue(taskDao.save(task, false)); + assertTrue(taskDao.save(task)); cursor = taskDao.query( Query.select(IDS)); assertEquals(1, cursor.getCount()); @@ -45,7 +45,7 @@ public class TaskDaoTests extends DatabaseTestCase { // create task "sad" task = new Task(); task.setValue(Task.TITLE, "sad"); - assertTrue(taskDao.save(task, false)); + assertTrue(taskDao.save(task)); cursor = taskDao.query( Query.select(IDS)); assertEquals(2, cursor.getCount()); @@ -55,7 +55,7 @@ public class TaskDaoTests extends DatabaseTestCase { long sadId = task.getId(); assertNotSame(Task.NO_ID, sadId); task.setValue(Task.TITLE, "melancholy"); - assertTrue(taskDao.save(task, false)); + assertTrue(taskDao.save(task)); cursor = taskDao.query( Query.select(IDS)); assertEquals(2, cursor.getCount()); @@ -75,35 +75,35 @@ public class TaskDaoTests extends DatabaseTestCase { // create normal task Task task = new Task(); task.setValue(Task.TITLE, "normal"); - assertTrue(taskDao.save(task, false)); + assertTrue(taskDao.save(task)); // create blank task task = new Task(); task.setValue(Task.TITLE, ""); - assertTrue(taskDao.save(task, false)); + assertTrue(taskDao.save(task)); // create hidden task task = new Task(); task.setValue(Task.TITLE, "hidden"); task.setValue(Task.HIDE_UNTIL, DateUtilities.now() + 10000); - assertTrue(taskDao.save(task, false)); + assertTrue(taskDao.save(task)); // create task with deadlines task = new Task(); task.setValue(Task.TITLE, "deadlineInFuture"); task.setValue(Task.DUE_DATE, DateUtilities.now() + 10000); - assertTrue(taskDao.save(task, false)); + assertTrue(taskDao.save(task)); task = new Task(); task.setValue(Task.TITLE, "deadlineInPast"); task.setValue(Task.DUE_DATE, DateUtilities.now() - 10000); - assertTrue(taskDao.save(task, false)); + assertTrue(taskDao.save(task)); // create completed task task = new Task(); task.setValue(Task.TITLE, "completed"); task.setValue(Task.COMPLETION_DATE, DateUtilities.now() - 10000); - assertTrue(taskDao.save(task, false)); + assertTrue(taskDao.save(task)); // check has no name TodorooCursor cursor = taskDao.query( @@ -165,7 +165,7 @@ public class TaskDaoTests extends DatabaseTestCase { // create task "happy" Task task = new Task(); task.setValue(Task.TITLE, "happy"); - assertTrue(taskDao.save(task, false)); + assertTrue(taskDao.save(task)); cursor = taskDao.query( Query.select(IDS)); assertEquals(1, cursor.getCount()); @@ -191,7 +191,7 @@ public class TaskDaoTests extends DatabaseTestCase { task.setValue(Task.TITLE, "happy"); task.setValue(Task.ID, 1L); - assertFalse(taskDao.save(task, false)); + assertFalse(taskDao.save(task)); cursor = taskDao.query( Query.select(IDS)); diff --git a/tests/src/com/todoroo/astrid/model/TaskTests.java b/tests/src/com/todoroo/astrid/model/TaskTests.java index 1b99ebcd2..ee2498e99 100644 --- a/tests/src/com/todoroo/astrid/model/TaskTests.java +++ b/tests/src/com/todoroo/astrid/model/TaskTests.java @@ -46,7 +46,7 @@ public class TaskTests extends DatabaseTestCase { /** Check task gets a creation date at some point */ public void checkCreationDate() { Task task = new Task(); - taskService.save(task, false); + taskService.save(task); assertTrue(task.getValue(Task.CREATION_DATE) > 0); } diff --git a/tests/src/com/todoroo/astrid/reminders/ReminderServiceTests.java b/tests/src/com/todoroo/astrid/reminders/ReminderServiceTests.java index a4c2cc0a3..9ddb7d6ae 100644 --- a/tests/src/com/todoroo/astrid/reminders/ReminderServiceTests.java +++ b/tests/src/com/todoroo/astrid/reminders/ReminderServiceTests.java @@ -37,7 +37,7 @@ public class ReminderServiceTests extends DatabaseTestCase { Task task = new Task(); task.setValue(Task.TITLE, "water"); task.setValue(Task.REMINDER_FLAGS, 0); - taskDao.save(task, false); + taskDao.save(task); service.scheduleAlarm(task); } @@ -49,7 +49,7 @@ public class ReminderServiceTests extends DatabaseTestCase { task.setValue(Task.TITLE, "water"); task.setValue(Task.DUE_DATE, DateUtilities.now() - DateUtilities.ONE_DAY); task.setValue(Task.REMINDER_FLAGS, Task.NOTIFY_AT_DEADLINE); - taskDao.save(task, false); + taskDao.save(task); // test due date in the future task.setValue(Task.DUE_DATE, DateUtilities.now() + DateUtilities.ONE_DAY); @@ -61,7 +61,7 @@ public class ReminderServiceTests extends DatabaseTestCase { assertEquals(type, ReminderService.TYPE_DUE); } }); - taskDao.save(task, false); + taskDao.save(task); assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated); } @@ -80,7 +80,7 @@ public class ReminderServiceTests extends DatabaseTestCase { assertEquals(type, ReminderService.TYPE_RANDOM); } }); - taskDao.save(task, false); + taskDao.save(task); assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated); } @@ -92,7 +92,7 @@ public class ReminderServiceTests extends DatabaseTestCase { task.setValue(Task.TITLE, "water"); task.setValue(Task.DUE_DATE, DateUtilities.now() + DateUtilities.ONE_DAY); task.setValue(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE); - taskDao.save(task, false); + taskDao.save(task); // test due date in the past task.setValue(Task.DUE_DATE, DateUtilities.now() - DateUtilities.ONE_DAY); @@ -105,7 +105,7 @@ public class ReminderServiceTests extends DatabaseTestCase { assertEquals(type, ReminderService.TYPE_OVERDUE); } }); - taskDao.save(task, false); + taskDao.save(task); assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated); } @@ -126,7 +126,7 @@ public class ReminderServiceTests extends DatabaseTestCase { assertEquals(type, ReminderService.TYPE_RANDOM); } }); - taskDao.save(task, false); + taskDao.save(task); assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated); // now set the due date in the past @@ -145,7 +145,7 @@ public class ReminderServiceTests extends DatabaseTestCase { assertEquals(type, ReminderService.TYPE_DUE); } }); - taskDao.save(task, false); + taskDao.save(task); assertTrue(((AlarmExpected)service.getScheduler()).alarmCreated); } diff --git a/tests/src/com/todoroo/astrid/repeats/RepeatTests.java b/tests/src/com/todoroo/astrid/repeats/RepeatTests.java index ddaefe750..7b485e0f3 100644 --- a/tests/src/com/todoroo/astrid/repeats/RepeatTests.java +++ b/tests/src/com/todoroo/astrid/repeats/RepeatTests.java @@ -36,10 +36,10 @@ public class RepeatTests extends DatabaseTestCase { public void testNoRepeats() throws Exception{ Task task = new Task(); task.setValue(Task.TITLE, "nothing"); - taskDao.save(task, false); + taskDao.save(task); task.setValue(Task.COMPLETION_DATE, DateUtilities.now()); - taskDao.save(task, false); + taskDao.save(task); // wait for repeat handler Thread.sleep(REPEAT_WAIT); @@ -60,10 +60,10 @@ public class RepeatTests extends DatabaseTestCase { rrule.setInterval(5); rrule.setFreq(Frequency.DAILY); task.setValue(Task.RECURRENCE, rrule.toIcal()); - taskDao.save(task, false); + taskDao.save(task); task.setValue(Task.COMPLETION_DATE, DateUtilities.now()); - taskDao.save(task, false); + taskDao.save(task); // wait for repeat handler Thread.sleep(REPEAT_WAIT); @@ -102,10 +102,10 @@ public class RepeatTests extends DatabaseTestCase { task.setValue(Task.RECURRENCE, rrule.toIcal()); long originalDueDate = (DateUtilities.now() - 3 * DateUtilities.ONE_DAY) / 1000L * 1000L; task.setValue(Task.DUE_DATE, task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, originalDueDate)); - taskDao.save(task, false); + taskDao.save(task); task.setValue(Task.COMPLETION_DATE, DateUtilities.now()); - taskDao.save(task, false); + taskDao.save(task); // wait for repeat handler Thread.sleep(REPEAT_WAIT); @@ -144,10 +144,10 @@ public class RepeatTests extends DatabaseTestCase { task.setValue(Task.RECURRENCE, rrule.toIcal()); long originalDueDate = (DateUtilities.now() + DateUtilities.ONE_DAY) / 1000L * 1000L; task.setValue(Task.DUE_DATE, task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, originalDueDate)); - taskDao.save(task, false); + taskDao.save(task); task.setValue(Task.COMPLETION_DATE, DateUtilities.now()); - taskDao.save(task, false); + taskDao.save(task); // wait for repeat handler Thread.sleep(2 * REPEAT_WAIT); @@ -187,10 +187,10 @@ public class RepeatTests extends DatabaseTestCase { long originalDueDate = (DateUtilities.now() - 3 * DateUtilities.ONE_DAY) / 1000L * 1000L; task.setValue(Task.DUE_DATE, task.createDueDate(Task.URGENCY_SPECIFIC_DAY, originalDueDate)); task.setFlag(Task.FLAGS, Task.FLAG_REPEAT_AFTER_COMPLETION, true); - taskDao.save(task, false); + taskDao.save(task); task.setValue(Task.COMPLETION_DATE, DateUtilities.now()); - taskDao.save(task, false); + taskDao.save(task); // wait for repeat handler Thread.sleep(REPEAT_WAIT); @@ -226,7 +226,7 @@ public class RepeatTests extends DatabaseTestCase { rrule.setInterval(5); rrule.setFreq(Frequency.DAILY); task.setValue(Task.RECURRENCE, rrule.toIcal()); - taskDao.save(task, false); + taskDao.save(task); Metadata metadata = new Metadata(); metadata.setValue(Metadata.KEY, "special"); @@ -235,7 +235,7 @@ public class RepeatTests extends DatabaseTestCase { metadataDao.persist(metadata); task.setValue(Task.COMPLETION_DATE, DateUtilities.now()); - taskDao.save(task, false); + taskDao.save(task); // wait for repeat handler Thread.sleep(REPEAT_WAIT); @@ -266,10 +266,10 @@ public class RepeatTests extends DatabaseTestCase { task.setValue(Task.RECURRENCE, rrule.toIcal()); task.setValue(Task.DUE_DATE, task.createDueDate(Task.URGENCY_TODAY, 0)); task.setValue(Task.HIDE_UNTIL, task.createHideUntil(Task.HIDE_UNTIL_DAY_BEFORE, 0)); - taskDao.save(task, false); + taskDao.save(task); task.setValue(Task.COMPLETION_DATE, DateUtilities.now()); - taskDao.save(task, false); + taskDao.save(task); // wait for repeat handler Thread.sleep(REPEAT_WAIT);