diff --git a/README.md b/README.md
index fca0dc89..4129ce8b 100644
--- a/README.md
+++ b/README.md
@@ -14,10 +14,15 @@ AOSP.
## What makes this different?
This application is compiled with the operating system and does not require a rooted device for use. It uses the same
-internal APIs as `adb backup` and only requires one permission: `android.permission.BACKUP`.
+internal APIs as `adb backup` and only requires the permission `android.permission.BACKUP` for this.
## Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/stevesoltys/backup.
+## Permissions
+
+* `android.permission.BACKUP` to be allowed to back up apps
+* `android.permission.RECEIVE_BOOT_COMPLETED` to schedule automatic backups after boot
+
## License
This application is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
diff --git a/app/build.gradle b/app/build.gradle
index 3e495edf..5af8b0f1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -9,7 +9,7 @@ android {
defaultConfig {
minSdkVersion 26
- targetSdkVersion 26
+ targetSdkVersion 28
}
buildTypes {
@@ -50,6 +50,7 @@ gradle.projectsEvaluated {
}
}
+// http://www.31mins.com/android-studio-build-system-application/
preBuild.doLast {
def imlFile = file(project.name + ".iml")
diff --git a/app/src/main/Android.mk b/app/src/main/Android.mk
index b83908e9..28df4ae6 100644
--- a/app/src/main/Android.mk
+++ b/app/src/main/Android.mk
@@ -16,19 +16,20 @@ LOCAL_MODULE_PATH := $(TARGET_OUT_ETC)/sysconfig
LOCAL_SRC_FILES := $(LOCAL_MODULE)
include $(BUILD_PREBUILT)
+include $(CLEAR_VARS)
+LOCAL_MODULE_TAGS := optional
+LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
+ commons-io:../../libs/commons-io-2.6.jar
+include $(BUILD_MULTI_PREBUILT)
+
include $(CLEAR_VARS)
LOCAL_PACKAGE_NAME := Backup
LOCAL_MODULE_TAGS := optional
LOCAL_REQUIRED_MODULES := permissions_com.stevesoltys.backup.xml whitelist_com.stevesoltys.backup.xml
LOCAL_PRIVILEGED_MODULE := true
LOCAL_PRIVATE_PLATFORM_APIS := true
+LOCAL_CERTIFICATE := platform
LOCAL_STATIC_JAVA_LIBRARIES := commons-io
LOCAL_SRC_FILES := $(call all-java-files-under, java)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
include $(BUILD_PACKAGE)
-
-include $(CLEAR_VARS)
-LOCAL_MODULE_TAGS := optional
-LOCAL_PREBUILT_STATIC_JAVA_LIBRARIES := \
- commons-io:../../libs/commons-io-2.6.jar
-include $(BUILD_MULTI_PREBUILT)
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index eb5ca293..7e5e1297 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,13 +7,15 @@
+
+
+
+
diff --git a/app/src/main/java/com/stevesoltys/backup/Backup.java b/app/src/main/java/com/stevesoltys/backup/Backup.java
index d8989f78..b4b0474c 100644
--- a/app/src/main/java/com/stevesoltys/backup/Backup.java
+++ b/app/src/main/java/com/stevesoltys/backup/Backup.java
@@ -1,18 +1,12 @@
package com.stevesoltys.backup;
import android.app.Application;
-import android.content.Intent;
-import com.stevesoltys.backup.transport.ConfigurableBackupTransportService;
/**
* @author Steve Soltys
*/
public class Backup extends Application {
- @Override
- public void onCreate() {
- super.onCreate();
+ public static final int JOB_ID_BACKGROUND_BACKUP = 1;
- startForegroundService(new Intent(this, ConfigurableBackupTransportService.class));
- }
}
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..5f8cea31 100644
--- a/app/src/main/java/com/stevesoltys/backup/activity/MainActivityController.java
+++ b/app/src/main/java/com/stevesoltys/backup/activity/MainActivityController.java
@@ -1,59 +1,52 @@
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.ContentResolver;
+import android.content.ComponentName;
import android.content.Intent;
import android.net.Uri;
-import android.util.Log;
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 com.stevesoltys.backup.transport.ConfigurableBackupTransportService;
-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;
import static android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
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();
-
- private static final String DOCUMENT_MIME_TYPE = "application/octet-stream";
- private static final String DOCUMENT_SUFFIX = "yyyy-MM-dd_HH_mm_ss";
+ public static final String DOCUMENT_MIME_TYPE = "application/octet-stream";
void onBackupButtonClicked(Activity parent) {
Uri folderUri = getBackupFolderUri(parent);
if (folderUri == null) {
showChooseFolderActivity(parent, true);
} else {
- try {
- Uri fileUri = createBackupFile(parent.getContentResolver(), folderUri);
- showCreateBackupActivity(parent, fileUri);
-
- } catch (IOException e) {
- Log.w(TAG, "Error creating backup file: ", e);
- showChooseFolderActivity(parent, true);
- }
+ // ensure that backup service is started
+ parent.startService(new Intent(parent, ConfigurableBackupTransportService.class));
+ showCreateBackupActivity(parent);
}
}
@@ -90,6 +83,34 @@ 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;
+ }
+
+ // schedule backups
+ 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);
+
+ // remember that backups were scheduled
+ setBackupsScheduled(parent);
+
+ // show Toast informing the user
+ Toast.makeText(parent, "Backups will run automatically now", Toast.LENGTH_SHORT).show();
+
+ return true;
+ }
+
void onChangeBackupLocationButtonClicked(Activity parent) {
showChooseFolderActivity(parent, false);
}
@@ -101,34 +122,22 @@ class MainActivityController {
}
Uri folderUri = result.getData();
- ContentResolver contentResolver = parent.getContentResolver();
// persist permission to access backup folder across reboots
int takeFlags = result.getFlags() &
(FLAG_GRANT_READ_URI_PERMISSION | FLAG_GRANT_WRITE_URI_PERMISSION);
- contentResolver.takePersistableUriPermission(folderUri, takeFlags);
+ parent.getContentResolver().takePersistableUriPermission(folderUri, takeFlags);
// store backup folder location in settings
setBackupFolderUri(parent, folderUri);
if (!continueToBackup) return;
- try {
- // create a new backup file in folder
- Uri fileUri = createBackupFile(contentResolver, folderUri);
-
- showCreateBackupActivity(parent, fileUri);
-
- } catch (IOException e) {
- Log.e(TAG, "Error creating backup file: ", e);
- // TODO show better error message once more infrastructure is in place
- Toast.makeText(parent, "Error creating backup file", Toast.LENGTH_SHORT).show();
- }
+ showCreateBackupActivity(parent);
}
- private void showCreateBackupActivity(Activity parent, Uri fileUri) {
+ private void showCreateBackupActivity(Activity parent) {
Intent intent = new Intent(parent, CreateBackupActivity.class);
- intent.setData(fileUri);
parent.startActivity(intent);
}
@@ -143,23 +152,4 @@ class MainActivityController {
parent.startActivity(intent);
}
- private Uri createBackupFile(ContentResolver contentResolver, Uri folderUri) throws IOException {
- Uri documentUri = buildDocumentUriUsingTree(folderUri, getTreeDocumentId(folderUri));
- try {
- Uri fileUri = createDocument(contentResolver, documentUri, DOCUMENT_MIME_TYPE, getBackupFileName());
- if (fileUri == null) throw new IOException();
- return fileUri;
-
- } catch (SecurityException e) {
- // happens when folder was deleted and thus Uri permission don't exist anymore
- throw new IOException(e);
- }
- }
-
- private 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/activity/backup/CreateBackupActivity.java b/app/src/main/java/com/stevesoltys/backup/activity/backup/CreateBackupActivity.java
index 7ac6e255..cdfc0549 100644
--- a/app/src/main/java/com/stevesoltys/backup/activity/backup/CreateBackupActivity.java
+++ b/app/src/main/java/com/stevesoltys/backup/activity/backup/CreateBackupActivity.java
@@ -1,6 +1,5 @@
package com.stevesoltys.backup.activity.backup;
-import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
@@ -16,14 +15,12 @@ public class CreateBackupActivity extends PackageListActivity implements View.On
private CreateBackupActivityController controller;
- private Uri contentUri;
-
@Override
public void onClick(View view) {
int viewId = view.getId();
if (viewId == R.id.create_confirm_button) {
- controller.onCreateBackupButtonClicked(selectedPackageList, contentUri, this);
+ controller.onCreateBackupButtonClicked(selectedPackageList, this);
}
}
@@ -36,7 +33,6 @@ public class CreateBackupActivity extends PackageListActivity implements View.On
packageListView = findViewById(R.id.create_package_list);
selectedPackageList = new HashSet<>();
- contentUri = getIntent().getData();
controller = new CreateBackupActivityController();
AsyncTask.execute(() -> controller.populatePackageList(packageListView, CreateBackupActivity.this));
diff --git a/app/src/main/java/com/stevesoltys/backup/activity/backup/CreateBackupActivityController.java b/app/src/main/java/com/stevesoltys/backup/activity/backup/CreateBackupActivityController.java
index 67d2c156..74cbf4bb 100644
--- a/app/src/main/java/com/stevesoltys/backup/activity/backup/CreateBackupActivityController.java
+++ b/app/src/main/java/com/stevesoltys/backup/activity/backup/CreateBackupActivityController.java
@@ -2,7 +2,6 @@ package com.stevesoltys.backup.activity.backup;
import android.app.Activity;
import android.app.AlertDialog;
-import android.net.Uri;
import android.os.RemoteException;
import android.text.InputType;
import android.util.Log;
@@ -75,16 +74,16 @@ class CreateBackupActivityController {
});
}
- void onCreateBackupButtonClicked(Set selectedPackages, Uri contentUri, Activity parent) {
+ void onCreateBackupButtonClicked(Set selectedPackages, Activity parent) {
String password = SettingsManager.getBackupPassword(parent);
if (password == null) {
- showEnterPasswordAlert(selectedPackages, contentUri, parent);
+ showEnterPasswordAlert(selectedPackages, parent);
} else {
- backupService.backupPackageData(selectedPackages, contentUri, parent, password);
+ backupService.backupPackageData(selectedPackages, parent);
}
}
- private void showEnterPasswordAlert(Set selectedPackages, Uri contentUri, Activity parent) {
+ private void showEnterPasswordAlert(Set selectedPackages, Activity parent) {
final EditText passwordTextView = new EditText(parent);
passwordTextView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
@@ -97,9 +96,9 @@ class CreateBackupActivityController {
if (passwordTextView.getText().length() == 0) {
Toast.makeText(parent, "Please enter a password", Toast.LENGTH_SHORT).show();
dialog.cancel();
- showEnterPasswordAlert(selectedPackages, contentUri, parent);
+ showEnterPasswordAlert(selectedPackages, parent);
} else {
- showConfirmPasswordAlert(selectedPackages, contentUri, parent,
+ showConfirmPasswordAlert(selectedPackages, parent,
passwordTextView.getText().toString());
}
})
@@ -108,7 +107,7 @@ class CreateBackupActivityController {
.show();
}
- private void showConfirmPasswordAlert(Set selectedPackages, Uri contentUri, Activity parent,
+ private void showConfirmPasswordAlert(Set selectedPackages, Activity parent,
String originalPassword) {
final EditText passwordTextView = new EditText(parent);
passwordTextView.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
@@ -122,7 +121,7 @@ class CreateBackupActivityController {
if (originalPassword.equals(password)) {
SettingsManager.setBackupPassword(parent, password);
- backupService.backupPackageData(selectedPackages, contentUri, parent, password);
+ backupService.backupPackageData(selectedPackages, parent);
} else {
new AlertDialog.Builder(parent)
diff --git a/app/src/main/java/com/stevesoltys/backup/activity/restore/RestoreBackupActivityController.java b/app/src/main/java/com/stevesoltys/backup/activity/restore/RestoreBackupActivityController.java
index 6370d7e7..916a57dd 100644
--- a/app/src/main/java/com/stevesoltys/backup/activity/restore/RestoreBackupActivityController.java
+++ b/app/src/main/java/com/stevesoltys/backup/activity/restore/RestoreBackupActivityController.java
@@ -7,12 +7,15 @@ import android.os.ParcelFileDescriptor;
import android.text.InputType;
import android.util.Log;
import android.view.View;
-import android.widget.*;
+import android.widget.ArrayAdapter;
+import android.widget.EditText;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
import com.stevesoltys.backup.R;
import com.stevesoltys.backup.activity.PopupWindowUtil;
import com.stevesoltys.backup.service.restore.RestoreService;
-import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConfigurationBuilder;
-import libcore.io.IoUtils;
import java.io.File;
import java.io.FileInputStream;
@@ -24,6 +27,10 @@ import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
+import libcore.io.IoUtils;
+
+import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_FULL_BACKUP_DIRECTORY;
+
/**
* @author Steve Soltys
*/
@@ -76,7 +83,7 @@ class RestoreBackupActivityController {
while ((zipEntry = inputStream.getNextEntry()) != null) {
String zipEntryPath = zipEntry.getName();
- if (zipEntryPath.startsWith(ContentProviderBackupConfigurationBuilder.DEFAULT_FULL_BACKUP_DIRECTORY)) {
+ if (zipEntryPath.startsWith(DEFAULT_FULL_BACKUP_DIRECTORY)) {
String fileName = new File(zipEntryPath).getName();
results.add(fileName);
}
diff --git a/app/src/main/java/com/stevesoltys/backup/service/TransportService.java b/app/src/main/java/com/stevesoltys/backup/service/TransportService.java
index 934005b2..9dcbca4a 100644
--- a/app/src/main/java/com/stevesoltys/backup/service/TransportService.java
+++ b/app/src/main/java/com/stevesoltys/backup/service/TransportService.java
@@ -3,17 +3,11 @@ package com.stevesoltys.backup.service;
import android.app.backup.IBackupManager;
import android.os.RemoteException;
import android.os.ServiceManager;
+
import com.stevesoltys.backup.session.backup.BackupSession;
import com.stevesoltys.backup.session.backup.BackupSessionObserver;
import com.stevesoltys.backup.session.restore.RestoreSession;
import com.stevesoltys.backup.session.restore.RestoreSessionObserver;
-import com.stevesoltys.backup.transport.ConfigurableBackupTransport;
-import com.stevesoltys.backup.transport.ConfigurableBackupTransportService;
-import com.stevesoltys.backup.transport.component.BackupComponent;
-import com.stevesoltys.backup.transport.component.RestoreComponent;
-import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupComponent;
-import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConfiguration;
-import com.stevesoltys.backup.transport.component.provider.ContentProviderRestoreComponent;
import java.util.Set;
@@ -30,19 +24,6 @@ public class TransportService {
backupManager = IBackupManager.Stub.asInterface(ServiceManager.getService("backup"));
}
- public boolean initializeBackupTransport(ContentProviderBackupConfiguration configuration) {
- ConfigurableBackupTransport backupTransport = ConfigurableBackupTransportService.getBackupTransport();
-
- if (backupTransport.isActive()) {
- return false;
- }
-
- BackupComponent backupComponent = new ContentProviderBackupComponent(configuration);
- RestoreComponent restoreComponent = new ContentProviderRestoreComponent(configuration);
- backupTransport.initialize(backupComponent, restoreComponent);
- return true;
- }
-
public BackupSession backup(BackupSessionObserver observer, Set packages) throws RemoteException {
if (!BACKUP_TRANSPORT.equals(backupManager.getCurrentTransport())) {
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..0c62be8f
--- /dev/null
+++ b/app/src/main/java/com/stevesoltys/backup/service/backup/BackupJobService.java
@@ -0,0 +1,76 @@
+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.content.Intent;
+import android.os.RemoteException;
+import android.util.Log;
+
+import com.google.android.collect.Sets;
+import com.stevesoltys.backup.service.PackageService;
+import com.stevesoltys.backup.transport.ConfigurableBackupTransport;
+import com.stevesoltys.backup.transport.ConfigurableBackupTransportService;
+
+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.transport.ConfigurableBackupTransportService.getBackupTransport;
+
+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 PackageService packageService = new PackageService();
+
+ public BackupJobService() {
+ backupManager = IBackupManager.Stub.asInterface(getService("backup"));
+ }
+
+ @Override
+ public boolean onStartJob(JobParameters params) {
+ Log.i(TAG, "Triggering full backup");
+ startService(new Intent(this, ConfigurableBackupTransportService.class));
+ try {
+ LinkedList packages = new LinkedList<>(packageService.getEligiblePackages());
+ packages.removeAll(IGNORED_PACKAGES);
+ // TODO use an observer to know when backups fail
+ String[] packageArray = packages.toArray(new String[packages.size()]);
+ ConfigurableBackupTransport backupTransport = getBackupTransport(getApplication());
+ backupTransport.prepareBackup(packageArray.length);
+ 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 (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/service/backup/BackupObserver.java b/app/src/main/java/com/stevesoltys/backup/service/backup/BackupObserver.java
index abd2d075..8bcbfefa 100644
--- a/app/src/main/java/com/stevesoltys/backup/service/backup/BackupObserver.java
+++ b/app/src/main/java/com/stevesoltys/backup/service/backup/BackupObserver.java
@@ -6,14 +6,11 @@ import android.widget.PopupWindow;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
+
import com.stevesoltys.backup.R;
import com.stevesoltys.backup.session.backup.BackupResult;
import com.stevesoltys.backup.session.backup.BackupSession;
import com.stevesoltys.backup.session.backup.BackupSessionObserver;
-import com.stevesoltys.backup.transport.ConfigurableBackupTransport;
-import com.stevesoltys.backup.transport.ConfigurableBackupTransportService;
-
-import java.net.URI;
/**
* @author Steve Soltys
@@ -24,12 +21,9 @@ class BackupObserver implements BackupSessionObserver {
private final PopupWindow popupWindow;
- private final URI contentUri;
-
- BackupObserver(Activity context, PopupWindow popupWindow, URI contentUri) {
+ BackupObserver(Activity context, PopupWindow popupWindow) {
this.context = context;
this.popupWindow = popupWindow;
- this.contentUri = contentUri;
}
@Override
@@ -65,14 +59,6 @@ class BackupObserver implements BackupSessionObserver {
@Override
public void backupSessionCompleted(BackupSession backupSession, BackupResult backupResult) {
- ConfigurableBackupTransport backupTransport = ConfigurableBackupTransportService.getBackupTransport();
-
- if (!backupTransport.isActive()) {
- return;
- }
-
- backupTransport.reset();
-
context.runOnUiThread(() -> {
if (backupResult == BackupResult.SUCCESS) {
Toast.makeText(context, R.string.backup_success, Toast.LENGTH_LONG).show();
diff --git a/app/src/main/java/com/stevesoltys/backup/service/backup/BackupService.java b/app/src/main/java/com/stevesoltys/backup/service/backup/BackupService.java
index 3795170c..84be267f 100644
--- a/app/src/main/java/com/stevesoltys/backup/service/backup/BackupService.java
+++ b/app/src/main/java/com/stevesoltys/backup/service/backup/BackupService.java
@@ -1,23 +1,22 @@
package com.stevesoltys.backup.service.backup;
import android.app.Activity;
-import android.net.Uri;
import android.util.Log;
import android.view.View;
import android.widget.PopupWindow;
import android.widget.TextView;
-import android.widget.Toast;
+
import com.stevesoltys.backup.R;
import com.stevesoltys.backup.activity.PopupWindowUtil;
import com.stevesoltys.backup.activity.backup.BackupPopupWindowListener;
import com.stevesoltys.backup.service.TransportService;
import com.stevesoltys.backup.session.backup.BackupSession;
-import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConfiguration;
-import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConfigurationBuilder;
+import com.stevesoltys.backup.transport.ConfigurableBackupTransport;
-import java.net.URI;
import java.util.Set;
+import static com.stevesoltys.backup.transport.ConfigurableBackupTransportService.getBackupTransport;
+
/**
* @author Steve Soltys
*/
@@ -27,27 +26,14 @@ public class BackupService {
private final TransportService transportService = new TransportService();
- public void backupPackageData(Set selectedPackages, Uri contentUri, Activity parent,
- String selectedPassword) {
+ public void backupPackageData(Set selectedPackages, Activity parent) {
try {
selectedPackages.add("@pm@");
- ContentProviderBackupConfiguration backupConfiguration = new ContentProviderBackupConfigurationBuilder()
- .setContext(parent)
- .setOutputUri(contentUri)
- .setPackages(selectedPackages)
- .setPassword(selectedPassword)
- .build();
-
- boolean success = transportService.initializeBackupTransport(backupConfiguration);
-
- if (!success) {
- Toast.makeText(parent, R.string.backup_in_progress, Toast.LENGTH_LONG).show();
- return;
- }
-
PopupWindow popupWindow = PopupWindowUtil.showLoadingPopupWindow(parent);
- BackupObserver backupObserver = new BackupObserver(parent, popupWindow, new URI(contentUri.getPath()));
+ BackupObserver backupObserver = new BackupObserver(parent, popupWindow);
+ ConfigurableBackupTransport backupTransport = getBackupTransport(parent.getApplication());
+ backupTransport.prepareBackup(selectedPackages.size());
BackupSession backupSession = transportService.backup(backupObserver, selectedPackages);
View popupWindowButton = popupWindow.getContentView().findViewById(R.id.popup_cancel_button);
diff --git a/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreObserver.java b/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreObserver.java
index 69caa690..d7132af5 100644
--- a/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreObserver.java
+++ b/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreObserver.java
@@ -5,11 +5,10 @@ import android.widget.PopupWindow;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
+
import com.stevesoltys.backup.R;
import com.stevesoltys.backup.session.restore.RestoreResult;
import com.stevesoltys.backup.session.restore.RestoreSessionObserver;
-import com.stevesoltys.backup.transport.ConfigurableBackupTransport;
-import com.stevesoltys.backup.transport.ConfigurableBackupTransportService;
/**
* @author Steve Soltys
@@ -56,14 +55,6 @@ class RestoreObserver implements RestoreSessionObserver {
@Override
public void restoreSessionCompleted(RestoreResult restoreResult) {
- ConfigurableBackupTransport backupTransport = ConfigurableBackupTransportService.getBackupTransport();
-
- if (!backupTransport.isActive()) {
- return;
- }
-
- backupTransport.reset();
-
context.runOnUiThread(() -> {
if (restoreResult == RestoreResult.SUCCESS) {
Toast.makeText(context, R.string.restore_success, Toast.LENGTH_LONG).show();
diff --git a/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreService.java b/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreService.java
index 06e15c0b..6441397a 100644
--- a/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreService.java
+++ b/app/src/main/java/com/stevesoltys/backup/service/restore/RestoreService.java
@@ -6,17 +6,18 @@ import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.widget.PopupWindow;
-import android.widget.Toast;
+
import com.stevesoltys.backup.R;
import com.stevesoltys.backup.activity.PopupWindowUtil;
import com.stevesoltys.backup.activity.restore.RestorePopupWindowListener;
import com.stevesoltys.backup.service.TransportService;
import com.stevesoltys.backup.session.restore.RestoreSession;
-import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConfiguration;
-import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConfigurationBuilder;
+import com.stevesoltys.backup.transport.ConfigurableBackupTransport;
import java.util.Set;
+import static com.stevesoltys.backup.transport.ConfigurableBackupTransportService.getBackupTransport;
+
/**
* @author Steve Soltys
*/
@@ -27,21 +28,9 @@ public class RestoreService {
private final TransportService transportService = new TransportService();
public void restorePackages(Set selectedPackages, Uri contentUri, Activity parent, String password) {
+ ConfigurableBackupTransport backupTransport = getBackupTransport(parent.getApplication());
+ backupTransport.prepareRestore(password, contentUri);
try {
- ContentProviderBackupConfiguration backupConfiguration = new ContentProviderBackupConfigurationBuilder().
- setContext(parent)
- .setOutputUri(contentUri)
- .setPackages(selectedPackages)
- .setPassword(password)
- .build();
-
- boolean success = transportService.initializeBackupTransport(backupConfiguration);
-
- if (!success) {
- Toast.makeText(parent, R.string.restore_in_progress, Toast.LENGTH_LONG).show();
- return;
- }
-
PopupWindow popupWindow = PopupWindowUtil.showLoadingPopupWindow(parent);
RestoreObserver restoreObserver = new RestoreObserver(parent, popupWindow, selectedPackages.size());
RestoreSession restoreSession = transportService.restore(restoreObserver, selectedPackages);
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/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.java b/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.java
index 6cddb1a8..cc99aff9 100644
--- a/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.java
+++ b/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransport.java
@@ -3,14 +3,16 @@ package com.stevesoltys.backup.transport;
import android.app.backup.BackupTransport;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
+import android.content.Context;
import android.content.pm.PackageInfo;
+import android.net.Uri;
import android.os.ParcelFileDescriptor;
+import android.util.Log;
-import com.android.internal.util.Preconditions;
import com.stevesoltys.backup.transport.component.BackupComponent;
import com.stevesoltys.backup.transport.component.RestoreComponent;
-import com.stevesoltys.backup.transport.component.stub.StubBackupComponent;
-import com.stevesoltys.backup.transport.component.stub.StubRestoreComponent;
+import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupComponent;
+import com.stevesoltys.backup.transport.component.provider.ContentProviderRestoreComponent;
/**
* @author Steve Soltys
@@ -20,31 +22,23 @@ public class ConfigurableBackupTransport extends BackupTransport {
private static final String TRANSPORT_DIRECTORY_NAME =
"com.stevesoltys.backup.transport.ConfigurableBackupTransport";
- private BackupComponent backupComponent;
+ private static final String TAG = TRANSPORT_DIRECTORY_NAME;
- private RestoreComponent restoreComponent;
+ private final BackupComponent backupComponent;
- ConfigurableBackupTransport() {
- backupComponent = new StubBackupComponent();
- restoreComponent = new StubRestoreComponent();
+ private final RestoreComponent restoreComponent;
+
+ ConfigurableBackupTransport(Context context) {
+ backupComponent = new ContentProviderBackupComponent(context);
+ restoreComponent = new ContentProviderRestoreComponent(context);
}
- public void initialize(BackupComponent backupComponent, RestoreComponent restoreComponent) {
- Preconditions.checkNotNull(backupComponent);
- Preconditions.checkNotNull(restoreComponent);
- Preconditions.checkState(!isActive());
-
- this.restoreComponent = restoreComponent;
- this.backupComponent = backupComponent;
+ public void prepareBackup(int numberOfPackages) {
+ backupComponent.prepareBackup(numberOfPackages);
}
- public void reset() {
- backupComponent = new StubBackupComponent();
- restoreComponent = new StubRestoreComponent();
- }
-
- public boolean isActive() {
- return !(backupComponent instanceof StubBackupComponent || restoreComponent instanceof StubRestoreComponent);
+ public void prepareRestore(String password, Uri fileUri) {
+ restoreComponent.prepareRestore(password, fileUri);
}
@Override
@@ -58,6 +52,24 @@ public class ConfigurableBackupTransport extends BackupTransport {
return this.getClass().getName();
}
+ @Override
+ public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) {
+ // TODO re-include key-value (incremental)
+ // affected apps:
+ // * com.android.documentsui
+ // * android
+ // * com.android.nfc
+ // * com.android.calendar
+ // * com.android.providers.settings
+ // * com.android.cellbroadcastreceiver
+ // * com.android.calllogbackup
+ // * com.android.providers.blockednumber
+ // * com.android.providers.userdictionary
+ if (isFullBackup) return true;
+ Log.i(TAG, "Excluding key-value backup of " + targetPackage.packageName);
+ return false;
+ }
+
@Override
public long requestBackupTime() {
return backupComponent.requestBackupTime();
@@ -78,6 +90,12 @@ public class ConfigurableBackupTransport extends BackupTransport {
return backupComponent.currentDestinationString();
}
+ @Override
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) {
+ // TODO handle flags
+ return performBackup(packageInfo, inFd);
+ }
+
@Override
public int performBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor) {
return backupComponent.performIncrementalBackup(targetPackage, fileDescriptor);
@@ -88,6 +106,12 @@ public class ConfigurableBackupTransport extends BackupTransport {
return backupComponent.checkFullBackupSize(size);
}
+ @Override
+ public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket, int flags) {
+ // TODO handle flags
+ return performFullBackup(targetPackage, socket);
+ }
+
@Override
public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor) {
return backupComponent.performFullBackup(targetPackage, fileDescriptor);
diff --git a/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.java b/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.java
index cbbd89ad..da896889 100644
--- a/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.java
+++ b/app/src/main/java/com/stevesoltys/backup/transport/ConfigurableBackupTransportService.java
@@ -1,23 +1,24 @@
package com.stevesoltys.backup.transport;
-import android.app.Notification;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.os.IBinder;
+import android.util.Log;
/**
* @author Steve Soltys
*/
public class ConfigurableBackupTransportService extends Service {
- private static final int FOREGROUND_ID = 43594;
+ private static final String TAG = ConfigurableBackupTransportService.class.getName();
private static ConfigurableBackupTransport backupTransport = null;
- public static ConfigurableBackupTransport getBackupTransport() {
+ public static ConfigurableBackupTransport getBackupTransport(Context context) {
if (backupTransport == null) {
- backupTransport = new ConfigurableBackupTransport();
+ backupTransport = new ConfigurableBackupTransport(context);
}
return backupTransport;
@@ -26,11 +27,17 @@ public class ConfigurableBackupTransportService extends Service {
@Override
public void onCreate() {
super.onCreate();
- startForeground(FOREGROUND_ID, new Notification.Builder(this).build());
+ Log.d(TAG, "Service created.");
}
@Override
public IBinder onBind(Intent intent) {
- return getBackupTransport().getBinder();
+ return getBackupTransport(getApplicationContext()).getBinder();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ Log.d(TAG, "Service destroyed.");
}
}
diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/BackupComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/BackupComponent.java
index 1fafbe63..f3c7cd42 100644
--- a/app/src/main/java/com/stevesoltys/backup/transport/component/BackupComponent.java
+++ b/app/src/main/java/com/stevesoltys/backup/transport/component/BackupComponent.java
@@ -8,6 +8,8 @@ import android.os.ParcelFileDescriptor;
*/
public interface BackupComponent {
+ void prepareBackup(int numberOfPackages);
+
String currentDestinationString();
String dataManagementLabel();
diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/RestoreComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/RestoreComponent.java
index 2873cf1b..08f866a0 100644
--- a/app/src/main/java/com/stevesoltys/backup/transport/component/RestoreComponent.java
+++ b/app/src/main/java/com/stevesoltys/backup/transport/component/RestoreComponent.java
@@ -3,6 +3,7 @@ package com.stevesoltys.backup.transport.component;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.content.pm.PackageInfo;
+import android.net.Uri;
import android.os.ParcelFileDescriptor;
/**
@@ -10,6 +11,8 @@ import android.os.ParcelFileDescriptor;
*/
public interface RestoreComponent {
+ void prepareRestore(String password, Uri fileUri);
+
int startRestore(long token, PackageInfo[] packages);
RestoreDescription nextRestorePackage();
diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java
index 1100c59e..2f6a677c 100644
--- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java
+++ b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupComponent.java
@@ -1,28 +1,47 @@
package com.stevesoltys.backup.transport.component.provider;
import android.app.backup.BackupDataInput;
-import android.content.ContentResolver;
+import android.content.Context;
import android.content.pm.PackageInfo;
+import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Base64;
import android.util.Log;
+
import com.stevesoltys.backup.security.CipherUtil;
import com.stevesoltys.backup.security.KeyGenerator;
+import com.stevesoltys.backup.settings.SettingsManager;
import com.stevesoltys.backup.transport.component.BackupComponent;
-import libcore.io.IoUtils;
+
import org.apache.commons.io.IOUtils;
-import javax.crypto.Cipher;
-import javax.crypto.SecretKey;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.text.SimpleDateFormat;
import java.util.Arrays;
+import java.util.Date;
+import java.util.Locale;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
-import static android.app.backup.BackupTransport.*;
+import javax.crypto.Cipher;
+import javax.crypto.SecretKey;
+
+import libcore.io.IoUtils;
+
+import static android.app.backup.BackupTransport.TRANSPORT_ERROR;
+import static android.app.backup.BackupTransport.TRANSPORT_OK;
+import static android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED;
+import static android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED;
+import static android.provider.DocumentsContract.buildDocumentUriUsingTree;
+import static android.provider.DocumentsContract.createDocument;
+import static android.provider.DocumentsContract.getTreeDocumentId;
+import static com.stevesoltys.backup.activity.MainActivityController.DOCUMENT_MIME_TYPE;
+import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_BACKUP_QUOTA;
+import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_FULL_BACKUP_DIRECTORY;
+import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_INCREMENTAL_BACKUP_DIRECTORY;
import static java.util.Objects.requireNonNull;
/**
@@ -32,18 +51,22 @@ public class ContentProviderBackupComponent implements BackupComponent {
private static final String TAG = ContentProviderBackupComponent.class.getName();
+ private static final String DOCUMENT_SUFFIX = "yyyy-MM-dd_HH_mm_ss";
+
private static final String DESTINATION_DESCRIPTION = "Backing up to zip file";
private static final String TRANSPORT_DATA_MANAGEMENT_LABEL = "";
private static final int INITIAL_BUFFER_SIZE = 512;
- private final ContentProviderBackupConfiguration configuration;
+ private final Context context;
+
+ private int numberOfPackages = 0;
private ContentProviderBackupState backupState;
- public ContentProviderBackupComponent(ContentProviderBackupConfiguration configuration) {
- this.configuration = configuration;
+ public ContentProviderBackupComponent(Context context) {
+ this.context = context;
}
@Override
@@ -58,7 +81,7 @@ public class ContentProviderBackupComponent implements BackupComponent {
if (size <= 0) {
result = TRANSPORT_PACKAGE_REJECTED;
- } else if (size > configuration.getBackupSizeQuota()) {
+ } else if (size > DEFAULT_BACKUP_QUOTA) {
result = TRANSPORT_QUOTA_EXCEEDED;
}
@@ -70,6 +93,11 @@ public class ContentProviderBackupComponent implements BackupComponent {
return TRANSPORT_OK;
}
+ @Override
+ public void prepareBackup(int numberOfPackages) {
+ this.numberOfPackages = numberOfPackages;
+ }
+
@Override
public String currentDestinationString() {
return DESTINATION_DESCRIPTION;
@@ -87,7 +115,7 @@ public class ContentProviderBackupComponent implements BackupComponent {
@Override
public long getBackupQuota(String packageName, boolean fullBackup) {
- return configuration.getBackupSizeQuota();
+ return DEFAULT_BACKUP_QUOTA;
}
@Override
@@ -115,7 +143,7 @@ public class ContentProviderBackupComponent implements BackupComponent {
Cipher cipher = CipherUtil.startEncrypt(backupState.getSecretKey(), backupState.getSalt());
backupState.setCipher(cipher);
- ZipEntry zipEntry = new ZipEntry(configuration.getFullBackupDirectory() + backupState.getPackageName());
+ ZipEntry zipEntry = new ZipEntry(DEFAULT_FULL_BACKUP_DIRECTORY + backupState.getPackageName());
backupState.getOutputStream().putNextEntry(zipEntry);
} catch (Exception ex) {
@@ -164,7 +192,7 @@ public class ContentProviderBackupComponent implements BackupComponent {
long bytesTransferred = backupState.getBytesTransferred() + numBytes;
- if (bytesTransferred > configuration.getBackupSizeQuota()) {
+ if (bytesTransferred > DEFAULT_BACKUP_QUOTA) {
return TRANSPORT_QUOTA_EXCEEDED;
}
@@ -199,7 +227,7 @@ public class ContentProviderBackupComponent implements BackupComponent {
int dataSize = backupDataInput.getDataSize();
if (dataSize >= 0) {
- ZipEntry zipEntry = new ZipEntry(configuration.getIncrementalBackupDirectory() +
+ ZipEntry zipEntry = new ZipEntry(DEFAULT_INCREMENTAL_BACKUP_DIRECTORY +
backupState.getPackageName() + "/" + chunkFileName);
outputStream.putNextEntry(zipEntry);
@@ -246,14 +274,18 @@ public class ContentProviderBackupComponent implements BackupComponent {
backupState.getOutputStream().write(backupState.getSalt());
backupState.getOutputStream().closeEntry();
- String password = requireNonNull(configuration.getPassword());
+ String password = requireNonNull(SettingsManager.getBackupPassword(context));
backupState.setSecretKey(KeyGenerator.generate(password, backupState.getSalt()));
}
}
private void initializeOutputStream() throws IOException {
- ContentResolver contentResolver = configuration.getContext().getContentResolver();
- ParcelFileDescriptor outputFileDescriptor = contentResolver.openFileDescriptor(configuration.getUri(), "w");
+ Uri folderUri = SettingsManager.getBackupFolderUri(context);
+ // TODO notify about failure with notification
+ Uri fileUri = createBackupFile(folderUri);
+
+ ParcelFileDescriptor outputFileDescriptor = context.getContentResolver().openFileDescriptor(fileUri, "w");
+ if (outputFileDescriptor == null) throw new IOException();
backupState.setOutputFileDescriptor(outputFileDescriptor);
FileOutputStream fileOutputStream = new FileOutputStream(outputFileDescriptor.getFileDescriptor());
@@ -261,6 +293,25 @@ public class ContentProviderBackupComponent implements BackupComponent {
backupState.setOutputStream(zipOutputStream);
}
+ private Uri createBackupFile(Uri folderUri) throws IOException {
+ Uri documentUri = buildDocumentUriUsingTree(folderUri, getTreeDocumentId(folderUri));
+ try {
+ Uri fileUri = createDocument(context.getContentResolver(), documentUri, DOCUMENT_MIME_TYPE, getBackupFileName());
+ if (fileUri == null) throw new IOException();
+ return fileUri;
+
+ } catch (SecurityException e) {
+ // happens when folder was deleted and thus Uri permission don't exist anymore
+ throw new IOException(e);
+ }
+ }
+
+ private String getBackupFileName() {
+ SimpleDateFormat dateFormat = new SimpleDateFormat(DOCUMENT_SUFFIX, Locale.US);
+ String date = dateFormat.format(new Date());
+ return "backup-" + date;
+ }
+
private int clearBackupState(boolean closeFile) {
if (backupState == null) {
@@ -283,7 +334,7 @@ public class ContentProviderBackupComponent implements BackupComponent {
outputStream.closeEntry();
}
- if (backupState.getPackageIndex() == configuration.getPackageCount() || closeFile) {
+ if (backupState.getPackageIndex() == numberOfPackages || closeFile) {
if (outputStream != null) {
outputStream.finish();
outputStream.close();
diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConfiguration.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConfiguration.java
deleted file mode 100644
index bf90d5e5..00000000
--- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConfiguration.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package com.stevesoltys.backup.transport.component.provider;
-
-import android.content.Context;
-import android.net.Uri;
-
-import java.util.Set;
-
-/**
- * @author Steve Soltys
- */
-public class ContentProviderBackupConfiguration {
-
- private final Context context;
-
- private final Uri uri;
-
- private final String password;
-
- private final long backupSizeQuota;
-
- private final Set packages;
-
- private final String fullBackupDirectory;
-
- private final String incrementalBackupDirectory;
-
- ContentProviderBackupConfiguration(Context context, Uri uri, Set packages, String password,
- long backupSizeQuota, String fullBackupDirectory,
- String incrementalBackupDirectory) {
- this.context = context;
- this.uri = uri;
- this.packages = packages;
- this.password = password;
- this.backupSizeQuota = backupSizeQuota;
- this.fullBackupDirectory = fullBackupDirectory;
- this.incrementalBackupDirectory = incrementalBackupDirectory;
- }
-
- public long getBackupSizeQuota() {
- return backupSizeQuota;
- }
-
- public Context getContext() {
- return context;
- }
-
- public String getFullBackupDirectory() {
- return fullBackupDirectory;
- }
-
- public String getIncrementalBackupDirectory() {
- return incrementalBackupDirectory;
- }
-
- public int getPackageCount() {
- return packages.size();
- }
-
- public String getPassword() {
- return password;
- }
-
- public Uri getUri() {
- return uri;
- }
-}
diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConfigurationBuilder.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConfigurationBuilder.java
deleted file mode 100644
index 0f63b8c5..00000000
--- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConfigurationBuilder.java
+++ /dev/null
@@ -1,77 +0,0 @@
-package com.stevesoltys.backup.transport.component.provider;
-
-import android.content.Context;
-import android.net.Uri;
-import com.android.internal.util.Preconditions;
-
-import java.util.Set;
-
-/**
- * @author Steve Soltys
- */
-public class ContentProviderBackupConfigurationBuilder {
-
- public static final String DEFAULT_FULL_BACKUP_DIRECTORY = "full/";
-
- public static final String DEFAULT_INCREMENTAL_BACKUP_DIRECTORY = "incr/";
-
- private Context context;
-
- private Uri outputUri;
-
- private Set packages;
-
- private String password;
-
- private long backupSizeQuota = Long.MAX_VALUE;
-
- private String incrementalBackupDirectory = DEFAULT_INCREMENTAL_BACKUP_DIRECTORY;
-
- private String fullBackupDirectory = DEFAULT_FULL_BACKUP_DIRECTORY;
-
- public ContentProviderBackupConfiguration build() {
- Preconditions.checkState(context != null, "Context must be set.");
- Preconditions.checkState(outputUri != null, "Output URI must be set.");
- Preconditions.checkState(packages != null, "Package list must be set.");
- Preconditions.checkState(incrementalBackupDirectory != null, "Incremental backup directory must be set.");
- Preconditions.checkState(fullBackupDirectory != null, "Full backup directory must be set.");
-
- return new ContentProviderBackupConfiguration(context, outputUri, packages, password, backupSizeQuota,
- fullBackupDirectory, incrementalBackupDirectory);
- }
-
- public ContentProviderBackupConfigurationBuilder setBackupSizeQuota(long backupSizeQuota) {
- this.backupSizeQuota = backupSizeQuota;
- return this;
- }
-
- public ContentProviderBackupConfigurationBuilder setContext(Context context) {
- this.context = context;
- return this;
- }
-
- public ContentProviderBackupConfigurationBuilder setFullBackupDirectory(String fullBackupDirectory) {
- this.fullBackupDirectory = fullBackupDirectory;
- return this;
- }
-
- public ContentProviderBackupConfigurationBuilder setIncrementalBackupDirectory(String incrementalBackupDirectory) {
- this.incrementalBackupDirectory = incrementalBackupDirectory;
- return this;
- }
-
- public ContentProviderBackupConfigurationBuilder setOutputUri(Uri outputUri) {
- this.outputUri = outputUri;
- return this;
- }
-
- public ContentProviderBackupConfigurationBuilder setPackages(Set packages) {
- this.packages = packages;
- return this;
- }
-
- public ContentProviderBackupConfigurationBuilder setPassword(String password) {
- this.password = password;
- return this;
- }
-}
diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConstants.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConstants.java
index b87e30a8..4020f9d9 100644
--- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConstants.java
+++ b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderBackupConstants.java
@@ -3,7 +3,14 @@ package com.stevesoltys.backup.transport.component.provider;
/**
* @author Steve Soltys
*/
-class ContentProviderBackupConstants {
+public interface ContentProviderBackupConstants {
+
+ String SALT_FILE_PATH = "salt";
+
+ String DEFAULT_FULL_BACKUP_DIRECTORY = "full/";
+
+ String DEFAULT_INCREMENTAL_BACKUP_DIRECTORY = "incr/";
+
+ long DEFAULT_BACKUP_QUOTA = Long.MAX_VALUE;
- static final String SALT_FILE_PATH = "salt";
}
diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java
index 47aeeb55..f983bed9 100644
--- a/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java
+++ b/app/src/main/java/com/stevesoltys/backup/transport/component/provider/ContentProviderRestoreComponent.java
@@ -1,10 +1,13 @@
package com.stevesoltys.backup.transport.component.provider;
+import android.annotation.Nullable;
import android.app.backup.BackupDataOutput;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.pm.PackageInfo;
+import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.util.Base64;
import android.util.Log;
@@ -38,6 +41,9 @@ import static android.app.backup.BackupTransport.TRANSPORT_OK;
import static android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED;
import static android.app.backup.RestoreDescription.TYPE_FULL_STREAM;
import static android.app.backup.RestoreDescription.TYPE_KEY_VALUE;
+import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_FULL_BACKUP_DIRECTORY;
+import static com.stevesoltys.backup.transport.component.provider.ContentProviderBackupConstants.DEFAULT_INCREMENTAL_BACKUP_DIRECTORY;
+import static java.util.Objects.requireNonNull;
/**
* @author Steve Soltys
@@ -50,12 +56,23 @@ public class ContentProviderRestoreComponent implements RestoreComponent {
private static final int DEFAULT_BUFFER_SIZE = 2048;
- private ContentProviderBackupConfiguration configuration;
+ @Nullable
+ private String password;
+ @Nullable
+ private Uri fileUri;
private ContentProviderRestoreState restoreState;
- public ContentProviderRestoreComponent(ContentProviderBackupConfiguration configuration) {
- this.configuration = configuration;
+ private final Context context;
+
+ public ContentProviderRestoreComponent(Context context) {
+ this.context = context;
+ }
+
+ @Override
+ public void prepareRestore(String password, Uri fileUri) {
+ this.password = password;
+ this.fileUri = fileUri;
}
@Override
@@ -64,14 +81,16 @@ public class ContentProviderRestoreComponent implements RestoreComponent {
restoreState.setPackages(packages);
restoreState.setPackageIndex(-1);
- if (configuration.getPassword() != null && !configuration.getPassword().isEmpty()) {
+ String password = requireNonNull(this.password);
+
+ if (!password.isEmpty()) {
try {
ParcelFileDescriptor inputFileDescriptor = buildInputFileDescriptor();
ZipInputStream inputStream = buildInputStream(inputFileDescriptor);
seekToEntry(inputStream, ContentProviderBackupConstants.SALT_FILE_PATH);
restoreState.setSalt(Streams.readFullyNoClose(inputStream));
- restoreState.setSecretKey(KeyGenerator.generate(configuration.getPassword(), restoreState.getSalt()));
+ restoreState.setSecretKey(KeyGenerator.generate(password, restoreState.getSalt()));
IoUtils.closeQuietly(inputFileDescriptor);
IoUtils.closeQuietly(inputStream);
@@ -116,11 +135,11 @@ public class ContentProviderRestoreComponent implements RestoreComponent {
restoreState.setPackageIndex(packageIndex);
String name = packages[packageIndex].packageName;
- if (containsPackageFile(configuration.getIncrementalBackupDirectory() + name)) {
+ if (containsPackageFile(DEFAULT_INCREMENTAL_BACKUP_DIRECTORY + name)) {
restoreState.setRestoreType(TYPE_KEY_VALUE);
return new RestoreDescription(name, restoreState.getRestoreType());
- } else if (containsPackageFile(configuration.getFullBackupDirectory() + name)) {
+ } else if (containsPackageFile(DEFAULT_FULL_BACKUP_DIRECTORY + name)) {
restoreState.setRestoreType(TYPE_FULL_STREAM);
return new RestoreDescription(name, restoreState.getRestoreType());
}
@@ -159,7 +178,7 @@ public class ContentProviderRestoreComponent implements RestoreComponent {
BackupDataOutput backupDataOutput = new BackupDataOutput(outputFileDescriptor.getFileDescriptor());
Optional zipEntryOptional = seekToEntry(inputStream,
- configuration.getIncrementalBackupDirectory() + packageName);
+ DEFAULT_INCREMENTAL_BACKUP_DIRECTORY + packageName);
while (zipEntryOptional.isPresent()) {
String fileName = new File(zipEntryOptional.get().getName()).getName();
@@ -170,7 +189,7 @@ public class ContentProviderRestoreComponent implements RestoreComponent {
backupDataOutput.writeEntityData(backupData, backupData.length);
inputStream.closeEntry();
- zipEntryOptional = seekToEntry(inputStream, configuration.getIncrementalBackupDirectory() + packageName);
+ zipEntryOptional = seekToEntry(inputStream, DEFAULT_INCREMENTAL_BACKUP_DIRECTORY + packageName);
}
IoUtils.closeQuietly(inputFileDescriptor);
@@ -207,7 +226,7 @@ public class ContentProviderRestoreComponent implements RestoreComponent {
ZipInputStream inputStream = buildInputStream(inputFileDescriptor);
restoreState.setInputStream(inputStream);
- if (!seekToEntry(inputStream, configuration.getFullBackupDirectory() + name).isPresent()) {
+ if (!seekToEntry(inputStream, DEFAULT_FULL_BACKUP_DIRECTORY + name).isPresent()) {
IoUtils.closeQuietly(inputFileDescriptor);
IoUtils.closeQuietly(outputFileDescriptor);
return TRANSPORT_PACKAGE_REJECTED;
@@ -317,8 +336,8 @@ public class ContentProviderRestoreComponent implements RestoreComponent {
}
private ParcelFileDescriptor buildInputFileDescriptor() throws FileNotFoundException {
- ContentResolver contentResolver = configuration.getContext().getContentResolver();
- return contentResolver.openFileDescriptor(configuration.getUri(), "r");
+ ContentResolver contentResolver = context.getContentResolver();
+ return contentResolver.openFileDescriptor(requireNonNull(fileUri), "r");
}
private ZipInputStream buildInputStream(ParcelFileDescriptor inputFileDescriptor) {
diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/stub/StubBackupComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/stub/StubBackupComponent.java
deleted file mode 100644
index 5c335a80..00000000
--- a/app/src/main/java/com/stevesoltys/backup/transport/component/stub/StubBackupComponent.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.stevesoltys.backup.transport.component.stub;
-
-import android.content.pm.PackageInfo;
-import android.os.ParcelFileDescriptor;
-import com.stevesoltys.backup.transport.component.BackupComponent;
-
-/**
- * @author Steve Soltys
- */
-public class StubBackupComponent implements BackupComponent {
-
- @Override
- public void cancelFullBackup() {
-
- }
-
- @Override
- public int checkFullBackupSize(long size) {
- return 0;
- }
-
- @Override
- public int clearBackupData(PackageInfo packageInfo) {
- return 0;
- }
-
- @Override
- public String currentDestinationString() {
- return null;
- }
-
- @Override
- public String dataManagementLabel() {
- return null;
- }
-
- @Override
- public int finishBackup() {
- return 0;
- }
-
- @Override
- public long getBackupQuota(String packageName, boolean fullBackup) {
- return 0;
- }
-
- @Override
- public int initializeDevice() {
- return 0;
- }
-
- @Override
- public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor) {
- return 0;
- }
-
- @Override
- public int performIncrementalBackup(PackageInfo targetPackage, ParcelFileDescriptor data) {
- return 0;
- }
-
- @Override
- public long requestBackupTime() {
- return 0;
- }
-
- @Override
- public long requestFullBackupTime() {
- return 0;
- }
-
- @Override
- public int sendBackupData(int numBytes) {
- return 0;
- }
-}
diff --git a/app/src/main/java/com/stevesoltys/backup/transport/component/stub/StubRestoreComponent.java b/app/src/main/java/com/stevesoltys/backup/transport/component/stub/StubRestoreComponent.java
deleted file mode 100644
index 56d6cfd3..00000000
--- a/app/src/main/java/com/stevesoltys/backup/transport/component/stub/StubRestoreComponent.java
+++ /dev/null
@@ -1,53 +0,0 @@
-package com.stevesoltys.backup.transport.component.stub;
-
-import android.content.pm.PackageInfo;
-import android.os.ParcelFileDescriptor;
-import com.stevesoltys.backup.transport.component.RestoreComponent;
-import android.app.backup.RestoreDescription;
-import android.app.backup.RestoreSet;
-
-/**
- * @author Steve Soltys
- */
-public class StubRestoreComponent implements RestoreComponent {
-
- @Override
- public int startRestore(long token, PackageInfo[] packages) {
- return 0;
- }
-
- @Override
- public RestoreDescription nextRestorePackage() {
- return null;
- }
-
- @Override
- public int getRestoreData(ParcelFileDescriptor outputFileDescriptor) {
- return 0;
- }
-
- @Override
- public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
- return 0;
- }
-
- @Override
- public int abortFullRestore() {
- return 0;
- }
-
- @Override
- public long getCurrentRestoreSet() {
- return 0;
- }
-
- @Override
- public void finishRestore() {
-
- }
-
- @Override
- public RestoreSet[] getAvailableRestoreSets() {
- return new RestoreSet[0];
- }
-}
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" />
+
+