From 540147470d8d60dedda06ae98ecdd8dfe1173866 Mon Sep 17 00:00:00 2001 From: Torsten Grote Date: Fri, 7 Jun 2019 16:41:48 -0300 Subject: [PATCH 1/6] Allow the user to schedule full background backups --- app/src/main/AndroidManifest.xml | 7 ++ .../java/com/stevesoltys/backup/Backup.java | 3 + .../backup/activity/MainActivity.java | 12 +++ .../activity/MainActivityController.java | 37 +++++++- .../service/backup/BackupJobService.java | 89 +++++++++++++++++++ .../backup/settings/SettingsManager.java | 13 +++ app/src/main/res/layout/activity_main.xml | 9 ++ 7 files changed, 167 insertions(+), 3 deletions(-) create mode 100644 app/src/main/java/com/stevesoltys/backup/service/backup/BackupJobService.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index eb5ca293..0a6050ca 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,6 +14,8 @@ android:name="android.permission.BACKUP" tools:ignore="ProtectedPermissions" /> + + + + diff --git a/app/src/main/java/com/stevesoltys/backup/Backup.java b/app/src/main/java/com/stevesoltys/backup/Backup.java index d8989f78..00c1c7c5 100644 --- a/app/src/main/java/com/stevesoltys/backup/Backup.java +++ b/app/src/main/java/com/stevesoltys/backup/Backup.java @@ -2,6 +2,7 @@ package com.stevesoltys.backup; import android.app.Application; import android.content.Intent; + import com.stevesoltys.backup.transport.ConfigurableBackupTransportService; /** @@ -9,6 +10,8 @@ import com.stevesoltys.backup.transport.ConfigurableBackupTransportService; */ public class Backup extends Application { + public static final int JOB_ID_BACKGROUND_BACKUP = 1; + @Override public void onCreate() { super.onCreate(); diff --git a/app/src/main/java/com/stevesoltys/backup/activity/MainActivity.java b/app/src/main/java/com/stevesoltys/backup/activity/MainActivity.java index 64b06014..3d666957 100644 --- a/app/src/main/java/com/stevesoltys/backup/activity/MainActivity.java +++ b/app/src/main/java/com/stevesoltys/backup/activity/MainActivity.java @@ -11,6 +11,7 @@ import com.stevesoltys.backup.R; import static android.view.View.GONE; import static android.view.View.VISIBLE; +import static com.stevesoltys.backup.settings.SettingsManager.areBackupsScheduled; public class MainActivity extends Activity implements View.OnClickListener { @@ -21,6 +22,7 @@ public class MainActivity extends Activity implements View.OnClickListener { public static final int LOAD_DOCUMENT_REQUEST_CODE = 3; private MainActivityController controller; + private Button automaticBackupsButton; private Button changeLocationButton; @Override @@ -33,6 +35,10 @@ public class MainActivity extends Activity implements View.OnClickListener { findViewById(R.id.create_backup_button).setOnClickListener(this); findViewById(R.id.restore_backup_button).setOnClickListener(this); + automaticBackupsButton = findViewById(R.id.automatic_backups_button); + automaticBackupsButton.setOnClickListener(this); + if (areBackupsScheduled(this)) automaticBackupsButton.setVisibility(GONE); + changeLocationButton = findViewById(R.id.change_backup_location_button); changeLocationButton.setOnClickListener(this); } @@ -61,6 +67,12 @@ public class MainActivity extends Activity implements View.OnClickListener { controller.showLoadDocumentActivity(this); break; + case R.id.automatic_backups_button: + if (controller.onAutomaticBackupsButtonClicked(this)) { + automaticBackupsButton.setVisibility(GONE); + } + break; + case R.id.change_backup_location_button: controller.onChangeBackupLocationButtonClicked(this); break; diff --git a/app/src/main/java/com/stevesoltys/backup/activity/MainActivityController.java b/app/src/main/java/com/stevesoltys/backup/activity/MainActivityController.java index c92ba84d..0c7d6c6a 100644 --- a/app/src/main/java/com/stevesoltys/backup/activity/MainActivityController.java +++ b/app/src/main/java/com/stevesoltys/backup/activity/MainActivityController.java @@ -1,7 +1,10 @@ package com.stevesoltys.backup.activity; import android.app.Activity; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; import android.content.ActivityNotFoundException; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Intent; import android.net.Uri; @@ -10,12 +13,14 @@ import android.widget.Toast; import com.stevesoltys.backup.activity.backup.CreateBackupActivity; import com.stevesoltys.backup.activity.restore.RestoreBackupActivity; +import com.stevesoltys.backup.service.backup.BackupJobService; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Locale; +import static android.app.job.JobInfo.NETWORK_TYPE_UNMETERED; import static android.content.Intent.ACTION_OPEN_DOCUMENT; import static android.content.Intent.ACTION_OPEN_DOCUMENT_TREE; import static android.content.Intent.CATEGORY_OPENABLE; @@ -25,16 +30,21 @@ import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION; import static android.provider.DocumentsContract.buildDocumentUriUsingTree; import static android.provider.DocumentsContract.createDocument; import static android.provider.DocumentsContract.getTreeDocumentId; +import static com.stevesoltys.backup.Backup.JOB_ID_BACKGROUND_BACKUP; import static com.stevesoltys.backup.activity.MainActivity.OPEN_DOCUMENT_TREE_BACKUP_REQUEST_CODE; import static com.stevesoltys.backup.activity.MainActivity.OPEN_DOCUMENT_TREE_REQUEST_CODE; import static com.stevesoltys.backup.settings.SettingsManager.getBackupFolderUri; +import static com.stevesoltys.backup.settings.SettingsManager.getBackupPassword; import static com.stevesoltys.backup.settings.SettingsManager.setBackupFolderUri; +import static com.stevesoltys.backup.settings.SettingsManager.setBackupsScheduled; +import static java.util.Objects.requireNonNull; +import static java.util.concurrent.TimeUnit.DAYS; /** * @author Steve Soltys * @author Torsten Grote */ -class MainActivityController { +public class MainActivityController { private static final String TAG = MainActivityController.class.getName(); @@ -90,6 +100,27 @@ class MainActivityController { } } + boolean onAutomaticBackupsButtonClicked(Activity parent) { + if (getBackupFolderUri(parent) == null || getBackupPassword(parent) == null) { + Toast.makeText(parent, "Please make at least one manual backup first.", Toast.LENGTH_SHORT).show(); + return false; + } + final ComponentName serviceName = new ComponentName(parent, BackupJobService.class); + JobInfo job = new JobInfo.Builder(JOB_ID_BACKGROUND_BACKUP, serviceName) + .setRequiredNetworkType(NETWORK_TYPE_UNMETERED) + .setRequiresBatteryNotLow(true) + .setRequiresStorageNotLow(true) // TODO warn the user instead + .setPeriodic(DAYS.toMillis(1)) + .setRequiresCharging(true) + .setPersisted(true) + .build(); + JobScheduler scheduler = requireNonNull(parent.getSystemService(JobScheduler.class)); + scheduler.schedule(job); + setBackupsScheduled(parent); + Toast.makeText(parent, "Backups will run automatically now", Toast.LENGTH_SHORT).show(); + return true; + } + void onChangeBackupLocationButtonClicked(Activity parent) { showChooseFolderActivity(parent, false); } @@ -143,7 +174,7 @@ class MainActivityController { parent.startActivity(intent); } - private Uri createBackupFile(ContentResolver contentResolver, Uri folderUri) throws IOException { + public static Uri createBackupFile(ContentResolver contentResolver, Uri folderUri) throws IOException { Uri documentUri = buildDocumentUriUsingTree(folderUri, getTreeDocumentId(folderUri)); try { Uri fileUri = createDocument(contentResolver, documentUri, DOCUMENT_MIME_TYPE, getBackupFileName()); @@ -156,7 +187,7 @@ class MainActivityController { } } - private String getBackupFileName() { + private static String getBackupFileName() { SimpleDateFormat dateFormat = new SimpleDateFormat(DOCUMENT_SUFFIX, Locale.US); String date = dateFormat.format(new Date()); return "backup-" + date; diff --git a/app/src/main/java/com/stevesoltys/backup/service/backup/BackupJobService.java b/app/src/main/java/com/stevesoltys/backup/service/backup/BackupJobService.java new file mode 100644 index 00000000..ab74da64 --- /dev/null +++ b/app/src/main/java/com/stevesoltys/backup/service/backup/BackupJobService.java @@ -0,0 +1,89 @@ +package com.stevesoltys.backup.service.backup; + +import android.app.backup.BackupManager; +import android.app.backup.IBackupManager; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.net.Uri; +import android.os.RemoteException; +import android.util.Log; + +import com.google.android.collect.Sets; +import com.stevesoltys.backup.service.PackageService; +import com.stevesoltys.backup.service.TransportService; +import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConfiguration; +import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConfigurationBuilder; + +import java.io.IOException; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.Set; + +import static android.app.backup.BackupManager.FLAG_NON_INCREMENTAL_BACKUP; +import static android.os.ServiceManager.getService; +import static com.stevesoltys.backup.activity.MainActivityController.createBackupFile; +import static com.stevesoltys.backup.settings.SettingsManager.getBackupFolderUri; +import static com.stevesoltys.backup.settings.SettingsManager.getBackupPassword; + +public class BackupJobService extends JobService { + + private final static String TAG = BackupJobService.class.getName(); + + private static final Set IGNORED_PACKAGES = Sets.newArraySet( + "com.android.providers.downloads.ui", "com.android.providers.downloads", "com.android.providers.media", + "com.android.providers.calendar", "com.android.providers.contacts", "com.stevesoltys.backup" + ); + + private final IBackupManager backupManager; + private final TransportService transportService = new TransportService(); + + public BackupJobService() { + backupManager = IBackupManager.Stub.asInterface(getService("backup")); + } + + @Override + public boolean onStartJob(JobParameters params) { + Log.i(TAG, "Triggering full backup"); + try { + LinkedList packages = new LinkedList<>(new PackageService().getEligiblePackages()); + packages.removeAll(IGNORED_PACKAGES); + Uri fileUri = createBackupFile(getContentResolver(), getBackupFolderUri(this)); + ContentProviderBackupConfiguration backupConfiguration = new ContentProviderBackupConfigurationBuilder() + .setContext(this) + .setPackages(new HashSet<>(packages)) + .setOutputUri(fileUri) + .setPassword(getBackupPassword(this)) + .build(); + transportService.initializeBackupTransport(backupConfiguration); + + // TODO use an observer to know when backups fail + String[] packageArray = packages.toArray(new String[packages.size()]); + int result = backupManager.requestBackup(packageArray, null, null, FLAG_NON_INCREMENTAL_BACKUP); + if (result == BackupManager.SUCCESS) { + Log.i(TAG, "Backup succeeded "); + } else { + Log.e(TAG, "Backup failed: " + result); + } + + // TODO show notification on backup error + } catch (IOException e) { + Log.e(TAG, "Error creating backup file: ", e); + } catch (RemoteException e) { + Log.e(TAG, "Error during backup: ", e); + } finally { + jobFinished(params, false); + } + return true; + } + + @Override + public boolean onStopJob(JobParameters params) { + try { + backupManager.cancelBackups(); + } catch (RemoteException e) { + Log.e(TAG, "Error cancelling backup: ", e); + } + return true; + } + +} diff --git a/app/src/main/java/com/stevesoltys/backup/settings/SettingsManager.java b/app/src/main/java/com/stevesoltys/backup/settings/SettingsManager.java index d493765d..fa4b41ad 100644 --- a/app/src/main/java/com/stevesoltys/backup/settings/SettingsManager.java +++ b/app/src/main/java/com/stevesoltys/backup/settings/SettingsManager.java @@ -10,6 +10,7 @@ public class SettingsManager { private static final String PREF_KEY_BACKUP_URI = "backupUri"; private static final String PREF_KEY_BACKUP_PASSWORD = "backupLegacyPassword"; + private static final String PREF_KEY_BACKUPS_SCHEDULED = "backupsScheduled"; public static void setBackupFolderUri(Context context, Uri uri) { getDefaultSharedPreferences(context) @@ -41,4 +42,16 @@ public class SettingsManager { return getDefaultSharedPreferences(context).getString(PREF_KEY_BACKUP_PASSWORD, null); } + public static void setBackupsScheduled(Context context) { + getDefaultSharedPreferences(context) + .edit() + .putBoolean(PREF_KEY_BACKUPS_SCHEDULED, true) + .apply(); + } + + @Nullable + public static Boolean areBackupsScheduled(Context context) { + return getDefaultSharedPreferences(context).getBoolean(PREF_KEY_BACKUPS_SCHEDULED, false); + } + } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 705c21a7..aaf4c790 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -25,6 +25,15 @@ android:layout_marginRight="@dimen/button_horizontal_margin" android:text="@string/restore_backup_button" /> +