Allow the user to schedule full background backups
This commit is contained in:
parent
e079aaff1b
commit
540147470d
7 changed files with 167 additions and 3 deletions
app/src/main
|
@ -14,6 +14,8 @@
|
|||
android:name="android.permission.BACKUP"
|
||||
tools:ignore="ProtectedPermissions" />
|
||||
|
||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
|
||||
|
||||
<application
|
||||
android:name=".Backup"
|
||||
android:supportsRtl="true"
|
||||
|
@ -48,5 +50,10 @@
|
|||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name=".service.backup.BackupJobService"
|
||||
android:exported="false"
|
||||
android:permission="android.permission.BIND_JOB_SERVICE" />
|
||||
|
||||
</application>
|
||||
</manifest>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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<String> 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<String> 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -25,6 +25,15 @@
|
|||
android:layout_marginRight="@dimen/button_horizontal_margin"
|
||||
android:text="@string/restore_backup_button" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/automatic_backups_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="48dp"
|
||||
android:layout_marginLeft="@dimen/button_horizontal_margin"
|
||||
android:layout_marginRight="@dimen/button_horizontal_margin"
|
||||
android:text="Activate automatic backups" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/change_backup_location_button"
|
||||
android:layout_width="match_parent"
|
||||
|
|
Loading…
Add table
Reference in a new issue