Introduce BackupManager#requestBackup & BackupObserver API
Introduces a way to request immediate backup for list of packages
and receive callbacks on backup progress.
Bug: 25688526
Change-Id: Ib826933d44f4ebf2b981f8be366215b2d37847e2
diff --git a/Android.mk b/Android.mk
index d3e1cf5..115f100 100644
--- a/Android.mk
+++ b/Android.mk
@@ -96,6 +96,7 @@
core/java/android/app/trust/ITrustManager.aidl \
core/java/android/app/trust/ITrustListener.aidl \
core/java/android/app/backup/IBackupManager.aidl \
+ core/java/android/app/backup/IBackupObserver.aidl \
core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
core/java/android/app/backup/IRestoreObserver.aidl \
core/java/android/app/backup/IRestoreSession.aidl \
diff --git a/api/system-current.txt b/api/system-current.txt
index e992a77..cec0106 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6223,10 +6223,33 @@
method public java.lang.String getCurrentTransport();
method public boolean isBackupEnabled();
method public java.lang.String[] listAllTransports();
+ method public int requestBackup(java.lang.String[], android.app.backup.BackupObserver);
method public int requestRestore(android.app.backup.RestoreObserver);
method public java.lang.String selectBackupTransport(java.lang.String);
method public void setAutoRestore(boolean);
method public void setBackupEnabled(boolean);
+ field public static final int ERROR_AGENT_FAILURE = -1003; // 0xfffffc15
+ field public static final int ERROR_BACKUP_NOT_ALLOWED = -2001; // 0xfffff82f
+ field public static final int ERROR_PACKAGE_NOT_FOUND = -2002; // 0xfffff82e
+ field public static final int ERROR_TRANSPORT_ABORTED = -1000; // 0xfffffc18
+ field public static final int ERROR_TRANSPORT_PACKAGE_REJECTED = -1002; // 0xfffffc16
+ field public static final int SUCCESS = 0; // 0x0
+ }
+
+ public abstract class BackupObserver {
+ ctor public BackupObserver();
+ method public void backupFinished(int);
+ method public void onResult(java.lang.String, int);
+ method public void onUpdate(java.lang.String, android.app.backup.BackupProgress);
+ }
+
+ public class BackupProgress implements android.os.Parcelable {
+ ctor public BackupProgress(long, long);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.backup.BackupProgress> CREATOR;
+ field public final long bytesExpected;
+ field public final long bytesTransferred;
}
public class BackupTransport {
@@ -6249,7 +6272,9 @@
method public int initializeDevice();
method public java.lang.String name();
method public android.app.backup.RestoreDescription nextRestorePackage();
+ method public int performBackup(android.content.pm.PackageInfo, android.os.ParcelFileDescriptor, int);
method public int performBackup(android.content.pm.PackageInfo, android.os.ParcelFileDescriptor);
+ method public int performFullBackup(android.content.pm.PackageInfo, android.os.ParcelFileDescriptor, int);
method public int performFullBackup(android.content.pm.PackageInfo, android.os.ParcelFileDescriptor);
method public long requestBackupTime();
method public long requestFullBackupTime();
@@ -6258,6 +6283,7 @@
method public java.lang.String transportDirName();
field public static final int AGENT_ERROR = -1003; // 0xfffffc15
field public static final int AGENT_UNKNOWN = -1004; // 0xfffffc14
+ field public static final int FLAG_USER_INITIATED = 1; // 0x1
field public static final int NO_MORE_DATA = -1; // 0xffffffff
field public static final int TRANSPORT_ERROR = -1000; // 0xfffffc18
field public static final int TRANSPORT_NOT_INITIALIZED = -1001; // 0xfffffc17
diff --git a/core/java/android/app/backup/BackupManager.java b/core/java/android/app/backup/BackupManager.java
index 8b79305..193a0b2c 100644
--- a/core/java/android/app/backup/BackupManager.java
+++ b/core/java/android/app/backup/BackupManager.java
@@ -17,13 +17,13 @@
package android.app.backup;
import android.annotation.SystemApi;
-import android.app.backup.RestoreSession;
-import android.app.backup.IBackupManager;
-import android.app.backup.IRestoreSession;
import android.content.Context;
+import android.os.Handler;
+import android.os.Message;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.Log;
+import android.util.Pair;
/**
* The interface through which an application interacts with the Android backup service to
@@ -59,6 +59,65 @@
public class BackupManager {
private static final String TAG = "BackupManager";
+ // BackupObserver status codes
+ /**
+ * Indicates that backup succeeded.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int SUCCESS = 0;
+
+ /**
+ * Indicates that backup is either not enabled at all or
+ * backup for the package was rejected by backup service
+ * or backup transport,
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_BACKUP_NOT_ALLOWED = -2001;
+
+ /**
+ * The requested app is not installed on the device.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_PACKAGE_NOT_FOUND = -2002;
+
+ /**
+ * The transport for some reason was not in a good state and
+ * aborted the entire backup request. This is a transient
+ * failure and should not be retried immediately.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_TRANSPORT_ABORTED = BackupTransport.TRANSPORT_ERROR;
+
+ /**
+ * Returned when the transport was unable to process the
+ * backup request for a given package, for example if the
+ * transport hit a transient network failure. The remaining
+ * packages provided to {@link #requestBackup(String[], BackupObserver)}
+ * will still be attempted.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_TRANSPORT_PACKAGE_REJECTED =
+ BackupTransport.TRANSPORT_PACKAGE_REJECTED;
+
+ /**
+ * The {@link BackupAgent} for the requested package failed for some reason
+ * and didn't provide appropriate backup data.
+ *
+ * @hide
+ */
+ @SystemApi
+ public static final int ERROR_AGENT_FAILURE = BackupTransport.AGENT_ERROR;
+
private Context mContext;
private static IBackupManager sService;
@@ -365,4 +424,94 @@
}
return 0;
}
+
+ /**
+ * Request an immediate backup, providing an observer to which results of the backup operation
+ * will be published. The Android backup system will decide for each package whether it will
+ * be full app data backup or key/value-pair-based backup.
+ *
+ * <p>If this method returns {@link BackupManager#SUCCESS}, the OS will attempt to backup all
+ * provided packages using the remote transport.
+ *
+ * @param packages List of package names to backup.
+ * @param observer The {@link BackupObserver} to receive callbacks during the backup
+ * operation.
+ * @return {@link BackupManager#SUCCESS} on success; nonzero on error.
+ * @exception IllegalArgumentException on null or empty {@code packages} param.
+ *
+ * @hide
+ */
+ @SystemApi
+ public int requestBackup(String[] packages, BackupObserver observer) {
+ checkServiceBinder();
+ if (sService != null) {
+ try {
+ BackupObserverWrapper observerWrapper =
+ new BackupObserverWrapper(mContext, observer);
+ return sService.requestBackup(packages, observerWrapper);
+ } catch (RemoteException e) {
+ Log.e(TAG, "requestBackup() couldn't connect");
+ }
+ }
+ return -1;
+ }
+
+ /*
+ * We wrap incoming binder calls with a private class implementation that
+ * redirects them into main-thread actions. This serializes the backup
+ * progress callbacks nicely within the usual main-thread lifecycle pattern.
+ */
+ @SystemApi
+ private class BackupObserverWrapper extends IBackupObserver.Stub {
+ final Handler mHandler;
+ final BackupObserver mObserver;
+
+ static final int MSG_UPDATE = 1;
+ static final int MSG_RESULT = 2;
+ static final int MSG_FINISHED = 3;
+
+ BackupObserverWrapper(Context context, BackupObserver observer) {
+ mHandler = new Handler(context.getMainLooper()) {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_UPDATE:
+ Pair<String, BackupProgress> obj =
+ (Pair<String, BackupProgress>) msg.obj;
+ mObserver.onUpdate(obj.first, obj.second);
+ break;
+ case MSG_RESULT:
+ mObserver.onResult((String)msg.obj, msg.arg1);
+ break;
+ case MSG_FINISHED:
+ mObserver.backupFinished(msg.arg1);
+ break;
+ default:
+ Log.w(TAG, "Unknown message: " + msg);
+ break;
+ }
+ }
+ };
+ mObserver = observer;
+ }
+
+ // Binder calls into this object just enqueue on the main-thread handler
+ @Override
+ public void onUpdate(String currentPackage, BackupProgress backupProgress) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_UPDATE, Pair.create(currentPackage, backupProgress)));
+ }
+
+ @Override
+ public void onResult(String currentPackage, int status) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_FINISHED, status, 0, currentPackage));
+ }
+
+ @Override
+ public void backupFinished(int status) {
+ mHandler.sendMessage(
+ mHandler.obtainMessage(MSG_FINISHED, status, 0));
+ }
+ }
}
diff --git a/core/java/android/app/backup/BackupObserver.java b/core/java/android/app/backup/BackupObserver.java
new file mode 100644
index 0000000..0dd071e
--- /dev/null
+++ b/core/java/android/app/backup/BackupObserver.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.app.backup;
+
+import android.annotation.SystemApi;
+
+/**
+ * Callback class for receiving progress reports during a backup operation. These
+ * methods will all be called on your application's main thread.
+ *
+ * @hide
+ */
+@SystemApi
+public abstract class BackupObserver {
+ /**
+ * This method could be called several times for packages with full data backup.
+ * It will tell how much of backup data is already saved and how much is expected.
+ *
+ * @param currentBackupPackage The name of the package that now being backuped.
+ * @param backupProgress Current progress of backup for the package.
+ */
+ public void onUpdate(String currentBackupPackage, BackupProgress backupProgress) {
+ }
+
+ /**
+ * The backup of single package has completed. This method will be called at most one time
+ * for each package and could be not called if backup is failed before and
+ * backupFinished() is called.
+ *
+ * @param currentBackupPackage The name of the package that was backuped.
+ * @param status Zero on success; a nonzero error code if the backup operation failed.
+ */
+ public void onResult(String currentBackupPackage, int status) {
+ }
+
+ /**
+ * The backup process has completed. This method will always be called,
+ * even if no individual package backup operations were attempted.
+ *
+ * @param status Zero on success; a nonzero error code if the backup operation
+ * as a whole failed.
+ */
+ public void backupFinished(int status) {
+ }
+}
diff --git a/core/java/android/app/backup/BackupProgress.aidl b/core/java/android/app/backup/BackupProgress.aidl
new file mode 100644
index 0000000..c10b9a2
--- /dev/null
+++ b/core/java/android/app/backup/BackupProgress.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+parcelable BackupProgress;
\ No newline at end of file
diff --git a/core/java/android/app/backup/BackupProgress.java b/core/java/android/app/backup/BackupProgress.java
new file mode 100644
index 0000000..32e6212
--- /dev/null
+++ b/core/java/android/app/backup/BackupProgress.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.app.backup;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Information about current progress of full data backup
+ * Used in {@link BackupObserver#onUpdate(String, BackupProgress)}
+ *
+ * @hide
+ */
+@SystemApi
+public class BackupProgress implements Parcelable {
+
+ /**
+ * Expected size of data in full backup.
+ */
+ public final long bytesExpected;
+ /**
+ * Amount of backup data that is already saved in backup.
+ */
+ public final long bytesTransferred;
+
+ public BackupProgress(long _bytesExpected, long _bytesTransferred) {
+ bytesExpected = _bytesExpected;
+ bytesTransferred = _bytesTransferred;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel out, int flags) {
+ out.writeLong(bytesExpected);
+ out.writeLong(bytesTransferred);
+ }
+
+ public static final Creator<BackupProgress> CREATOR = new Creator<BackupProgress>() {
+ public BackupProgress createFromParcel(Parcel in) {
+ return new BackupProgress(in);
+ }
+
+ public BackupProgress[] newArray(int size) {
+ return new BackupProgress[size];
+ }
+ };
+
+ private BackupProgress(Parcel in) {
+ bytesExpected = in.readLong();
+ bytesTransferred = in.readLong();
+ }
+}
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 954ccef..4363604 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -50,6 +50,10 @@
public static final int AGENT_ERROR = -1003;
public static final int AGENT_UNKNOWN = -1004;
+ // Indicates that operation was initiated by user, not a scheduled one.
+ // Transport should ignore its own moratoriums for call with this flag set.
+ public static final int FLAG_USER_INITIATED = 1;
+
IBackupTransport mBinderImpl = new TransportImpl();
public IBinder getBinder() {
@@ -228,13 +232,10 @@
*
* @param packageInfo The identity of the application whose data is being backed up.
* This specifically includes the signature list for the package.
- * @param data The data stream that resulted from invoking the application's
+ * @param inFd Descriptor of file with data that resulted from invoking the application's
* BackupService.doBackup() method. This may be a pipe rather than a file on
* persistent media, so it may not be seekable.
- * @param wipeAllFirst When true, <i>all</i> backed-up data for the current device/account
- * must be erased prior to the storage of the data provided here. The purpose of this
- * is to provide a guarantee that no stale data exists in the restore set when the
- * device begins providing incremental backups.
+ * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0.
* @return one of {@link BackupTransport#TRANSPORT_OK} (OK so far),
* {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} (to suppress backup of this
* specific package, but allow others to proceed),
@@ -242,6 +243,14 @@
* {@link BackupTransport#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
* become lost due to inactivity purge or some other reason and needs re-initializing)
*/
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) {
+ return performBackup(packageInfo, inFd);
+ }
+
+ /**
+ * Legacy version of {@link #performBackup(PackageInfo, ParcelFileDescriptor, int)} that
+ * doesn't use flags parameter.
+ */
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd) {
return BackupTransport.TRANSPORT_ERROR;
}
@@ -392,11 +401,21 @@
* close this file descriptor now; otherwise it should be cached for use during
* succeeding calls to {@link #sendBackupData(int)}, and closed in response to
* {@link #finishBackup()}.
+ * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0.
* @return TRANSPORT_PACKAGE_REJECTED to indicate that the stated application is not
* to be backed up; TRANSPORT_OK to indicate that the OS may proceed with delivering
* backup data; TRANSPORT_ERROR to indicate a fatal error condition that precludes
* performing a backup at this time.
*/
+ public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket,
+ int flags) {
+ return performFullBackup(targetPackage, socket);
+ }
+
+ /**
+ * Legacy version of {@link #performFullBackup(PackageInfo, ParcelFileDescriptor, int)} that
+ * doesn't use flags parameter.
+ */
public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) {
return BackupTransport.TRANSPORT_PACKAGE_REJECTED;
}
@@ -568,9 +587,9 @@
}
@Override
- public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd)
+ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags)
throws RemoteException {
- return BackupTransport.this.performBackup(packageInfo, inFd);
+ return BackupTransport.this.performBackup(packageInfo, inFd, flags);
}
@Override
@@ -619,8 +638,9 @@
}
@Override
- public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) throws RemoteException {
- return BackupTransport.this.performFullBackup(targetPackage, socket);
+ public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket,
+ int flags) throws RemoteException {
+ return BackupTransport.this.performFullBackup(targetPackage, socket, flags);
}
@Override
diff --git a/core/java/android/app/backup/IBackupManager.aidl b/core/java/android/app/backup/IBackupManager.aidl
index 87e4ef1..2a1c00f 100644
--- a/core/java/android/app/backup/IBackupManager.aidl
+++ b/core/java/android/app/backup/IBackupManager.aidl
@@ -16,6 +16,7 @@
package android.app.backup;
+import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
import android.os.ParcelFileDescriptor;
@@ -326,4 +327,19 @@
* no suitable data is available.
*/
long getAvailableRestoreToken(String packageName);
+
+ /**
+ * Request an immediate backup, providing an observer to which results of the backup operation
+ * will be published. The Android backup system will decide for each package whether it will
+ * be full app data backup or key/value-pair-based backup.
+ *
+ * <p>If this method returns zero (meaning success), the OS will attempt to backup all provided
+ * packages using the remote transport.
+ *
+ * @param observer The {@link BackupObserver} to receive callbacks during the backup
+ * operation.
+ *
+ * @return Zero on success; nonzero on error.
+ */
+ int requestBackup(in String[] packages, IBackupObserver observer);
}
diff --git a/core/java/android/app/backup/IBackupObserver.aidl b/core/java/android/app/backup/IBackupObserver.aidl
new file mode 100644
index 0000000..821a589
--- /dev/null
+++ b/core/java/android/app/backup/IBackupObserver.aidl
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import android.app.backup.BackupProgress;
+
+/**
+ * Callback class for receiving progress reports during a backup operation. These
+ * methods will all be called on your application's main thread.
+ *
+ * @hide
+ */
+oneway interface IBackupObserver {
+ /**
+ * This method could be called several times for packages with full data backup.
+ * It will tell how much of backup data is already saved and how much is expected.
+ *
+ * @param currentBackupPackage The name of the package that now being backuped.
+ * @param backupProgress Current progress of backup for the package.
+ */
+ void onUpdate(String currentPackage, in BackupProgress backupProgress);
+
+ /**
+ * The backup of single package has completed. This method will be called at most one time
+ * for each package and could be not called if backup is failed before and
+ * backupFinished() is called.
+ *
+ * @param currentBackupPackage The name of the package that was backuped.
+ * @param status Zero on success; a nonzero error code if the backup operation failed.
+ */
+ void onResult(String currentPackage, int status);
+
+ /**
+ * The backup process has completed. This method will always be called,
+ * even if no individual package backup operations were attempted.
+ *
+ * @param status Zero on success; a nonzero error code if the backup operation
+ * as a whole failed.
+ */
+ void backupFinished(int status);
+}
diff --git a/core/java/android/app/backup/RestoreSet.java b/core/java/android/app/backup/RestoreSet.java
index aacaf7c..4a6316c 100644
--- a/core/java/android/app/backup/RestoreSet.java
+++ b/core/java/android/app/backup/RestoreSet.java
@@ -58,7 +58,6 @@
token = _token;
}
-
// Parcelable implementation
public int describeContents() {
return 0;
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 083d6c7..b1fc20d 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -133,19 +133,16 @@
*
* @param packageInfo The identity of the application whose data is being backed up.
* This specifically includes the signature list for the package.
- * @param data The data stream that resulted from invoking the application's
+ * @param inFd Descriptor of file with data that resulted from invoking the application's
* BackupService.doBackup() method. This may be a pipe rather than a file on
* persistent media, so it may not be seekable.
- * @param wipeAllFirst When true, <i>all</i> backed-up data for the current device/account
- * will be erased prior to the storage of the data provided here. The purpose of this
- * is to provide a guarantee that no stale data exists in the restore set when the
- * device begins providing backups.
+ * @param flags Some of {@link BackupTransport#FLAG_USER_INITIATED}.
* @return one of {@link BackupConstants#TRANSPORT_OK} (OK so far),
* {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure), or
* {@link BackupConstants#TRANSPORT_NOT_INITIALIZED} (if the backend dataset has
* become lost due to inactive expiry or some other reason and needs re-initializing)
*/
- int performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd);
+ int performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd, int flags);
/**
* Erase the give application's data from the backup destination. This clears
@@ -237,7 +234,7 @@
// full backup stuff
long requestFullBackupTime();
- int performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket);
+ int performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket, int flags);
int checkFullBackupSize(long size);
int sendBackupData(int numBytes);
void cancelFullBackup();
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 2264c69..ebe2ec5 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -27,9 +27,12 @@
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
+import android.app.backup.BackupManager;
+import android.app.backup.BackupProgress;
import android.app.backup.BackupTransport;
import android.app.backup.FullBackup;
import android.app.backup.FullBackupDataOutput;
+import android.app.backup.IBackupObserver;
import android.app.backup.RestoreDescription;
import android.app.backup.RestoreSet;
import android.app.backup.IBackupManager;
@@ -217,6 +220,7 @@
private static final int MSG_RETRY_CLEAR = 12;
private static final int MSG_WIDGET_BROADCAST = 13;
private static final int MSG_RUN_FULL_TRANSPORT_BACKUP = 14;
+ private static final int MSG_REQUEST_BACKUP = 15;
// backup task state machine tick
static final int MSG_BACKUP_RESTORE_STEP = 20;
@@ -527,6 +531,25 @@
}
}
+ class BackupParams {
+ public IBackupTransport transport;
+ public String dirName;
+ public ArrayList<String> kvPackages;
+ public ArrayList<String> fullPackages;
+ public IBackupObserver observer;
+ public boolean userInitiated;
+
+ BackupParams(IBackupTransport transport, String dirName, ArrayList<String> kvPackages,
+ ArrayList<String> fullPackages, IBackupObserver observer, boolean userInitiated) {
+ this.transport = transport;
+ this.dirName = dirName;
+ this.kvPackages = kvPackages;
+ this.fullPackages = fullPackages;
+ this.observer = observer;
+ this.userInitiated = userInitiated;
+ }
+ }
+
// Bookkeeping of in-flight operations for timeout etc. purposes. The operation
// token is the index of the entry in the pending-operations list.
static final int OP_PENDING = 0;
@@ -714,7 +737,7 @@
try {
String dirName = transport.transportDirName();
PerformBackupTask pbt = new PerformBackupTask(transport, dirName,
- queue, oldJournal);
+ queue, oldJournal, null, null, false);
Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
sendMessage(pbtMessage);
} catch (RemoteException e) {
@@ -937,6 +960,26 @@
mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
break;
}
+
+ case MSG_REQUEST_BACKUP:
+ {
+ BackupParams params = (BackupParams)msg.obj;
+ if (MORE_DEBUG) {
+ Slog.d(TAG, "MSG_REQUEST_BACKUP observer=" + params.observer);
+ }
+ ArrayList<BackupRequest> kvQueue = new ArrayList<>();
+ for (String packageName : params.kvPackages) {
+ kvQueue.add(new BackupRequest(packageName));
+ }
+ mBackupRunning = true;
+ mWakelock.acquire();
+
+ PerformBackupTask pbt = new PerformBackupTask(params.transport, params.dirName,
+ kvQueue, null, params.observer, params.fullPackages, true);
+ Message pbtMessage = obtainMessage(MSG_BACKUP_RESTORE_STEP, pbt);
+ sendMessage(pbtMessage);
+ break;
+ }
}
}
}
@@ -2325,6 +2368,63 @@
return token;
}
+ public int requestBackup(String[] packages, IBackupObserver observer) {
+ mContext.enforceCallingPermission(android.Manifest.permission.BACKUP, "requestBackup");
+
+ if (packages == null || packages.length < 1) {
+ Slog.e(TAG, "No packages named for backup request");
+ sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
+ throw new IllegalArgumentException("No packages are provided for backup");
+ }
+
+ IBackupTransport transport = getTransport(mCurrentTransport);
+ if (transport == null) {
+ sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
+ return BackupManager.ERROR_TRANSPORT_ABORTED;
+ }
+
+ ArrayList<String> fullBackupList = new ArrayList<>();
+ ArrayList<String> kvBackupList = new ArrayList<>();
+ for (String packageName : packages) {
+ try {
+ PackageInfo packageInfo = mPackageManager.getPackageInfo(packageName,
+ PackageManager.GET_SIGNATURES);
+ if (!appIsEligibleForBackup(packageInfo.applicationInfo)) {
+ sendBackupOnResult(observer, packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
+ continue;
+ }
+ if (appGetsFullBackup(packageInfo)) {
+ fullBackupList.add(packageInfo.packageName);
+ } else {
+ kvBackupList.add(packageInfo.packageName);
+ }
+ } catch (NameNotFoundException e) {
+ sendBackupOnResult(observer, packageName, BackupManager.ERROR_PACKAGE_NOT_FOUND);
+ }
+ }
+ EventLog.writeEvent(EventLogTags.BACKUP_REQUESTED, packages.length, kvBackupList.size(),
+ fullBackupList.size());
+ if (MORE_DEBUG) {
+ Slog.i(TAG, "Backup requested for " + packages.length + " packages, of them: " +
+ fullBackupList.size() + " full backups, " + kvBackupList.size() + " k/v backups");
+ }
+
+ String dirName;
+ try {
+ dirName = transport.transportDirName();
+ } catch (RemoteException e) {
+ Slog.e(TAG, "Transport became unavailable while attempting backup");
+ sendBackupFinished(observer, BackupManager.ERROR_TRANSPORT_ABORTED);
+ return BackupManager.ERROR_TRANSPORT_ABORTED;
+ }
+ Message msg = mBackupHandler.obtainMessage(MSG_REQUEST_BACKUP);
+ msg.obj = new BackupParams(transport, dirName, kvBackupList, fullBackupList, observer,
+ true);
+ mBackupHandler.sendMessage(msg);
+ return BackupManager.SUCCESS;
+ }
+
// -----
// Interface and methods used by the asynchronous-with-timeout backup/restore operations
@@ -2424,6 +2524,8 @@
File mStateDir;
File mJournal;
BackupState mCurrentState;
+ ArrayList<String> mPendingFullBackups;
+ IBackupObserver mObserver;
// carried information about the current in-flight operation
IBackupAgent mAgentBinder;
@@ -2436,12 +2538,17 @@
ParcelFileDescriptor mNewState;
int mStatus;
boolean mFinished;
+ boolean mUserInitiated;
public PerformBackupTask(IBackupTransport transport, String dirName,
- ArrayList<BackupRequest> queue, File journal) {
+ ArrayList<BackupRequest> queue, File journal, IBackupObserver observer,
+ ArrayList<String> pendingFullBackups, boolean userInitiated) {
mTransport = transport;
mOriginalQueue = queue;
mJournal = journal;
+ mObserver = observer;
+ mPendingFullBackups = pendingFullBackups;
+ mUserInitiated = userInitiated;
mStateDir = new File(mBaseStateDir, dirName);
@@ -2493,9 +2600,10 @@
mStatus = BackupTransport.TRANSPORT_OK;
// Sanity check: if the queue is empty we have no work to do.
- if (mOriginalQueue.isEmpty()) {
+ if (mOriginalQueue.isEmpty() && mPendingFullBackups.isEmpty()) {
Slog.w(TAG, "Backup begun with an empty queue - nothing to do.");
addBackupTrace("queue empty at begin");
+ sendBackupFinished(mObserver, BackupManager.SUCCESS);
executeNextState(BackupState.FINAL);
return;
}
@@ -2579,6 +2687,8 @@
// if things went wrong at this point, we need to
// restage everything and try again later.
resetBackupState(mStateDir); // Just to make sure.
+ // In case of any other error, it's backup transport error.
+ sendBackupFinished(mObserver, BackupManager.ERROR_TRANSPORT_ABORTED);
executeNextState(BackupState.FINAL);
}
}
@@ -2620,6 +2730,10 @@
Slog.i(TAG, "Package " + request.packageName
+ " no longer supports backup; skipping");
addBackupTrace("skipping - not eligible, completion is noop");
+ // Shouldn't happen in case of requested backup, as pre-check was done in
+ // #requestBackup(), except to app update done concurrently
+ sendBackupOnResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
executeNextState(BackupState.RUNNING_QUEUE);
return;
}
@@ -2631,6 +2745,10 @@
Slog.i(TAG, "Package " + request.packageName
+ " requests full-data rather than key/value; skipping");
addBackupTrace("skipping - fullBackupOnly, completion is noop");
+ // Shouldn't happen in case of requested backup, as pre-check was done in
+ // #requestBackup()
+ sendBackupOnResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
executeNextState(BackupState.RUNNING_QUEUE);
return;
}
@@ -2640,6 +2758,8 @@
// and not yet launched out of that state, so just as it won't
// receive broadcasts, we won't run it for backup.
addBackupTrace("skipping - stopped");
+ sendBackupOnResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
executeNextState(BackupState.RUNNING_QUEUE);
return;
}
@@ -2687,10 +2807,14 @@
dataChangedImpl(request.packageName);
mStatus = BackupTransport.TRANSPORT_OK;
if (mQueue.isEmpty()) nextState = BackupState.FINAL;
+ sendBackupOnResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_AGENT_FAILURE);
} else if (mStatus == BackupTransport.AGENT_UNKNOWN) {
// Failed lookup of the app, so we couldn't bring up an agent, but
// we're otherwise fine. Just drop it and go on to the next as usual.
mStatus = BackupTransport.TRANSPORT_OK;
+ sendBackupOnResult(mObserver, mCurrentPackage.packageName,
+ BackupManager.ERROR_PACKAGE_NOT_FOUND);
} else {
// Transport-level failure means we reenqueue everything
revertAndEndBackup();
@@ -2745,9 +2869,37 @@
}
}
- // Only once we're entirely finished do we release the wakelock
clearBackupTrace();
- Slog.i(BackupManagerService.TAG, "Backup pass finished.");
+
+ if (mStatus == BackupTransport.TRANSPORT_OK &&
+ mPendingFullBackups != null && !mPendingFullBackups.isEmpty()) {
+ Slog.d(TAG, "Starting full backups for: " + mPendingFullBackups);
+ CountDownLatch latch = new CountDownLatch(1);
+ String[] fullBackups =
+ mPendingFullBackups.toArray(new String[mPendingFullBackups.size()]);
+ PerformFullTransportBackupTask task =
+ new PerformFullTransportBackupTask(/*fullBackupRestoreObserver*/ null,
+ fullBackups, /*updateSchedule*/ false, /*runningJob*/ null, latch,
+ mObserver, mUserInitiated);
+ // Acquiring wakelock for PerformFullTransportBackupTask before its start.
+ mWakelock.acquire();
+ (new Thread(task, "full-transport-requested")).start();
+ } else {
+ switch (mStatus) {
+ case BackupTransport.TRANSPORT_OK:
+ sendBackupFinished(mObserver, BackupManager.SUCCESS);
+ break;
+ case BackupTransport.TRANSPORT_NOT_INITIALIZED:
+ sendBackupFinished(mObserver, BackupManager.ERROR_TRANSPORT_ABORTED);
+ break;
+ case BackupTransport.TRANSPORT_ERROR:
+ default:
+ sendBackupFinished(mObserver, BackupManager.ERROR_TRANSPORT_ABORTED);
+ break;
+ }
+ }
+ Slog.i(BackupManagerService.TAG, "K/V backup pass finished.");
+ // Only once we're entirely finished do we release the wakelock for k/v backup.
mWakelock.release();
}
@@ -2954,6 +3106,8 @@
EventLog.writeEvent(EventLogTags.BACKUP_AGENT_FAILURE, pkgName,
"bad key");
mBackupHandler.removeMessages(MSG_TIMEOUT);
+ sendBackupOnResult(mObserver, pkgName,
+ BackupManager.ERROR_AGENT_FAILURE);
agentErrorCleanup();
// agentErrorCleanup() implicitly executes next state properly
return;
@@ -2998,7 +3152,8 @@
backupData = ParcelFileDescriptor.open(mBackupDataName,
ParcelFileDescriptor.MODE_READ_ONLY);
addBackupTrace("sending data to transport");
- mStatus = mTransport.performBackup(mCurrentPackage, backupData);
+ int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
+ mStatus = mTransport.performBackup(mCurrentPackage, backupData, flags);
}
// TODO - We call finishBackup() for each application backed up, because
@@ -3025,6 +3180,7 @@
// with the new state file it just created.
mBackupDataName.delete();
mNewStateName.renameTo(mSavedStateName);
+ sendBackupOnResult(mObserver, pkgName, BackupManager.SUCCESS);
EventLog.writeEvent(EventLogTags.BACKUP_PACKAGE, pkgName, size);
logBackupComplete(pkgName);
} else if (mStatus == BackupTransport.TRANSPORT_PACKAGE_REJECTED) {
@@ -3032,12 +3188,16 @@
// back but proceed with running the rest of the queue.
mBackupDataName.delete();
mNewStateName.delete();
+ sendBackupOnResult(mObserver, pkgName,
+ BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
EventLogTags.writeBackupAgentFailure(pkgName, "Transport rejected");
} else {
// Actual transport-level failure to communicate the data to the backend
+ sendBackupOnResult(mObserver, pkgName, BackupManager.ERROR_TRANSPORT_ABORTED);
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
}
} catch (Exception e) {
+ sendBackupOnResult(mObserver, pkgName, BackupManager.ERROR_TRANSPORT_ABORTED);
Slog.e(TAG, "Transport error backing up " + pkgName, e);
EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_FAILURE, pkgName);
mStatus = BackupTransport.TRANSPORT_ERROR;
@@ -3285,6 +3445,8 @@
* or one of the other BackupTransport.* error codes as appropriate
*/
int preflightFullBackup(PackageInfo pkg, IBackupAgent agent);
+
+ long expectedSize();
};
class FullBackupEngine {
@@ -3992,16 +4154,21 @@
CountDownLatch mLatch;
AtomicBoolean mKeepRunning; // signal from job scheduler
FullBackupJob mJob; // if a scheduled job needs to be finished afterwards
+ IBackupObserver mBackupObserver;
+ boolean mUserInitiated;
PerformFullTransportBackupTask(IFullBackupRestoreObserver observer,
String[] whichPackages, boolean updateSchedule,
- FullBackupJob runningJob, CountDownLatch latch) {
+ FullBackupJob runningJob, CountDownLatch latch, IBackupObserver backupObserver,
+ boolean userInitiated) {
super(observer);
mUpdateSchedule = updateSchedule;
mLatch = latch;
mKeepRunning = new AtomicBoolean(true);
mJob = runningJob;
mPackages = new ArrayList<PackageInfo>(whichPackages.length);
+ mBackupObserver = backupObserver;
+ mUserInitiated = userInitiated;
for (String pkg : whichPackages) {
try {
@@ -4015,6 +4182,8 @@
if (MORE_DEBUG) {
Slog.d(TAG, "Ignoring opted-out package " + pkg);
}
+ sendBackupOnResult(mBackupObserver, pkg,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
} else if ((info.applicationInfo.uid < Process.FIRST_APPLICATION_UID)
&& (info.applicationInfo.backupAgentName == null)) {
@@ -4023,6 +4192,8 @@
if (MORE_DEBUG) {
Slog.d(TAG, "Ignoring non-agent system package " + pkg);
}
+ sendBackupOnResult(mBackupObserver, pkg,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
} else if ((info.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) {
// Cull any packages in the 'stopped' state: they've either just been
@@ -4031,6 +4202,8 @@
if (MORE_DEBUG) {
Slog.d(TAG, "Ignoring stopped package " + pkg);
}
+ sendBackupOnResult(mBackupObserver, pkg,
+ BackupManager.ERROR_BACKUP_NOT_ALLOWED);
continue;
}
mPackages.add(info);
@@ -4063,17 +4236,20 @@
+ " p=" + mProvisioned + "; ignoring");
}
mUpdateSchedule = false;
+ sendBackupFinished(mBackupObserver, BackupManager.ERROR_BACKUP_NOT_ALLOWED);
return;
}
IBackupTransport transport = getTransport(mCurrentTransport);
if (transport == null) {
Slog.w(TAG, "Transport not present; full data backup not performed");
+ sendBackupFinished(mBackupObserver, BackupManager.ERROR_TRANSPORT_ABORTED);
return;
}
// Set up to send data to the transport
final int N = mPackages.size();
+ final byte[] buffer = new byte[8192];
for (int i = 0; i < N; i++) {
currentPackage = mPackages.get(i);
if (DEBUG) {
@@ -4086,8 +4262,9 @@
transportPipes = ParcelFileDescriptor.createPipe();
// Tell the transport the data's coming
+ int flags = mUserInitiated ? BackupTransport.FLAG_USER_INITIATED : 0;
int result = transport.performFullBackup(currentPackage,
- transportPipes[0]);
+ transportPipes[0], flags);
if (result == BackupTransport.TRANSPORT_OK) {
// The transport has its own copy of the read end of the pipe,
// so close ours now
@@ -4114,7 +4291,13 @@
enginePipes[0].getFileDescriptor());
FileOutputStream out = new FileOutputStream(
transportPipes[1].getFileDescriptor());
- byte[] buffer = new byte[8192];
+ long totalRead = 0;
+ final long expectedSize = backupRunner.expectedSize();
+ if (expectedSize < 0) {
+ result = BackupTransport.AGENT_ERROR;
+ sendBackupOnResult(mBackupObserver, currentPackage.packageName,
+ BackupManager.ERROR_AGENT_FAILURE);
+ }
int nRead = 0;
do {
if (!mKeepRunning.get()) {
@@ -4130,6 +4313,11 @@
if (nRead > 0) {
out.write(buffer, 0, nRead);
result = transport.sendBackupData(nRead);
+ totalRead += nRead;
+ if (mBackupObserver != null && expectedSize > 0) {
+ sendBackupOnUpdate(mBackupObserver, currentPackage.packageName,
+ new BackupProgress(expectedSize, totalRead));
+ }
}
} while (nRead > 0 && result == BackupTransport.TRANSPORT_OK);
@@ -4183,16 +4371,22 @@
}
EventLog.writeEvent(EventLogTags.FULL_BACKUP_AGENT_FAILURE,
currentPackage.packageName, "transport rejected");
+ sendBackupOnResult(mBackupObserver, currentPackage.packageName,
+ BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED);
// do nothing, clean up, and continue looping
} else if (result != BackupTransport.TRANSPORT_OK) {
Slog.w(TAG, "Transport failed; aborting backup: " + result);
EventLog.writeEvent(EventLogTags.FULL_BACKUP_TRANSPORT_FAILURE);
+ sendBackupOnResult(mBackupObserver, currentPackage.packageName,
+ BackupManager.ERROR_TRANSPORT_ABORTED);
return;
} else {
// Success!
EventLog.writeEvent(EventLogTags.FULL_BACKUP_SUCCESS,
currentPackage.packageName);
logBackupComplete(currentPackage.packageName);
+ sendBackupOnResult(mBackupObserver, currentPackage.packageName,
+ BackupManager.SUCCESS);
}
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
@@ -4202,8 +4396,10 @@
if (DEBUG) {
Slog.i(TAG, "Full backup completed.");
}
+ sendBackupFinished(mBackupObserver, BackupManager.SUCCESS);
} catch (Exception e) {
Slog.w(TAG, "Exception trying full transport backup", e);
+ sendBackupFinished(mBackupObserver, BackupManager.ERROR_TRANSPORT_ABORTED);
} finally {
cleanUpPipes(transportPipes);
cleanUpPipes(enginePipes);
@@ -4216,6 +4412,7 @@
mRunningFullBackupTask = null;
}
+
mLatch.countDown();
// Now that we're actually done with schedule-driven work, reschedule
@@ -4223,6 +4420,8 @@
if (mUpdateSchedule) {
scheduleNextFullBackupJob(backoff);
}
+ Slog.i(BackupManagerService.TAG, "Full data backup pass finished.");
+ mWakelock.release();
}
}
@@ -4311,7 +4510,16 @@
mResult.set(BackupTransport.AGENT_ERROR);
mLatch.countDown();
}
-
+
+ @Override
+ public long expectedSize() {
+ try {
+ mLatch.await();
+ return mResult.get();
+ } catch (InterruptedException e) {
+ return BackupTransport.NO_MORE_DATA;
+ }
+ }
}
class SinglePackageBackupRunner implements Runnable {
@@ -4346,6 +4554,10 @@
}
}
}
+
+ long expectedSize() {
+ return mPreflight.expectedSize();
+ }
}
}
@@ -4544,7 +4756,9 @@
CountDownLatch latch = new CountDownLatch(1);
String[] pkg = new String[] {entry.packageName};
mRunningFullBackupTask = new PerformFullTransportBackupTask(null, pkg, true,
- scheduledJob, latch);
+ scheduledJob, latch, null, false /* userInitiated */);
+ // Acquiring wakelock for PerformFullTransportBackupTask before its start.
+ mWakelock.acquire();
(new Thread(mRunningFullBackupTask)).start();
}
@@ -8702,8 +8916,10 @@
}
CountDownLatch latch = new CountDownLatch(1);
- PerformFullTransportBackupTask task =
- new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
+ PerformFullTransportBackupTask task = new PerformFullTransportBackupTask(null, pkgNames,
+ false, null, latch, null, false /* userInitiated */);
+ // Acquiring wakelock for PerformFullTransportBackupTask before its start.
+ mWakelock.acquire();
(new Thread(task, "full-transport-master")).start();
do {
try {
@@ -9727,4 +9943,42 @@
}
}
}
+
+ private static void sendBackupOnUpdate(IBackupObserver observer, String packageName,
+ BackupProgress progress) {
+ if (observer != null) {
+ try {
+ observer.onUpdate(packageName, progress);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Backup observer went away: onUpdate");
+ }
+ }
+ }
+ }
+
+ private static void sendBackupOnResult(IBackupObserver observer, String packageName,
+ int status) {
+ if (observer != null) {
+ try {
+ observer.onResult(packageName, status);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Backup observer went away: onResult");
+ }
+ }
+ }
+ }
+
+ private static void sendBackupFinished(IBackupObserver observer, int status) {
+ if (observer != null) {
+ try {
+ observer.backupFinished(status);
+ } catch (RemoteException e) {
+ if (DEBUG) {
+ Slog.w(TAG, "Backup observer went away: backupFinished");
+ }
+ }
+ }
+ }
}
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index a51ab55..505a1a5 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -17,6 +17,7 @@
package com.android.server.backup;
import android.app.backup.IBackupManager;
+import android.app.backup.IBackupObserver;
import android.app.backup.IFullBackupRestoreObserver;
import android.app.backup.IRestoreSession;
import android.content.Context;
@@ -325,6 +326,12 @@
}
@Override
+ public int requestBackup(String[] packages, IBackupObserver observer) throws RemoteException {
+ BackupManagerService svc = mService;
+ return (svc != null) ? svc.requestBackup(packages, observer) : null;
+ }
+
+ @Override
public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG);
diff --git a/services/core/java/com/android/server/EventLogTags.logtags b/services/core/java/com/android/server/EventLogTags.logtags
index 516e2f4..7bf1dea 100644
--- a/services/core/java/com/android/server/EventLogTags.logtags
+++ b/services/core/java/com/android/server/EventLogTags.logtags
@@ -107,6 +107,7 @@
2825 backup_success (Packages|1|1),(Time|1|3)
2826 backup_reset (Transport|3)
2827 backup_initialize
+2828 backup_requested (Total|1|1),(Key-Value|1|1),(Full|1|1)
2830 restore_start (Transport|3),(Source|2|5)
2831 restore_transport_failure
2832 restore_agent_failure (Package|3),(Message|3)