1
0
Fork 0

Allow the user to schedule full background backups

This commit is contained in:
Torsten Grote 2019-06-07 16:41:48 -03:00
parent e079aaff1b
commit 540147470d
No known key found for this signature in database
GPG key ID: 3E5F77D92CF891FF
7 changed files with 167 additions and 3 deletions

View file

@ -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>

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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"