Object prettyPrint(T object) throws IOException {
+ if (BuildConfig.DEBUG) {
+ if (object instanceof GenericJson) {
+ return ((GenericJson) object).toPrettyString();
+ }
+ }
+ return object;
+ }
+
+ private String getCaller() {
+ if (BuildConfig.DEBUG) {
+ try {
+ return Thread.currentThread().getStackTrace()[4].getMethodName();
+ } catch (Exception e) {
+ Timber.e(e);
+ }
+ }
+ return "";
+ }
+}
diff --git a/app/src/main/java/org/tasks/drive/DriveLoginActivity.java b/app/src/main/java/org/tasks/drive/DriveLoginActivity.java
new file mode 100644
index 000000000..d141062c3
--- /dev/null
+++ b/app/src/main/java/org/tasks/drive/DriveLoginActivity.java
@@ -0,0 +1,98 @@
+/**
+ * Copyright (c) 2012 Todoroo Inc
+ *
+ * See the file "LICENSE" for the full license governing this code.
+ */
+package org.tasks.drive;
+
+import android.app.ProgressDialog;
+import android.content.Intent;
+import android.os.Bundle;
+import android.widget.Toast;
+import com.todoroo.andlib.utility.DialogUtilities;
+import javax.inject.Inject;
+import org.tasks.R;
+import org.tasks.dialogs.DialogBuilder;
+import org.tasks.gtasks.GoogleAccountManager;
+import org.tasks.gtasks.PlayServices;
+import org.tasks.injection.ActivityComponent;
+import org.tasks.injection.InjectingAppCompatActivity;
+import org.tasks.play.AuthResultHandler;
+import org.tasks.preferences.Preferences;
+
+/**
+ * This activity allows users to sign in or log in to Google Tasks through the Android account
+ * manager
+ *
+ * @author Sam Bosley
+ */
+public class DriveLoginActivity extends InjectingAppCompatActivity {
+
+ public static final int RC_REQUEST_OAUTH = 10987;
+ private static final int RC_CHOOSE_ACCOUNT = 10988;
+ @Inject DialogBuilder dialogBuilder;
+ @Inject GoogleAccountManager accountManager;
+ @Inject PlayServices playServices;
+ @Inject Preferences preferences;
+ private String accountName;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent chooseAccountIntent =
+ android.accounts.AccountManager.newChooseAccountIntent(
+ null, null, new String[] {"com.google"}, false, null, null, null, null);
+ startActivityForResult(chooseAccountIntent, RC_CHOOSE_ACCOUNT);
+ }
+
+ @Override
+ public void inject(ActivityComponent component) {
+ component.inject(this);
+ }
+
+ private void getAuthToken(String account) {
+ final ProgressDialog pd = dialogBuilder.newProgressDialog(R.string.gtasks_GLA_authenticating);
+ pd.show();
+ accountName = account;
+ getAuthToken(account, pd);
+ }
+
+ private void getAuthToken(String a, final ProgressDialog pd) {
+ playServices.getDriveAuthToken(
+ this,
+ a,
+ new AuthResultHandler() {
+ @Override
+ public void authenticationSuccessful(String accountName) {
+ preferences.setString(R.string.p_google_drive_backup_account, accountName);
+ setResult(RESULT_OK);
+ finish();
+ DialogUtilities.dismissDialog(DriveLoginActivity.this, pd);
+ }
+
+ @Override
+ public void authenticationFailed(final String message) {
+ runOnUiThread(
+ () -> Toast.makeText(DriveLoginActivity.this, message, Toast.LENGTH_LONG).show());
+ DialogUtilities.dismissDialog(DriveLoginActivity.this, pd);
+ }
+ });
+ }
+
+ @Override
+ protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (requestCode == RC_CHOOSE_ACCOUNT && resultCode == RESULT_OK) {
+ String account = data.getStringExtra(android.accounts.AccountManager.KEY_ACCOUNT_NAME);
+ getAuthToken(account);
+ } else if (requestCode == RC_REQUEST_OAUTH && resultCode == RESULT_OK) {
+ final ProgressDialog pd = dialogBuilder.newProgressDialog(R.string.gtasks_GLA_authenticating);
+ pd.show();
+ getAuthToken(accountName, pd);
+ } else {
+ // User didn't give permission--cancel
+ finish();
+ }
+ }
+}
diff --git a/app/src/main/java/org/tasks/gtasks/CreateListDialog.java b/app/src/main/java/org/tasks/gtasks/CreateListDialog.java
index f3c0540da..4cd4a1e80 100644
--- a/app/src/main/java/org/tasks/gtasks/CreateListDialog.java
+++ b/app/src/main/java/org/tasks/gtasks/CreateListDialog.java
@@ -6,17 +6,22 @@ import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
+
import com.google.api.services.tasks.model.TaskList;
import com.todoroo.astrid.gtasks.api.GtasksInvoker;
-import java.io.IOException;
-import javax.inject.Inject;
+
import org.tasks.R;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.DialogFragmentComponent;
import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingDialogFragment;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import timber.log.Timber;
public class CreateListDialog extends InjectingDialogFragment {
@@ -25,7 +30,6 @@ public class CreateListDialog extends InjectingDialogFragment {
private static final String EXTRA_NAME = "extra_name";
@Inject DialogBuilder dialogBuilder;
@Inject @ForApplication Context context;
- @Inject PlayServices playServices;
private CreateListDialogCallback callback;
private ProgressDialog dialog;
private String account;
@@ -74,7 +78,7 @@ public class CreateListDialog extends InjectingDialogFragment {
@Override
protected TaskList doInBackground(Void... voids) {
try {
- return new GtasksInvoker(context, playServices, account).createGtaskList(name);
+ return new GtasksInvoker(context, account).createGtaskList(name);
} catch (IOException e) {
Timber.e(e);
return null;
diff --git a/app/src/main/java/org/tasks/gtasks/DeleteListDialog.java b/app/src/main/java/org/tasks/gtasks/DeleteListDialog.java
index 497bd67fa..02891e54e 100644
--- a/app/src/main/java/org/tasks/gtasks/DeleteListDialog.java
+++ b/app/src/main/java/org/tasks/gtasks/DeleteListDialog.java
@@ -6,17 +6,22 @@ import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
+
import com.todoroo.astrid.gtasks.api.GtasksInvoker;
-import java.io.IOException;
-import javax.inject.Inject;
+
import org.tasks.R;
import org.tasks.data.GoogleTaskList;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.DialogFragmentComponent;
import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingDialogFragment;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import timber.log.Timber;
public class DeleteListDialog extends InjectingDialogFragment {
@@ -24,7 +29,6 @@ public class DeleteListDialog extends InjectingDialogFragment {
private static final String EXTRA_LIST = "extra_list";
@Inject @ForApplication Context context;
@Inject DialogBuilder dialogBuilder;
- @Inject PlayServices playServices;
private DeleteListDialogCallback callback;
private GoogleTaskList googleTaskList;
private ProgressDialog dialog;
@@ -70,7 +74,7 @@ public class DeleteListDialog extends InjectingDialogFragment {
@Override
protected Boolean doInBackground(Void... voids) {
try {
- new GtasksInvoker(context, playServices, googleTaskList.getAccount())
+ new GtasksInvoker(context, googleTaskList.getAccount())
.deleteGtaskList(googleTaskList.getRemoteId());
return true;
} catch (IOException e) {
diff --git a/app/src/main/java/org/tasks/gtasks/GoogleTaskSynchronizer.java b/app/src/main/java/org/tasks/gtasks/GoogleTaskSynchronizer.java
index c6d174b74..dc6d86f33 100644
--- a/app/src/main/java/org/tasks/gtasks/GoogleTaskSynchronizer.java
+++ b/app/src/main/java/org/tasks/gtasks/GoogleTaskSynchronizer.java
@@ -1,12 +1,10 @@
package org.tasks.gtasks;
-import static org.tasks.date.DateTimeUtils.newDateTime;
-
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
-import androidx.core.app.NotificationCompat;
import android.text.TextUtils;
+
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import com.google.api.services.tasks.model.TaskList;
import com.google.api.services.tasks.model.TaskLists;
@@ -28,10 +26,7 @@ import com.todoroo.astrid.gtasks.sync.GtasksTaskContainer;
import com.todoroo.astrid.service.TaskCreator;
import com.todoroo.astrid.service.TaskDeleter;
import com.todoroo.astrid.utility.Constants;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-import javax.inject.Inject;
+
import org.tasks.LocalBroadcastManager;
import org.tasks.R;
import org.tasks.analytics.Tracker;
@@ -47,8 +42,18 @@ import org.tasks.preferences.DefaultFilterProvider;
import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.inject.Inject;
+
+import androidx.core.app.NotificationCompat;
import timber.log.Timber;
+import static org.tasks.date.DateTimeUtils.newDateTime;
+
public class GoogleTaskSynchronizer {
private static final String DEFAULT_LIST = "@default"; // $NON-NLS-1$
@@ -65,7 +70,6 @@ public class GoogleTaskSynchronizer {
private final GoogleTaskDao googleTaskDao;
private final TaskCreator taskCreator;
private final DefaultFilterProvider defaultFilterProvider;
- private final PlayServices playServices;
private final PermissionChecker permissionChecker;
private final GoogleAccountManager googleAccountManager;
private final LocalBroadcastManager localBroadcastManager;
@@ -86,7 +90,6 @@ public class GoogleTaskSynchronizer {
GoogleTaskDao googleTaskDao,
TaskCreator taskCreator,
DefaultFilterProvider defaultFilterProvider,
- PlayServices playServices,
PermissionChecker permissionChecker,
GoogleAccountManager googleAccountManager,
LocalBroadcastManager localBroadcastManager,
@@ -104,7 +107,6 @@ public class GoogleTaskSynchronizer {
this.googleTaskDao = googleTaskDao;
this.taskCreator = taskCreator;
this.defaultFilterProvider = defaultFilterProvider;
- this.playServices = playServices;
this.permissionChecker = permissionChecker;
this.googleAccountManager = googleAccountManager;
this.localBroadcastManager = localBroadcastManager;
@@ -182,7 +184,7 @@ public class GoogleTaskSynchronizer {
return;
}
- GtasksInvoker gtasksInvoker = new GtasksInvoker(context, playServices, account.getAccount());
+ GtasksInvoker gtasksInvoker = new GtasksInvoker(context, account.getAccount());
pushLocalChanges(account, gtasksInvoker);
List gtaskLists = new ArrayList<>();
diff --git a/app/src/main/java/org/tasks/gtasks/GoogleTasksUnsuccessfulResponseHandler.java b/app/src/main/java/org/tasks/gtasks/GoogleTasksUnsuccessfulResponseHandler.java
deleted file mode 100644
index c29ab77d6..000000000
--- a/app/src/main/java/org/tasks/gtasks/GoogleTasksUnsuccessfulResponseHandler.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.tasks.gtasks;
-
-import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
-import com.google.api.client.http.HttpBackOffUnsuccessfulResponseHandler;
-import com.google.api.client.http.HttpRequest;
-import com.google.api.client.http.HttpResponse;
-import com.google.api.client.http.HttpResponseException;
-import com.google.api.client.http.HttpStatusCodes;
-import com.google.api.client.http.HttpUnsuccessfulResponseHandler;
-import com.google.api.client.util.BackOff;
-import com.google.api.client.util.ExponentialBackOff;
-import com.todoroo.astrid.gtasks.api.HttpNotFoundException;
-import java.io.IOException;
-import timber.log.Timber;
-
-public class GoogleTasksUnsuccessfulResponseHandler implements HttpUnsuccessfulResponseHandler {
-
- private static final BackOff BACKOFF = new ExponentialBackOff.Builder().build();
-
- private final PlayServices playServices;
- private final GoogleAccountCredential googleAccountCredential;
- private final HttpBackOffUnsuccessfulResponseHandler backoffHandler =
- new HttpBackOffUnsuccessfulResponseHandler(BACKOFF);
-
- public GoogleTasksUnsuccessfulResponseHandler(
- PlayServices playServices, GoogleAccountCredential googleAccountCredential) {
- this.playServices = playServices;
- this.googleAccountCredential = googleAccountCredential;
- }
-
- @Override
- public boolean handleResponse(HttpRequest request, HttpResponse response, boolean supportsRetry)
- throws IOException {
- HttpResponseException httpResponseException = new HttpResponseException(response);
- Timber.e(httpResponseException);
- if (!supportsRetry) {
- return false;
- }
- int statusCode = response.getStatusCode();
- if ((statusCode == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED
- || statusCode == HttpStatusCodes.STATUS_CODE_FORBIDDEN)) {
- boolean shouldRetry = playServices.clearToken(googleAccountCredential);
- if (!shouldRetry) {
- return false;
- }
- } else if (statusCode == 400) { // bad request
- throw httpResponseException;
- } else if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
- throw new HttpNotFoundException(httpResponseException);
- }
-
- return backoffHandler.handleResponse(request, response, supportsRetry);
- }
-}
diff --git a/app/src/main/java/org/tasks/gtasks/RenameListDialog.java b/app/src/main/java/org/tasks/gtasks/RenameListDialog.java
index c81a479d1..757a18eb6 100644
--- a/app/src/main/java/org/tasks/gtasks/RenameListDialog.java
+++ b/app/src/main/java/org/tasks/gtasks/RenameListDialog.java
@@ -6,18 +6,23 @@ import android.app.ProgressDialog;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
+
import com.google.api.services.tasks.model.TaskList;
import com.todoroo.astrid.gtasks.api.GtasksInvoker;
-import java.io.IOException;
-import javax.inject.Inject;
+
import org.tasks.R;
import org.tasks.data.GoogleTaskList;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.DialogFragmentComponent;
import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingDialogFragment;
+
+import java.io.IOException;
+
+import javax.inject.Inject;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import timber.log.Timber;
public class RenameListDialog extends InjectingDialogFragment {
@@ -26,7 +31,6 @@ public class RenameListDialog extends InjectingDialogFragment {
private static final String EXTRA_LIST = "extra_list";
@Inject @ForApplication Context context;
@Inject DialogBuilder dialogBuilder;
- @Inject PlayServices playServices;
private RenameListDialogCallback callback;
private ProgressDialog dialog;
private GoogleTaskList googleTaskList;
@@ -75,7 +79,7 @@ public class RenameListDialog extends InjectingDialogFragment {
@Override
protected TaskList doInBackground(Void... voids) {
try {
- return new GtasksInvoker(context, playServices, googleTaskList.getAccount())
+ return new GtasksInvoker(context, googleTaskList.getAccount())
.renameGtaskList(googleTaskList.getRemoteId(), name);
} catch (IOException e) {
Timber.e(e);
diff --git a/app/src/main/java/org/tasks/injection/ActivityComponent.java b/app/src/main/java/org/tasks/injection/ActivityComponent.java
index d21da9b4a..81ba6b447 100644
--- a/app/src/main/java/org/tasks/injection/ActivityComponent.java
+++ b/app/src/main/java/org/tasks/injection/ActivityComponent.java
@@ -24,6 +24,7 @@ import org.tasks.billing.PurchaseActivity;
import org.tasks.caldav.CaldavAccountSettingsActivity;
import org.tasks.caldav.CaldavCalendarSettingsActivity;
import org.tasks.dashclock.DashClockSettings;
+import org.tasks.drive.DriveLoginActivity;
import org.tasks.files.FileExplore;
import org.tasks.files.MyFilePickerActivity;
import org.tasks.locale.ui.activity.TaskerCreateTaskActivity;
@@ -128,4 +129,6 @@ public interface ActivityComponent {
void inject(PurchaseActivity purchaseActivity);
void inject(CaldavAccountSettingsActivity caldavAccountSettingsActivity);
+
+ void inject(DriveLoginActivity driveLoginActivity);
}
diff --git a/app/src/main/java/org/tasks/play/AuthResultHandler.java b/app/src/main/java/org/tasks/play/AuthResultHandler.java
new file mode 100644
index 000000000..b9088b4a8
--- /dev/null
+++ b/app/src/main/java/org/tasks/play/AuthResultHandler.java
@@ -0,0 +1,7 @@
+package org.tasks.play;
+
+public interface AuthResultHandler {
+ void authenticationSuccessful(String accountName);
+
+ void authenticationFailed(String message);
+}
diff --git a/app/src/main/java/org/tasks/preferences/BasicPreferences.java b/app/src/main/java/org/tasks/preferences/BasicPreferences.java
index 3c2a11dac..f8148ba8d 100644
--- a/app/src/main/java/org/tasks/preferences/BasicPreferences.java
+++ b/app/src/main/java/org/tasks/preferences/BasicPreferences.java
@@ -6,7 +6,9 @@ import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
+import android.preference.CheckBoxPreference;
import android.preference.Preference;
+import android.widget.CheckBox;
import com.google.common.base.Strings;
import com.todoroo.astrid.core.OldTaskPreferences;
@@ -22,7 +24,9 @@ import org.tasks.analytics.Tracking.Events;
import org.tasks.billing.BillingClient;
import org.tasks.billing.Inventory;
import org.tasks.dialogs.DialogBuilder;
+import org.tasks.drive.DriveLoginActivity;
import org.tasks.files.FileHelper;
+import org.tasks.gtasks.PlayServices;
import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.locale.Locale;
@@ -58,6 +62,7 @@ public class BasicPreferences extends InjectingPreferenceActivity
private static final int REQUEST_CODE_BACKUP_DIR = 10005;
private static final int REQUEST_PICKER = 10006;
private static final int REQUEST_LAUNCHER_PICKER = 10007;
+ private static final int RC_DRIVE_BACKUP = 10008;
@Inject Tracker tracker;
@Inject Preferences preferences;
@Inject ThemeBase themeBase;
@@ -68,6 +73,7 @@ public class BasicPreferences extends InjectingPreferenceActivity
@Inject ThemeCache themeCache;
@Inject BillingClient billingClient;
@Inject Inventory inventory;
+ @Inject PlayServices playServices;
private Bundle result;
@@ -166,6 +172,21 @@ public class BasicPreferences extends InjectingPreferenceActivity
initializeBackupDirectory();
+ CheckBoxPreference googleDriveBackup = (CheckBoxPreference) findPreference(R.string.p_google_drive_backup);
+ googleDriveBackup.setChecked(preferences.getBoolean(R.string.p_google_drive_backup, false));
+ googleDriveBackup
+ .setOnPreferenceChangeListener(
+ (preference, newValue) -> {
+ if (newValue != null && (Boolean) newValue) {
+ if (!playServices.refreshAndCheck()) {
+ playServices.resolve(this);
+ } else {
+ requestLogin();
+ }
+ }
+ return false;
+ });
+
requires(
R.string.settings_localization,
atLeastJellybeanMR1(),
@@ -174,10 +195,15 @@ public class BasicPreferences extends InjectingPreferenceActivity
//noinspection ConstantConditions
if (!BuildConfig.FLAVOR.equals("googleplay")) {
+ remove(R.string.p_google_drive_backup);
requires(R.string.privacy, false, R.string.p_collect_statistics);
}
}
+ private void requestLogin() {
+ startActivityForResult(new Intent(this, DriveLoginActivity.class), RC_DRIVE_BACKUP);
+ }
+
private void setupActivity(int key, final Class> target) {
findPreference(getString(key))
.setOnPreferenceClickListener(
@@ -249,6 +275,9 @@ public class BasicPreferences extends InjectingPreferenceActivity
newImportTasksDialog(data.getData())
.show(getFragmentManager(), FRAG_TAG_IMPORT_TASKS);
}
+ } else if (requestCode == RC_DRIVE_BACKUP) {
+ ((CheckBoxPreference) findPreference(R.string.p_google_drive_backup))
+ .setChecked(resultCode == RESULT_OK);
} else {
super.onActivityResult(requestCode, resultCode, data);
}
diff --git a/app/src/main/java/org/tasks/sync/SynchronizationPreferences.java b/app/src/main/java/org/tasks/sync/SynchronizationPreferences.java
index a1a65e915..5ec3c63cc 100644
--- a/app/src/main/java/org/tasks/sync/SynchronizationPreferences.java
+++ b/app/src/main/java/org/tasks/sync/SynchronizationPreferences.java
@@ -111,8 +111,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
}
private void requestLogin() {
- startActivityForResult(
- new Intent(SynchronizationPreferences.this, GtasksLoginActivity.class), REQUEST_LOGIN);
+ startActivityForResult(new Intent(this, GtasksLoginActivity.class), REQUEST_LOGIN);
}
@Override
diff --git a/app/src/main/res/values/keys.xml b/app/src/main/res/values/keys.xml
index 65c43ff73..df4cbf794 100644
--- a/app/src/main/res/values/keys.xml
+++ b/app/src/main/res/values/keys.xml
@@ -19,6 +19,8 @@
custom_files_dir
p_backup_dir
+ p_google_drive_backup
+ p_google_drive_backup_account
notif_enabled
enable_qhours
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index dd4116f00..a32897182 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -656,6 +656,7 @@ File %1$s contained %2$s.\n\n
Quiet hours
Attachment directory
Backup directory
+ Copy to Google Drive
Miscellaneous
Synchronization
Enabled
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 6345add54..1701b29fd 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -72,6 +72,11 @@
android:key="@string/p_backup_dir"
android:title="@string/backup_directory"/>
+
+