Merge pull request #29 from grote/full-key-value-backups

Fix backup state handling, end of backup detection
This commit is contained in:
Steve Soltys 2019-06-14 17:34:44 -04:00 committed by GitHub
commit bd599db492
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 106 additions and 75 deletions

View file

@ -5,8 +5,10 @@ import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;
import com.stevesoltys.backup.R;
import java.util.HashSet;
import java.util.Set;
import java.util.stream.IntStream;
@ -17,7 +19,7 @@ public abstract class PackageListActivity extends Activity implements AdapterVie
protected ListView packageListView;
protected Set<String> selectedPackageList;
protected final Set<String> selectedPackageList = new HashSet<>();
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
String clickedPackage = (String) packageListView.getItemAtPosition(position);
@ -34,12 +36,12 @@ public abstract class PackageListActivity extends Activity implements AdapterVie
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_select_all) {
if (item.getItemId() == R.id.action_unselect_all) {
IntStream.range(0, packageListView.getCount())
.forEach(position -> {
selectedPackageList.add((String) packageListView.getItemAtPosition(position));
packageListView.setItemChecked(position, true);
selectedPackageList.remove((String) packageListView.getItemAtPosition(position));
packageListView.setItemChecked(position, false);
});
return true;
@ -47,4 +49,12 @@ public abstract class PackageListActivity extends Activity implements AdapterVie
return super.onOptionsItemSelected(item);
}
public void preSelectAllPackages() {
IntStream.range(0, packageListView.getCount())
.forEach(position -> {
selectedPackageList.add((String) packageListView.getItemAtPosition(position));
packageListView.setItemChecked(position, true);
});
}
}

View file

@ -9,8 +9,6 @@ import android.view.View;
import com.stevesoltys.backup.R;
import com.stevesoltys.backup.activity.PackageListActivity;
import java.util.HashSet;
public class CreateBackupActivity extends PackageListActivity implements View.OnClickListener {
private CreateBackupActivityController controller;
@ -32,7 +30,7 @@ public class CreateBackupActivity extends PackageListActivity implements View.On
findViewById(R.id.create_confirm_button).setOnClickListener(this);
packageListView = findViewById(R.id.create_package_list);
selectedPackageList = new HashSet<>();
selectedPackageList.clear();
controller = new CreateBackupActivityController();
AsyncTask.execute(() -> controller.populatePackageList(packageListView, CreateBackupActivity.this));

View file

@ -64,6 +64,7 @@ class CreateBackupActivityController {
packageListView.setOnItemClickListener(parent);
packageListView.setAdapter(new ArrayAdapter<>(parent, R.layout.checked_list_item, eligiblePackageList));
packageListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
parent.preSelectAllPackages();
});
}

View file

@ -6,11 +6,10 @@ import android.os.Bundle;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
import com.stevesoltys.backup.R;
import com.stevesoltys.backup.activity.PackageListActivity;
import java.util.HashSet;
public class RestoreBackupActivity extends PackageListActivity implements View.OnClickListener {
private RestoreBackupActivityController controller;
@ -34,7 +33,8 @@ public class RestoreBackupActivity extends PackageListActivity implements View.O
findViewById(R.id.restore_confirm_button).setOnClickListener(this);
packageListView = findViewById(R.id.restore_package_list);
selectedPackageList = new HashSet<>();
selectedPackageList.clear();
contentUri = getIntent().getData();
controller = new RestoreBackupActivityController();

View file

@ -69,6 +69,7 @@ class RestoreBackupActivityController {
packageListView.setOnItemClickListener(parent);
packageListView.setAdapter(new ArrayAdapter<>(parent, R.layout.checked_list_item, eligiblePackageList));
packageListView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
parent.preSelectAllPackages();
});
}

View file

@ -9,12 +9,10 @@ import android.os.RemoteException;
import android.util.Log;
import com.stevesoltys.backup.service.PackageService;
import com.stevesoltys.backup.transport.ConfigurableBackupTransport;
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService;
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 {
@ -34,8 +32,6 @@ public class BackupJobService extends JobService {
try {
String[] packages = packageService.getEligiblePackages();
// TODO use an observer to know when backups fail
ConfigurableBackupTransport backupTransport = getBackupTransport(getApplication());
backupTransport.prepareBackup(packages.length);
int result = backupManager.requestBackup(packages, null, null, FLAG_NON_INCREMENTAL_BACKUP);
if (result == BackupManager.SUCCESS) {
Log.i(TAG, "Backup succeeded ");

View file

@ -12,6 +12,8 @@ import com.stevesoltys.backup.session.backup.BackupResult;
import com.stevesoltys.backup.session.backup.BackupSession;
import com.stevesoltys.backup.session.backup.BackupSessionObserver;
import static com.stevesoltys.backup.transport.ConfigurableBackupTransportService.getBackupTransport;
/**
* @author Steve Soltys
*/
@ -59,6 +61,9 @@ class BackupObserver implements BackupSessionObserver {
@Override
public void backupSessionCompleted(BackupSession backupSession, BackupResult backupResult) {
if (backupResult == BackupResult.SUCCESS) getBackupTransport(context).backupFinished();
context.runOnUiThread(() -> {
if (backupResult == BackupResult.SUCCESS) {
Toast.makeText(context, R.string.backup_success, Toast.LENGTH_LONG).show();

View file

@ -11,12 +11,9 @@ 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.ConfigurableBackupTransport;
import java.util.Set;
import static com.stevesoltys.backup.transport.ConfigurableBackupTransportService.getBackupTransport;
/**
* @author Steve Soltys
*/
@ -30,10 +27,10 @@ public class BackupService {
try {
selectedPackages.add("@pm@");
Log.i(TAG, "Backing up " + selectedPackages.size() + " packages...");
PopupWindow popupWindow = PopupWindowUtil.showLoadingPopupWindow(parent);
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);

View file

@ -0,0 +1,20 @@
package com.stevesoltys.backup.session.backup;
import android.app.backup.IBackupManagerMonitor;
import android.os.Bundle;
import android.util.Log;
import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY;
import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_ID;
import static android.app.backup.BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME;
class BackupMonitor extends IBackupManagerMonitor.Stub {
@Override
public void onEvent(Bundle bundle) {
Log.d("BackupMonitor", "ID: " + bundle.getInt(EXTRA_LOG_EVENT_ID));
Log.d("BackupMonitor", "CATEGORY: " + bundle.getInt(EXTRA_LOG_EVENT_CATEGORY, -1));
Log.d("BackupMonitor", "PACKAGE: " + bundle.getString(EXTRA_LOG_EVENT_PACKAGE_NAME, "?"));
}
}

View file

@ -30,7 +30,7 @@ public class BackupSession extends IBackupObserver.Stub {
public void start() throws RemoteException {
String [] selectedPackageArray = packages.toArray(new String[0]);
backupManager.requestBackup(selectedPackageArray, this, null, FLAG_NON_INCREMENTAL_BACKUP);
backupManager.requestBackup(selectedPackageArray, this, new BackupMonitor(), FLAG_NON_INCREMENTAL_BACKUP);
}
public void stop(BackupResult result) throws RemoteException {

View file

@ -14,6 +14,9 @@ import com.stevesoltys.backup.transport.component.RestoreComponent;
import com.stevesoltys.backup.transport.component.provider.ContentProviderBackupComponent;
import com.stevesoltys.backup.transport.component.provider.ContentProviderRestoreComponent;
import static android.app.backup.BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
import static android.os.Build.VERSION.SDK_INT;
/**
* @author Steve Soltys
*/
@ -33,10 +36,6 @@ public class ConfigurableBackupTransport extends BackupTransport {
restoreComponent = new ContentProviderRestoreComponent(context);
}
public void prepareBackup(int numberOfPackages) {
backupComponent.prepareBackup(numberOfPackages);
}
public void prepareRestore(String password, Uri fileUri) {
restoreComponent.prepareRestore(password, fileUri);
}
@ -52,22 +51,15 @@ public class ConfigurableBackupTransport extends BackupTransport {
return this.getClass().getName();
}
@Override
public int getTransportFlags() {
if (SDK_INT >= 28) return FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
return 0;
}
@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;
return true;
}
@Override
@ -90,15 +82,17 @@ public class ConfigurableBackupTransport extends BackupTransport {
return backupComponent.currentDestinationString();
}
/* Methods related to Backup */
@Override
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) {
// TODO handle flags
return performBackup(packageInfo, inFd);
return backupComponent.performIncrementalBackup(packageInfo, inFd, flags);
}
@Override
public int performBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor) {
return backupComponent.performIncrementalBackup(targetPackage, fileDescriptor);
Log.w(TAG, "Warning: Legacy performBackup() method called.");
return performBackup(targetPackage, fileDescriptor, 0);
}
@Override
@ -147,6 +141,12 @@ public class ConfigurableBackupTransport extends BackupTransport {
return backupComponent.clearBackupData(packageInfo);
}
public void backupFinished() {
backupComponent.backupFinished();
}
/* Methods related to Restore */
@Override
public long getCurrentRestoreSet() {
return restoreComponent.getCurrentRestoreSet();

View file

@ -8,8 +8,6 @@ import android.os.ParcelFileDescriptor;
*/
public interface BackupComponent {
void prepareBackup(int numberOfPackages);
String currentDestinationString();
String dataManagementLabel();
@ -20,7 +18,7 @@ public interface BackupComponent {
int finishBackup();
int performIncrementalBackup(PackageInfo targetPackage, ParcelFileDescriptor data);
int performIncrementalBackup(PackageInfo targetPackage, ParcelFileDescriptor data, int flags);
int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor);
@ -35,4 +33,6 @@ public interface BackupComponent {
long requestBackupTime();
long requestFullBackupTime();
void backupFinished();
}

View file

@ -31,7 +31,10 @@ import javax.crypto.SecretKey;
import libcore.io.IoUtils;
import static android.app.backup.BackupTransport.FLAG_INCREMENTAL;
import static android.app.backup.BackupTransport.FLAG_NON_INCREMENTAL;
import static android.app.backup.BackupTransport.TRANSPORT_ERROR;
import static android.app.backup.BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED;
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;
@ -49,7 +52,7 @@ import static java.util.Objects.requireNonNull;
*/
public class ContentProviderBackupComponent implements BackupComponent {
private static final String TAG = ContentProviderBackupComponent.class.getName();
private static final String TAG = ContentProviderBackupComponent.class.getSimpleName();
private static final String DOCUMENT_SUFFIX = "yyyy-MM-dd_HH_mm_ss";
@ -61,8 +64,6 @@ public class ContentProviderBackupComponent implements BackupComponent {
private final Context context;
private int numberOfPackages = 0;
private ContentProviderBackupState backupState;
public ContentProviderBackupComponent(Context context) {
@ -93,11 +94,6 @@ public class ContentProviderBackupComponent implements BackupComponent {
return TRANSPORT_OK;
}
@Override
public void prepareBackup(int numberOfPackages) {
this.numberOfPackages = numberOfPackages;
}
@Override
public String currentDestinationString() {
return DESTINATION_DESCRIPTION;
@ -133,7 +129,6 @@ public class ContentProviderBackupComponent implements BackupComponent {
try {
initializeBackupState();
backupState.setPackageIndex(backupState.getPackageIndex() + 1);
backupState.setPackageName(targetPackage.packageName);
backupState.setInputFileDescriptor(fileDescriptor);
@ -156,12 +151,24 @@ public class ContentProviderBackupComponent implements BackupComponent {
}
@Override
public int performIncrementalBackup(PackageInfo packageInfo, ParcelFileDescriptor data) {
public int performIncrementalBackup(PackageInfo packageInfo, ParcelFileDescriptor data, int flags) {
boolean isIncremental = (flags & FLAG_INCREMENTAL) != 0;
if (isIncremental) {
Log.w(TAG, "Can not handle incremental backup. Requesting non-incremental for " + packageInfo.packageName);
return TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED;
}
boolean isNonIncremental = (flags & FLAG_NON_INCREMENTAL) != 0;
if (isNonIncremental) {
Log.i(TAG, "Performing non-incremental backup for " + packageInfo.packageName);
} else {
Log.i(TAG, "Performing backup for " + packageInfo.packageName);
}
BackupDataInput backupDataInput = new BackupDataInput(data.getFileDescriptor());
try {
initializeBackupState();
backupState.setPackageIndex(backupState.getPackageIndex() + 1);
backupState.setPackageName(packageInfo.packageName);
return transferIncrementalBackupData(backupDataInput);
@ -261,6 +268,11 @@ public class ContentProviderBackupComponent implements BackupComponent {
return TRANSPORT_OK;
}
@Override
public void backupFinished() {
clearBackupState(true);
}
private void initializeBackupState() throws Exception {
if (backupState == null) {
backupState = new ContentProviderBackupState();
@ -333,8 +345,8 @@ public class ContentProviderBackupComponent implements BackupComponent {
outputStream.closeEntry();
}
if (backupState.getPackageIndex() == numberOfPackages || closeFile) {
if (closeFile) {
Log.d(TAG, "Closing backup file...");
if (outputStream != null) {
outputStream.finish();
outputStream.close();

View file

@ -2,12 +2,13 @@ package com.stevesoltys.backup.transport.component.provider;
import android.os.ParcelFileDescriptor;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import java.io.InputStream;
import java.security.SecureRandom;
import java.util.zip.ZipOutputStream;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
/**
* @author Steve Soltys
*/
@ -29,13 +30,11 @@ class ContentProviderBackupState {
private String packageName;
private int packageIndex;
private byte[] salt;
private SecretKey secretKey;
public ContentProviderBackupState() {
ContentProviderBackupState() {
salt = new byte[16];
SECURE_RANDOM.nextBytes(salt);
}
@ -88,14 +87,6 @@ class ContentProviderBackupState {
this.outputStream = outputStream;
}
int getPackageIndex() {
return packageIndex;
}
void setPackageIndex(int packageIndex) {
this.packageIndex = packageIndex;
}
String getPackageName() {
return packageName;
}
@ -108,11 +99,11 @@ class ContentProviderBackupState {
return salt;
}
public SecretKey getSecretKey() {
SecretKey getSecretKey() {
return secretKey;
}
public void setSecretKey(SecretKey secretKey) {
void setSecretKey(SecretKey secretKey) {
this.secretKey = secretKey;
}
}

View file

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/action_select_all"
android:title="@string/select_all"
<item android:id="@+id/action_unselect_all"
android:title="@string/unselect_all"
android:showAsAction="never" />
</menu>

View file

@ -18,7 +18,7 @@
<string name="popup_cancel">Cancel</string>
<string name="select_all">Select all</string>
<string name="unselect_all">Unselect all</string>
<string name="loading_backup">Loading backup…</string>
<string name="loading_packages">Loading packages…</string>
<string name="initializing">Initializing…</string>