Merge pull request #29 from grote/full-key-value-backups
Fix backup state handling, end of backup detection
This commit is contained in:
commit
bd599db492
16 changed files with 106 additions and 75 deletions
|
@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ");
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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, "?"));
|
||||
}
|
||||
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
Loading…
Add table
Reference in a new issue