diff --git a/api/src/com/todoroo/astrid/sync/SyncProvider.java b/api/src/com/todoroo/astrid/sync/SyncProvider.java index ceb71626f..c16b3df01 100644 --- a/api/src/com/todoroo/astrid/sync/SyncProvider.java +++ b/api/src/com/todoroo/astrid/sync/SyncProvider.java @@ -152,13 +152,15 @@ public abstract class SyncProvider { public void synchronize(final Context context) { // display toast if(context instanceof Activity) { - ((Activity) context).runOnUiThread(new Runnable() { - @Override - public void run() { - Toast.makeText(context, R.string.SyP_progress_toast, - Toast.LENGTH_LONG).show(); - } - }); + if(getUtilities().isLoggedIn()) { + ((Activity) context).runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(context, R.string.SyP_progress_toast, + Toast.LENGTH_LONG).show(); + } + }); + } initiateManual((Activity)context); } else if(context instanceof Service) { // display notification diff --git a/astrid/.classpath b/astrid/.classpath index a77d74271..c7efb1be2 100644 --- a/astrid/.classpath +++ b/astrid/.classpath @@ -11,11 +11,17 @@ - + + + + + + + diff --git a/astrid/AndroidManifest.xml b/astrid/AndroidManifest.xml index a1d0115b7..6f9f77c08 100644 --- a/astrid/AndroidManifest.xml +++ b/astrid/AndroidManifest.xml @@ -63,6 +63,7 @@ + + + android:theme="@style/Theme" android:configChanges="orientation|keyboardHidden"/> diff --git a/astrid/libs/google-api-client-1.4.1-beta.jar b/astrid/libs/google-api-client-1.4.1-beta.jar new file mode 100644 index 000000000..c22af9721 Binary files /dev/null and b/astrid/libs/google-api-client-1.4.1-beta.jar differ diff --git a/astrid/libs/google-api-client-extensions-android2-1.4.1-beta.jar b/astrid/libs/google-api-client-extensions-android2-1.4.1-beta.jar new file mode 100644 index 000000000..e486bb08a Binary files /dev/null and b/astrid/libs/google-api-client-extensions-android2-1.4.1-beta.jar differ diff --git a/astrid/libs/google-api-client-googleapis-1.4.1-beta.jar b/astrid/libs/google-api-client-googleapis-1.4.1-beta.jar new file mode 100644 index 000000000..002689f63 Binary files /dev/null and b/astrid/libs/google-api-client-googleapis-1.4.1-beta.jar differ diff --git a/astrid/libs/google-api-client-googleapis-extensions-android2-1.4.1-beta.jar b/astrid/libs/google-api-client-googleapis-extensions-android2-1.4.1-beta.jar new file mode 100644 index 000000000..efffa088d Binary files /dev/null and b/astrid/libs/google-api-client-googleapis-extensions-android2-1.4.1-beta.jar differ diff --git a/astrid/libs/google-api-services-tasks-v1-1.0.0-beta.jar b/astrid/libs/google-api-services-tasks-v1-1.0.0-beta.jar new file mode 100644 index 000000000..2ce3ccbeb Binary files /dev/null and b/astrid/libs/google-api-services-tasks-v1-1.0.0-beta.jar differ diff --git a/astrid/libs/guava-r09.jar b/astrid/libs/guava-r09.jar new file mode 100644 index 000000000..30dbc5692 Binary files /dev/null and b/astrid/libs/guava-r09.jar differ diff --git a/astrid/libs/jackson-core-asl-1.6.7.jar b/astrid/libs/jackson-core-asl-1.6.7.jar new file mode 100644 index 000000000..7c1207210 Binary files /dev/null and b/astrid/libs/jackson-core-asl-1.6.7.jar differ diff --git a/astrid/libs/todoroo-g.jar b/astrid/libs/todoroo-g.jar deleted file mode 100644 index ecb78c862..000000000 Binary files a/astrid/libs/todoroo-g.jar and /dev/null differ diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListService.java index 620d135d0..eece3143a 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListService.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksListService.java @@ -1,5 +1,6 @@ package com.todoroo.astrid.gtasks; +import com.google.api.services.tasks.v1.model.TaskLists; import com.todoroo.andlib.data.TodorooCursor; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.DependencyInjectionService; @@ -7,7 +8,6 @@ import com.todoroo.andlib.sql.Query; import com.todoroo.astrid.dao.StoreObjectDao; import com.todoroo.astrid.dao.StoreObjectDao.StoreObjectCriteria; import com.todoroo.astrid.data.StoreObject; -import com.todoroo.gtasks.GoogleTaskListInfo; public class GtasksListService { @@ -58,16 +58,33 @@ public class GtasksListService { return LIST_NOT_FOUND; } + public void migrateListIds (TaskLists remoteLists) { + readLists(); + + for (int i = 0; i < remoteLists.items.size(); i++) { + com.google.api.services.tasks.v1.model.TaskList remote = remoteLists.items.get(i); + + for (StoreObject list : lists) { + if (list.getValue(GtasksList.NAME).equals(remote.title)) { + list.setValue(GtasksList.REMOTE_ID, remote.id); + storeObjectDao.persist(list); + break; + } + } + } + } + @SuppressWarnings("nls") - public void updateLists(GoogleTaskListInfo[] remoteLists) { + public void updateLists(TaskLists remoteLists) { readLists(); + for(StoreObject list : lists) list.setValue(StoreObject.TYPE, ""); - for(int i = 0; i < remoteLists.length; i++) { - GoogleTaskListInfo remote = remoteLists[i]; + for(int i = 0; i < remoteLists.items.size(); i++) { + com.google.api.services.tasks.v1.model.TaskList remote = remoteLists.items.get(i); - String id = remote.getId(); + String id = remote.id; StoreObject local = null; for(StoreObject list : lists) { if(list.getValue(GtasksList.REMOTE_ID).equals(id)) { @@ -81,7 +98,7 @@ public class GtasksListService { local.setValue(StoreObject.TYPE, GtasksList.TYPE); local.setValue(GtasksList.REMOTE_ID, id); - local.setValue(GtasksList.NAME, remote.getName()); + local.setValue(GtasksList.NAME, remote.title); local.setValue(GtasksList.ORDER, i); storeObjectDao.persist(local); } diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksPreferenceService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksPreferenceService.java index 3cbb29a25..66f75cdcc 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksPreferenceService.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/GtasksPreferenceService.java @@ -30,9 +30,6 @@ public class GtasksPreferenceService extends SyncProviderUtilities { /** GTasks user name */ public static final String PREF_USER_NAME = IDENTIFIER + "_user"; //$NON-NLS-1$ - /** GTasks user password */ - public static final String PREF_PASSWORD = IDENTIFIER + "_password"; //$NON-NLS-1$ - /** GTasks is apps for domain boolean */ public static final String PREF_IS_DOMAIN = IDENTIFIER + "_domain"; //$NON-NLS-1$ diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/api/CreateRequest.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/CreateRequest.java new file mode 100644 index 000000000..27e0cbc0f --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/CreateRequest.java @@ -0,0 +1,44 @@ +package com.todoroo.astrid.gtasks.api; + +import java.io.IOException; + +import com.google.api.services.tasks.v1.model.Task; +/** + * Encapsulates a request to the api to create a task on the remote server + * @author Sam Bosley + * + */ +public class CreateRequest extends PushRequest { + + private String parent; + private String priorSiblingId; + + public CreateRequest(GtasksService service, String listId, Task toUpdate, String parent, String priorSiblingId) { + super(service, listId, toUpdate); + this.parent = parent; + this.priorSiblingId = priorSiblingId; + } + + @Override + public Task executePush() throws IOException { + return service.createGtask(listId, toPush, parent, priorSiblingId); + } + + public String getParent() { + return parent; + } + + public void setParent(String parent) { + this.parent = parent; + } + + public String getPriorSiblingId() { + return priorSiblingId; + } + + public void setPriorSiblingId(String priorSiblingId) { + this.priorSiblingId = priorSiblingId; + } + + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GoogleTasksException.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GoogleTasksException.java new file mode 100644 index 000000000..9af472712 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GoogleTasksException.java @@ -0,0 +1,25 @@ +package com.todoroo.astrid.gtasks.api; + +import java.io.IOException; + +public class GoogleTasksException extends IOException { + private static final long serialVersionUID = -5585448790574862510L; + + public GoogleTasksException() { + + } + + public GoogleTasksException(String message) { + super(message); + } + + public GoogleTasksException(String message, Throwable cause) { + super(message); + initCause(cause); + } + + public GoogleTasksException(Throwable cause) { + super(cause.getMessage()); + initCause(cause); + } +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GtasksApiUtilities.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GtasksApiUtilities.java new file mode 100644 index 000000000..245080fda --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GtasksApiUtilities.java @@ -0,0 +1,66 @@ +package com.todoroo.astrid.gtasks.api; + +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.TimeZone; + +import com.google.api.client.util.DateTime; +import com.google.api.services.tasks.v1.model.Task; + +@SuppressWarnings("nls") +public class GtasksApiUtilities { + + private static SimpleDateFormat timeWriter = new SimpleDateFormat("yyyy-MM-dd'T'hh:m:ss.SSSZ", Locale.US); + + //When setting completion date, gtasks api will convert to UTC AND change hours/minutes/seconds to match + public static long gtasksCompletedTimeToUnixTime(String gtasksCompletedTime, long defaultValue) { + if (gtasksCompletedTime == null) return defaultValue; + synchronized(timeWriter) { + try { + long utcTime = DateTime.parseRfc3339(gtasksCompletedTime).value; + return new DateTime(new Date(utcTime), TimeZone.getDefault()).value; + } catch (NumberFormatException e) { + return defaultValue; + } + } + } + + //When setting due date, gtasks api will convert to UTC time without changing hours/minutes/seconds + public static long gtasksDueTimeToUnixTime(String gtasksDueTime, long defaultValue) { + if (gtasksDueTime == null) return defaultValue; + synchronized(timeWriter) { + try { + long utcTime = DateTime.parseRfc3339(gtasksDueTime).value; + return utcTime - TimeZone.getDefault().getOffset(utcTime); + } catch (NumberFormatException e) { + return defaultValue; + } + } + } + + public static String unixTimeToGtasksTime(long time) { + if (time == 0) return null; + synchronized(timeWriter) { + return new DateTime(new Date(time), TimeZone.getDefault()).toStringRfc3339(); + } + } + + /* + * The two methods below are useful for testing + */ + public static String gtasksCompletedTimeStringToLocalTimeString(String gtasksTime) { + return GtasksApiUtilities.unixTimeToGtasksTime(GtasksApiUtilities.gtasksCompletedTimeToUnixTime(gtasksTime, 0)); + } + + public static String gtasksDueTimeStringToLocalTimeString(String gtasksTime) { + return GtasksApiUtilities.unixTimeToGtasksTime(GtasksApiUtilities.gtasksDueTimeToUnixTime(gtasksTime, 0)); + } + + public static String extractListIdFromSelfLink(Task task) { + String selfLink = task.selfLink; + String [] urlComponents = selfLink.split("/"); + int listIdIndex = urlComponents.length - 3; + return urlComponents[listIdIndex]; + } +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GtasksService.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GtasksService.java new file mode 100644 index 000000000..affb21e85 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/GtasksService.java @@ -0,0 +1,195 @@ +package com.todoroo.astrid.gtasks.api; + +import java.io.IOException; + +import com.google.api.client.extensions.android2.AndroidHttp; +import com.google.api.client.googleapis.auth.oauth2.draft10.GoogleAccessProtectedResource; +import com.google.api.client.json.jackson.JacksonFactory; +import com.google.api.services.tasks.v1.Tasks; +import com.google.api.services.tasks.v1.Tasks.TasksOperations.Insert; +import com.google.api.services.tasks.v1.Tasks.TasksOperations.List; +import com.google.api.services.tasks.v1.Tasks.TasksOperations.Move; +import com.google.api.services.tasks.v1.model.Task; +import com.google.api.services.tasks.v1.model.TaskList; +import com.google.api.services.tasks.v1.model.TaskLists; + +/** + * Wrapper around the official Google Tasks API to simplify common operations. In the case + * of an exception, each request is tried twice in case of a timeout. + * @author Sam Bosley + * + */ +@SuppressWarnings("nls") +public class GtasksService { + private Tasks service; + + private static final String API_KEY = "AIzaSyCIYZTBo6haRHxmiplZsfYdagFEpaiFnAk"; //Dummy + + public static final String AUTH_TOKEN_TYPE = "oauth2:https://www.googleapis.com/auth/tasks"; //$NON-NLS-1$ + + public GtasksService(String authToken) { + try { + authenticate(authToken); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public void authenticate(String authToken) throws IOException { + + GoogleAccessProtectedResource accessProtectedResource = new GoogleAccessProtectedResource(authToken); + + service = new Tasks(AndroidHttp.newCompatibleTransport(), accessProtectedResource, new JacksonFactory()); + service.accessKey = API_KEY; + service.setApplicationName("Astrid"); + } + + public TaskLists allGtaskLists() throws IOException { + TaskLists toReturn; + try { + toReturn = service.tasklists.list().execute(); + } catch (IOException e) { + toReturn = service.tasklists.list().execute(); + } + return toReturn; + } + + public TaskList getGtaskList(String id) throws IOException { + TaskList toReturn; + try { + toReturn = service.tasklists.get(id).execute(); + } catch (IOException e) { + toReturn = service.tasklists.get(id).execute(); + } + return toReturn; + } + + public TaskList createGtaskList(String title) throws IOException { + TaskList newList = new TaskList(); + newList.title = title; + TaskList toReturn; + try { + toReturn = service.tasklists.insert(newList).execute(); + } catch (IOException e) { + toReturn = service.tasklists.insert(newList).execute(); + } + return toReturn; + } + + public TaskList updateGtaskList(TaskList list) throws IOException { + TaskList toReturn; + try { + toReturn = service.tasklists.update(list.id, list).execute(); + } catch (IOException e) { + toReturn = service.tasklists.update(list.id, list).execute(); + } + return toReturn; + } + + public void deleteGtaskList(String listId) throws IOException { + try { + service.tasklists.delete(listId).execute(); + } catch (IOException e) { + service.tasks.clear(listId).execute(); + } + } + + public com.google.api.services.tasks.v1.model.Tasks getAllGtasksFromTaskList(TaskList list, boolean includeDeleted) throws IOException { + com.google.api.services.tasks.v1.model.Tasks toReturn; + try { + toReturn = getAllGtasksFromListId(list.id, includeDeleted); + } catch (IOException e) { + toReturn = getAllGtasksFromListId(list.id, includeDeleted); + } + return toReturn; + } + + public com.google.api.services.tasks.v1.model.Tasks getAllGtasksFromListId(String listId, boolean includeDeleted) throws IOException { + com.google.api.services.tasks.v1.model.Tasks toReturn; + List request = service.tasks.list(listId); + request.showDeleted = includeDeleted; + try { + toReturn = request.execute(); + } catch (IOException e) { + toReturn = request.execute(); + } + return toReturn; + } + + public Task getGtask(String listId, String taskId) throws IOException { + Task toReturn; + try { + toReturn = service.tasks.get(listId, taskId).execute(); + } catch (IOException e) { + toReturn = service.tasks.get(listId, taskId).execute(); + } + return toReturn; + } + + public Task createGtask(String listId, String title, String notes, String due) throws IOException { + Task newGtask = new Task(); + newGtask.title = title; + newGtask.notes = notes; + newGtask.due = due; + + return createGtask(listId, newGtask); + } + + public Task createGtask(String listId, Task task) throws IOException { + return createGtask(listId, task, null, null); + } + + public Task createGtask(String listId, Task task, String parent, String priorSiblingId) throws IOException { + Insert insertOp = service.tasks.insert(listId, task); + insertOp.parent = parent; + insertOp.previous = priorSiblingId; + + Task toReturn; + try { + toReturn = insertOp.execute(); + } catch (IOException e) { + toReturn = insertOp.execute(); + } + return toReturn; + } + + public Task updateGtask(String listId, Task task) throws IOException { + Task toReturn; + try { + toReturn = service.tasks.update(listId, task.id, task).execute(); + } catch (IOException e) { + toReturn = service.tasks.update(listId, task.id, task).execute(); + } + return toReturn; + } + + public Task moveGtask(String listId, String taskId, String parentId, String previousId) throws IOException { + Move move = service.tasks.move(listId, taskId); + move.parent = parentId; + move.previous = previousId; + + Task toReturn; + try { + toReturn = move.execute(); + } catch (IOException e) { + toReturn = move.execute(); + } + return toReturn; + } + + public void deleteGtask(String listId, String taskId) throws IOException { + try { + service.tasks.delete(listId, taskId).execute(); + } catch (IOException e) { + service.tasks.delete(listId, taskId).execute(); + } + } + + public void clearCompletedTasks(String listId) throws IOException { + try { + service.tasks.clear(listId).execute(); + } catch (IOException e) { + service.tasks.clear(listId).execute(); + } + } +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/api/MoveListRequest.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/MoveListRequest.java new file mode 100644 index 000000000..be59cefe3 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/MoveListRequest.java @@ -0,0 +1,61 @@ +package com.todoroo.astrid.gtasks.api; + +import java.io.IOException; + +import com.google.api.services.tasks.v1.model.Task; +/** + * Encapsulates a request to the api to move a task from one list to another + * @author Sam Bosley + * + */ +public class MoveListRequest extends PushRequest { + + private String idTaskToMove; + private String dstList; + private final String newParent; + + public MoveListRequest(GtasksService service, String idTask, String srcList, String dstList, String newParent) { + super(service, srcList, new Task()); + this.idTaskToMove = idTask; + this.dstList = dstList; + this.newParent = newParent; + } + + @Override + public Task executePush() throws IOException { + Task localSave = service.getGtask(super.getListId(), idTaskToMove); + service.deleteGtask(super.getListId(), idTaskToMove); + transferProperties(localSave); + return service.createGtask(dstList, toPush); + } + + private void transferProperties(Task local) { + toPush.completed = local.completed; + toPush.deleted = local.deleted; + toPush.due = local.due; + toPush.hidden = local.hidden; + toPush.notes = local.notes; + toPush.status = local.status; + toPush.title = local.title; + + toPush.parent = newParent; + } + + public String getIdTaskToMove() { + return idTaskToMove; + } + + public void setIdTaskToMove(String idTaskToMove) { + this.idTaskToMove = idTaskToMove; + } + + public String getDstList() { + return dstList; + } + + public void setDstList(String dstList) { + this.dstList = dstList; + } + + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/api/MoveRequest.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/MoveRequest.java new file mode 100644 index 000000000..e6eb77fa3 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/MoveRequest.java @@ -0,0 +1,52 @@ +package com.todoroo.astrid.gtasks.api; + +import java.io.IOException; + +import com.google.api.services.tasks.v1.model.Task; +/** + * Encapsulates a request to the api to change the ordering on the given task + * @author Sam Bosley + * + */ +public class MoveRequest extends PushRequest { + + private String taskId; + private String parentId; + private String priorSiblingId; + + public MoveRequest(GtasksService service, String taskId, String destinationList, String parentId, String priorSiblingId) { + super(service, destinationList, null); + this.taskId = taskId; + this.parentId = parentId; + this.priorSiblingId = priorSiblingId; + } + + public String getTaskId() { + return taskId; + } + + public void setTaskId(String taskId) { + this.taskId = taskId; + } + + @Override + public Task executePush() throws IOException { + return service.moveGtask(super.listId, taskId, parentId, priorSiblingId); + } + + public String getParentId() { + return parentId; + } + + public void setParentId(String parentId) { + this.parentId = parentId; + } + + public String getPriorSiblingId() { + return priorSiblingId; + } + + public void setPriorSiblingId(String priorSiblingId) { + this.priorSiblingId = priorSiblingId; + } +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/api/PushRequest.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/PushRequest.java new file mode 100644 index 000000000..24dc68f7d --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/PushRequest.java @@ -0,0 +1,45 @@ +package com.todoroo.astrid.gtasks.api; + +import java.io.IOException; + +import com.google.api.services.tasks.v1.model.Task; + +/** + * Abstract class that encapsulates some push request to the server + * @author Sam Bosley + * + */ +public abstract class PushRequest { + protected String listId; + protected Task toPush; + protected GtasksService service; + + public PushRequest(GtasksService service, String listId, Task toPush) { + this.service = service; + this.listId = listId; + this.toPush = toPush; + } + + public String getListId() { + return listId; + } + public void setListId(String listId) { + this.listId = listId; + } + + public Task getToPush() { + return toPush; + } + public void setToPush(Task toPush) { + this.toPush = toPush; + } + + public GtasksService getService() { + return service; + } + public void setService(GtasksService service) { + this.service = service; + } + + public abstract Task executePush() throws IOException; +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/api/UpdateRequest.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/UpdateRequest.java new file mode 100644 index 000000000..1f367cc5f --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/api/UpdateRequest.java @@ -0,0 +1,23 @@ +package com.todoroo.astrid.gtasks.api; + +import java.io.IOException; + +import com.google.api.services.tasks.v1.model.Task; + +/** + * Encapsulates a request to the api to update a task on the remote server + * @author Sam Bosley + * + */ +public class UpdateRequest extends PushRequest { + + public UpdateRequest(GtasksService service, String listId, Task toUpdate) { + super(service, listId, toUpdate); + } + + @Override + public Task executePush() throws IOException { + return service.updateGtask(listId, toPush); + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/auth/GtasksLoginActivity.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/auth/GtasksLoginActivity.java index f1a37c344..d9dbfeed5 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/auth/GtasksLoginActivity.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/auth/GtasksLoginActivity.java @@ -19,54 +19,51 @@ */ package com.todoroo.astrid.gtasks.auth; -import java.io.IOException; +import java.util.ArrayList; -import org.json.JSONException; - -import android.app.Activity; -import android.app.ProgressDialog; +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerCallback; +import android.accounts.AccountManagerFuture; +import android.app.ListActivity; import android.content.Intent; -import android.os.Build; import android.os.Bundle; -import android.text.Editable; -import android.text.Html; -import android.text.method.LinkMovementMethod; -import android.util.Log; import android.view.View; -import android.view.View.OnClickListener; -import android.widget.Button; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.TextView; - -import com.google.android.googlelogin.GoogleLoginServiceConstants; -import com.google.android.googlelogin.GoogleLoginServiceHelper; +import android.widget.ArrayAdapter; +import android.widget.ListView; +import android.widget.Toast; + +import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager; import com.timsu.astrid.R; import com.todoroo.andlib.service.Autowired; import com.todoroo.andlib.service.ContextManager; import com.todoroo.andlib.service.DependencyInjectionService; -import com.todoroo.andlib.utility.DialogUtilities; import com.todoroo.andlib.utility.Preferences; import com.todoroo.astrid.gtasks.GtasksBackgroundService; import com.todoroo.astrid.gtasks.GtasksPreferenceService; +import com.todoroo.astrid.gtasks.api.GtasksService; import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.StatisticsService; -import com.todoroo.gtasks.GoogleConnectionManager; -import com.todoroo.gtasks.GoogleLoginException; -import com.todoroo.gtasks.GoogleTasksException; /** - * This activity allows users to sign in or log in to Producteev + * This activity allows users to sign in or log in to Google Tasks + * through the Android account manager * - * @author arne.jans + * @author Sam Bosley * */ -public class GtasksLoginActivity extends Activity { +public class GtasksLoginActivity extends ListActivity { @Autowired private GtasksPreferenceService gtasksPreferenceService; // --- ui initialization + private GoogleAccountManager accountManager; + private String[] nameArray; + + private String authToken; + private String accountName; + static { AstridDependencyInjector.initialize(); } @@ -81,104 +78,53 @@ public class GtasksLoginActivity extends Activity { super.onCreate(savedInstanceState); ContextManager.setContext(this); - setContentView(R.layout.gtasks_login_activity); setTitle(R.string.gtasks_GLA_title); + accountManager = new GoogleAccountManager(this); + Account[] accounts = accountManager.getAccounts(); + ArrayList accountNames = new ArrayList(); + for (Account a : accounts) { + accountNames.add(a.name); + } - final TextView errors = (TextView) findViewById(R.id.error); - final EditText emailEditText = (EditText) findViewById(R.id.email); - final EditText passwordEditText = (EditText) findViewById(R.id.password); - final CheckBox isDomain = (CheckBox) findViewById(R.id.isDomain); - - Button signIn = (Button) findViewById(R.id.signIn); - signIn.setOnClickListener(new OnClickListener() { - public void onClick(View v) { - errors.setVisibility(View.GONE); - Editable email = emailEditText.getText(); - Editable password = passwordEditText.getText(); - if(email.length() == 0 || password.length() == 0) { - errors.setVisibility(View.VISIBLE); - errors.setText(R.string.producteev_PLA_errorEmpty); - return; - } - - performLogin(email.toString(), password.toString(), isDomain.isChecked()); - } - - }); - - getCredentials(new OnGetCredentials() { - @Override - public void getCredentials(String[] accounts) { - if(accounts != null && accounts.length > 0) - emailEditText.setText(accounts[0]); - } - }); + nameArray = accountNames.toArray(new String[accountNames.size()]); + setListAdapter(new ArrayAdapter(this, android.R.layout.simple_list_item_1, nameArray)); } - private int loginTries = 0; - - private void performLogin(final String email, final String password, final boolean isDomain) { - final ProgressDialog dialog = DialogUtilities.progressDialog(this, - getString(R.string.DLG_wait)); - final TextView errors = (TextView) findViewById(R.id.error); - dialog.show(); - new Thread() { - @SuppressWarnings("nls") - @Override - public void run() { - final StringBuilder errorMessage = new StringBuilder(); - GoogleConnectionManager gcm = new GoogleConnectionManager(email.toString(), - password.toString(), !isDomain); - - loginTries++; - + @Override + protected void onListItemClick(ListView l, View v, int position, long id) { + super.onListItemClick(l, v, position, id); + Toast.makeText(this, R.string.gtasks_GLA_authenticating, Toast.LENGTH_LONG); + final Account a = accountManager.getAccountByName(nameArray[position]); + accountName = a.name; + AccountManagerCallback callback = new AccountManagerCallback() { + public void run(AccountManagerFuture future) { try { - gcm.authenticate(false); - gcm.get(); - String token = gcm.getToken(); - gtasksPreferenceService.setToken(token); - StatisticsService.reportEvent("gtasks-login"); - Preferences.setString(GtasksPreferenceService.PREF_USER_NAME, email); - Preferences.setString(GtasksPreferenceService.PREF_PASSWORD, password); - Preferences.setBoolean(GtasksPreferenceService.PREF_IS_DOMAIN, isDomain); - - synchronize(); - } catch (GoogleLoginException e) { - errorMessage.append(getString(R.string.gtasks_GLA_errorAuth)); - - if(loginTries > 1) { - errorMessage.append("

").append(getString( - R.string.gtasks_GLA_errorAuth_captcha)).append( - "
Google Sign In"); + Bundle bundle = future.getResult(); + if (bundle.containsKey(AccountManager.KEY_INTENT)) { + Intent i = (Intent) bundle.get(AccountManager.KEY_INTENT); + startActivityForResult(i, REQUEST_AUTHENTICATE); + } else if (bundle.containsKey(AccountManager.KEY_AUTHTOKEN)) { + authToken = bundle.getString(AccountManager.KEY_AUTHTOKEN); + onAuthTokenSuccess(); } - - Log.e("gtasks", "login-auth", e); - return; - } catch (GoogleTasksException e) { - errorMessage.append(getString(R.string.gtasks_GLA_errorAuth)); - Log.e("gtasks", "login-gtasks", e); - } catch (JSONException e) { - errorMessage.append(getString(R.string.gtasks_GLA_errorAuth)); - Log.e("gtasks", "login-json", e); - } catch (IOException e) { - errorMessage.append(getString(R.string.SyP_ioerror)); - Log.e("gtasks", "login-io", e); - return; - } finally { - runOnUiThread(new Runnable() { - public void run() { - dialog.dismiss(); - if(errorMessage.length() > 0) { - errors.setVisibility(View.VISIBLE); - errors.setText(Html.fromHtml(errorMessage.toString())); - errors.setMovementMethod(LinkMovementMethod.getInstance()); - } - } - }); + } catch (Exception e) { + onAuthCancel(); } } - }.start(); + }; + accountManager.manager.getAuthToken(a, GtasksService.AUTH_TOKEN_TYPE, true, callback, null); + } + + private void onAuthCancel() { + setResult(RESULT_CANCELED); + finish(); + } + + private void onAuthTokenSuccess() { + gtasksPreferenceService.setToken(authToken); + Preferences.setString(GtasksPreferenceService.PREF_USER_NAME, accountName); + synchronize(); } /** @@ -202,31 +148,18 @@ public class GtasksLoginActivity extends Activity { StatisticsService.sessionStop(this); } - // --- account management - - - private static final int REQUEST_CODE_GOOGLE = 1; + private static final int REQUEST_AUTHENTICATE = 0; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); - if(requestCode == REQUEST_CODE_GOOGLE){ - String accounts[] = data.getExtras().getStringArray(GoogleLoginServiceConstants.ACCOUNTS_KEY); - credentialsListener.getCredentials(accounts); - } - } - public interface OnGetCredentials { - public void getCredentials(String[] accounts); - } - - private OnGetCredentials credentialsListener; + if(requestCode == REQUEST_AUTHENTICATE && resultCode == RESULT_OK){ + //User gave permission--huzzah! - public void getCredentials(OnGetCredentials onGetCredentials) { - credentialsListener = onGetCredentials; - if(Integer.parseInt(Build.VERSION.SDK) >= 7) - credentialsListener.getCredentials(ModernAuthManager.getAccounts(this)); - else - GoogleLoginServiceHelper.getAccount(this, REQUEST_CODE_GOOGLE, false); + } else { + //User didn't give permission--cancel + onAuthCancel(); + } } } \ No newline at end of file diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/auth/GtasksTokenValidator.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/auth/GtasksTokenValidator.java new file mode 100644 index 000000000..3d3a74b96 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/auth/GtasksTokenValidator.java @@ -0,0 +1,42 @@ +package com.todoroo.astrid.gtasks.auth; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.accounts.AccountManagerFuture; +import android.os.Bundle; + +import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager; +import com.todoroo.andlib.service.ContextManager; +import com.todoroo.andlib.utility.Preferences; +import com.todoroo.astrid.gtasks.GtasksPreferenceService; +import com.todoroo.astrid.gtasks.api.GtasksService; + +public class GtasksTokenValidator { + + private static GoogleAccountManager accountManager = new GoogleAccountManager(ContextManager.getContext()); + + /** + * Invalidates and then revalidates the auth token for the currently logged in user + * @param token + * @return + */ + public static String validateAuthToken(String token) { + Account a = accountManager.getAccountByName(Preferences.getStringValue(GtasksPreferenceService.PREF_USER_NAME)); + if (a == null) return null; + + accountManager.invalidateAuthToken(token); + AccountManagerFuture future = accountManager.manager.getAuthToken(a, GtasksService.AUTH_TOKEN_TYPE, true, null, null); + + try { + if (future.getResult().containsKey(AccountManager.KEY_AUTHTOKEN)) { + Bundle result = future.getResult(); + return result.getString(AccountManager.KEY_AUTHTOKEN); + } + } catch (Exception e) { + e.printStackTrace(); + return null; + } + return null; + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksLegacyMigrator.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksLegacyMigrator.java new file mode 100644 index 000000000..382b15b77 --- /dev/null +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksLegacyMigrator.java @@ -0,0 +1,129 @@ +package com.todoroo.astrid.gtasks.sync; + +import java.io.IOException; +import java.util.HashMap; + +import com.google.api.services.tasks.v1.model.TaskLists; +import com.google.api.services.tasks.v1.model.Tasks; +import com.todoroo.andlib.data.TodorooCursor; +import com.todoroo.andlib.service.Autowired; +import com.todoroo.andlib.service.DependencyInjectionService; +import com.todoroo.andlib.sql.Query; +import com.todoroo.andlib.utility.Preferences; +import com.todoroo.astrid.data.Metadata; +import com.todoroo.astrid.data.Task; +import com.todoroo.astrid.gtasks.GtasksListService; +import com.todoroo.astrid.gtasks.GtasksMetadata; +import com.todoroo.astrid.gtasks.GtasksMetadataService; +import com.todoroo.astrid.gtasks.GtasksPreferenceService; +import com.todoroo.astrid.gtasks.api.GtasksService; +import com.todoroo.astrid.service.AstridDependencyInjector; +import com.todoroo.astrid.service.MetadataService; +import com.todoroo.astrid.service.TaskService; + +/** + * Class to handle migration of legacy metadata (old remote ids) to new + * metadata based on the official remote ids returned by the api. + * @author Sam Bosley + * + */ +public class GtasksLegacyMigrator { + + @Autowired GtasksMetadataService gtasksMetadataService; + @Autowired TaskService taskService; + @Autowired MetadataService metadataService; + @Autowired GtasksListService gtasksListService; + + private static final String MIGRATION_HAS_OCCURRED = "gtasksLegacySyncMigrated"; //$NON-NLS-1$ + + private final GtasksService gtasksService; + private final GtasksListService listService; + private final TaskLists allLists; + + static { + AstridDependencyInjector.initialize(); + } + + public GtasksLegacyMigrator(GtasksService service,GtasksListService listService, TaskLists allLists) { + DependencyInjectionService.getInstance().inject(this); + this.gtasksService = service; + this.listService = listService; + this.allLists = allLists; + } + + public boolean checkAndMigrateLegacy() { + if (!Preferences.getBoolean(MIGRATION_HAS_OCCURRED, false)) { + + listService.migrateListIds(allLists); + + //Fetch all tasks that have associated gtask metadata + String defaultListTitle = gtasksListService.getListName(Preferences.getStringValue(GtasksPreferenceService.PREF_DEFAULT_LIST)); + String defaultListId = null; + + TodorooCursor allTasksWithGtaskData = taskService.query(Query.select(Task.PROPERTIES). + where(Task.ID.in( + Query.select(Metadata.TASK).from(Metadata.TABLE). + where(Metadata.KEY.eq(GtasksMetadata.METADATA_KEY))))); + + + + try { + if (allTasksWithGtaskData.getCount() > 0) { + //Fetch all remote tasks from all remote lists (this may be an expensive operation) + //and map their titles to their real remote ids + HashMap taskAndListTitlesToRemoteTaskIds = new HashMap(); + + for (com.google.api.services.tasks.v1.model.TaskList list : allLists.items) { + if (list.title.equals(defaultListTitle)) { + defaultListId = list.id; + } + + Tasks allTasks = gtasksService.getAllGtasksFromListId(list.id, true); + + if (allTasks.items != null) { + for (com.google.api.services.tasks.v1.model.Task t : allTasks.items) { + String key = constructKeyFromTitles(t.title, list.title); + taskAndListTitlesToRemoteTaskIds.put(key, t.id); + } + } + } + + //For each local task, check to see if its title paired with any list title has a match in the map + while (!allTasksWithGtaskData.isLast()) { + allTasksWithGtaskData.moveToNext(); + GtasksTaskContainer container = gtasksMetadataService.readTaskAndMetadata(allTasksWithGtaskData); + + //Search through lists to see if one of them has match + String taskTitle = container.task.getValue(Task.TITLE); + for (com.google.api.services.tasks.v1.model.TaskList list : allLists.items) { + String expectedKey = constructKeyFromTitles(taskTitle, list.title); + + if (taskAndListTitlesToRemoteTaskIds.containsKey(expectedKey)) { + String newRemoteTaskId = taskAndListTitlesToRemoteTaskIds.get(expectedKey); + String newRemoteListId = list.id; + + container.gtaskMetadata.setValue(GtasksMetadata.ID, newRemoteTaskId); + container.gtaskMetadata.setValue(GtasksMetadata.LIST_ID, newRemoteListId); + gtasksMetadataService.saveTaskAndMetadata(container); + break; + } + } + } + if (defaultListId == null) defaultListId = "@default"; //$NON-NLS-1$ + Preferences.setString(GtasksPreferenceService.PREF_DEFAULT_LIST, defaultListId); + } + } catch (IOException e) { + return false; + } finally { + allTasksWithGtaskData.close(); + } + Preferences.setBoolean(MIGRATION_HAS_OCCURRED, true); //Record successful migration + } + return true; + } + + private String constructKeyFromTitles(String taskTitle, String listTitle) { + return taskTitle + "//" + listTitle; //$NON-NLS-1$ + } + +} diff --git a/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncProvider.java b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncProvider.java index eef7f0ad9..b9d03defe 100644 --- a/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncProvider.java +++ b/astrid/plugin-src/com/todoroo/astrid/gtasks/sync/GtasksSyncProvider.java @@ -21,6 +21,8 @@ import android.content.Intent; import android.text.TextUtils; import android.util.Log; +import com.google.api.services.tasks.v1.model.TaskList; +import com.google.api.services.tasks.v1.model.TaskLists; import com.timsu.astrid.R; import com.todoroo.andlib.data.AbstractModel; import com.todoroo.andlib.data.Property; @@ -44,28 +46,22 @@ import com.todoroo.astrid.gtasks.GtasksMetadataService; import com.todoroo.astrid.gtasks.GtasksPreferenceService; import com.todoroo.astrid.gtasks.GtasksPreferences; import com.todoroo.astrid.gtasks.GtasksTaskListUpdater; +import com.todoroo.astrid.gtasks.api.CreateRequest; +import com.todoroo.astrid.gtasks.api.GoogleTasksException; +import com.todoroo.astrid.gtasks.api.GtasksApiUtilities; +import com.todoroo.astrid.gtasks.api.GtasksService; +import com.todoroo.astrid.gtasks.api.MoveListRequest; +import com.todoroo.astrid.gtasks.api.MoveRequest; +import com.todoroo.astrid.gtasks.api.PushRequest; +import com.todoroo.astrid.gtasks.api.UpdateRequest; import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity; +import com.todoroo.astrid.gtasks.auth.GtasksTokenValidator; import com.todoroo.astrid.service.AstridDependencyInjector; import com.todoroo.astrid.service.StatisticsService; import com.todoroo.astrid.sync.SyncContainer; import com.todoroo.astrid.sync.SyncProvider; import com.todoroo.astrid.sync.SyncProviderUtilities; import com.todoroo.astrid.utility.Constants; -import com.todoroo.gtasks.GoogleConnectionManager; -import com.todoroo.gtasks.GoogleLoginException; -import com.todoroo.gtasks.GoogleTaskService; -import com.todoroo.gtasks.GoogleTaskTask; -import com.todoroo.gtasks.GoogleTaskView; -import com.todoroo.gtasks.GoogleTasksException; -import com.todoroo.gtasks.actions.Action; -import com.todoroo.gtasks.actions.Actions; -import com.todoroo.gtasks.actions.GetTasksAction; -import com.todoroo.gtasks.actions.ListAction; -import com.todoroo.gtasks.actions.ListActions; -import com.todoroo.gtasks.actions.ListActions.TaskBuilder; -import com.todoroo.gtasks.actions.ListActions.TaskCreator; -import com.todoroo.gtasks.actions.ListActions.TaskModifier; -import com.todoroo.gtasks.actions.ListCreationAction; @SuppressWarnings("nls") public class GtasksSyncProvider extends SyncProvider { @@ -76,12 +72,15 @@ public class GtasksSyncProvider extends SyncProvider { @Autowired private GtasksTaskListUpdater gtasksTaskListUpdater; /** google task service fields */ - private GoogleTaskService taskService = null; - private static final Actions a = new Actions(); - private static final ListActions l = new ListActions(); + private GtasksService taskService = null; + + public GtasksService getGtasksService() { + return taskService; + } /** tasks to read id for */ ArrayList createdWithoutId; + ArrayList createdWithoutParent; Semaphore pushedTaskSemaphore = new Semaphore(0); AtomicInteger pushedTaskCount = new AtomicInteger(0); @@ -104,7 +103,7 @@ public class GtasksSyncProvider extends SyncProvider { public void signOut() { gtasksPreferenceService.clearLastSyncDate(); gtasksPreferenceService.setToken(null); - + Preferences.setString(GtasksPreferenceService.PREF_USER_NAME, null); gtasksMetadataService.clearMetadata(); } @@ -119,19 +118,10 @@ public class GtasksSyncProvider extends SyncProvider { protected void initiateBackground() { try { String authToken = gtasksPreferenceService.getToken(); + authToken = GtasksTokenValidator.validateAuthToken(authToken); + gtasksPreferenceService.setToken(authToken); - final GoogleConnectionManager connectionManager; - if(authToken == null) { - Log.e("astrid-sync", "No token, unable to sync"); - return; - } else { - connectionManager = new GoogleConnectionManager( - Preferences.getStringValue(GtasksPreferenceService.PREF_USER_NAME), - Preferences.getStringValue(GtasksPreferenceService.PREF_PASSWORD), - !Preferences.getBoolean(GtasksPreferenceService.PREF_IS_DOMAIN, false)); - } - - taskService = new GoogleTaskService(connectionManager); + taskService = new GtasksService(authToken); performSync(); } catch (IllegalStateException e) { // occurs when application was closed @@ -171,11 +161,16 @@ public class GtasksSyncProvider extends SyncProvider { if(Constants.DEBUG) Log.e("gtasks-debug", "- -------- SYNC STARTED"); createdWithoutId = new ArrayList(); + createdWithoutParent = new ArrayList(); try { - GoogleTaskView taskView = taskService.getTaskView(); - getActiveList(taskView); + TaskLists allTaskLists = taskService.allGtaskLists(); - gtasksListService.updateLists(taskView.getAllLists()); + //TODO: do something with result of migration check? + new GtasksLegacyMigrator(taskService, gtasksListService, allTaskLists).checkAndMigrateLegacy(); + + getActiveList(allTaskLists); + + gtasksListService.updateLists(allTaskLists); gtasksTaskListUpdater.createParentSiblingMaps(); @@ -207,19 +202,18 @@ public class GtasksSyncProvider extends SyncProvider { } } - private void getActiveList(GoogleTaskView taskView) throws JSONException, - IOException, GoogleLoginException { + private void getActiveList(TaskLists taskView) throws JSONException, + IOException { String listId; - if(taskView.getActiveTaskList() != null && taskView.getActiveTaskList().getInfo() != null) - listId = taskView.getActiveTaskList().getInfo().getId(); - else if(taskView.getAllLists().length == 0) { - ListCreationAction createList = a.createList(0, ContextManager.getString(R.string.app_name)); + if(taskView.items.size() == 0) { if(Constants.DEBUG) Log.e("gtasks-debug", "ACTION: createList(4)"); - taskService.executeActions(createList); - listId = createList.getNewId(); + TaskList newList = taskService.createGtaskList(ContextManager.getString(R.string.app_name)); + listId = newList.id; + } else if (Preferences.getStringValue(GtasksPreferenceService.PREF_DEFAULT_LIST) != null) { + listId = Preferences.getStringValue(GtasksPreferenceService.PREF_DEFAULT_LIST); } else { - listId = taskView.getAllLists()[0].getId(); + listId = "@default"; } Preferences.setString(GtasksPreferenceService.PREF_DEFAULT_LIST, listId); @@ -232,41 +226,54 @@ public class GtasksSyncProvider extends SyncProvider { // wait for pushed threads try { pushedTaskSemaphore.acquire(pushedTaskCount.get()); + pushedTaskCount.set(0); } catch (InterruptedException e) { return; } + // first, pull all tasks. then we can write them // include deleted tasks so we can delete them in astrid data.remoteUpdated = readAllRemoteTasks(true); // match remote tasks to locally created tasks HashMap locals = new HashMap(); + HashMap localIdsToRemoteIds = new HashMap(); for(GtasksTaskContainer task : createdWithoutId) { - locals.put(task.task.getValue(Task.TITLE), task); + locals.put(task.gtaskMetadata.getValue(GtasksMetadata.ID), task); + localIdsToRemoteIds.put(task.task.getId(), task.gtaskMetadata.getValue(GtasksMetadata.ID)); } - ArrayList moveActions = new ArrayList(); + + verifyCreatedOrder(locals, localIdsToRemoteIds); + for(GtasksTaskContainer remote : data.remoteUpdated) { if(remote.task.getId() < 1) { - GtasksTaskContainer local = locals.get(remote.task.getValue(Task.TITLE)); + GtasksTaskContainer local = locals.get(remote.gtaskMetadata.getValue(GtasksMetadata.ID)); if(local != null) { if(Constants.DEBUG) - Log.e("gtasks-debug", "FOUND LOCAL - " + remote.task.getValue(Task.TITLE)); + Log.e("gtasks-debug", "FOUND LOCAL - " + remote.task.getId()); remote.task.setId(local.task.getId()); } } } - if(moveActions.size() > 0) { - try { - taskService.executeActions(moveActions.toArray(new Action[moveActions.size()])); - } catch (JSONException e) { - Log.e("gtasks-sync", "Error Running Local Action", e); - } - } super.readRemotelyUpdated(data); } + private void verifyCreatedOrder(HashMap locals, + HashMap localIdsToRemoteIds) throws IOException { + for (GtasksTaskContainer t : createdWithoutParent) { + String toMove = t.gtaskMetadata.getValue(GtasksMetadata.ID); + String listId = t.gtaskMetadata.getValue(GtasksMetadata.LIST_ID); + long parentTask = t.gtaskMetadata.getValue(GtasksMetadata.PARENT_TASK); + if (parentTask > 0) { + String remoteParent = localIdsToRemoteIds.get(parentTask); + MoveRequest move = new MoveRequest(taskService, toMove, listId, remoteParent, null); + move.executePush(); + } + } + } + // ---------------------------------------------------------------------- // ------------------------------------------------------------ sync data @@ -288,8 +295,7 @@ public class GtasksSyncProvider extends SyncProvider { * Populate SyncData data structure * @throws JSONException */ - private SyncData populateSyncData() throws JSONException, - GoogleLoginException, IOException { + private SyncData populateSyncData() throws JSONException, IOException { // fetch remote tasks ArrayList remoteTasks = readAllRemoteTasks(false); @@ -321,9 +327,7 @@ public class GtasksSyncProvider extends SyncProvider { String listId = dashboard.getValue(GtasksList.REMOTE_ID); if(Constants.DEBUG) Log.e("gtasks-debug", "ACTION: getTasks, " + listId); - GetTasksAction action = new GetTasksAction(listId, includeDeleted); - taskService.executeActions(action); - List list = action.getGoogleTasks(); + List list = taskService.getAllGtasksFromListId(listId, includeDeleted).items; addRemoteTasksToList(list, remoteTasks); } catch (Exception e) { handleException("read-remotes", e, false); @@ -342,167 +346,194 @@ public class GtasksSyncProvider extends SyncProvider { return remoteTasks; } - private void addRemoteTasksToList(List list, - ArrayList remoteTasks) { - - int order = 0; - HashMap parents = new HashMap(); - HashMap indentation = new HashMap(); - HashMap parentToPriorSiblingMap = new HashMap(); - - for(GoogleTaskTask remoteTask : list) { - if(TextUtils.isEmpty(remoteTask.getName())) - continue; - - GtasksTaskContainer container = parseRemoteTask(remoteTask); - String id = remoteTask.getId(); - - // update parents, prior sibling - for(String child : remoteTask.getChild_ids()) - parents.put(child, id); - String parent = parents.get(id); // can be null, which means top level task - container.parentId = parent; - if(parentToPriorSiblingMap.containsKey(parent)) - container.priorSiblingId = parentToPriorSiblingMap.get(parent); - parentToPriorSiblingMap.put(parent, id); - - // update order, indent - container.gtaskMetadata.setValue(GtasksMetadata.ORDER, order++); - int indent = findIndentation(parents, indentation, id); - indentation.put(id, indent); - container.gtaskMetadata.setValue(GtasksMetadata.INDENT, indent); - - // update reminder flags for incoming remote tasks to prevent annoying - if(container.task.hasDueDate() && container.task.getValue(Task.DUE_DATE) < DateUtilities.now()) - container.task.setFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE, false); - - gtasksMetadataService.findLocalMatch(container); - remoteTasks.add(container); + private void addRemoteTasksToList(List remoteTasks, + ArrayList list) { + + if (remoteTasks != null) { + int order = 0; + //HashMap> children = new HashMap>(); + HashMap idsToTasks = new HashMap(); + HashMap indentation = new HashMap(); + HashMap parentToPriorSiblingMap = new HashMap(); + + + //Build map of String ids to task objects + for (com.google.api.services.tasks.v1.model.Task task : remoteTasks) { + String id = task.id; + idsToTasks.put(id, task); + } + + for(com.google.api.services.tasks.v1.model.Task remoteTask : remoteTasks) { + if(TextUtils.isEmpty(remoteTask.title)) + continue; + + GtasksTaskContainer container = parseRemoteTask(remoteTask); + String id = remoteTask.id; + + // update parents, prior sibling + String parent = remoteTask.parent; // can be null, which means top level task + container.parentId = parent; + if(parentToPriorSiblingMap.containsKey(parent)) + container.priorSiblingId = parentToPriorSiblingMap.get(parent); + parentToPriorSiblingMap.put(parent, id); + + // update order, indent + container.gtaskMetadata.setValue(GtasksMetadata.ORDER, order++); + int indent = findIndentation(idsToTasks, indentation, remoteTask); + indentation.put(id, indent); + container.gtaskMetadata.setValue(GtasksMetadata.INDENT, indent); + + // update reminder flags for incoming remote tasks to prevent annoying + if(container.task.hasDueDate() && container.task.getValue(Task.DUE_DATE) < DateUtilities.now()) + container.task.setFlag(Task.REMINDER_FLAGS, Task.NOTIFY_AFTER_DEADLINE, false); + + gtasksMetadataService.findLocalMatch(container); + synchronized(list) { + list.add(container); + } + } } } - private int findIndentation(HashMap parents, - HashMap indentation, String task) { - if(indentation.containsKey(task)) - return indentation.get(task); + private int findIndentation(HashMap idsToTasks, + HashMap indentation, com.google.api.services.tasks.v1.model.Task task) { + if(indentation.containsKey(task.id)) + return indentation.get(task.id); - if(!parents.containsKey(task)) + if(TextUtils.isEmpty(task.parent)) return 0; - return findIndentation(parents, indentation, parents.get(task)) + 1; + return findIndentation(idsToTasks, indentation, idsToTasks.get(task.parent)) + 1; } @Override protected GtasksTaskContainer create(GtasksTaskContainer local) throws IOException { - String list = Preferences.getStringValue(GtasksPreferenceService.PREF_DEFAULT_LIST); + String listId = Preferences.getStringValue(GtasksPreferenceService.PREF_DEFAULT_LIST); if(local.gtaskMetadata.containsNonNullValue(GtasksMetadata.LIST_ID)) - list = local.gtaskMetadata.getValue(GtasksMetadata.LIST_ID); + listId = local.gtaskMetadata.getValue(GtasksMetadata.LIST_ID); gtasksTaskListUpdater.updateParentAndSibling(local); local.gtaskMetadata.setValue(GtasksMetadata.ID, null); - local.gtaskMetadata.setValue(GtasksMetadata.LIST_ID, list); + local.gtaskMetadata.setValue(GtasksMetadata.LIST_ID, listId); createdWithoutId.add(local); - try { - TaskCreator createdTask = l.createTask(local.task.getValue(Task.TITLE)); - createdTask.parentId(local.parentId); - updateTaskHelper(local, null, createdTask); - return local; - } catch (JSONException e) { - throw new GoogleTasksException(e); + if (local.gtaskMetadata.containsNonNullValue(GtasksMetadata.PARENT_TASK)) { + createdWithoutParent.add(local); + } + com.google.api.services.tasks.v1.model.Task createdTask = new com.google.api.services.tasks.v1.model.Task(); + + CreateRequest createRequest = new CreateRequest(taskService, listId, createdTask, local.parentId, local.priorSiblingId); + updateTaskHelper(local, null, createRequest); + return local; + }//*/ + + private void localPropertiesToModel(GtasksTaskContainer local, GtasksTaskContainer remote, + com.google.api.services.tasks.v1.model.Task model) { + if(shouldTransmit(local, Task.TITLE, remote)) + model.title = local.task.getValue(Task.TITLE); + if(shouldTransmit(local, Task.DUE_DATE, remote)) + model.due = GtasksApiUtilities.unixTimeToGtasksTime(local.task.getValue(Task.DUE_DATE)); + if(shouldTransmit(local, Task.COMPLETION_DATE, remote)) { + model.completed = GtasksApiUtilities.unixTimeToGtasksTime(local.task.getValue(Task.COMPLETION_DATE)); + model.status = (local.task.isCompleted() ? "completed" : "needsAction"); } + if(shouldTransmit(local, Task.DELETION_DATE, remote)) + model.deleted = local.task.isDeleted(); + if(shouldTransmit(local, Task.NOTES, remote)) + model.notes = local.task.getValue(Task.NOTES); } private void updateTaskHelper(final GtasksTaskContainer local, - final GtasksTaskContainer remote, TaskBuilder builder) throws IOException { + final GtasksTaskContainer remote, final PushRequest request) throws IOException { final String idTask = local.gtaskMetadata.getValue(GtasksMetadata.ID); final String idList = local.gtaskMetadata.getValue(GtasksMetadata.LIST_ID); try { // set properties - if(shouldTransmit(local, Task.DUE_DATE, remote)) - builder.taskDate(local.task.getValue(Task.DUE_DATE)); - if(shouldTransmit(local, Task.COMPLETION_DATE, remote)) - builder.completed(local.task.isCompleted()); - if(shouldTransmit(local, Task.DELETION_DATE, remote)) - builder.deleted(local.task.isDeleted()); - if(shouldTransmit(local, Task.NOTES, remote)) - builder.notes(local.task.getValue(Task.NOTES)); + localPropertiesToModel(local, null, request.getToPush()); // write task (and perform move action if requested) - final ListAction action; - if(builder instanceof TaskModifier) { + if(request instanceof UpdateRequest) { if(Constants.DEBUG) Log.e("gtasks-debug", "ACTION: task edit (6), " + idTask); - action = ((TaskModifier) builder).done(); - } else if(builder instanceof TaskCreator) { + } else if(request instanceof CreateRequest) { if(Constants.DEBUG) Log.e("gtasks-debug", "ACTION: task create (7), " + local.task.getValue(Task.TITLE)); - action = ((TaskCreator) builder).done(); } else - throw new GoogleTasksException("Unknown builder " + builder.getClass()); + throw new GoogleTasksException("Unknown request type " + request.getClass()); pushedTaskCount.incrementAndGet(); new Thread(new Runnable() { @Override public void run() { + String newIdTask = idTask; try { - if(!TextUtils.isEmpty(idTask) && - !TextUtils.isEmpty(local.parentId) && (remote == null || local.parentId != remote.parentId || + if (request instanceof CreateRequest) { + com.google.api.services.tasks.v1.model.Task createResult = request.executePush(); + newIdTask = createResult.id; + local.gtaskMetadata.setValue(GtasksMetadata.ID, newIdTask); + } + if(!TextUtils.isEmpty(newIdTask) && (remote == null || local.parentId != remote.parentId || local.priorSiblingId != remote.priorSiblingId)) { if(Constants.DEBUG) - Log.e("gtasks-debug", "ACTION: move(1) - " + idTask + ", " + local.parentId + ", " + local.priorSiblingId); - ListAction moveAction = l.move(idTask, local.parentId, local.priorSiblingId); - taskService.executeListActions(idList, action, moveAction); - } else if(action.toJson(idList).getJSONObject("entity_delta").length() > 0) { - taskService.executeListActions(idList, action); + Log.e("gtasks-debug", "ACTION: move(1) - " + newIdTask + ", " + local.parentId + ", " + local.priorSiblingId); + //This case basically defaults to whatever local settings are. Future versions could try and merge better + MoveRequest moveRequest = new MoveRequest(taskService, newIdTask, idList, local.parentId, local.priorSiblingId); + moveRequest.executePush(); + + } + if (request instanceof UpdateRequest) { + request.executePush(); } - // moving between lists + //Strategy--delete, migrate properties, recreate, update local AND remote ids; happens in MoveListRequest if(remote != null && !idList.equals(remote.gtaskMetadata.getValue( GtasksMetadata.LIST_ID))) { if(Constants.DEBUG) - Log.e("gtasks-debug", "ACTION: moveTask(5), " + idTask + ", " + idList + " to " + + Log.e("gtasks-debug", "ACTION: moveTask(5), " + newIdTask + ", " + idList + " to " + remote.gtaskMetadata.getValue(GtasksMetadata.LIST_ID)); - taskService.executeActions(a.moveTask(idTask, idList, - remote.gtaskMetadata.getValue(GtasksMetadata.LIST_ID), null)); + MoveListRequest moveList = new MoveListRequest(taskService, newIdTask, remote.gtaskMetadata.getValue(GtasksMetadata.LIST_ID), idList, null); + com.google.api.services.tasks.v1.model.Task result = moveList.executePush(); + local.gtaskMetadata.setValue(GtasksMetadata.ID, result.id); + remote.gtaskMetadata.setValue(GtasksMetadata.ID, result.id); } } catch (IOException e) { handleException("update-task", e, false); - } catch (JSONException e) { - handleException("update-task-json", e, false); } finally { pushedTaskSemaphore.release(); } } }).start(); - } catch (JSONException e) { + } catch (Exception e) { throw new GoogleTasksException(e); } - } + }//*/ /** Create a task container for the given remote task * @throws JSONException */ - private GtasksTaskContainer parseRemoteTask(GoogleTaskTask remoteTask) { + private GtasksTaskContainer parseRemoteTask(com.google.api.services.tasks.v1.model.Task remoteTask) { Task task = new Task(); TaskDao.setDefaultReminders(task); ArrayList metadata = new ArrayList(); - task.setValue(Task.TITLE, remoteTask.getName()); + task.setValue(Task.TITLE, remoteTask.title); task.setValue(Task.CREATION_DATE, DateUtilities.now()); - task.setValue(Task.COMPLETION_DATE, remoteTask.getCompleted_date()); - task.setValue(Task.DELETION_DATE, remoteTask.isDeleted() ? DateUtilities.now() : 0); + task.setValue(Task.COMPLETION_DATE, GtasksApiUtilities.gtasksCompletedTimeToUnixTime(remoteTask.completed, 0)); + if (remoteTask.deleted == null || !remoteTask.deleted.booleanValue()) + task.setValue(Task.DELETION_DATE, 0L); + else if (remoteTask.deleted) + task.setValue(Task.DELETION_DATE, DateUtilities.now()); - long dueDate = remoteTask.getTask_date(); - task.setValue(Task.DUE_DATE, task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate)); - task.setValue(Task.NOTES, remoteTask.getNotes()); - task.setValue(Task.NOTES, remoteTask.getNotes()); + long dueDate = GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.due, 0); + long createdDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY, dueDate); + task.setValue(Task.DUE_DATE, createdDate); + task.setValue(Task.NOTES, remoteTask.notes); Metadata gtasksMetadata = GtasksMetadata.createEmptyMetadata(AbstractModel.NO_ID); - gtasksMetadata.setValue(GtasksMetadata.ID, remoteTask.getId()); - gtasksMetadata.setValue(GtasksMetadata.LIST_ID, remoteTask.getList_id()); + gtasksMetadata.setValue(GtasksMetadata.ID, remoteTask.id); + gtasksMetadata.setValue(GtasksMetadata.LIST_ID, GtasksApiUtilities.extractListIdFromSelfLink(remoteTask)); GtasksTaskContainer container = new GtasksTaskContainer(task, metadata, gtasksMetadata); @@ -523,23 +554,18 @@ public class GtasksSyncProvider extends SyncProvider { */ @Override protected GtasksTaskContainer push(GtasksTaskContainer local, GtasksTaskContainer remote) throws IOException { - try { - gtasksTaskListUpdater.updateParentAndSibling(local); + gtasksTaskListUpdater.updateParentAndSibling(local); - String id = local.gtaskMetadata.getValue(GtasksMetadata.ID); - if(Constants.DEBUG) - Log.e("gtasks-debug", "ACTION: modifyTask(3) - " + id); - TaskModifier modifyTask = l.modifyTask(id); - if(shouldTransmit(local, Task.TITLE, remote)) - modifyTask.name(local.task.getValue(Task.TITLE)); - updateTaskHelper(local, remote, modifyTask); + String id = local.gtaskMetadata.getValue(GtasksMetadata.ID); + if(Constants.DEBUG) + Log.e("gtasks-debug", "ACTION: modifyTask(3) - " + id); - } catch (JSONException e) { - throw new GoogleTasksException(e); - } + com.google.api.services.tasks.v1.model.Task toUpdate = taskService.getGtask(local.gtaskMetadata.getValue(GtasksMetadata.LIST_ID), id); + UpdateRequest modifyTask = new UpdateRequest(taskService, local.gtaskMetadata.getValue(GtasksMetadata.LIST_ID), toUpdate); + updateTaskHelper(local, remote, modifyTask); return pull(remote); - } + }//*/ // ---------------------------------------------------------------------- // --------------------------------------------------------- read / write @@ -561,7 +587,6 @@ public class GtasksSyncProvider extends SyncProvider { } else { StatisticsService.reportEvent("gtasks-task-created"); //$NON-NLS-1$ } - gtasksMetadataService.saveTaskAndMetadata(task); } @@ -573,8 +598,9 @@ public class GtasksSyncProvider extends SyncProvider { newDate.setHours(oldDate.getHours()); newDate.setMinutes(oldDate.getMinutes()); newDate.setSeconds(oldDate.getSeconds()); - remote.setValue(Task.DUE_DATE, remote.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, - newDate.getTime())); + long setDate = Task.createDueDate(Task.URGENCY_SPECIFIC_DAY_TIME, + newDate.getTime()); + remote.setValue(Task.DUE_DATE, setDate); } } diff --git a/astrid/res/layout/gtasks_login_activity.xml b/astrid/res/layout/gtasks_login_activity.xml index a71e83ff9..7e1fd031f 100644 --- a/astrid/res/layout/gtasks_login_activity.xml +++ b/astrid/res/layout/gtasks_login_activity.xml @@ -1,67 +1,14 @@ - - - - - - - - -