Automatically import Android AutoBackup data

gtask_related_email
Alex Baker 4 years ago
parent e119fb34f0
commit 59a722f251

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name" tools:ignore="PrivateResource">Tasks Debug</string>
<string name="backup_api_key">AEdPqrEAAAAImTf5DbfspggWrU9h06685ONycpUVwJj1JwawQQ</string>
<string name="debug_strict_mode_thread">Strict mode - Thread</string>
<string name="debug_strict_mode_vm">Strict mode - VM</string>
<string name="debug_flipper">Flipper</string>

@ -7,11 +7,7 @@
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="@string/google_key"/>
<meta-data
android:name="com.google.android.backup.api_key"
android:value="AEdPqrEAAAAI49v5bBusi_bq1bgLBB1LIsepNV0eBrFkQrBZkw"/>
android:value="@string/google_key" />
<meta-data
android:name="com.google.android.gms.version"

@ -100,7 +100,7 @@
<application
android:allowBackup="true"
android:fullBackupContent="@xml/backup_config"
android:backupAgent="org.tasks.backup.TasksBackupAgent"
android:networkSecurityConfig="@xml/network_security_config"
android:icon="@mipmap/ic_launcher_blue"
android:label="@string/app_name"
@ -119,6 +119,10 @@
android:name="firebase_analytics_collection_enabled"
android:value="false"/>
<meta-data
android:name="com.google.android.backup.api_key"
android:value="@string/backup_api_key" />
<!-- ====================================================== Activities = -->
<activity

@ -39,9 +39,9 @@ public class BackupConstants {
// --- general
public static final String INTERNAL_BACKUP = "backup.json";
public static final String EXPORT_FILE_NAME = "user.%s.json";
public static final String BACKUP_FILE_NAME = "auto.%s.json";
public static final String UPGRADE_FILE_NAME = "upgradefrom.%s.json";
}

@ -0,0 +1,49 @@
package org.tasks.backup;
import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.FileBackupHelper;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import com.todoroo.astrid.backup.BackupConstants;
import java.io.File;
import java.io.IOException;
import javax.inject.Inject;
import org.tasks.injection.InjectingApplication;
import timber.log.Timber;
public class TasksBackupAgent extends BackupAgentHelper {
private static final String BACKUP_KEY = "backup";
@Inject TasksJsonImporter importer;
@Override
public void onCreate() {
((InjectingApplication) getApplicationContext()).getComponent().inject(this);
addHelper(BACKUP_KEY, new FileBackupHelper(this, BackupConstants.INTERNAL_BACKUP));
}
@Override
public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState)
throws IOException {
super.onRestore(data, appVersionCode, newState);
File backup =
new File(
String.format(
"%s/%s", getFilesDir().getAbsolutePath(), BackupConstants.INTERNAL_BACKUP));
if (backup.exists()) {
importer.importTasks(this, Uri.fromFile(backup), null);
} else {
Timber.w("%s not found", backup.getAbsolutePath());
}
}
@Override
public void onQuotaExceeded(long backupDataBytes, long quotaBytes) {
Timber.e("onQuotaExceeded(%s, %s)", backupDataBytes, quotaBytes);
}
}

@ -4,6 +4,7 @@ import static org.tasks.date.DateTimeUtils.newDateTime;
import android.app.Activity;
import android.app.ProgressDialog;
import android.app.backup.BackupManager;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
@ -16,6 +17,7 @@ import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.backup.BackupConstants;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
@ -37,7 +39,6 @@ import org.tasks.data.TagDao;
import org.tasks.data.TagDataDao;
import org.tasks.data.TaskAttachmentDao;
import org.tasks.data.UserActivityDao;
import org.tasks.drive.DriveInvoker;
import org.tasks.files.FileHelper;
import org.tasks.jobs.WorkManager;
import org.tasks.preferences.Preferences;
@ -45,6 +46,7 @@ import timber.log.Timber;
public class TasksJsonExporter {
@SuppressWarnings("CharsetObjectCanBeUsed")
static final Charset UTF_8 = Charset.forName("UTF-8");
private static final String MIME = "application/json";
private static final String EXTENSION = ".json";
@ -69,7 +71,6 @@ public class TasksJsonExporter {
private int exportCount = 0;
private ProgressDialog progressDialog;
private Handler handler;
private String latestSetVersionName;
@Inject
public TasksJsonExporter(
@ -85,8 +86,7 @@ public class TasksJsonExporter {
GoogleTaskListDao googleTaskListDao,
TaskAttachmentDao taskAttachmentDao,
CaldavDao caldavDao,
WorkManager workManager,
DriveInvoker driveInvoker) {
WorkManager workManager) {
this.tagDataDao = tagDataDao;
this.taskDao = taskDao;
this.userActivityDao = userActivityDao;
@ -126,7 +126,6 @@ public class TasksJsonExporter {
@Nullable final ProgressDialog progressDialog) {
this.context = context;
this.exportCount = 0;
this.latestSetVersionName = null;
this.progressDialog = progressDialog;
if (exportType == ExportType.EXPORT_TYPE_MANUAL) {
@ -141,16 +140,30 @@ public class TasksJsonExporter {
try {
String filename = getFileName(exportType);
List<Task> tasks = taskDao.getAll();
if (tasks.size() > 0) {
String basename = Files.getNameWithoutExtension(filename);
Uri uri =
FileHelper.newFile(
context, preferences.getBackupDirectory(), MIME, basename, EXTENSION);
OutputStream os = context.getContentResolver().openOutputStream(uri);
File file =
new File(
String.format("%s/%s", context.getFilesDir(), BackupConstants.INTERNAL_BACKUP));
file.delete();
file.createNewFile();
Uri internalStorageBackup = Uri.fromFile(file);
OutputStream os = context.getContentResolver().openOutputStream(internalStorageBackup);
doTasksExport(os, tasks);
os.close();
workManager.scheduleDriveUpload(uri, exportType == ExportType.EXPORT_TYPE_SERVICE);
Uri externalStorageBackup =
FileHelper.newFile(
context,
preferences.getBackupDirectory(),
MIME,
Files.getNameWithoutExtension(filename),
EXTENSION);
FileHelper.copyStream(context, internalStorageBackup, externalStorageBackup);
workManager.scheduleDriveUpload(externalStorageBackup, exportType == ExportType.EXPORT_TYPE_SERVICE);
new BackupManager(context).dataChanged();
}
if (exportType == ExportType.EXPORT_TYPE_MANUAL) {
@ -241,8 +254,6 @@ public class TasksJsonExporter {
return String.format(BackupConstants.BACKUP_FILE_NAME, getDateForExport());
case EXPORT_TYPE_MANUAL:
return String.format(BackupConstants.EXPORT_FILE_NAME, getDateForExport());
case EXPORT_TYPE_ON_UPGRADE:
return String.format(BackupConstants.UPGRADE_FILE_NAME, latestSetVersionName);
default:
throw new UnsupportedOperationException("Unhandled export type");
}

@ -5,15 +5,14 @@ import static com.todoroo.astrid.data.SyncFlags.GTASKS_SUPPRESS_SYNC;
import static org.tasks.backup.TasksJsonExporter.UTF_8;
import static org.tasks.data.Place.newPlace;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.res.Resources;
import android.content.Context;
import android.net.Uri;
import android.os.Handler;
import androidx.annotation.Nullable;
import com.google.gson.Gson;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.dao.TaskDao;
import com.todoroo.astrid.data.Task;
import java.io.FileNotFoundException;
@ -48,7 +47,6 @@ import org.tasks.data.TaskAttachment;
import org.tasks.data.TaskAttachmentDao;
import org.tasks.data.UserActivity;
import org.tasks.data.UserActivityDao;
import org.tasks.dialogs.DialogBuilder;
import org.tasks.preferences.Preferences;
import timber.log.Timber;
@ -56,7 +54,6 @@ public class TasksJsonImporter {
private final TagDataDao tagDataDao;
private final UserActivityDao userActivityDao;
private final DialogBuilder dialogBuilder;
private final TaskDao taskDao;
private final LocalBroadcastManager localBroadcastManager;
private final AlarmDao alarmDao;
@ -69,19 +66,12 @@ public class TasksJsonImporter {
private final Preferences preferences;
private final LocationDao locationDao;
private Activity activity;
private Handler handler;
private int taskCount;
private int importCount = 0;
private int skipCount = 0;
private ProgressDialog progressDialog;
private Uri input;
private ImportResult result = new ImportResult();
@Inject
public TasksJsonImporter(
TagDataDao tagDataDao,
UserActivityDao userActivityDao,
DialogBuilder dialogBuilder,
TaskDao taskDao,
LocationDao locationDao,
LocalBroadcastManager localBroadcastManager,
@ -95,7 +85,6 @@ public class TasksJsonImporter {
Preferences preferences) {
this.tagDataDao = tagDataDao;
this.userActivityDao = userActivityDao;
this.dialogBuilder = dialogBuilder;
this.taskDao = taskDao;
this.locationDao = locationDao;
this.localBroadcastManager = localBroadcastManager;
@ -109,25 +98,21 @@ public class TasksJsonImporter {
this.preferences = preferences;
}
private void setProgressMessage(final String message) {
handler.post(() -> progressDialog.setMessage(message));
}
public void importTasks(Activity activity, Uri input, ProgressDialog progressDialog) {
this.activity = activity;
this.input = input;
this.progressDialog = progressDialog;
handler = new Handler();
private void setProgressMessage(
Handler handler, ProgressDialog progressDialog, final String message) {
if (progressDialog == null) {
return;
}
new Thread(this::performImport).start();
handler.post(() -> progressDialog.setMessage(message));
}
private void performImport() {
public ImportResult importTasks(Context context, Uri backupFile, @Nullable ProgressDialog progressDialog) {
Handler handler = new Handler(context.getMainLooper());
Gson gson = new Gson();
InputStream is;
try {
is = activity.getContentResolver().openInputStream(this.input);
is = context.getContentResolver().openInputStream(backupFile);
} catch (FileNotFoundException e) {
throw new IllegalStateException(e);
}
@ -174,11 +159,14 @@ public class TasksJsonImporter {
}
}
for (BackupContainer.TaskBackup backup : backupContainer.getTasks()) {
taskCount++;
setProgressMessage(activity.getString(R.string.import_progress_read, taskCount));
result.taskCount++;
setProgressMessage(
handler,
progressDialog,
context.getString(R.string.import_progress_read, result.taskCount));
Task task = backup.task;
if (taskDao.fetch(task.getUuid()) != null) {
skipCount++;
result.skipCount++;
continue;
}
task.putTransitory(TRANS_SUPPRESS_REFRESH, true);
@ -238,7 +226,7 @@ public class TasksJsonImporter {
caldavTask.setTask(taskId);
caldavDao.insert(caldavTask);
}
importCount++;
result.importCount++;
}
googleTaskDao.updateParents();
@ -261,32 +249,16 @@ public class TasksJsonImporter {
is.close();
} catch (IOException e) {
Timber.e(e);
} finally {
localBroadcastManager.broadcastRefresh();
handler.post(
() -> {
if (progressDialog.isShowing()) {
DialogUtilities.dismissDialog(activity, progressDialog);
showSummary();
}
});
}
localBroadcastManager.broadcastRefresh();
return result;
}
private void showSummary() {
Resources r = activity.getResources();
dialogBuilder
.newDialog(R.string.import_summary_title)
.setMessage(
activity.getString(
R.string.import_summary_message,
"",
r.getQuantityString(R.plurals.Ntasks, taskCount, taskCount),
r.getQuantityString(R.plurals.Ntasks, importCount, importCount),
r.getQuantityString(R.plurals.Ntasks, skipCount, skipCount),
r.getQuantityString(R.plurals.Ntasks, 0, 0)))
.setPositiveButton(android.R.string.ok, (dialog, id) -> dialog.dismiss())
.show();
public static class ImportResult {
public int taskCount;
public int importCount;
public int skipCount;
}
static class LegacyLocation {

@ -1,16 +1,22 @@
package org.tasks.dialogs;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import com.todoroo.andlib.utility.DialogUtilities;
import com.todoroo.astrid.backup.TasksXmlImporter;
import javax.inject.Inject;
import org.tasks.R;
import org.tasks.analytics.Tracker;
import org.tasks.analytics.Tracking;
import org.tasks.backup.TasksJsonImporter;
import org.tasks.injection.ForApplication;
import org.tasks.backup.TasksJsonImporter.ImportResult;
import org.tasks.injection.ForActivity;
import org.tasks.injection.InjectingNativeDialogFragment;
import org.tasks.injection.NativeDialogFragmentComponent;
import org.tasks.ui.Toaster;
@ -24,7 +30,7 @@ public class ImportTasksDialog extends InjectingNativeDialogFragment {
@Inject TasksJsonImporter jsonImporter;
@Inject DialogBuilder dialogBuilder;
@Inject Tracker tracker;
@Inject @ForApplication Context context;
@Inject @ForActivity Context context;
@Inject Toaster toaster;
public static ImportTasksDialog newImportTasksDialog(Uri data, String extension) {
@ -49,7 +55,19 @@ public class ImportTasksDialog extends InjectingNativeDialogFragment {
setCancelable(false);
switch (extension) {
case "json":
jsonImporter.importTasks(getActivity(), data, progressDialog);
Handler handler = new Handler();
new Thread(
() -> {
ImportResult result =
jsonImporter.importTasks(getActivity(), data, progressDialog);
handler.post(() -> {
if (progressDialog.isShowing()) {
DialogUtilities.dismissDialog((Activity) context, progressDialog);
}
showSummary(result);
});
})
.start();
tracker.reportEvent(Tracking.Events.IMPORT_JSON);
break;
case "xml":
@ -62,6 +80,22 @@ public class ImportTasksDialog extends InjectingNativeDialogFragment {
return progressDialog;
}
private void showSummary(ImportResult result) {
Resources r = context.getResources();
dialogBuilder
.newDialog(R.string.import_summary_title)
.setMessage(
context.getString(
R.string.import_summary_message,
"",
r.getQuantityString(R.plurals.Ntasks, result.taskCount, result.taskCount),
r.getQuantityString(R.plurals.Ntasks, result.importCount, result.importCount),
r.getQuantityString(R.plurals.Ntasks, result.skipCount, result.skipCount),
r.getQuantityString(R.plurals.Ntasks, 0, 0)))
.setPositiveButton(android.R.string.ok, (dialog, id) -> dialog.dismiss())
.show();
}
@Override
protected void inject(NativeDialogFragmentComponent component) {
component.inject(this);

@ -246,7 +246,6 @@ public class FileHelper {
}
public static Uri copyToUri(Context context, Uri destination, Uri input, String basename) {
ContentResolver contentResolver = context.getContentResolver();
try {
Uri output =
newFile(
@ -255,12 +254,21 @@ public class FileHelper {
getMimeType(context, input),
basename,
getExtension(context, input));
copyStream(context, input, output);
return output;
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
public static void copyStream(Context context, Uri input, Uri output) {
ContentResolver contentResolver = context.getContentResolver();
try {
InputStream inputStream = contentResolver.openInputStream(input);
OutputStream outputStream = contentResolver.openOutputStream(output);
ByteStreams.copy(inputStream, outputStream);
inputStream.close();
outputStream.close();
return output;
} catch (IOException e) {
throw new IllegalStateException(e);
}

@ -2,6 +2,7 @@ package org.tasks.injection;
import dagger.Component;
import org.tasks.Tasks;
import org.tasks.backup.TasksBackupAgent;
import org.tasks.dashclock.DashClockExtension;
import org.tasks.widget.ScrollableWidgetUpdateService;
@ -22,4 +23,6 @@ public interface ApplicationComponent {
ServiceComponent plus(ServiceModule module);
JobComponent plus(WorkModule module);
void inject(TasksBackupAgent tasksBackupAgent);
}

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<full-backup-content>
<exclude
domain="sharedpref"
path="evernote_jobs.xml"/>
<exclude
domain="database"
path="evernote_jobs.db"/>
</full-backup-content>

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources xmlns:tools="http://schemas.android.com/tools">
<string name="app_name" tools:ignore="PrivateResource">Tasks</string>
<string name="backup_api_key">AEdPqrEAAAAI49v5bBusi_bq1bgLBB1LIsepNV0eBrFkQrBZkw</string>
</resources>
Loading…
Cancel
Save