Imported auth manager and account chooser code from Google MyTracks (Apache License). Wired up gtasks to use auth manager. Modern seems to work but Old does not.

pull/14/head
Tim Su 14 years ago
parent c1079acf93
commit d2d4f8150b

@ -15,5 +15,6 @@
<classpathentry exported="true" kind="lib" path="libs/rfc2445-no-joda.jar"/>
<classpathentry exported="true" kind="lib" path="libs/locale_platform.jar"/>
<classpathentry exported="true" kind="lib" path="libs/todoroo-g.jar"/>
<classpathentry kind="lib" path="libs/googleloginclient-helper.jar"/>
<classpathentry kind="output" path="ecbuild"/>
</classpath>

@ -22,6 +22,13 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<!-- for analytics -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<!-- for google tasks -->
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.MANAGE_ACCOUNTS" />
<uses-permission android:name="android.permission.USE_CREDENTIALS" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH" />
<uses-permission android:name="com.google.android.googleapps.permission.GOOGLE_AUTH.goanna_mobile" />
<uses-permission android:name="com.google.android.providers.gsf.permission.READ_GSERVICES" />
<!-- ============================================== Exported Permissions = -->

@ -0,0 +1,125 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.todoroo.astrid.gtasks.auth;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import com.timsu.astrid.R;
/**
* Choose which account to upload track information to.
* @author Sandor Dornbush
*/
public class AccountChooser {
/**
* The last selected account.
*/
private int selectedAccountIndex = -1;
private Account selectedAccount = null;
/**
* An interface for receiving updates once the user has selected the account.
*/
public interface AccountHandler {
/**
* Handle the account being selected.
* @param account The selected account or null if none could be found
*/
public void handleAccountSelected(Account account);
}
/**
* Chooses the best account to upload to.
* If no account is found the user will be alerted.
* If only one account is found that will be used.
* If multiple accounts are found the user will be allowed to choose.
*
* @param activity The parent activity
* @param handler The handler to be notified when an account has been selected
*/
public void chooseAccount(final Activity activity,
final AccountHandler handler) {
final Account[] accounts = AccountManager.get(activity)
.getAccountsByType("com.google"); //$NON-NLS-1$
if (accounts.length < 1) {
alertNoAccounts(activity, handler);
return;
}
if (accounts.length == 1) {
handler.handleAccountSelected(accounts[0]);
return;
}
// TODO This should be read out of a preference.
if (selectedAccount != null) {
handler.handleAccountSelected(selectedAccount);
return;
}
// Let the user choose.
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.choose_account_title);
builder.setCancelable(false);
builder.setPositiveButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
selectedAccount = accounts[selectedAccountIndex];
handler.handleAccountSelected(selectedAccount);
}
});
builder.setNegativeButton(android.R.string.cancel,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
handler.handleAccountSelected(null);
}
});
String[] choices = new String[accounts.length];
for (int i = 0; i < accounts.length; i++) {
choices[i] = accounts[i].name;
}
builder.setSingleChoiceItems(choices, selectedAccountIndex,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
selectedAccountIndex = which;
}
});
builder.show();
}
/**
* Puts up a dialog alerting the user that no suitable account was found.
*/
private void alertNoAccounts(final Activity activity,
final AccountHandler handler) {
final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(R.string.no_account_found_title);
builder.setMessage(R.string.no_account_found);
builder.setCancelable(true);
builder.setNegativeButton(android.R.string.ok,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
handler.handleAccountSelected(null);
}
});
builder.show();
}
}

@ -0,0 +1,77 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.todoroo.astrid.gtasks.auth;
import android.content.Intent;
/**
* This interface describes a class that will fetch and maintain a Google
* authentication token.
*
* @author Sandor Dornbush
*/
public interface AuthManager {
/**
* Initializes the login process. The user should be asked to login if they
* haven't already. The {@link Runnable} provided will be executed when the
* auth token is successfully fetched.
*
* @param whenFinished A {@link Runnable} to execute when the auth token
* has been successfully fetched and is available via
* {@link #getAuthToken()}
*/
public abstract void doLogin(Runnable whenFinished, Object o);
/**
* The {@link android.app.Activity} owner of this class should call this
* function when it gets {@link android.app.Activity#onActivityResult} with
* the request code passed into the constructor. The resultCode and results
* should come directly from the {@link android.app.Activity#onActivityResult}
* function. This function will return true if an auth token was successfully
* fetched or the process is not finished.
*
* @param resultCode The result code passed in to the
* {@link android.app.Activity}'s
* {@link android.app.Activity#onActivityResult} function
* @param results The data passed in to the {@link android.app.Activity}'s
* {@link android.app.Activity#onActivityResult} function
* @return True if the auth token was fetched or we aren't done fetching
* the auth token, or False if there was an error or the request was
* canceled
*/
public abstract boolean authResult(int resultCode, Intent results);
/**
* Returns the current auth token. Response may be null if no valid auth
* token has been fetched.
*
* @return The current auth token or null if no auth token has been
* fetched
*/
public abstract String getAuthToken();
/**
* Invalidates the existing auth token and request a new one. The
* {@link Runnable} provided will be executed when the new auth token is
* successfully fetched.
*
* @param whenFinished A {@link Runnable} to execute when a new auth token
* is successfully fetched
*/
public abstract void invalidateAndRefresh(Runnable whenFinished);
}

@ -0,0 +1,53 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.todoroo.astrid.gtasks.auth;
import android.app.Activity;
import android.os.Build;
import android.os.Bundle;
/**
* A factory for getting the platform specific AuthManager.
*
* @author Sandor Dornbush
*/
public class AuthManagerFactory {
private AuthManagerFactory() {
// don't construct me
}
/**
* Returns whether the modern AuthManager should be used
*/
public static boolean useModernAuthManager() {
return Integer.parseInt(Build.VERSION.SDK) >= 7;
}
/**
* Get a right {@link AuthManager} for the platform.
* @return A new AuthManager
*/
public static AuthManager getAuthManager(Activity activity, int code,
Bundle extras, boolean requireGoogle, String service) {
if (useModernAuthManager()) {
return new ModernAuthManager(activity, code, extras, requireGoogle, service);
} else {
return new AuthManagerOld(activity, code, extras, requireGoogle, service);
}
}
}

@ -0,0 +1,198 @@
/*
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.todoroo.astrid.gtasks.auth;
import java.util.Iterator;
import java.util.Vector;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import com.google.android.googlelogindist.GoogleLoginServiceConstants;
import com.google.android.googlelogindist.GoogleLoginServiceHelper;
/**
* AuthManager keeps track of the current auth token for a user. The advantage
* over just passing around a String is that this class can renew the auth
* token if necessary, and it will change for all classes using this
* AuthManager.
*/
public class AuthManagerOld implements AuthManager {
/** The activity that will handle auth result callbacks. */
private final Activity activity;
/** The code used to tell the activity that it is an auth result. */
private final int code;
/** Extras to pass into the getCredentials function. */
private final Bundle extras;
/** True if the account must be a Google account (not a domain account). */
private final boolean requireGoogle;
/** The name of the service to authorize for. */
private final String service;
/** A list of handlers to call when a new auth token is fetched. */
private final Vector<Runnable> newTokenListeners = new Vector<Runnable>();
/** The most recently fetched auth token or null if none is available. */
private String authToken;
/**
* The number of handlers at the beginning of the above list that shouldn't
* be removed after they are called.
*/
private int stickyNewTokenListenerCount;
/**
* AuthManager requires many of the same parameters as
* {@link GoogleLoginServiceHelper#getCredentials(Activity, int, Bundle,
* boolean, String, boolean)}. The activity must have
* a handler in {@link Activity#onActivityResult} that calls
* {@link #authResult(int, Intent)} if the request code is the code given
* here.
*
* @param activity An activity with a handler in
* {@link Activity#onActivityResult} that calls
* {@link #authResult(int, Intent)} when {@literal code} is the request
* code
* @param code The request code to pass to
* {@link Activity#onActivityResult} when
* {@link #authResult(int, Intent)} should be called
* @param extras A {@link Bundle} of extras for
* {@link GoogleLoginServiceHelper}
* @param requireGoogle True if the account must be a Google account
* @param service The name of the service to authenticate as
*/
public AuthManagerOld(Activity activity, int code, Bundle extras,
boolean requireGoogle, String service) {
this.activity = activity;
this.code = code;
this.extras = extras;
this.requireGoogle = requireGoogle;
this.service = service;
}
/* (non-Javadoc)
* @see com.google.android.apps.mytracks.io.AuthManager#doLogin(java.lang.Runnable)
*/
public void doLogin(Runnable whenFinished, Object o) {
synchronized (newTokenListeners) {
if (whenFinished != null) {
newTokenListeners.add(whenFinished);
}
}
activity.runOnUiThread(new LoginRunnable());
}
/**
* Runnable which actually gets login credentials.
*/
private class LoginRunnable implements Runnable {
@Override
public void run() {
GoogleLoginServiceHelper.getCredentials(
activity, code, extras, requireGoogle, service, true);
}
}
/* (non-Javadoc)
* @see com.google.android.apps.mytracks.io.AuthManager#authResult(int, android.content.Intent)
*/
public boolean authResult(int resultCode, Intent results) {
if (resultCode == Activity.RESULT_OK) {
authToken = results.getStringExtra(
GoogleLoginServiceConstants.AUTHTOKEN_KEY);
if (authToken == null) {
GoogleLoginServiceHelper.getCredentials(
activity, code, extras, requireGoogle, service, false);
return true;
} else {
// Notify all active listeners that we have a new auth token.
synchronized (newTokenListeners) {
Iterator<Runnable> iter = newTokenListeners.iterator();
while (iter.hasNext()) {
iter.next().run();
}
iter = null;
// Remove anything not in the sticky part of the list.
newTokenListeners.setSize(stickyNewTokenListenerCount);
}
return true;
}
}
return false;
}
/* (non-Javadoc)
* @see com.google.android.apps.mytracks.io.AuthManager#getAuthToken()
*/
public String getAuthToken() {
return authToken;
}
/* (non-Javadoc)
* @see com.google.android.apps.mytracks.io.AuthManager#invalidateAndRefresh(java.lang.Runnable)
*/
public void invalidateAndRefresh(Runnable whenFinished) {
synchronized (newTokenListeners) {
if (whenFinished != null) {
newTokenListeners.add(whenFinished);
}
}
activity.runOnUiThread(new Runnable() {
public void run() {
GoogleLoginServiceHelper.invalidateAuthToken(activity, code, authToken);
}
});
}
/**
* Adds a {@link Runnable} to be executed every time the auth token is
* updated. The {@link Runnable} will not be removed until manually removed
* with {@link #removeStickyNewTokenListener(Runnable)}.
*
* @param listener The {@link Runnable} to execute every time a new auth
* token is fetched
*/
public void addStickyNewTokenListener(Runnable listener) {
synchronized (newTokenListeners) {
newTokenListeners.add(0, listener);
stickyNewTokenListenerCount++;
}
}
/**
* Stops executing the given {@link Runnable} every time the auth token is
* updated. This {@link Runnable} must have been added with
* {@link #addStickyNewTokenListener(Runnable)} above. If the
* {@link Runnable} was added more than once, only the first occurrence
* will be removed.
*
* @param listener The {@link Runnable} to stop executing every time a new
* auth token is fetched
*/
public void removeStickyNewTokenListener(Runnable listener) {
synchronized (newTokenListeners) {
if (stickyNewTokenListenerCount > 0
&& newTokenListeners.remove(listener)) {
stickyNewTokenListenerCount--;
}
}
}
}

@ -0,0 +1,210 @@
/*
* Copyright 2010 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.todoroo.astrid.gtasks.auth;
import java.io.IOException;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.accounts.AccountManagerCallback;
import android.accounts.AccountManagerFuture;
import android.accounts.AuthenticatorException;
import android.accounts.OperationCanceledException;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
/**
* AuthManager keeps track of the current auth token for a user. The advantage
* over just passing around a String is that this class can renew the auth
* token if necessary, and it will change for all classes using this
* AuthManager.
*/
public class ModernAuthManager implements AuthManager {
protected static final int GET_LOGIN_REQUEST = 1;
/** The activity that will handle auth result callbacks. */
private final Activity activity;
/** The name of the service to authorize for. */
private final String service;
/** The most recently fetched auth token or null if none is available. */
private String authToken;
private final AccountManager accountManager;
private Runnable whenFinished;
/**
* AuthManager requires many of the same parameters as
* {@link com.google.android.googlelogindist.GoogleLoginServiceHelper
* #getCredentials(Activity, int, Bundle, boolean, String, boolean)}.
* The activity must have a handler in {@link Activity#onActivityResult} that
* calls {@link #authResult(int, Intent)} if the request code is the code
* given here.
*
* @param activity An activity with a handler in
* {@link Activity#onActivityResult} that calls
* {@link #authResult(int, Intent)} when {@literal code} is the request
* code
* @param code The request code to pass to
* {@link Activity#onActivityResult} when
* {@link #authResult(int, Intent)} should be called
* @param extras A {@link Bundle} of extras for
* {@link com.google.android.googlelogindist.GoogleLoginServiceHelper}
* @param requireGoogle True if the account must be a Google account
* @param service The name of the service to authenticate as
*/
public ModernAuthManager(Activity activity, int code, Bundle extras,
boolean requireGoogle, String service) {
this.activity = activity;
this.service = service;
this.accountManager = AccountManager.get(activity);
}
/**
* Call this to do the initial login. The user will be asked to login if
* they haven't already. The {@link Runnable} provided will be executed
* when the auth token is successfully fetched.
*
* @param runnable A {@link Runnable} to execute when the auth token
* has been successfully fetched and is available via
* {@link #getAuthToken()}
*/
@SuppressWarnings("nls")
public void doLogin(final Runnable runnable, Object o) {
this.whenFinished = runnable;
if (!(o instanceof Account)) {
throw new IllegalArgumentException("FroyoAuthManager requires an account.");
}
Account account = (Account) o;
accountManager.getAuthToken(account, service, true,
new AccountManagerCallback<Bundle>() {
public void run(AccountManagerFuture<Bundle> future) {
try {
Bundle result = future.getResult();
// AccountManager needs user to grant permission
if (result.containsKey(AccountManager.KEY_INTENT)) {
Intent intent = (Intent) result.get(AccountManager.KEY_INTENT);
clearNewTaskFlag(intent);
activity.startActivityForResult(intent, GET_LOGIN_REQUEST);
return;
}
authToken = result.getString(
AccountManager.KEY_AUTHTOKEN);
Log.e("gtasks-auth", "Got auth token.");
runWhenFinished();
} catch (OperationCanceledException e) {
Log.e("gtasks-auth", "Operation Canceled", e);
} catch (IOException e) {
Log.e("gtasks-auth", "IOException", e);
} catch (AuthenticatorException e) {
Log.e("gtasks-auth", "Authentication Failed", e);
}
}
}, null /* handler */);
}
private static void clearNewTaskFlag(Intent intent) {
int flags = intent.getFlags();
flags &= ~Intent.FLAG_ACTIVITY_NEW_TASK;
intent.setFlags(flags);
}
/**
* The {@link Activity} passed into the constructor should call this
* function when it gets {@link Activity#onActivityResult} with the request
* code passed into the constructor. The resultCode and results should
* come directly from the {@link Activity#onActivityResult} function. This
* function will return true if an auth token was successfully fetched or
* the process is not finished.
*
* @param resultCode The result code passed in to the {@link Activity}'s
* {@link Activity#onActivityResult} function
* @param results The data passed in to the {@link Activity}'s
* {@link Activity#onActivityResult} function
* @return True if the auth token was fetched or we aren't done fetching
* the auth token, or False if there was an error or the request was
* canceled
*/
@SuppressWarnings("nls")
public boolean authResult(int resultCode, Intent results) {
if (results != null) {
authToken = results.getStringExtra(
AccountManager.KEY_AUTHTOKEN);
Log.w("google-auth", "authResult: " + authToken);
} else {
Log.e("google-auth", "No auth result results!!");
}
runWhenFinished();
return authToken != null;
}
/**
* Returns the current auth token. Response may be null if no valid auth
* token has been fetched.
*
* @return The current auth token or null if no auth token has been
* fetched
*/
public String getAuthToken() {
return authToken;
}
/**
* Invalidates the existing auth token and request a new one. The
* {@link Runnable} provided will be executed when the new auth token is
* successfully fetched.
*
* @param runnable A {@link Runnable} to execute when a new auth token
* is successfully fetched
*/
public void invalidateAndRefresh(final Runnable runnable) {
this.whenFinished = runnable;
activity.runOnUiThread(new Runnable() {
public void run() {
accountManager.invalidateAuthToken("com.google", authToken); //$NON-NLS-1$
new AccountChooser().chooseAccount(activity,
new AccountChooser.AccountHandler() {
@Override
public void handleAccountSelected(Account account) {
if (account != null) {
doLogin(whenFinished, account);
} else {
runWhenFinished();
}
}
});
}
});
}
private void runWhenFinished() {
if (whenFinished != null) {
(new Thread() {
@Override
public void run() {
whenFinished.run();
}
}).start();
}
}
}

@ -16,6 +16,8 @@ import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import com.flurry.android.FlurryAgent;
import com.timsu.astrid.R;
@ -41,6 +43,8 @@ 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.auth.AuthManager;
import com.todoroo.astrid.gtasks.auth.AuthManagerFactory;
import com.todoroo.astrid.producteev.api.ApiServiceException;
import com.todoroo.astrid.service.AstridDependencyInjector;
import com.todoroo.astrid.sync.SyncBackgroundService;
@ -48,7 +52,6 @@ import com.todoroo.astrid.sync.SyncContainer;
import com.todoroo.astrid.sync.SyncProvider;
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;
@ -83,8 +86,6 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
public GtasksSyncProvider() {
super();
DependencyInjectionService.getInstance().inject(this);
// TODO?
gtasksPreferenceService.stopOngoing();
}
// ----------------------------------------------------------------------
@ -152,7 +153,8 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
final GoogleConnectionManager connectionManager;
if(authToken == null) {
connectionManager = logInHelper();
Log.e("astrid-sync", "No token, unable to sync");
return;
} else {
connectionManager = new GoogleConnectionManager(authToken);
}
@ -168,36 +170,39 @@ public class GtasksSyncProvider extends SyncProvider<GtasksTaskContainer> {
}
}
private GoogleConnectionManager logInHelper() throws GoogleLoginException,
IOException {
// TODO get email and password or something?
String email = "tasktest@todoroo.com";
String password = "tasktest0000";
GoogleConnectionManager connectionManager = new GoogleConnectionManager(email, password);
connectionManager.authenticate(true);
gtasksPreferenceService.setToken(connectionManager.getToken());
return connectionManager;
}
/**
* If user isn't already signed in, show sign in dialog. Else perform sync.
*/
@Override
protected void initiateManual(Activity activity) {
protected void initiateManual(final Activity activity) {
String authToken = gtasksPreferenceService.getToken();
gtasksPreferenceService.stopOngoing();
// check if we have a token & it works
if(authToken == null) {
try {
logInHelper();
final AuthManager authManager = AuthManagerFactory.getAuthManager(activity, 0, new Bundle(), true, "goanna_mobile");
authManager.invalidateAndRefresh(new Runnable() {
@Override
public void run() {
String token = authManager.getAuthToken();
if(token != null) {
gtasksPreferenceService.setToken(token);
//activity.startService(new Intent(SyncBackgroundService.SYNC_ACTION, null,
// activity, GtasksBackgroundService.class));
System.err.println("yay! " + token);
activity.finish();
}
}
});
} catch (Exception e) {
handleException("auth", e, true);
}
}
activity.startService(new Intent(SyncBackgroundService.SYNC_ACTION, null,
} else {
activity.startService(new Intent(SyncBackgroundService.SYNC_ACTION, null,
activity, GtasksBackgroundService.class));
activity.finish();
}
}

@ -31,6 +31,7 @@ public class ProducteevPreferences extends SyncProviderPreferences {
@Override
public void startSync() {
new ProducteevSyncProvider().synchronize(this);
finish();
}
@Override

@ -35,5 +35,14 @@
<!-- title for notification tray when synchronizing -->
<string name="gtasks_notification_title">Astrid: Google Tasks</string>
<!-- title for picking a google account -->
<string name="choose_account_title">Choose Account</string>
<!-- title for no google accounts found -->
<string name="no_account_found_title">No Accounts Found</string>
<!-- message body for no google accounts found -->
<string name="no_account_found">We were unable to find a Google account on this phone. You will not be able to synchronize to Google Tasks without one!</string>
</resources>

@ -23,6 +23,7 @@ public class MilkPreferences extends SyncProviderPreferences {
@Override
public void startSync() {
new MilkSyncProvider().synchronize(this);
finish();
}
@Override

Loading…
Cancel
Save