Add option to copy backup files to Google Drive

pull/795/head
Alex Baker 6 years ago
parent f471b47b12
commit 31fb7e4397

@ -164,6 +164,7 @@ dependencies {
implementation('com.wdullaer:materialdatetimepicker:4.0.1') implementation('com.wdullaer:materialdatetimepicker:4.0.1')
implementation "me.leolin:ShortcutBadger:1.1.22@aar" implementation "me.leolin:ShortcutBadger:1.1.22@aar"
implementation 'com.google.apis:google-api-services-tasks:v1-rev55-1.25.0' implementation 'com.google.apis:google-api-services-tasks:v1-rev55-1.25.0'
implementation 'com.google.apis:google-api-services-drive:v3-rev136-1.25.0'
implementation 'com.google.api-client:google-api-client-android:1.27.0' implementation 'com.google.api-client:google-api-client-android:1.27.0'
implementation 'com.android.billingclient:billing:1.1' implementation 'com.android.billingclient:billing:1.1'
implementation("android.arch.work:work-runtime:${WORK_VERSION}") { implementation("android.arch.work:work-runtime:${WORK_VERSION}") {

@ -1,8 +1,13 @@
package org.tasks.gtasks; package org.tasks.gtasks;
import android.app.Activity; import android.app.Activity;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity; import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import org.tasks.drive.DriveLoginActivity;
import org.tasks.play.AuthResultHandler;
import javax.inject.Inject; import javax.inject.Inject;
public class PlayServices { public class PlayServices {
@ -24,12 +29,6 @@ public class PlayServices {
return null; return null;
} }
public boolean clearToken(GoogleAccountCredential googleAccountCredential) { public void getDriveAuthToken(
return false; DriveLoginActivity driveLoginActivity, String a, AuthResultHandler authResultHandler) {}
}
public void getAuthToken(
GtasksLoginActivity gtasksLoginActivity,
String a,
GtasksLoginActivity.AuthResultHandler authResultHandler) {}
} }

@ -1,8 +1,12 @@
package org.tasks.gtasks; package org.tasks.gtasks;
import android.app.Activity; import android.app.Activity;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity; import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import org.tasks.drive.DriveLoginActivity;
import org.tasks.play.AuthResultHandler;
import javax.inject.Inject; import javax.inject.Inject;
public class PlayServices { public class PlayServices {
@ -24,12 +28,9 @@ public class PlayServices {
return null; return null;
} }
public boolean clearToken(GoogleAccountCredential googleAccountCredential) { public void getTasksAuthToken(
return false; GtasksLoginActivity gtasksLoginActivity, String a, AuthResultHandler authResultHandler) {}
}
public void getAuthToken( public void getDriveAuthToken(
GtasksLoginActivity gtasksLoginActivity, DriveLoginActivity driveLoginActivity, String a, AuthResultHandler authResultHandler) {}
String a,
GtasksLoginActivity.AuthResultHandler authResultHandler) {}
} }

@ -47,6 +47,10 @@
<activity <activity
android:name="com.todoroo.astrid.gtasks.auth.GtasksLoginActivity" android:name="com.todoroo.astrid.gtasks.auth.GtasksLoginActivity"
android:theme="@style/TranslucentDialog"/> android:theme="@style/TranslucentDialog"/>
<activity
android:name=".drive.DriveLoginActivity"
android:theme="@style/TranslucentDialog" />
</application> </application>
</manifest> </manifest>

@ -4,19 +4,25 @@ import android.accounts.Account;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.widget.Toast; import android.widget.Toast;
import com.google.android.gms.auth.GoogleAuthException; import com.google.android.gms.auth.GoogleAuthException;
import com.google.android.gms.auth.GoogleAuthUtil; import com.google.android.gms.auth.GoogleAuthUtil;
import com.google.android.gms.auth.UserRecoverableAuthException; import com.google.android.gms.auth.UserRecoverableAuthException;
import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability; import com.google.android.gms.common.GoogleApiAvailability;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; import com.google.api.services.drive.DriveScopes;
import com.google.api.services.tasks.TasksScopes; import com.google.api.services.tasks.TasksScopes;
import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity; import com.todoroo.astrid.gtasks.auth.GtasksLoginActivity;
import java.io.IOException;
import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.play.AuthResultHandler;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.io.IOException;
import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
public class PlayServices { public class PlayServices {
@ -76,46 +82,36 @@ public class PlayServices {
return preferences.getInt(R.string.play_services_available, -1); return preferences.getInt(R.string.play_services_available, -1);
} }
public boolean clearToken(GoogleAccountCredential credential) { public void getTasksAuthToken(
try { final Activity activity, final String accountName, final AuthResultHandler handler) {
String token = credential.getToken(); getToken(TasksScopes.TASKS, activity, accountName, handler);
Timber.d("Invalidating %s", token); }
GoogleAuthUtil.clearToken(context, token);
GoogleAuthUtil.getToken( public void getDriveAuthToken(
context, credential.getSelectedAccount(), "oauth2:" + TasksScopes.TASKS, null); final Activity activity, final String accountName, final AuthResultHandler handler) {
return true; getToken(DriveScopes.DRIVE_FILE, activity, accountName, handler);
} catch (GoogleAuthException e) {
Timber.e(e);
return false;
} catch (IOException e) {
Timber.e(e);
return true;
}
} }
public void getAuthToken( private void getToken(String scope, Activity activity, String accountName, AuthResultHandler handler) {
final Activity activity,
final String accountName,
final GtasksLoginActivity.AuthResultHandler handler) {
final Account account = accountManager.getAccount(accountName); final Account account = accountManager.getAccount(accountName);
if (account == null) { if (account == null) {
handler.authenticationFailed( handler.authenticationFailed(
activity.getString(R.string.gtasks_error_accountNotFound, accountName)); activity.getString(R.string.gtasks_error_accountNotFound, accountName));
} else { } else {
new Thread( new Thread(
() -> { () -> {
try { try {
GoogleAuthUtil.getToken(activity, account, "oauth2:" + TasksScopes.TASKS, null); GoogleAuthUtil.getToken(activity, account, "oauth2:" + scope, null);
handler.authenticationSuccessful(accountName); handler.authenticationSuccessful(accountName);
} catch (UserRecoverableAuthException e) { } catch (UserRecoverableAuthException e) {
Timber.e(e); Timber.e(e);
activity.startActivityForResult( activity.startActivityForResult(
e.getIntent(), GtasksLoginActivity.RC_REQUEST_OAUTH); e.getIntent(), GtasksLoginActivity.RC_REQUEST_OAUTH);
} catch (GoogleAuthException | IOException e) { } catch (GoogleAuthException | IOException e) {
Timber.e(e); Timber.e(e);
handler.authenticationFailed(activity.getString(R.string.gtasks_GLA_errorIOAuth)); handler.authenticationFailed(activity.getString(R.string.gtasks_GLA_errorIOAuth));
} }
}) })
.start(); .start();
} }
} }

@ -1,23 +1,26 @@
package com.todoroo.astrid.gtasks.api; package com.todoroo.astrid.gtasks.api;
import android.content.Context; import android.content.Context;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential; import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpResponse;
import com.google.api.client.http.javanet.NetHttpTransport; import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.GenericJson; import com.google.api.client.json.GenericJson;
import com.google.api.client.json.jackson2.JacksonFactory; import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.services.tasks.Tasks; import com.google.api.services.tasks.Tasks;
import com.google.api.services.tasks.TasksRequest; import com.google.api.services.tasks.TasksRequest;
import com.google.api.services.tasks.TasksScopes; import com.google.api.services.tasks.TasksScopes;
import com.google.api.services.tasks.model.Task; import com.google.api.services.tasks.model.Task;
import com.google.api.services.tasks.model.TaskList; import com.google.api.services.tasks.model.TaskList;
import com.google.api.services.tasks.model.TaskLists; import com.google.api.services.tasks.model.TaskLists;
import org.tasks.BuildConfig;
import java.io.IOException; import java.io.IOException;
import java.util.Collections; import java.util.Collections;
import org.tasks.BuildConfig;
import org.tasks.gtasks.GoogleTasksUnsuccessfulResponseHandler;
import org.tasks.gtasks.PlayServices;
import timber.log.Timber; import timber.log.Timber;
/** /**
@ -28,15 +31,12 @@ import timber.log.Timber;
*/ */
public class GtasksInvoker { public class GtasksInvoker {
private final GoogleAccountCredential credential;
private final PlayServices playServices;
private final Tasks service; private final Tasks service;
public GtasksInvoker(Context context, PlayServices playServices, String account) { public GtasksInvoker(Context context, String account) {
this.playServices = playServices; GoogleAccountCredential credential = GoogleAccountCredential.usingOAuth2(context, Collections.singletonList(TasksScopes.TASKS))
credential = .setBackOff(new ExponentialBackOff.Builder().build())
GoogleAccountCredential.usingOAuth2(context, Collections.singletonList(TasksScopes.TASKS)) .setSelectedAccountName(account);
.setSelectedAccountName(account);
service = service =
new Tasks.Builder(new NetHttpTransport(), new JacksonFactory(), credential) new Tasks.Builder(new NetHttpTransport(), new JacksonFactory(), credential)
.setApplicationName(String.format("Tasks/%s", BuildConfig.VERSION_NAME)) .setApplicationName(String.format("Tasks/%s", BuildConfig.VERSION_NAME))
@ -111,8 +111,6 @@ public class GtasksInvoker {
String caller = getCaller(); String caller = getCaller();
Timber.d("%s request: %s", caller, request); Timber.d("%s request: %s", caller, request);
HttpRequest httpRequest = request.buildHttpRequest(); HttpRequest httpRequest = request.buildHttpRequest();
httpRequest.setUnsuccessfulResponseHandler(
new GoogleTasksUnsuccessfulResponseHandler(playServices, credential));
HttpResponse httpResponse = httpRequest.execute(); HttpResponse httpResponse = httpRequest.execute();
T response = httpResponse.parseAs(request.getResponseClass()); T response = httpResponse.parseAs(request.getResponseClass());
Timber.d("%s response: %s", caller, prettyPrint(response)); Timber.d("%s response: %s", caller, prettyPrint(response));

@ -19,6 +19,7 @@ import org.tasks.gtasks.GoogleAccountManager;
import org.tasks.gtasks.PlayServices; import org.tasks.gtasks.PlayServices;
import org.tasks.injection.ActivityComponent; import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingAppCompatActivity; import org.tasks.injection.InjectingAppCompatActivity;
import org.tasks.play.AuthResultHandler;
/** /**
* This activity allows users to sign in or log in to Google Tasks through the Android account * This activity allows users to sign in or log in to Google Tasks through the Android account
@ -59,7 +60,7 @@ public class GtasksLoginActivity extends InjectingAppCompatActivity {
} }
private void getAuthToken(String a, final ProgressDialog pd) { private void getAuthToken(String a, final ProgressDialog pd) {
playServices.getAuthToken( playServices.getTasksAuthToken(
this, this,
a, a,
new AuthResultHandler() { new AuthResultHandler() {
@ -103,11 +104,4 @@ public class GtasksLoginActivity extends InjectingAppCompatActivity {
finish(); finish();
} }
} }
public interface AuthResultHandler {
void authenticationSuccessful(String accountName);
void authenticationFailed(String message);
}
} }

@ -7,6 +7,7 @@ package com.todoroo.astrid.gtasks.sync;
import android.content.Context; import android.content.Context;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException; import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException;
import com.todoroo.andlib.utility.AndroidUtilities; import com.todoroo.andlib.utility.AndroidUtilities;
import com.todoroo.astrid.dao.TaskDao; import com.todoroo.astrid.dao.TaskDao;
@ -14,23 +15,26 @@ import com.todoroo.astrid.data.Task;
import com.todoroo.astrid.gtasks.GtasksTaskListUpdater; import com.todoroo.astrid.gtasks.GtasksTaskListUpdater;
import com.todoroo.astrid.gtasks.api.GtasksInvoker; import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import com.todoroo.astrid.gtasks.api.MoveRequest; import com.todoroo.astrid.gtasks.api.MoveRequest;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
import org.tasks.data.GoogleTask; import org.tasks.data.GoogleTask;
import org.tasks.data.GoogleTaskDao; import org.tasks.data.GoogleTaskDao;
import org.tasks.data.GoogleTaskList; import org.tasks.data.GoogleTaskList;
import org.tasks.data.GoogleTaskListDao; import org.tasks.data.GoogleTaskListDao;
import org.tasks.gtasks.GtaskSyncAdapterHelper; import org.tasks.gtasks.GtaskSyncAdapterHelper;
import org.tasks.gtasks.PlayServices;
import org.tasks.injection.ApplicationScope; import org.tasks.injection.ApplicationScope;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import javax.inject.Inject;
import timber.log.Timber; import timber.log.Timber;
@ApplicationScope @ApplicationScope
@ -44,7 +48,6 @@ public class GtasksSyncService {
private final GtaskSyncAdapterHelper gtaskSyncAdapterHelper; private final GtaskSyncAdapterHelper gtaskSyncAdapterHelper;
private final Tracker tracker; private final Tracker tracker;
private final GoogleTaskDao googleTaskDao; private final GoogleTaskDao googleTaskDao;
private final PlayServices playServices;
@Inject @Inject
public GtasksSyncService( public GtasksSyncService(
@ -54,15 +57,13 @@ public class GtasksSyncService {
GtaskSyncAdapterHelper gtaskSyncAdapterHelper, GtaskSyncAdapterHelper gtaskSyncAdapterHelper,
Tracker tracker, Tracker tracker,
GoogleTaskDao googleTaskDao, GoogleTaskDao googleTaskDao,
GoogleTaskListDao googleTaskListDao, GoogleTaskListDao googleTaskListDao) {
PlayServices playServices) {
this.context = context; this.context = context;
this.taskDao = taskDao; this.taskDao = taskDao;
this.preferences = preferences; this.preferences = preferences;
this.gtaskSyncAdapterHelper = gtaskSyncAdapterHelper; this.gtaskSyncAdapterHelper = gtaskSyncAdapterHelper;
this.tracker = tracker; this.tracker = tracker;
this.googleTaskDao = googleTaskDao; this.googleTaskDao = googleTaskDao;
this.playServices = playServices;
new OperationPushThread(operationQueue).start(); new OperationPushThread(operationQueue).start();
} }
@ -176,7 +177,7 @@ public class GtasksSyncService {
@Override @Override
public void op() throws IOException { public void op() throws IOException {
GtasksInvoker invoker = new GtasksInvoker(context, playServices, googleTaskList.getAccount()); GtasksInvoker invoker = new GtasksInvoker(context, googleTaskList.getAccount());
pushMetadataOnSave(googleTask, invoker); pushMetadataOnSave(googleTask, invoker);
} }
} }
@ -191,7 +192,7 @@ public class GtasksSyncService {
@Override @Override
public void op() throws IOException { public void op() throws IOException {
GtasksInvoker invoker = new GtasksInvoker(context, playServices, googleTaskList.getAccount()); GtasksInvoker invoker = new GtasksInvoker(context, googleTaskList.getAccount());
invoker.clearCompleted(googleTaskList.getRemoteId()); invoker.clearCompleted(googleTaskList.getRemoteId());
} }
} }

@ -7,6 +7,8 @@ import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.widget.Toast; import android.widget.Toast;
import com.google.api.services.drive.model.File;
import com.google.api.services.drive.model.FileList;
import com.google.common.io.Files; import com.google.common.io.Files;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
@ -27,7 +29,9 @@ import org.tasks.data.TagDao;
import org.tasks.data.TagDataDao; import org.tasks.data.TagDataDao;
import org.tasks.data.TaskAttachmentDao; import org.tasks.data.TaskAttachmentDao;
import org.tasks.data.UserActivityDao; import org.tasks.data.UserActivityDao;
import org.tasks.drive.DriveInvoker;
import org.tasks.files.FileHelper; import org.tasks.files.FileHelper;
import org.tasks.gtasks.PlayServices;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import java.io.IOException; import java.io.IOException;
@ -47,6 +51,9 @@ import static org.tasks.date.DateTimeUtils.newDateTime;
public class TasksJsonExporter { public class TasksJsonExporter {
private static final String MIME = "application/json";
private static final String EXTENSION = ".json";
// --- public interface // --- public interface
private final TagDataDao tagDataDao; private final TagDataDao tagDataDao;
@ -59,6 +66,7 @@ public class TasksJsonExporter {
private final GoogleTaskListDao googleTaskListDao; private final GoogleTaskListDao googleTaskListDao;
private final TaskAttachmentDao taskAttachmentDao; private final TaskAttachmentDao taskAttachmentDao;
private final CaldavDao caldavDao; private final CaldavDao caldavDao;
private final DriveInvoker driveInvoker;
private final TaskDao taskDao; private final TaskDao taskDao;
private final UserActivityDao userActivityDao; private final UserActivityDao userActivityDao;
private final Preferences preferences; private final Preferences preferences;
@ -66,7 +74,6 @@ public class TasksJsonExporter {
private int exportCount = 0; private int exportCount = 0;
private ProgressDialog progressDialog; private ProgressDialog progressDialog;
private Handler handler; private Handler handler;
private Uri backupDirectory;
private String latestSetVersionName; private String latestSetVersionName;
@Inject @Inject
@ -82,7 +89,8 @@ public class TasksJsonExporter {
FilterDao filterDao, FilterDao filterDao,
GoogleTaskListDao googleTaskListDao, GoogleTaskListDao googleTaskListDao,
TaskAttachmentDao taskAttachmentDao, TaskAttachmentDao taskAttachmentDao,
CaldavDao caldavDao) { CaldavDao caldavDao,
DriveInvoker driveInvoker) {
this.tagDataDao = tagDataDao; this.tagDataDao = tagDataDao;
this.taskDao = taskDao; this.taskDao = taskDao;
this.userActivityDao = userActivityDao; this.userActivityDao = userActivityDao;
@ -95,6 +103,7 @@ public class TasksJsonExporter {
this.googleTaskListDao = googleTaskListDao; this.googleTaskListDao = googleTaskListDao;
this.taskAttachmentDao = taskAttachmentDao; this.taskAttachmentDao = taskAttachmentDao;
this.caldavDao = caldavDao; this.caldavDao = caldavDao;
this.driveInvoker = driveInvoker;
} }
private static String getDateForExport() { private static String getDateForExport() {
@ -121,7 +130,6 @@ public class TasksJsonExporter {
@Nullable final ProgressDialog progressDialog) { @Nullable final ProgressDialog progressDialog) {
this.context = context; this.context = context;
this.exportCount = 0; this.exportCount = 0;
this.backupDirectory = preferences.getBackupDirectory();
this.latestSetVersionName = null; this.latestSetVersionName = null;
this.progressDialog = progressDialog; this.progressDialog = progressDialog;
@ -140,10 +148,19 @@ public class TasksJsonExporter {
if (tasks.size() > 0) { if (tasks.size() > 0) {
String basename = Files.getNameWithoutExtension(filename); String basename = Files.getNameWithoutExtension(filename);
Uri uri = FileHelper.newFile(context, backupDirectory, "application/json", basename, ".json"); Uri uri =
FileHelper.newFile(
context, preferences.getBackupDirectory(), MIME, basename, EXTENSION);
OutputStream os = context.getContentResolver().openOutputStream(uri); OutputStream os = context.getContentResolver().openOutputStream(uri);
doTasksExport(os, tasks); doTasksExport(os, tasks);
os.close(); os.close();
if (preferences.getBoolean(R.string.p_google_drive_backup, false)) {
List<File> files = driveInvoker.findFolder("org.tasks");
File folder = files.isEmpty()
? driveInvoker.createFolder("org.tasks")
: files.get(0);
driveInvoker.createFile(MIME, folder.getId(), FileHelper.getFilename(context, uri), uri);
}
} }
if (exportType == ExportType.EXPORT_TYPE_MANUAL) { if (exportType == ExportType.EXPORT_TYPE_MANUAL) {

@ -0,0 +1,104 @@
package org.tasks.drive;
import android.content.Context;
import android.net.Uri;
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential;
import com.google.api.client.http.InputStreamContent;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.GenericJson;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.client.util.ExponentialBackOff;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.DriveRequest;
import com.google.api.services.drive.DriveScopes;
import com.google.api.services.drive.model.File;
import org.tasks.BuildConfig;
import org.tasks.R;
import org.tasks.injection.ForApplication;
import org.tasks.preferences.Preferences;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import javax.inject.Inject;
import timber.log.Timber;
public class DriveInvoker {
private final Drive service;
private final Context context;
@Inject
public DriveInvoker(@ForApplication Context context, Preferences preferences) {
this.context = context;
if (preferences.getBoolean(R.string.p_google_drive_backup, false)) {
GoogleAccountCredential credential =
GoogleAccountCredential.usingOAuth2(
context, Collections.singletonList(DriveScopes.DRIVE_FILE))
.setBackOff(new ExponentialBackOff.Builder().build())
.setSelectedAccountName(
preferences.getStringValue(R.string.p_google_drive_backup_account));
service =
new Drive.Builder(new NetHttpTransport(), new JacksonFactory(), credential)
.setApplicationName(String.format("Tasks/%s", BuildConfig.VERSION_NAME))
.build();
} else {
service = null;
}
}
public List<File> findFolder(String name) throws IOException {
String query = String.format("name='%s'", name);
return execute(service.files().list().setQ(query).setSpaces("drive")).getFiles();
}
public File createFolder(String name) throws IOException {
File folder = new File()
.setName(name)
.setMimeType("application/vnd.google-apps.folder");
return execute(service.files().create(folder).setFields("id"));
}
public void createFile(String mime, String parent, String name, Uri uri) throws IOException {
File metadata = new File()
.setParents(Collections.singletonList(parent))
.setMimeType(mime)
.setName(name);
InputStreamContent content =
new InputStreamContent(mime, context.getContentResolver().openInputStream(uri));
execute(service.files().create(metadata, content));
}
private synchronized <T> T execute(DriveRequest<T> request) throws IOException {
String caller = getCaller();
Timber.d("%s request: %s", caller, request);
T response = request.execute();
Timber.d("%s response: %s", caller, prettyPrint(response));
return response;
}
private <T> 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 "";
}
}

@ -0,0 +1,98 @@
/**
* Copyright (c) 2012 Todoroo Inc
*
* <p>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();
}
}
}

@ -6,17 +6,22 @@ import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.api.services.tasks.model.TaskList; import com.google.api.services.tasks.model.TaskList;
import com.todoroo.astrid.gtasks.api.GtasksInvoker; import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import java.io.IOException;
import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.DialogFragmentComponent; import org.tasks.injection.DialogFragmentComponent;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingDialogFragment; 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; import timber.log.Timber;
public class CreateListDialog extends InjectingDialogFragment { public class CreateListDialog extends InjectingDialogFragment {
@ -25,7 +30,6 @@ public class CreateListDialog extends InjectingDialogFragment {
private static final String EXTRA_NAME = "extra_name"; private static final String EXTRA_NAME = "extra_name";
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject @ForApplication Context context; @Inject @ForApplication Context context;
@Inject PlayServices playServices;
private CreateListDialogCallback callback; private CreateListDialogCallback callback;
private ProgressDialog dialog; private ProgressDialog dialog;
private String account; private String account;
@ -74,7 +78,7 @@ public class CreateListDialog extends InjectingDialogFragment {
@Override @Override
protected TaskList doInBackground(Void... voids) { protected TaskList doInBackground(Void... voids) {
try { try {
return new GtasksInvoker(context, playServices, account).createGtaskList(name); return new GtasksInvoker(context, account).createGtaskList(name);
} catch (IOException e) { } catch (IOException e) {
Timber.e(e); Timber.e(e);
return null; return null;

@ -6,17 +6,22 @@ import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.todoroo.astrid.gtasks.api.GtasksInvoker; import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import java.io.IOException;
import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.GoogleTaskList; import org.tasks.data.GoogleTaskList;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.DialogFragmentComponent; import org.tasks.injection.DialogFragmentComponent;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingDialogFragment; 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; import timber.log.Timber;
public class DeleteListDialog extends InjectingDialogFragment { public class DeleteListDialog extends InjectingDialogFragment {
@ -24,7 +29,6 @@ public class DeleteListDialog extends InjectingDialogFragment {
private static final String EXTRA_LIST = "extra_list"; private static final String EXTRA_LIST = "extra_list";
@Inject @ForApplication Context context; @Inject @ForApplication Context context;
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject PlayServices playServices;
private DeleteListDialogCallback callback; private DeleteListDialogCallback callback;
private GoogleTaskList googleTaskList; private GoogleTaskList googleTaskList;
private ProgressDialog dialog; private ProgressDialog dialog;
@ -70,7 +74,7 @@ public class DeleteListDialog extends InjectingDialogFragment {
@Override @Override
protected Boolean doInBackground(Void... voids) { protected Boolean doInBackground(Void... voids) {
try { try {
new GtasksInvoker(context, playServices, googleTaskList.getAccount()) new GtasksInvoker(context, googleTaskList.getAccount())
.deleteGtaskList(googleTaskList.getRemoteId()); .deleteGtaskList(googleTaskList.getRemoteId());
return true; return true;
} catch (IOException e) { } catch (IOException e) {

@ -1,12 +1,10 @@
package org.tasks.gtasks; package org.tasks.gtasks;
import static org.tasks.date.DateTimeUtils.newDateTime;
import android.app.PendingIntent; import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import androidx.core.app.NotificationCompat;
import android.text.TextUtils; import android.text.TextUtils;
import com.google.api.client.googleapis.extensions.android.gms.auth.UserRecoverableAuthIOException; 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.TaskList;
import com.google.api.services.tasks.model.TaskLists; 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.TaskCreator;
import com.todoroo.astrid.service.TaskDeleter; import com.todoroo.astrid.service.TaskDeleter;
import com.todoroo.astrid.utility.Constants; 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.LocalBroadcastManager;
import org.tasks.R; import org.tasks.R;
import org.tasks.analytics.Tracker; import org.tasks.analytics.Tracker;
@ -47,8 +42,18 @@ import org.tasks.preferences.DefaultFilterProvider;
import org.tasks.preferences.PermissionChecker; import org.tasks.preferences.PermissionChecker;
import org.tasks.preferences.Preferences; import org.tasks.preferences.Preferences;
import org.tasks.time.DateTime; 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 timber.log.Timber;
import static org.tasks.date.DateTimeUtils.newDateTime;
public class GoogleTaskSynchronizer { public class GoogleTaskSynchronizer {
private static final String DEFAULT_LIST = "@default"; // $NON-NLS-1$ private static final String DEFAULT_LIST = "@default"; // $NON-NLS-1$
@ -65,7 +70,6 @@ public class GoogleTaskSynchronizer {
private final GoogleTaskDao googleTaskDao; private final GoogleTaskDao googleTaskDao;
private final TaskCreator taskCreator; private final TaskCreator taskCreator;
private final DefaultFilterProvider defaultFilterProvider; private final DefaultFilterProvider defaultFilterProvider;
private final PlayServices playServices;
private final PermissionChecker permissionChecker; private final PermissionChecker permissionChecker;
private final GoogleAccountManager googleAccountManager; private final GoogleAccountManager googleAccountManager;
private final LocalBroadcastManager localBroadcastManager; private final LocalBroadcastManager localBroadcastManager;
@ -86,7 +90,6 @@ public class GoogleTaskSynchronizer {
GoogleTaskDao googleTaskDao, GoogleTaskDao googleTaskDao,
TaskCreator taskCreator, TaskCreator taskCreator,
DefaultFilterProvider defaultFilterProvider, DefaultFilterProvider defaultFilterProvider,
PlayServices playServices,
PermissionChecker permissionChecker, PermissionChecker permissionChecker,
GoogleAccountManager googleAccountManager, GoogleAccountManager googleAccountManager,
LocalBroadcastManager localBroadcastManager, LocalBroadcastManager localBroadcastManager,
@ -104,7 +107,6 @@ public class GoogleTaskSynchronizer {
this.googleTaskDao = googleTaskDao; this.googleTaskDao = googleTaskDao;
this.taskCreator = taskCreator; this.taskCreator = taskCreator;
this.defaultFilterProvider = defaultFilterProvider; this.defaultFilterProvider = defaultFilterProvider;
this.playServices = playServices;
this.permissionChecker = permissionChecker; this.permissionChecker = permissionChecker;
this.googleAccountManager = googleAccountManager; this.googleAccountManager = googleAccountManager;
this.localBroadcastManager = localBroadcastManager; this.localBroadcastManager = localBroadcastManager;
@ -182,7 +184,7 @@ public class GoogleTaskSynchronizer {
return; return;
} }
GtasksInvoker gtasksInvoker = new GtasksInvoker(context, playServices, account.getAccount()); GtasksInvoker gtasksInvoker = new GtasksInvoker(context, account.getAccount());
pushLocalChanges(account, gtasksInvoker); pushLocalChanges(account, gtasksInvoker);
List<TaskList> gtaskLists = new ArrayList<>(); List<TaskList> gtaskLists = new ArrayList<>();

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

@ -6,18 +6,23 @@ import android.app.ProgressDialog;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.api.services.tasks.model.TaskList; import com.google.api.services.tasks.model.TaskList;
import com.todoroo.astrid.gtasks.api.GtasksInvoker; import com.todoroo.astrid.gtasks.api.GtasksInvoker;
import java.io.IOException;
import javax.inject.Inject;
import org.tasks.R; import org.tasks.R;
import org.tasks.data.GoogleTaskList; import org.tasks.data.GoogleTaskList;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.injection.DialogFragmentComponent; import org.tasks.injection.DialogFragmentComponent;
import org.tasks.injection.ForApplication; import org.tasks.injection.ForApplication;
import org.tasks.injection.InjectingDialogFragment; 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; import timber.log.Timber;
public class RenameListDialog extends InjectingDialogFragment { public class RenameListDialog extends InjectingDialogFragment {
@ -26,7 +31,6 @@ public class RenameListDialog extends InjectingDialogFragment {
private static final String EXTRA_LIST = "extra_list"; private static final String EXTRA_LIST = "extra_list";
@Inject @ForApplication Context context; @Inject @ForApplication Context context;
@Inject DialogBuilder dialogBuilder; @Inject DialogBuilder dialogBuilder;
@Inject PlayServices playServices;
private RenameListDialogCallback callback; private RenameListDialogCallback callback;
private ProgressDialog dialog; private ProgressDialog dialog;
private GoogleTaskList googleTaskList; private GoogleTaskList googleTaskList;
@ -75,7 +79,7 @@ public class RenameListDialog extends InjectingDialogFragment {
@Override @Override
protected TaskList doInBackground(Void... voids) { protected TaskList doInBackground(Void... voids) {
try { try {
return new GtasksInvoker(context, playServices, googleTaskList.getAccount()) return new GtasksInvoker(context, googleTaskList.getAccount())
.renameGtaskList(googleTaskList.getRemoteId(), name); .renameGtaskList(googleTaskList.getRemoteId(), name);
} catch (IOException e) { } catch (IOException e) {
Timber.e(e); Timber.e(e);

@ -24,6 +24,7 @@ import org.tasks.billing.PurchaseActivity;
import org.tasks.caldav.CaldavAccountSettingsActivity; import org.tasks.caldav.CaldavAccountSettingsActivity;
import org.tasks.caldav.CaldavCalendarSettingsActivity; import org.tasks.caldav.CaldavCalendarSettingsActivity;
import org.tasks.dashclock.DashClockSettings; import org.tasks.dashclock.DashClockSettings;
import org.tasks.drive.DriveLoginActivity;
import org.tasks.files.FileExplore; import org.tasks.files.FileExplore;
import org.tasks.files.MyFilePickerActivity; import org.tasks.files.MyFilePickerActivity;
import org.tasks.locale.ui.activity.TaskerCreateTaskActivity; import org.tasks.locale.ui.activity.TaskerCreateTaskActivity;
@ -128,4 +129,6 @@ public interface ActivityComponent {
void inject(PurchaseActivity purchaseActivity); void inject(PurchaseActivity purchaseActivity);
void inject(CaldavAccountSettingsActivity caldavAccountSettingsActivity); void inject(CaldavAccountSettingsActivity caldavAccountSettingsActivity);
void inject(DriveLoginActivity driveLoginActivity);
} }

@ -0,0 +1,7 @@
package org.tasks.play;
public interface AuthResultHandler {
void authenticationSuccessful(String accountName);
void authenticationFailed(String message);
}

@ -6,7 +6,9 @@ import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.Preference; import android.preference.Preference;
import android.widget.CheckBox;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.todoroo.astrid.core.OldTaskPreferences; 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.BillingClient;
import org.tasks.billing.Inventory; import org.tasks.billing.Inventory;
import org.tasks.dialogs.DialogBuilder; import org.tasks.dialogs.DialogBuilder;
import org.tasks.drive.DriveLoginActivity;
import org.tasks.files.FileHelper; import org.tasks.files.FileHelper;
import org.tasks.gtasks.PlayServices;
import org.tasks.injection.ActivityComponent; import org.tasks.injection.ActivityComponent;
import org.tasks.injection.InjectingPreferenceActivity; import org.tasks.injection.InjectingPreferenceActivity;
import org.tasks.locale.Locale; 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_CODE_BACKUP_DIR = 10005;
private static final int REQUEST_PICKER = 10006; private static final int REQUEST_PICKER = 10006;
private static final int REQUEST_LAUNCHER_PICKER = 10007; private static final int REQUEST_LAUNCHER_PICKER = 10007;
private static final int RC_DRIVE_BACKUP = 10008;
@Inject Tracker tracker; @Inject Tracker tracker;
@Inject Preferences preferences; @Inject Preferences preferences;
@Inject ThemeBase themeBase; @Inject ThemeBase themeBase;
@ -68,6 +73,7 @@ public class BasicPreferences extends InjectingPreferenceActivity
@Inject ThemeCache themeCache; @Inject ThemeCache themeCache;
@Inject BillingClient billingClient; @Inject BillingClient billingClient;
@Inject Inventory inventory; @Inject Inventory inventory;
@Inject PlayServices playServices;
private Bundle result; private Bundle result;
@ -166,6 +172,21 @@ public class BasicPreferences extends InjectingPreferenceActivity
initializeBackupDirectory(); 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( requires(
R.string.settings_localization, R.string.settings_localization,
atLeastJellybeanMR1(), atLeastJellybeanMR1(),
@ -174,10 +195,15 @@ public class BasicPreferences extends InjectingPreferenceActivity
//noinspection ConstantConditions //noinspection ConstantConditions
if (!BuildConfig.FLAVOR.equals("googleplay")) { if (!BuildConfig.FLAVOR.equals("googleplay")) {
remove(R.string.p_google_drive_backup);
requires(R.string.privacy, false, R.string.p_collect_statistics); 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) { private void setupActivity(int key, final Class<?> target) {
findPreference(getString(key)) findPreference(getString(key))
.setOnPreferenceClickListener( .setOnPreferenceClickListener(
@ -249,6 +275,9 @@ public class BasicPreferences extends InjectingPreferenceActivity
newImportTasksDialog(data.getData()) newImportTasksDialog(data.getData())
.show(getFragmentManager(), FRAG_TAG_IMPORT_TASKS); .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 { } else {
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
} }

@ -111,8 +111,7 @@ public class SynchronizationPreferences extends InjectingPreferenceActivity {
} }
private void requestLogin() { private void requestLogin() {
startActivityForResult( startActivityForResult(new Intent(this, GtasksLoginActivity.class), REQUEST_LOGIN);
new Intent(SynchronizationPreferences.this, GtasksLoginActivity.class), REQUEST_LOGIN);
} }
@Override @Override

@ -19,6 +19,8 @@
<string name="p_attachment_dir">custom_files_dir</string> <string name="p_attachment_dir">custom_files_dir</string>
<string name="p_backup_dir">p_backup_dir</string> <string name="p_backup_dir">p_backup_dir</string>
<string name="p_google_drive_backup">p_google_drive_backup</string>
<string name="p_google_drive_backup_account">p_google_drive_backup_account</string>
<string name="p_rmd_enabled">notif_enabled</string> <string name="p_rmd_enabled">notif_enabled</string>
<!-- boolean : whether to enable quiet hours or not --> <!-- boolean : whether to enable quiet hours or not -->
<string name="p_rmd_enable_quiet">enable_qhours</string> <string name="p_rmd_enable_quiet">enable_qhours</string>

@ -656,6 +656,7 @@ File %1$s contained %2$s.\n\n
<string name="quiet_hours">Quiet hours</string> <string name="quiet_hours">Quiet hours</string>
<string name="attachment_directory">Attachment directory</string> <string name="attachment_directory">Attachment directory</string>
<string name="backup_directory">Backup directory</string> <string name="backup_directory">Backup directory</string>
<string name="google_drive_backup">Copy to Google Drive</string>
<string name="miscellaneous">Miscellaneous</string> <string name="miscellaneous">Miscellaneous</string>
<string name="synchronization">Synchronization</string> <string name="synchronization">Synchronization</string>
<string name="enabled">Enabled</string> <string name="enabled">Enabled</string>

@ -72,6 +72,11 @@
android:key="@string/p_backup_dir" android:key="@string/p_backup_dir"
android:title="@string/backup_directory"/> android:title="@string/backup_directory"/>
<CheckBoxPreference
android:defaultValue="false"
android:key="@string/p_google_drive_backup"
android:title="@string/google_drive_backup" />
<Preference <Preference
android:key="@string/backup_BAc_import" android:key="@string/backup_BAc_import"
android:title="@string/backup_BAc_import"/> android:title="@string/backup_BAc_import"/>

Loading…
Cancel
Save