Google Tasks Api integration. Basic tests are passing, various edge cases may still need work. Code for accessing account manager has been factored in to the repurposed GtasksLoginActivity activity class. A one-time migration will occur for legacy users to update remote task ids.

pull/14/head
Sam Bosley 13 years ago
parent 8d3e7f5a24
commit 89dc6d462a

@ -152,13 +152,15 @@ public abstract class SyncProvider<TYPE extends SyncContainer> {
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

@ -11,11 +11,17 @@
<classpathentry exported="true" kind="lib" path="libs/commons-codec-1.3.jar"/>
<classpathentry exported="true" kind="lib" path="libs/jsr305.jar"/>
<classpathentry exported="true" kind="lib" path="libs/locale_platform.jar"/>
<classpathentry exported="true" kind="lib" path="libs/todoroo-g.jar"/>
<classpathentry exported="true" kind="lib" path="libs/framework.jar"/>
<classpathentry exported="true" kind="lib" path="libs/httpmime-4.1.1.jar"/>
<classpathentry exported="true" kind="lib" path="libs/rfc2445-4Mar2011.jar"/>
<classpathentry kind="src" path="GreenDroid_src"/>
<classpathentry exported="true" kind="lib" path="libs/google-api-client-1.4.1-beta.jar"/>
<classpathentry exported="true" kind="lib" path="libs/google-api-client-googleapis-1.4.1-beta.jar"/>
<classpathentry exported="true" kind="lib" path="libs/google-api-services-tasks-v1-1.0.0-beta.jar"/>
<classpathentry exported="true" kind="lib" path="libs/guava-r09.jar"/>
<classpathentry exported="true" kind="lib" path="libs/jackson-core-asl-1.6.7.jar"/>
<classpathentry exported="true" kind="lib" path="libs/google-api-client-googleapis-extensions-android2-1.4.1-beta.jar"/>
<classpathentry exported="true" kind="lib" path="libs/google-api-client-extensions-android2-1.4.1-beta.jar"/>
<classpathentry kind="src" path="astridApi_src"/>
<classpathentry kind="src" path="facebook_src"/>
<classpathentry kind="output" path="ecbuild"/>

@ -63,6 +63,7 @@
<uses-sdk android:targetSdkVersion="10"
android:minSdkVersion="4" />
<supports-screens />
<uses-permission android:name="android.permission.USE_CREDENTIALS"></uses-permission>
<application android:icon="@drawable/icon" android:label="@string/app_name"
android:theme="@style/Theme"
@ -251,8 +252,9 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity android:name="com.todoroo.astrid.gtasks.auth.GtasksAuthTokenProvider"/>
<activity android:name="com.todoroo.astrid.gtasks.auth.GtasksLoginActivity"
android:theme="@style/White" android:configChanges="orientation|keyboardHidden"/>
android:theme="@style/Theme" android:configChanges="orientation|keyboardHidden"/>
<receiver android:name="com.todoroo.astrid.gtasks.GtasksCustomFilterCriteriaExposer">
<intent-filter>
<action android:name="com.todoroo.astrid.REQUEST_CUSTOM_FILTER_CRITERIA" />

Binary file not shown.

Binary file not shown.

@ -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);
}

@ -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$

@ -0,0 +1,22 @@
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 {
public CreateRequest(GtasksService service, String listId, Task toUpdate) {
super(service, listId, toUpdate);
}
@Override
public Task executePush() throws IOException {
return service.createGtask(listId, toPush);
}
}

@ -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);
}
}

@ -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];
}
}

@ -0,0 +1,186 @@
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.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 {
Task toReturn;
try {
toReturn = service.tasks.insert(listId, task).execute();
} catch (IOException e) {
toReturn = service.tasks.insert(listId, task).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();
}
}
}

@ -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;
}
}

@ -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;
}
}

@ -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;
}

@ -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);
}
}

@ -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<String> accountNames = new ArrayList<String>();
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<String>(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<Bundle> callback = new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> 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("<br/><br/>").append(getString(
R.string.gtasks_GLA_errorAuth_captcha)).append(
"<br/><a href='https://www.google.com/accounts/ServiceLogin'>Google Sign In</a>");
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();
}
}
}

@ -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<Bundle> 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;
}
}

@ -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<Task> 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<String, String> taskAndListTitlesToRemoteTaskIds = new HashMap<String, String>();
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$
}
}

@ -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<GtasksTaskContainer> {
@ -76,9 +72,11 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
@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<GtasksTaskContainer> createdWithoutId;
@ -104,7 +102,7 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
public void signOut() {
gtasksPreferenceService.clearLastSyncDate();
gtasksPreferenceService.setToken(null);
Preferences.setString(GtasksPreferenceService.PREF_USER_NAME, null);
gtasksMetadataService.clearMetadata();
}
@ -119,19 +117,10 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
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
@ -172,10 +161,14 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
Log.e("gtasks-debug", "- -------- SYNC STARTED");
createdWithoutId = new ArrayList<GtasksTaskContainer>();
try {
GoogleTaskView taskView = taskService.getTaskView();
getActiveList(taskView);
TaskLists allTaskLists = taskService.allGtaskLists();
//TODO: do something with result of migration check?
new GtasksLegacyMigrator(taskService, gtasksListService, allTaskLists).checkAndMigrateLegacy();
gtasksListService.updateLists(taskView.getAllLists());
getActiveList(allTaskLists);
gtasksListService.updateLists(allTaskLists);
gtasksTaskListUpdater.createParentSiblingMaps();
@ -207,19 +200,18 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
}
}
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,6 +224,7 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
// wait for pushed threads
try {
pushedTaskSemaphore.acquire(pushedTaskCount.get());
pushedTaskCount.set(0);
} catch (InterruptedException e) {
return;
}
@ -245,7 +238,7 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
for(GtasksTaskContainer task : createdWithoutId) {
locals.put(task.task.getValue(Task.TITLE), task);
}
ArrayList<Action> moveActions = new ArrayList<Action>();
for(GtasksTaskContainer remote : data.remoteUpdated) {
if(remote.task.getId() < 1) {
GtasksTaskContainer local = locals.get(remote.task.getValue(Task.TITLE));
@ -256,13 +249,6 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
}
}
}
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);
}
@ -288,8 +274,7 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
* Populate SyncData data structure
* @throws JSONException
*/
private SyncData<GtasksTaskContainer> populateSyncData() throws JSONException,
GoogleLoginException, IOException {
private SyncData<GtasksTaskContainer> populateSyncData() throws JSONException, IOException {
// fetch remote tasks
ArrayList<GtasksTaskContainer> remoteTasks = readAllRemoteTasks(false);
@ -321,9 +306,7 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
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<GoogleTaskTask> list = action.getGoogleTasks();
List<com.google.api.services.tasks.v1.model.Task> list = taskService.getAllGtasksFromListId(listId, includeDeleted).items;
addRemoteTasksToList(list, remoteTasks);
} catch (Exception e) {
handleException("read-remotes", e, false);
@ -342,167 +325,185 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
return remoteTasks;
}
private void addRemoteTasksToList(List<GoogleTaskTask> list,
ArrayList<GtasksTaskContainer> remoteTasks) {
int order = 0;
HashMap<String, String> parents = new HashMap<String, String>();
HashMap<String, Integer> indentation = new HashMap<String, Integer>();
HashMap<String, String> parentToPriorSiblingMap = new HashMap<String, String>();
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<com.google.api.services.tasks.v1.model.Task> remoteTasks,
ArrayList<GtasksTaskContainer> list) {
if (remoteTasks != null) {
int order = 0;
//HashMap<String, List<String>> children = new HashMap<String, List<String>>();
HashMap<String, com.google.api.services.tasks.v1.model.Task> idsToTasks = new HashMap<String, com.google.api.services.tasks.v1.model.Task>();
HashMap<String, Integer> indentation = new HashMap<String, Integer>();
HashMap<String, String> parentToPriorSiblingMap = new HashMap<String, String>();
//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<String, String> parents,
HashMap<String, Integer> indentation, String task) {
if(indentation.containsKey(task))
return indentation.get(task);
private int findIndentation(HashMap<String, com.google.api.services.tasks.v1.model.Task> idsToTasks,
HashMap<String, Integer> 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);
com.google.api.services.tasks.v1.model.Task createdTask = new com.google.api.services.tasks.v1.model.Task();
createdTask.parent = local.parentId;
CreateRequest createRequest = new CreateRequest(taskService, listId, createdTask);
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() {
try {
if(!TextUtils.isEmpty(idTask) &&
!TextUtils.isEmpty(local.parentId) && (remote == null || local.parentId != remote.parentId ||
if(!TextUtils.isEmpty(idTask) && (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);
//This case basically defaults to whatever local settings are. Future versions could try and merge better
MoveRequest moveRequest = new MoveRequest(taskService, idTask, idList, local.parentId, local.priorSiblingId);
moveRequest.executePush();
} else {
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 " +
remote.gtaskMetadata.getValue(GtasksMetadata.LIST_ID));
taskService.executeActions(a.moveTask(idTask, idList,
remote.gtaskMetadata.getValue(GtasksMetadata.LIST_ID), null));
MoveListRequest moveList = new MoveListRequest(taskService, idTask, 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> metadata = new ArrayList<Metadata>();
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 +524,18 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
*/
@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 +557,6 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
} else {
StatisticsService.reportEvent("gtasks-task-created"); //$NON-NLS-1$
}
gtasksMetadataService.saveTaskAndMetadata(task);
}
@ -573,8 +568,9 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
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);
}
}

@ -1,67 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#ffffff">
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<ImageView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:paddingTop="5dip"
android:paddingBottom="5dip"
android:scaleType="fitCenter"
android:src="@drawable/gtasks_logo" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingBottom="10dip"
android:textSize="16sp"
android:text="@string/gtasks_GLA_body" />
<TextView
android:id="@+id/error"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:paddingBottom="20dip"
android:textColor="#ff0000"
android:textSize="16sp"
android:textStyle="bold"
android:visibility="gone" />
<EditText
android:id="@+id/email"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:hint="@string/gtasks_GLA_email"
android:inputType="textEmailAddress" />
<EditText
android:id="@+id/password"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:hint="@string/gtasks_GLA_password"
android:inputType="textPassword" />
<CheckBox
android:id="@+id/isDomain"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:text="@string/gtasks_GLA_domain"
android:visibility="gone" />
<Button
android:id="@+id/signIn"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="@string/gtasks_GLA_signIn" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingTop="10dip"
android:textSize="14sp"
android:text="@string/gtasks_GLA_further_help" />
</LinearLayout>
</ScrollView>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ListView android:id="@android:id/list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<TextView android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/gtasks_GLA_noaccounts"/>
</LinearLayout>

@ -32,6 +32,8 @@
<!-- Instructions: Gtasks login -->
<string name="gtasks_GLA_body">Please log in to Google Tasks Sync (Beta!). Non-migrated Google Apps
accounts are currently unsupported.</string>
<string name="gtasks_GLA_noaccounts">No available Google accounts to sync with.</string>
<!-- Instructions: Gtasks further help -->
<string name="gtasks_GLA_further_help">To view your tasks with indentation
@ -47,6 +49,9 @@
<!-- Password Label -->
<string name="gtasks_GLA_password">Password</string>
<!-- Authenticating toast -->
<string name="gtasks_GLA_authenticating">Authenticating...</string>
<!-- Google Apps for Domain checkbox -->
<string name="gtasks_GLA_domain">Google Apps for Domain account</string>

@ -0,0 +1,265 @@
package com.todoroo.astrid.gtasks;
import java.util.Date;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.content.Intent;
import android.os.Bundle;
import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager;
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;
import com.google.api.services.tasks.v1.model.Tasks;
import com.todoroo.andlib.service.ContextManager;
import com.todoroo.andlib.utility.Preferences;
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.auth.GtasksTokenValidator;
import com.todoroo.astrid.test.DatabaseTestCase;
@SuppressWarnings("nls")
public class GtasksApiTest extends DatabaseTestCase {
private static final String DEFAULT_LIST = "@default";
private static final String TEST_ACCOUNT = "sync_tester@astrid.com";
private static GtasksService service;
private static boolean initialized = false;
public void testCreateTask() throws Exception {
Task newTask = new Task();
String title = newTask.title = "New task";
service.createGtask(DEFAULT_LIST, newTask);
assertTrue(taskWithTitleExists(title));
}
public void testUpdateTaskProperties() throws Exception {
Task newTask = new Task();
String title = newTask.title = "This title will change";
newTask = service.createGtask(DEFAULT_LIST, newTask);
assertTrue(taskWithTitleExists(title));
String title2 = newTask.title = "Changed Title";
service.updateGtask(DEFAULT_LIST, newTask);
assertTrue(taskWithTitleExists(title2));
assertFalse(taskWithTitleExists(title));
}
public void testTaskDateFormatting() throws Exception {
Task newTask = new Task();
String title = newTask.title = "Due date will change";
newTask = service.createGtask(DEFAULT_LIST, newTask);
assertTrue(taskWithTitleExists(title));
long dueTime = new Date(114, 1, 13).getTime();
String dueTimeString = GtasksApiUtilities.unixTimeToGtasksTime(dueTime);
newTask.due = dueTimeString;
newTask = service.updateGtask(DEFAULT_LIST, newTask);
assertEquals(dueTimeString, GtasksApiUtilities.gtasksDueTimeStringToLocalTimeString(newTask.due));
assertEquals(dueTime, GtasksApiUtilities.gtasksDueTimeToUnixTime(newTask.due, 0));
long compTime = new Date(115, 2, 14).getTime();
String compTimeString = GtasksApiUtilities.unixTimeToGtasksTime(compTime);
newTask.completed = compTimeString;
newTask.status = "completed";
newTask = service.updateGtask(DEFAULT_LIST, newTask);
assertEquals(compTimeString, GtasksApiUtilities.gtasksCompletedTimeStringToLocalTimeString(newTask.completed));
assertEquals(compTime, GtasksApiUtilities.gtasksCompletedTimeToUnixTime(newTask.completed, 0));
}
public void testTaskDeleted() throws Exception {
Task newTask = new Task();
String title = newTask.title = "This task will be deleted";
newTask = service.createGtask(DEFAULT_LIST, newTask);
assertTrue(taskWithTitleExists(title));
service.deleteGtask(DEFAULT_LIST, newTask.id);
assertFalse(taskWithTitleExists(title));
}
public void testTaskMoved() throws Exception {
Task newTask1 = new Task();
String title1 = newTask1.title = "Task 1";
Task newTask2 = new Task();
String title2 = newTask2.title = "Task 2";
newTask1 = service.createGtask(DEFAULT_LIST, newTask1);
newTask2 = service.createGtask(DEFAULT_LIST, newTask2);
assertTrue(taskWithTitleExists(title1));
assertTrue(taskWithTitleExists(title2));
System.err.println("Task 1 id: " + newTask1.id);
System.err.println("Task 2 id: " + newTask2.id);
service.moveGtask(DEFAULT_LIST, newTask1.id, newTask2.id, null);
newTask1 = service.getGtask(DEFAULT_LIST, newTask1.id);
newTask2 = service.getGtask(DEFAULT_LIST, newTask2.id);
assertEquals(newTask1.parent, newTask2.id);
service.moveGtask(DEFAULT_LIST, newTask1.id, null, newTask2.id);
newTask1 = service.getGtask(DEFAULT_LIST, newTask1.id);
newTask2 = service.getGtask(DEFAULT_LIST, newTask2.id);
assertNull(newTask1.parent);
assertTrue(newTask2.position.compareTo(newTask1.position) < 0);
}
public void testMoveBetweenLists() throws Exception {
Task newTask = new Task();
String title = newTask.title = "This task will move lists";
newTask = service.createGtask(DEFAULT_LIST, newTask);
assertTrue(taskWithTitleExists(title));
String listTitle = "New list";
service.createGtaskList(listTitle);
TaskList newList;
assertNotNull(newList = listWithTitle(listTitle));
MoveListRequest moveTask = new MoveListRequest(service, newTask.id, DEFAULT_LIST, newList.id, null);
moveTask.executePush();
assertFalse(taskWithTitleExists(title));
assertTrue(listHasTaskWithTitle(newList.id, title));
}
private boolean listHasTaskWithTitle(String listId, String title) throws Exception {
com.google.api.services.tasks.v1.model.Tasks newListTasks = service.getAllGtasksFromListId(listId, false);
if (newListTasks.items != null) {
for (Task t : newListTasks.items) {
if (t.title.equals(title)) {
return true;
}
}
}
return false;
}
private boolean taskWithTitleExists(String title) throws Exception {
Tasks defaultList = service.getAllGtasksFromListId(DEFAULT_LIST, false);
if (defaultList.items != null) {
for (Task t : defaultList.items) {
if (t.title.equals(title))
return true;
}
}
return false;
}
public void testCreateList() throws Exception {
String title1 = "My new list!";
service.createGtaskList(title1);
assertNotNull(listWithTitle(title1));
String title2 = "Another new list!";
service.createGtaskList("Another new list!");
assertNotNull(listWithTitle(title2));
assertNotNull(listWithTitle(title1));
}
public void testDeleteList() throws Exception {
String title = "This list will be deleted";
TaskList t = service.createGtaskList(title);
assertNotNull(listWithTitle(title));
service.deleteGtaskList(t.id);
assertNull(listWithTitle(title));
}
public void testUpdateListProperties() throws Exception {
String title1 = "This title will change";
TaskList t = service.createGtaskList(title1);
assertNotNull(listWithTitle(title1));
String title2 = t.title = "New title";
service.updateGtaskList(t);
assertNotNull(listWithTitle(title2));
assertNull(listWithTitle(title1));
}
private TaskList listWithTitle(String title) throws Exception {
TaskLists allLists = service.allGtaskLists();
for (TaskList t : allLists.items) {
if (t.title.equals(title))
return t;
}
return null;
}
@Override
protected void setUp() throws Exception {
super.setUp();
if (!initialized) {
GoogleAccountManager manager = new GoogleAccountManager(ContextManager.getContext());
Account[] accounts = manager.getAccounts();
Account toUse = null;
for (Account a : accounts) {
if (a.name.equals(TEST_ACCOUNT)) {
toUse = a;
break;
}
}
if (toUse == null) {
toUse = accounts[0];
}
Preferences.setString(GtasksPreferenceService.PREF_USER_NAME, toUse.name);
AccountManagerFuture<Bundle> accountManagerFuture = manager.manager.getAuthToken(toUse, GtasksService.AUTH_TOKEN_TYPE, true, null, null);
Bundle authTokenBundle = accountManagerFuture.getResult();
if (authTokenBundle.containsKey(AccountManager.KEY_INTENT)) {
Intent i = (Intent) authTokenBundle.get(AccountManager.KEY_INTENT);
ContextManager.getContext().startActivity(i);
return;
}
String authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
authToken = GtasksTokenValidator.validateAuthToken(authToken);
service = new GtasksService(authToken);
initialized = true;
}
deleteAllLists();
clearDefaultList();
}
private void deleteAllLists() {
try {
TaskLists allLists = service.allGtaskLists();
for (TaskList t : allLists.items) {
if (!t.title.equals("Default List"))
service.deleteGtaskList(t.id);
}
} catch (Exception e) {
e.printStackTrace();
fail("Failed to clear lists");
}
}
private void clearDefaultList() {
try {
Tasks tasks = service.getAllGtasksFromListId(DEFAULT_LIST, false);
if (tasks.items != null) {
for (Task t : tasks.items) {
service.deleteGtask(DEFAULT_LIST, t.id);
}
}
} catch (Exception e) {
e.printStackTrace();
fail("Failed to clear default list");
}
}
}

@ -1,10 +1,15 @@
package com.todoroo.astrid.gtasks;
import java.util.ArrayList;
import java.util.List;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import com.google.api.services.tasks.v1.model.TaskList;
import com.google.api.services.tasks.v1.model.TaskLists;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.api.AstridApiConstants;
@ -12,13 +17,12 @@ import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.test.DatabaseTestCase;
import com.todoroo.gtasks.GoogleTaskListInfo;
public class GtasksDetailExposerTest extends DatabaseTestCase {
@Autowired private GtasksListService gtasksListService;
private GtasksTestPreferenceService preferences = new GtasksTestPreferenceService();
private DetailListener detailListener = new DetailListener();
private final GtasksTestPreferenceService preferences = new GtasksTestPreferenceService();
private final DetailListener detailListener = new DetailListener();
private Task task;
private String detail;
@ -86,12 +90,18 @@ public class GtasksDetailExposerTest extends DatabaseTestCase {
}
private void givenTwoListSetup() {
GoogleTaskListInfo[] newLists = new GoogleTaskListInfo[2];
GoogleTaskListInfo list = new GoogleTaskListInfo("listone-id", "List One");
newLists[0] = list;
list = new GoogleTaskListInfo("listtwo-id", "List Two");
newLists[1] = list;
gtasksListService.updateLists(newLists);
TaskLists lists = new TaskLists();
List<TaskList> newLists = new ArrayList<TaskList>();
TaskList list = new TaskList();
list.id = "listone-id";
list.title = "List One"; //new GoogleTaskListInfo("listone-id", "List One");
newLists.add(list);
list = new TaskList();
list.id = "listtwo-id";
list.title = "List Two"; //("listtwo-id", "List Two");
newLists.add(list);
lists.items = newLists;
gtasksListService.updateLists(lists);
}
private Task givenTaskWithList(String list) {
@ -142,4 +152,4 @@ public class GtasksDetailExposerTest extends DatabaseTestCase {
}
}
}
}//*/

@ -1,12 +1,17 @@
package com.todoroo.astrid.gtasks;
import java.util.ArrayList;
import java.util.List;
import com.google.api.services.tasks.v1.model.TaskList;
import com.google.api.services.tasks.v1.model.TaskLists;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.test.DatabaseTestCase;
import com.todoroo.gtasks.GoogleTaskListInfo;
@SuppressWarnings("nls")
public class GtasksIndentActionTest extends DatabaseTestCase {
@Autowired private GtasksMetadataService gtasksMetadataService;
@ -124,9 +129,13 @@ public class GtasksIndentActionTest extends DatabaseTestCase {
protected void setUp() throws Exception {
super.setUp();
GoogleTaskListInfo[] lists = new GoogleTaskListInfo[1];
GoogleTaskListInfo list = new GoogleTaskListInfo("list", "Test Tasks");
lists[0] = list;
TaskLists lists = new TaskLists();
List<TaskList> items = new ArrayList<TaskList>();
TaskList list = new TaskList();
list.id = "list";
list.title = "Test Tasks";
items.add(list);
lists.items = items;
gtasksListService.updateLists(lists);
}
@ -164,4 +173,4 @@ public class GtasksIndentActionTest extends DatabaseTestCase {
return task;
}
}
}//*/

@ -11,6 +11,7 @@ import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.test.DatabaseTestCase;
@SuppressWarnings("nls")
public class GtasksMetadataServiceTest extends DatabaseTestCase {
private final GtasksTestPreferenceService preferences = new GtasksTestPreferenceService();

@ -0,0 +1,515 @@
package com.todoroo.astrid.gtasks;
import java.util.Date;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerFuture;
import android.content.Intent;
import android.os.Bundle;
import com.google.api.client.googleapis.extensions.android2.auth.GoogleAccountManager;
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.ContextManager;
import com.todoroo.andlib.sql.Criterion;
import com.todoroo.andlib.sql.Query;
import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.andlib.utility.DateUtilities;
import com.todoroo.andlib.utility.Preferences;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.api.GtasksApiUtilities;
import com.todoroo.astrid.gtasks.api.GtasksService;
import com.todoroo.astrid.gtasks.auth.GtasksTokenValidator;
import com.todoroo.astrid.gtasks.sync.GtasksSyncProvider;
import com.todoroo.astrid.service.MetadataService;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.test.DatabaseTestCase;
@SuppressWarnings("nls")
public class GtasksNewSyncTest extends DatabaseTestCase {
private static GtasksService gtasksService;
private GtasksSyncProvider syncProvider;
private static boolean initialized = false;
private static String DEFAULT_LIST = "@default";
private static final String TEST_ACCOUNT = "sync_tester@astrid.com";
private static final long TIME_BETWEEN_SYNCS = 3000l;
@Autowired TaskService taskService;
@Autowired MetadataService metadataService;
@Autowired GtasksMetadataService gtasksMetadataService;
@Autowired GtasksPreferenceService gtasksPreferenceService;
/*
* Basic creation tests
*/
public void testTaskCreatedLocally() {
String title = "Astrid task 1";
Task localTask = createNewLocalTask(title);
whenInvokeSync();
assertTaskExistsRemotely(localTask, title);
}
public void testTaskCreatedRemotely() throws Exception {
String title = "Gtasks task 1";
com.google.api.services.tasks.v1.model.Task remoteTask = new com.google.api.services.tasks.v1.model.Task();
remoteTask.title = title;
remoteTask = gtasksService.createGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
assertTaskExistsLocally(remoteTask, title);
}
/*
* Title editing tests
*/
public void testTitleChangedLocally() throws Exception {
String title = "Astrid task 2";
Task localTask = createNewLocalTask(title);
whenInvokeSync();
com.google.api.services.tasks.v1.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new title on local task
String newTitle = "Astrid task 2 edited";
localTask.setValue(Task.TITLE, newTitle);
taskService.save(localTask);
whenInvokeSync();
//Refetch remote task and assert that both local and remote titles match expected
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newTitle, localTask.getValue(Task.TITLE));
assertEquals(newTitle, remoteTask.title);
}
public void testTitleChangedRemotely() throws Exception {
String title = "Astrid task 3";
Task localTask = createNewLocalTask(title);
whenInvokeSync();
com.google.api.services.tasks.v1.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new title on remote task
String newRemoteTitle = "Task 3 edited on gtasks";
remoteTask.title = newRemoteTitle;
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
//Refetch local/remote tasks, assert that both titles match expected
remoteTask = refetchRemoteTask(remoteTask);
localTask = refetchLocalTask(localTask);
assertEquals(newRemoteTitle, remoteTask.title);
assertEquals(newRemoteTitle, localTask.getValue(Task.TITLE));
}
public void testDateChangedLocally() throws Exception {
Task localTask = createLocalTaskForDateTests(" locally");
String title = localTask.getValue(Task.TITLE);
long startDate = localTask.getValue(Task.DUE_DATE);
whenInvokeSync();
com.google.api.services.tasks.v1.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
localTask = refetchLocalTask(localTask);
assertEquals(startDate, localTask.getValue(Task.DUE_DATE).longValue());
assertEquals(startDate, GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.due, 0));
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new due date on local task
long newDueDate = new Date(116, 1, 8).getTime();
localTask.setValue(Task.DUE_DATE, newDueDate);
taskService.save(localTask);
whenInvokeSync();
//Refetch remote task and assert that both tasks match expected due date
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newDueDate, localTask.getValue(Task.DUE_DATE).longValue());
assertEquals(newDueDate, GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.due, 0));
}
public void testDateChangedRemotely() throws Exception {
Task localTask = createLocalTaskForDateTests(" remotely");
String title = localTask.getValue(Task.TITLE);
long startDate = localTask.getValue(Task.DUE_DATE);
whenInvokeSync();
com.google.api.services.tasks.v1.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
localTask = refetchLocalTask(localTask);
assertEquals(startDate, localTask.getValue(Task.DUE_DATE).longValue());
assertEquals(startDate, GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.due, 0));
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new due date on remote task
long newDueDate = new Date(116, 1, 8).getTime();
remoteTask.due = GtasksApiUtilities.unixTimeToGtasksTime(newDueDate);
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
//Refetch remote task and assert that both tasks match expected due date
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newDueDate, localTask.getValue(Task.DUE_DATE).longValue());
assertEquals(newDueDate, GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.due, 0));
}
public void testDateChangedBoth_ChooseLocal() throws Exception {
Task localTask = createLocalTaskForDateTests(" remotely");
String title = localTask.getValue(Task.TITLE);
long startDate = localTask.getValue(Task.DUE_DATE);
whenInvokeSync();
com.google.api.services.tasks.v1.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
localTask = refetchLocalTask(localTask);
assertEquals(startDate, localTask.getValue(Task.DUE_DATE).longValue());
assertEquals(startDate, GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.due, 0));
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new due date on remote task first
long newLocalDate = new Date(128, 5, 11).getTime();
long newRemoteDate = new Date(121, 5, 25).getTime();
remoteTask.due = GtasksApiUtilities.unixTimeToGtasksTime(newRemoteDate);
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
localTask.setValue(Task.DUE_DATE, newLocalDate);
taskService.save(localTask);
whenInvokeSync();
//Refetch both and assert that due dates match the one we set to local (more recent)
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newLocalDate, localTask.getValue(Task.DUE_DATE).longValue());
assertEquals(newLocalDate, GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.due, 0));
}
public void DISABLED_testDateChangedBoth_ChooseRemote() throws Exception {
Task localTask = createLocalTaskForDateTests(" remotely");
String title = localTask.getValue(Task.TITLE);
long startDate = localTask.getValue(Task.DUE_DATE);
whenInvokeSync();
com.google.api.services.tasks.v1.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
localTask = refetchLocalTask(localTask);
assertEquals(startDate, localTask.getValue(Task.DUE_DATE).longValue());
assertEquals(startDate, GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.due, 0));
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
//Set new due date on local task first
long newLocalDate = new Date(128, 5, 11).getTime();
long newRemoteDate = new Date(121, 5, 25).getTime();
localTask.setValue(Task.DUE_DATE, newLocalDate);
taskService.save(localTask);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
remoteTask.due = GtasksApiUtilities.unixTimeToGtasksTime(newRemoteDate);
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
//Refetch both and assert that due dates match the one we set to local (more recent)
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newLocalDate, localTask.getValue(Task.DUE_DATE).longValue());
assertEquals(newLocalDate, GtasksApiUtilities.gtasksDueTimeToUnixTime(remoteTask.due, 0));
}
/*
* Helper method for due date tests
*/
private Task createLocalTaskForDateTests(String addToTitle) {
Task localTask = createNewLocalTask("Due date will change" + addToTitle);
long dueDate = new Date(115, 2, 14).getTime();
localTask.setValue(Task.DUE_DATE, dueDate);
taskService.save(localTask);
return localTask;
}
public void testNoteEditedLocally() throws Exception {
Task localTask = createLocalTaskForNoteTests(" locally");
String title = localTask.getValue(Task.TITLE);
String originalNote = localTask.getValue(Task.NOTES);
whenInvokeSync();
com.google.api.services.tasks.v1.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
assertEquals(originalNote, localTask.getValue(Task.NOTES));
assertEquals(originalNote, remoteTask.notes);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
String newNote = "New local note";
localTask.setValue(Task.NOTES, newNote);
taskService.save(localTask);
whenInvokeSync();
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newNote, localTask.getValue(Task.NOTES));
assertEquals(newNote, remoteTask.notes);
}
public void testNoteEditedRemotely() throws Exception {
Task localTask = createLocalTaskForNoteTests(" remotely");
String title = localTask.getValue(Task.TITLE);
String originalNote = localTask.getValue(Task.NOTES);
whenInvokeSync();
com.google.api.services.tasks.v1.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
assertEquals(originalNote, localTask.getValue(Task.NOTES));
assertEquals(originalNote, remoteTask.notes);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
String newNote = "New remote note";
remoteTask.notes = newNote;
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(newNote, localTask.getValue(Task.NOTES));
assertEquals(newNote, remoteTask.notes);
}
public void DISABLED_testNoteEditedBoth() throws Exception {
Task localTask = createLocalTaskForNoteTests(" remotely");
String title = localTask.getValue(Task.TITLE);
String originalNote = localTask.getValue(Task.NOTES);
whenInvokeSync();
com.google.api.services.tasks.v1.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
assertEquals(originalNote, localTask.getValue(Task.NOTES));
assertEquals(originalNote, remoteTask.notes);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
String newLocalNote = "New local note";
String newRemoteNote = "New remote note";
localTask.setValue(Task.NOTES, newLocalNote);
taskService.save(localTask);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
remoteTask.notes = newRemoteNote;
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
System.err.println("Local note: " + localTask.getValue(Task.NOTES));
System.err.println("Remote note: " + remoteTask.notes);
}
private Task createLocalTaskForNoteTests(String addToTitle) {
Task localTask = createNewLocalTask("Note will change" + addToTitle);
String note = "Original note";
localTask.setValue(Task.NOTES, note);
taskService.save(localTask);
return localTask;
}
/*
* Completion tests
*/
public void testTaskCompletedLocally() throws Exception {
String title = "Will complete locally";
Task localTask = createNewLocalTask(title);
whenInvokeSync();
com.google.api.services.tasks.v1.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
long completion = DateUtilities.now();
localTask.setValue(Task.COMPLETION_DATE, completion);
taskService.save(localTask);
whenInvokeSync();
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(completion, localTask.getValue(Task.COMPLETION_DATE).longValue());
assertEquals(GtasksApiUtilities.unixTimeToGtasksTime(completion), GtasksApiUtilities.gtasksCompletedTimeStringToLocalTimeString(remoteTask.completed));
assertEquals("completed", remoteTask.status);
}
public void testTaskCompletedRemotely() throws Exception {
String title = "Will complete remotely";
Task localTask = createNewLocalTask(title);
whenInvokeSync();
com.google.api.services.tasks.v1.model.Task remoteTask = assertTaskExistsRemotely(localTask, title);
AndroidUtilities.sleepDeep(TIME_BETWEEN_SYNCS);
long completion = DateUtilities.now();
remoteTask.status = "completed";
remoteTask.completed = GtasksApiUtilities.unixTimeToGtasksTime(completion);
gtasksService.updateGtask(DEFAULT_LIST, remoteTask);
whenInvokeSync();
localTask = refetchLocalTask(localTask);
remoteTask = refetchRemoteTask(remoteTask);
assertEquals(completion, localTask.getValue(Task.COMPLETION_DATE).longValue());
assertEquals(GtasksApiUtilities.unixTimeToGtasksTime(completion), GtasksApiUtilities.gtasksCompletedTimeStringToLocalTimeString(remoteTask.completed));
assertEquals("completed", remoteTask.status);
}
private com.google.api.services.tasks.v1.model.Task assertTaskExistsRemotely(Task localTask, String title) {
//Get the corresponding remote id for a local task
Metadata metadata = gtasksMetadataService.getTaskMetadata(localTask.getId());
String taskId = metadata.getValue(GtasksMetadata.ID);
String listId = metadata.getValue(GtasksMetadata.LIST_ID);
//Fetch the remote task belonging to that id
com.google.api.services.tasks.v1.model.Task remoteTask = null;
try {
remoteTask = gtasksService.getGtask(listId, taskId);
} catch (Exception e) {
e.printStackTrace();
fail("Failed to find remote task " + taskId);
}
//Do a basic title match
assertNotNull(remoteTask);
assertEquals(title, localTask.getValue(Task.TITLE));
assertEquals(title, remoteTask.title);
return remoteTask;
}
private Task assertTaskExistsLocally(com.google.api.services.tasks.v1.model.Task remoteTask, String title) {
long localId = localIdForTask(remoteTask);
//Fetch the local task from the database
Task localTask = taskService.fetchById(localId, Task.PROPERTIES);
assertNotNull(localTask);
assertEquals(title, remoteTask.title);
assertEquals(title, localTask.getValue(Task.TITLE));
return localTask;
}
private Task refetchLocalTask(Task localTask) {
return taskService.fetchById(localTask.getValue(Task.ID), Task.PROPERTIES);
}
private com.google.api.services.tasks.v1.model.Task refetchRemoteTask(com.google.api.services.tasks.v1.model.Task remoteTask) throws Exception {
return gtasksService.getGtask(GtasksApiUtilities.extractListIdFromSelfLink(remoteTask), remoteTask.id);
}
private long localIdForTask(com.google.api.services.tasks.v1.model.Task remoteTask) {
TodorooCursor<Metadata> cursor = metadataService.query(Query.select(Metadata.TASK).
where(Criterion.and(Metadata.KEY.eq(GtasksMetadata.METADATA_KEY), GtasksMetadata.ID.eq(remoteTask.id))));
try {
assertEquals(1, cursor.getCount());
cursor.moveToFirst();
return cursor.get(Metadata.TASK);
} finally {
cursor.close();
}
}
//Create a new Astrid task and save it to the database
private Task createNewLocalTask(String title) {
Task task = new Task();
task.setValue(Task.TITLE, title);
taskService.save(task);
return task;
}
//Perform a synchronization
private void whenInvokeSync() {
syncProvider.synchronize(getContext());
gtasksService = syncProvider.getGtasksService(); //This is to prevent token mismatch; the sync provider invalidates the old one
}
@Override
protected void setUp() throws Exception {
super.setUp();
if (!initialized) {
initializeTestService();
}
setupTestList();
syncProvider = new GtasksSyncProvider();
}
private void initializeTestService() throws Exception {
GoogleAccountManager manager = new GoogleAccountManager(ContextManager.getContext());
Account[] accounts = manager.getAccounts();
Account toUse = null;
for (Account a : accounts) {
if (a.name.equals(TEST_ACCOUNT)) {
toUse = a;
break;
}
}
if (toUse == null) {
toUse = accounts[0];
}
Preferences.setString(GtasksPreferenceService.PREF_USER_NAME, toUse.name);
AccountManagerFuture<Bundle> accountManagerFuture = manager.manager.getAuthToken(toUse, "oauth2:https://www.googleapis.com/auth/tasks", true, null, null);
Bundle authTokenBundle = accountManagerFuture.getResult();
if (authTokenBundle.containsKey(AccountManager.KEY_INTENT)) {
Intent i = (Intent) authTokenBundle.get(AccountManager.KEY_INTENT);
ContextManager.getContext().startActivity(i);
return;
}
String authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
authToken = GtasksTokenValidator.validateAuthToken(authToken);
gtasksService = new GtasksService(authToken);
initialized = true;
}
private void setupTestList() throws Exception {
Tasks defaultListTasks = gtasksService.getAllGtasksFromListId(DEFAULT_LIST, false);
if (defaultListTasks.items != null) {
for (com.google.api.services.tasks.v1.model.Task t : defaultListTasks.items) {
gtasksService.deleteGtask(DEFAULT_LIST, t.id);
}
}
}
}

@ -1,4 +1,4 @@
package com.todoroo.astrid.gtasks;
/*package com.todoroo.astrid.gtasks;
import java.util.ArrayList;
import java.util.List;
@ -9,13 +9,6 @@ import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.sync.GtasksSyncProvider;
import com.todoroo.astrid.service.TaskService;
import com.todoroo.astrid.test.DatabaseTestCase;
import com.todoroo.gtasks.GoogleConnectionManager;
import com.todoroo.gtasks.GoogleTaskListInfo;
import com.todoroo.gtasks.GoogleTaskService;
import com.todoroo.gtasks.GoogleTaskTask;
import com.todoroo.gtasks.GoogleTaskView;
import com.todoroo.gtasks.actions.ListAction;
import com.todoroo.gtasks.actions.ListActions;
public class GtasksSyncTest extends DatabaseTestCase {
@ -135,4 +128,4 @@ public class GtasksSyncTest extends DatabaseTestCase {
}
}//*/

@ -1,13 +1,18 @@
package com.todoroo.astrid.gtasks;
import java.util.ArrayList;
import java.util.List;
import com.google.api.services.tasks.v1.model.TaskList;
import com.google.api.services.tasks.v1.model.TaskLists;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.StoreObject;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.test.DatabaseTestCase;
import com.todoroo.gtasks.GoogleTaskListInfo;
@SuppressWarnings("nls")
public class GtasksTaskListUpdaterTest extends DatabaseTestCase {
@Autowired private GtasksTaskListUpdater gtasksTaskListUpdater;
@ -113,9 +118,13 @@ public class GtasksTaskListUpdaterTest extends DatabaseTestCase {
protected void setUp() throws Exception {
super.setUp();
GoogleTaskListInfo[] lists = new GoogleTaskListInfo[1];
GoogleTaskListInfo list = new GoogleTaskListInfo("1", "Tim's Tasks");
lists[0] = list;
TaskLists lists = new TaskLists();
List<TaskList> items = new ArrayList<TaskList>();
TaskList list = new TaskList();
list.id = "1";
list.title = "Tim's Tasks";
items.add(list);
lists.items = items;
gtasksListService.updateLists(lists);
}
@ -161,4 +170,4 @@ public class GtasksTaskListUpdaterTest extends DatabaseTestCase {
return task;
}
}
}//*/

@ -1,12 +1,17 @@
package com.todoroo.astrid.gtasks;
import java.util.ArrayList;
import java.util.List;
import com.google.api.services.tasks.v1.model.TaskList;
import com.google.api.services.tasks.v1.model.TaskLists;
import com.todoroo.andlib.service.Autowired;
import com.todoroo.astrid.core.PluginServices;
import com.todoroo.astrid.data.Metadata;
import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.test.DatabaseTestCase;
import com.todoroo.gtasks.GoogleTaskListInfo;
@SuppressWarnings("nls")
public class GtasksTaskMovingTest extends DatabaseTestCase {
@Autowired private GtasksListService gtasksListService;
@ -229,9 +234,13 @@ public class GtasksTaskMovingTest extends DatabaseTestCase {
protected void setUp() throws Exception {
super.setUp();
GoogleTaskListInfo[] lists = new GoogleTaskListInfo[1];
GoogleTaskListInfo list = new GoogleTaskListInfo("1", "Tim's Tasks");
lists[0] = list;
TaskLists lists = new TaskLists();
List<TaskList> items = new ArrayList<TaskList>();
TaskList list = new TaskList();
list.id = "1";
list.title = "Tim's Tasks";
items.add(list);
lists.items = items;
gtasksListService.updateLists(lists);
}

Loading…
Cancel
Save