Fix detection of the end of backup
For the current transport it is important to know when the backup ends, because it resets its state only then and closes the ZIP file. The detection was broken, because some packages didn't have data to back up (LOG_EVENT_ID_NO_DATA_TO_SEND), so the transport's methods weren't called and the package counter not updated. The hacky solution is to use the BackupObserver to call back into the transport at the end of backup. Ideally, future transports won't need to know when the backup finishes.
This commit is contained in:
parent
84d91290ac
commit
7b95256ba5
9 changed files with 72 additions and 62 deletions
|
@ -9,12 +9,10 @@ import android.os.RemoteException;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import com.stevesoltys.backup.service.PackageService;
|
import com.stevesoltys.backup.service.PackageService;
|
||||||
import com.stevesoltys.backup.transport.ConfigurableBackupTransport;
|
|
||||||
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService;
|
import com.stevesoltys.backup.transport.ConfigurableBackupTransportService;
|
||||||
|
|
||||||
import static android.app.backup.BackupManager.FLAG_NON_INCREMENTAL_BACKUP;
|
import static android.app.backup.BackupManager.FLAG_NON_INCREMENTAL_BACKUP;
|
||||||
import static android.os.ServiceManager.getService;
|
import static android.os.ServiceManager.getService;
|
||||||
import static com.stevesoltys.backup.transport.ConfigurableBackupTransportService.getBackupTransport;
|
|
||||||
|
|
||||||
public class BackupJobService extends JobService {
|
public class BackupJobService extends JobService {
|
||||||
|
|
||||||
|
@ -34,8 +32,6 @@ public class BackupJobService extends JobService {
|
||||||
try {
|
try {
|
||||||
String[] packages = packageService.getEligiblePackages();
|
String[] packages = packageService.getEligiblePackages();
|
||||||
// TODO use an observer to know when backups fail
|
// 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);
|
int result = backupManager.requestBackup(packages, null, null, FLAG_NON_INCREMENTAL_BACKUP);
|
||||||
if (result == BackupManager.SUCCESS) {
|
if (result == BackupManager.SUCCESS) {
|
||||||
Log.i(TAG, "Backup succeeded ");
|
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.BackupSession;
|
||||||
import com.stevesoltys.backup.session.backup.BackupSessionObserver;
|
import com.stevesoltys.backup.session.backup.BackupSessionObserver;
|
||||||
|
|
||||||
|
import static com.stevesoltys.backup.transport.ConfigurableBackupTransportService.getBackupTransport;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Soltys
|
* @author Steve Soltys
|
||||||
*/
|
*/
|
||||||
|
@ -59,6 +61,9 @@ class BackupObserver implements BackupSessionObserver {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void backupSessionCompleted(BackupSession backupSession, BackupResult backupResult) {
|
public void backupSessionCompleted(BackupSession backupSession, BackupResult backupResult) {
|
||||||
|
|
||||||
|
if (backupResult == BackupResult.SUCCESS) getBackupTransport(context).backupFinished();
|
||||||
|
|
||||||
context.runOnUiThread(() -> {
|
context.runOnUiThread(() -> {
|
||||||
if (backupResult == BackupResult.SUCCESS) {
|
if (backupResult == BackupResult.SUCCESS) {
|
||||||
Toast.makeText(context, R.string.backup_success, Toast.LENGTH_LONG).show();
|
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.activity.backup.BackupPopupWindowListener;
|
||||||
import com.stevesoltys.backup.service.TransportService;
|
import com.stevesoltys.backup.service.TransportService;
|
||||||
import com.stevesoltys.backup.session.backup.BackupSession;
|
import com.stevesoltys.backup.session.backup.BackupSession;
|
||||||
import com.stevesoltys.backup.transport.ConfigurableBackupTransport;
|
|
||||||
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static com.stevesoltys.backup.transport.ConfigurableBackupTransportService.getBackupTransport;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Soltys
|
* @author Steve Soltys
|
||||||
*/
|
*/
|
||||||
|
@ -34,8 +31,6 @@ public class BackupService {
|
||||||
|
|
||||||
PopupWindow popupWindow = PopupWindowUtil.showLoadingPopupWindow(parent);
|
PopupWindow popupWindow = PopupWindowUtil.showLoadingPopupWindow(parent);
|
||||||
BackupObserver backupObserver = new BackupObserver(parent, popupWindow);
|
BackupObserver backupObserver = new BackupObserver(parent, popupWindow);
|
||||||
ConfigurableBackupTransport backupTransport = getBackupTransport(parent.getApplication());
|
|
||||||
backupTransport.prepareBackup(selectedPackages.size());
|
|
||||||
BackupSession backupSession = transportService.backup(backupObserver, selectedPackages);
|
BackupSession backupSession = transportService.backup(backupObserver, selectedPackages);
|
||||||
|
|
||||||
View popupWindowButton = popupWindow.getContentView().findViewById(R.id.popup_cancel_button);
|
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 {
|
public void start() throws RemoteException {
|
||||||
String [] selectedPackageArray = packages.toArray(new String[0]);
|
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 {
|
public void stop(BackupResult result) throws RemoteException {
|
||||||
|
|
|
@ -36,10 +36,6 @@ public class ConfigurableBackupTransport extends BackupTransport {
|
||||||
restoreComponent = new ContentProviderRestoreComponent(context);
|
restoreComponent = new ContentProviderRestoreComponent(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void prepareBackup(int numberOfPackages) {
|
|
||||||
backupComponent.prepareBackup(numberOfPackages);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void prepareRestore(String password, Uri fileUri) {
|
public void prepareRestore(String password, Uri fileUri) {
|
||||||
restoreComponent.prepareRestore(password, fileUri);
|
restoreComponent.prepareRestore(password, fileUri);
|
||||||
}
|
}
|
||||||
|
@ -63,20 +59,7 @@ public class ConfigurableBackupTransport extends BackupTransport {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) {
|
public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) {
|
||||||
// TODO re-include key-value (incremental)
|
return true;
|
||||||
// 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
|
@Override
|
||||||
|
@ -99,15 +82,17 @@ public class ConfigurableBackupTransport extends BackupTransport {
|
||||||
return backupComponent.currentDestinationString();
|
return backupComponent.currentDestinationString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Methods related to Backup */
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) {
|
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) {
|
||||||
// TODO handle flags
|
return backupComponent.performIncrementalBackup(packageInfo, inFd, flags);
|
||||||
return performBackup(packageInfo, inFd);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int performBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor) {
|
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
|
@Override
|
||||||
|
@ -156,6 +141,12 @@ public class ConfigurableBackupTransport extends BackupTransport {
|
||||||
return backupComponent.clearBackupData(packageInfo);
|
return backupComponent.clearBackupData(packageInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void backupFinished() {
|
||||||
|
backupComponent.backupFinished();
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Methods related to Restore */
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getCurrentRestoreSet() {
|
public long getCurrentRestoreSet() {
|
||||||
return restoreComponent.getCurrentRestoreSet();
|
return restoreComponent.getCurrentRestoreSet();
|
||||||
|
|
|
@ -8,8 +8,6 @@ import android.os.ParcelFileDescriptor;
|
||||||
*/
|
*/
|
||||||
public interface BackupComponent {
|
public interface BackupComponent {
|
||||||
|
|
||||||
void prepareBackup(int numberOfPackages);
|
|
||||||
|
|
||||||
String currentDestinationString();
|
String currentDestinationString();
|
||||||
|
|
||||||
String dataManagementLabel();
|
String dataManagementLabel();
|
||||||
|
@ -20,7 +18,7 @@ public interface BackupComponent {
|
||||||
|
|
||||||
int finishBackup();
|
int finishBackup();
|
||||||
|
|
||||||
int performIncrementalBackup(PackageInfo targetPackage, ParcelFileDescriptor data);
|
int performIncrementalBackup(PackageInfo targetPackage, ParcelFileDescriptor data, int flags);
|
||||||
|
|
||||||
int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor);
|
int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor fileDescriptor);
|
||||||
|
|
||||||
|
@ -35,4 +33,6 @@ public interface BackupComponent {
|
||||||
long requestBackupTime();
|
long requestBackupTime();
|
||||||
|
|
||||||
long requestFullBackupTime();
|
long requestFullBackupTime();
|
||||||
|
|
||||||
|
void backupFinished();
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,10 @@ import javax.crypto.SecretKey;
|
||||||
|
|
||||||
import libcore.io.IoUtils;
|
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_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_OK;
|
||||||
import static android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED;
|
import static android.app.backup.BackupTransport.TRANSPORT_PACKAGE_REJECTED;
|
||||||
import static android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED;
|
import static android.app.backup.BackupTransport.TRANSPORT_QUOTA_EXCEEDED;
|
||||||
|
@ -49,7 +52,7 @@ import static java.util.Objects.requireNonNull;
|
||||||
*/
|
*/
|
||||||
public class ContentProviderBackupComponent implements BackupComponent {
|
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";
|
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 final Context context;
|
||||||
|
|
||||||
private int numberOfPackages = 0;
|
|
||||||
|
|
||||||
private ContentProviderBackupState backupState;
|
private ContentProviderBackupState backupState;
|
||||||
|
|
||||||
public ContentProviderBackupComponent(Context context) {
|
public ContentProviderBackupComponent(Context context) {
|
||||||
|
@ -93,11 +94,6 @@ public class ContentProviderBackupComponent implements BackupComponent {
|
||||||
return TRANSPORT_OK;
|
return TRANSPORT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void prepareBackup(int numberOfPackages) {
|
|
||||||
this.numberOfPackages = numberOfPackages;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String currentDestinationString() {
|
public String currentDestinationString() {
|
||||||
return DESTINATION_DESCRIPTION;
|
return DESTINATION_DESCRIPTION;
|
||||||
|
@ -133,7 +129,6 @@ public class ContentProviderBackupComponent implements BackupComponent {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
initializeBackupState();
|
initializeBackupState();
|
||||||
backupState.setPackageIndex(backupState.getPackageIndex() + 1);
|
|
||||||
backupState.setPackageName(targetPackage.packageName);
|
backupState.setPackageName(targetPackage.packageName);
|
||||||
|
|
||||||
backupState.setInputFileDescriptor(fileDescriptor);
|
backupState.setInputFileDescriptor(fileDescriptor);
|
||||||
|
@ -156,12 +151,24 @@ public class ContentProviderBackupComponent implements BackupComponent {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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());
|
BackupDataInput backupDataInput = new BackupDataInput(data.getFileDescriptor());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
initializeBackupState();
|
initializeBackupState();
|
||||||
backupState.setPackageIndex(backupState.getPackageIndex() + 1);
|
|
||||||
backupState.setPackageName(packageInfo.packageName);
|
backupState.setPackageName(packageInfo.packageName);
|
||||||
|
|
||||||
return transferIncrementalBackupData(backupDataInput);
|
return transferIncrementalBackupData(backupDataInput);
|
||||||
|
@ -261,6 +268,11 @@ public class ContentProviderBackupComponent implements BackupComponent {
|
||||||
return TRANSPORT_OK;
|
return TRANSPORT_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void backupFinished() {
|
||||||
|
clearBackupState(true);
|
||||||
|
}
|
||||||
|
|
||||||
private void initializeBackupState() throws Exception {
|
private void initializeBackupState() throws Exception {
|
||||||
if (backupState == null) {
|
if (backupState == null) {
|
||||||
backupState = new ContentProviderBackupState();
|
backupState = new ContentProviderBackupState();
|
||||||
|
@ -333,8 +345,8 @@ public class ContentProviderBackupComponent implements BackupComponent {
|
||||||
|
|
||||||
outputStream.closeEntry();
|
outputStream.closeEntry();
|
||||||
}
|
}
|
||||||
|
if (closeFile) {
|
||||||
if (backupState.getPackageIndex() == numberOfPackages || closeFile) {
|
Log.d(TAG, "Closing backup file...");
|
||||||
if (outputStream != null) {
|
if (outputStream != null) {
|
||||||
outputStream.finish();
|
outputStream.finish();
|
||||||
outputStream.close();
|
outputStream.close();
|
||||||
|
|
|
@ -2,12 +2,13 @@ package com.stevesoltys.backup.transport.component.provider;
|
||||||
|
|
||||||
import android.os.ParcelFileDescriptor;
|
import android.os.ParcelFileDescriptor;
|
||||||
|
|
||||||
import javax.crypto.Cipher;
|
|
||||||
import javax.crypto.SecretKey;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.zip.ZipOutputStream;
|
import java.util.zip.ZipOutputStream;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author Steve Soltys
|
* @author Steve Soltys
|
||||||
*/
|
*/
|
||||||
|
@ -29,13 +30,11 @@ class ContentProviderBackupState {
|
||||||
|
|
||||||
private String packageName;
|
private String packageName;
|
||||||
|
|
||||||
private int packageIndex;
|
|
||||||
|
|
||||||
private byte[] salt;
|
private byte[] salt;
|
||||||
|
|
||||||
private SecretKey secretKey;
|
private SecretKey secretKey;
|
||||||
|
|
||||||
public ContentProviderBackupState() {
|
ContentProviderBackupState() {
|
||||||
salt = new byte[16];
|
salt = new byte[16];
|
||||||
SECURE_RANDOM.nextBytes(salt);
|
SECURE_RANDOM.nextBytes(salt);
|
||||||
}
|
}
|
||||||
|
@ -88,14 +87,6 @@ class ContentProviderBackupState {
|
||||||
this.outputStream = outputStream;
|
this.outputStream = outputStream;
|
||||||
}
|
}
|
||||||
|
|
||||||
int getPackageIndex() {
|
|
||||||
return packageIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
void setPackageIndex(int packageIndex) {
|
|
||||||
this.packageIndex = packageIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getPackageName() {
|
String getPackageName() {
|
||||||
return packageName;
|
return packageName;
|
||||||
}
|
}
|
||||||
|
@ -108,11 +99,11 @@ class ContentProviderBackupState {
|
||||||
return salt;
|
return salt;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SecretKey getSecretKey() {
|
SecretKey getSecretKey() {
|
||||||
return secretKey;
|
return secretKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSecretKey(SecretKey secretKey) {
|
void setSecretKey(SecretKey secretKey) {
|
||||||
this.secretKey = secretKey;
|
this.secretKey = secretKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue