diff options
| author | 2021-12-20 06:45:19 +0000 | |
|---|---|---|
| committer | 2021-12-20 06:45:19 +0000 | |
| commit | 4c44a2f18a80329aba7445a4277d31c450f4d308 (patch) | |
| tree | 7d26e0c2f4d935038079d0101407e3b4493892c2 | |
| parent | 67a92323a7b7d0de38bd934efe735ab1fa01d0e9 (diff) | |
| parent | 6aba9656ec6752d65b53aefad6bf4bb6ec63c4ef (diff) | |
Merge changes from topic "async-transport"
* changes:
Update BackupTransportCallback to use async AIDL
Make IBackupTransport AIDL async
6 files changed, 682 insertions, 186 deletions
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java index 567eb4a68a1d..9bb048d7a75f 100644 --- a/core/java/android/app/backup/BackupTransport.java +++ b/core/java/android/app/backup/BackupTransport.java @@ -25,6 +25,11 @@ import android.os.ParcelFileDescriptor; import android.os.RemoteException; import com.android.internal.backup.IBackupTransport; +import com.android.internal.backup.ITransportStatusCallback; +import com.android.internal.infra.AndroidFuture; + +import java.util.Arrays; +import java.util.List; /** * Concrete class that provides a stable-API bridge between IBackupTransport @@ -659,141 +664,185 @@ public class BackupTransport { class TransportImpl extends IBackupTransport.Stub { @Override - public String name() throws RemoteException { - return BackupTransport.this.name(); + public void name(AndroidFuture<String> resultFuture) throws RemoteException { + String result = BackupTransport.this.name(); + resultFuture.complete(result); } @Override - public Intent configurationIntent() throws RemoteException { - return BackupTransport.this.configurationIntent(); + public void configurationIntent(AndroidFuture<Intent> resultFuture) + throws RemoteException { + Intent result = BackupTransport.this.configurationIntent(); + resultFuture.complete(result); } @Override - public String currentDestinationString() throws RemoteException { - return BackupTransport.this.currentDestinationString(); + public void currentDestinationString(AndroidFuture<String> resultFuture) + throws RemoteException { + String result = BackupTransport.this.currentDestinationString(); + resultFuture.complete(result); } @Override - public Intent dataManagementIntent() { - return BackupTransport.this.dataManagementIntent(); + public void dataManagementIntent(AndroidFuture<Intent> resultFuture) + throws RemoteException { + Intent result = BackupTransport.this.dataManagementIntent(); + resultFuture.complete(result); } @Override - public CharSequence dataManagementIntentLabel() { - return BackupTransport.this.dataManagementIntentLabel(); + public void dataManagementIntentLabel(AndroidFuture<CharSequence> resultFuture) + throws RemoteException { + CharSequence result = BackupTransport.this.dataManagementIntentLabel(); + resultFuture.complete(result); } @Override - public String transportDirName() throws RemoteException { - return BackupTransport.this.transportDirName(); + public void transportDirName(AndroidFuture<String> resultFuture) throws RemoteException { + String result = BackupTransport.this.transportDirName(); + resultFuture.complete(result); } @Override - public long requestBackupTime() throws RemoteException { - return BackupTransport.this.requestBackupTime(); + public void requestBackupTime(AndroidFuture<Long> resultFuture) throws RemoteException { + long result = BackupTransport.this.requestBackupTime(); + resultFuture.complete(result); } @Override - public int initializeDevice() throws RemoteException { - return BackupTransport.this.initializeDevice(); + public void initializeDevice(ITransportStatusCallback callback) throws RemoteException { + int result = BackupTransport.this.initializeDevice(); + callback.onOperationCompleteWithStatus(result); } @Override - public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) - throws RemoteException { - return BackupTransport.this.performBackup(packageInfo, inFd, flags); + public void performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags, + ITransportStatusCallback callback) throws RemoteException { + int result = BackupTransport.this.performBackup(packageInfo, inFd, flags); + callback.onOperationCompleteWithStatus(result); } @Override - public int clearBackupData(PackageInfo packageInfo) throws RemoteException { - return BackupTransport.this.clearBackupData(packageInfo); + public void clearBackupData(PackageInfo packageInfo, ITransportStatusCallback callback) + throws RemoteException { + int result = BackupTransport.this.clearBackupData(packageInfo); + callback.onOperationCompleteWithStatus(result); } @Override - public int finishBackup() throws RemoteException { - return BackupTransport.this.finishBackup(); + public void finishBackup(ITransportStatusCallback callback) throws RemoteException { + int result = BackupTransport.this.finishBackup(); + callback.onOperationCompleteWithStatus(result); } @Override - public RestoreSet[] getAvailableRestoreSets() throws RemoteException { - return BackupTransport.this.getAvailableRestoreSets(); + public void getAvailableRestoreSets(AndroidFuture<List<RestoreSet>> resultFuture) + throws RemoteException { + RestoreSet[] result = BackupTransport.this.getAvailableRestoreSets(); + resultFuture.complete(Arrays.asList(result)); } @Override - public long getCurrentRestoreSet() throws RemoteException { - return BackupTransport.this.getCurrentRestoreSet(); + public void getCurrentRestoreSet(AndroidFuture<Long> resultFuture) + throws RemoteException { + long result = BackupTransport.this.getCurrentRestoreSet(); + resultFuture.complete(result); } @Override - public int startRestore(long token, PackageInfo[] packages) throws RemoteException { - return BackupTransport.this.startRestore(token, packages); + public void startRestore(long token, PackageInfo[] packages, + ITransportStatusCallback callback) throws RemoteException { + int result = BackupTransport.this.startRestore(token, packages); + callback.onOperationCompleteWithStatus(result); } @Override - public RestoreDescription nextRestorePackage() throws RemoteException { - return BackupTransport.this.nextRestorePackage(); + public void nextRestorePackage(AndroidFuture<RestoreDescription> resultFuture) + throws RemoteException { + RestoreDescription result = BackupTransport.this.nextRestorePackage(); + resultFuture.complete(result); } @Override - public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException { - return BackupTransport.this.getRestoreData(outFd); + public void getRestoreData(ParcelFileDescriptor outFd, + ITransportStatusCallback callback) throws RemoteException { + int result = BackupTransport.this.getRestoreData(outFd); + callback.onOperationCompleteWithStatus(result); } @Override - public void finishRestore() throws RemoteException { + public void finishRestore(ITransportStatusCallback callback) + throws RemoteException { BackupTransport.this.finishRestore(); + callback.onOperationComplete(); } @Override - public long requestFullBackupTime() throws RemoteException { - return BackupTransport.this.requestFullBackupTime(); + public void requestFullBackupTime(AndroidFuture<Long> resultFuture) + throws RemoteException { + long result = BackupTransport.this.requestFullBackupTime(); + resultFuture.complete(result); } @Override - public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket, - int flags) throws RemoteException { - return BackupTransport.this.performFullBackup(targetPackage, socket, flags); + public void performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket, + int flags, ITransportStatusCallback callback) throws RemoteException { + int result = BackupTransport.this.performFullBackup(targetPackage, socket, flags); + callback.onOperationCompleteWithStatus(result); } @Override - public int checkFullBackupSize(long size) { - return BackupTransport.this.checkFullBackupSize(size); + public void checkFullBackupSize(long size, ITransportStatusCallback callback) + throws RemoteException { + int result = BackupTransport.this.checkFullBackupSize(size); + callback.onOperationCompleteWithStatus(result); } @Override - public int sendBackupData(int numBytes) throws RemoteException { - return BackupTransport.this.sendBackupData(numBytes); + public void sendBackupData(int numBytes, ITransportStatusCallback callback) + throws RemoteException { + int result = BackupTransport.this.sendBackupData(numBytes); + callback.onOperationCompleteWithStatus(result); } @Override - public void cancelFullBackup() throws RemoteException { + public void cancelFullBackup(ITransportStatusCallback callback) throws RemoteException { BackupTransport.this.cancelFullBackup(); + callback.onOperationComplete(); } @Override - public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) - throws RemoteException { - return BackupTransport.this.isAppEligibleForBackup(targetPackage, isFullBackup); + public void isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup, + AndroidFuture<Boolean> resultFuture) throws RemoteException { + boolean result = BackupTransport.this.isAppEligibleForBackup(targetPackage, + isFullBackup); + resultFuture.complete(result); } @Override - public long getBackupQuota(String packageName, boolean isFullBackup) { - return BackupTransport.this.getBackupQuota(packageName, isFullBackup); + public void getBackupQuota(String packageName, boolean isFullBackup, + AndroidFuture<Long> resultFuture) throws RemoteException { + long result = BackupTransport.this.getBackupQuota(packageName, isFullBackup); + resultFuture.complete(result); } @Override - public int getTransportFlags() { - return BackupTransport.this.getTransportFlags(); + public void getTransportFlags(AndroidFuture<Integer> resultFuture) throws RemoteException { + int result = BackupTransport.this.getTransportFlags(); + resultFuture.complete(result); } @Override - public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) { - return BackupTransport.this.getNextFullRestoreDataChunk(socket); + public void getNextFullRestoreDataChunk(ParcelFileDescriptor socket, + ITransportStatusCallback callback) throws RemoteException { + int result = BackupTransport.this.getNextFullRestoreDataChunk(socket); + callback.onOperationCompleteWithStatus(result); } @Override - public int abortFullRestore() { - return BackupTransport.this.abortFullRestore(); + public void abortFullRestore(ITransportStatusCallback callback) throws RemoteException { + int result = BackupTransport.this.abortFullRestore(); + callback.onOperationCompleteWithStatus(result); } } } diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl index c9baf004d798..f09e176beea0 100644 --- a/core/java/com/android/internal/backup/IBackupTransport.aidl +++ b/core/java/com/android/internal/backup/IBackupTransport.aidl @@ -22,39 +22,46 @@ import android.content.Intent; import android.content.pm.PackageInfo; import android.os.ParcelFileDescriptor; +import com.android.internal.backup.ITransportStatusCallback; +import com.android.internal.infra.AndroidFuture; + /** {@hide} */ -interface IBackupTransport { +oneway interface IBackupTransport { /** - * Ask the transport for the name under which it should be registered. This will + * Ask the transport for the String name under which it should be registered. This will * typically be its host service's component name, but need not be. + * + * @param resultFuture an {@link AndroidFuture} that is completed with the {@code String} name + * of the transport. + */ + void name(in AndroidFuture<String> result); + + /** + * Ask the transport for an Intent that can be used to launch any internal + * configuration Activity that it wishes to present. For example, the transport + * may offer a UI for allowing the user to supply login credentials for the + * transport's off-device backend. + * + * If the transport does not supply any user-facing configuration UI, it should + * return null from this method. + * + * @param resultFuture an {@link AndroidFuture} that is completed with an {@code Intent} that + * can be passed to Context.startActivity() in order to launch the transport's + * configuration UI. This future will complete with null if the transport does not + * offer any user-facing configuration UI. + */ + void configurationIntent(in AndroidFuture<Intent> resultFuture); + + /** + * Ask the transport for a one-line string that can be shown to the user that + * describes the current backend destination. For example, a transport that + * can potentially associate backup data with arbitrary user accounts should + * include the name of the currently-active account here. + * + * @param resultFuture an {@link AndroidFuture} that is completed with a {@code String} + * describing the destination to which the transport is currently sending data. */ - String name(); - - /** - * Ask the transport for an Intent that can be used to launch any internal - * configuration Activity that it wishes to present. For example, the transport - * may offer a UI for allowing the user to supply login credentials for the - * transport's off-device backend. - * - * If the transport does not supply any user-facing configuration UI, it should - * return null from this method. - * - * @return An Intent that can be passed to Context.startActivity() in order to - * launch the transport's configuration UI. This method will return null - * if the transport does not offer any user-facing configuration UI. - */ - Intent configurationIntent(); - - /** - * On demand, supply a one-line string that can be shown to the user that - * describes the current backend destination. For example, a transport that - * can potentially associate backup data with arbitrary user accounts should - * include the name of the currently-active account here. - * - * @return A string describing the destination to which the transport is currently - * sending data. This method should not return null. - */ - String currentDestinationString(); + void currentDestinationString(in AndroidFuture<String> resultFuture); /** * Ask the transport for an Intent that can be used to launch a more detailed @@ -71,22 +78,23 @@ interface IBackupTransport { * <p>If the transport does not supply any user-facing data management * UI, then it should return {@code null} from this method. * - * @return An intent that can be passed to Context.startActivity() in order to - * launch the transport's data-management UI. This method will return - * {@code null} if the transport does not offer any user-facing data - * management UI. + * @param resultFuture an {@link AndroidFuture} that is completed with an {@code Intent} that + * can be passed to Context.startActivity() in order to launch the transport's + * data-management UI. The callback will supply {@code null} if the transport does not + * offer any user-facing data management UI. */ - Intent dataManagementIntent(); + void dataManagementIntent(in AndroidFuture<Intent> resultFuture); /** - * On demand, supply a short {@link CharSequence} that can be shown to the user as the label on - * an overflow menu item used to invoke the data management UI. + * Ask the transport for a short {@link CharSequence} that can be shown to the user as the label + * on an overflow menu item used to invoke the data management UI. * - * @return A {@link CharSequence} to be used as the label for the transport's data management - * affordance. If the transport supplies a data management intent, this - * method must not return {@code null}. + * @param resultFuture an {@link AndroidFuture} that is completed with a {@code CharSequence} + * to be used as the label for the transport's data management affordance. If the + * transport supplies a data management Intent via {@link #dataManagementIntent}, + * this method must not return {@code null}. */ - CharSequence dataManagementIntentLabel(); + void dataManagementIntentLabel(in AndroidFuture<CharSequence> resultFuture); /** * Ask the transport where, on local device storage, to keep backup state blobs. @@ -96,11 +104,11 @@ interface IBackupTransport { * available backup transports; the name of the class implementing the transport * is a good choice. This MUST be constant. * - * @return A unique name, suitable for use as a file or directory name, that the - * Backup Manager could use to disambiguate state files associated with - * different backup transports. + * @param resultFuture an {@link AndroidFuture} that is completed with a unique {@code String} + * name, suitable for use as a file or directory name, that the Backup Manager could use + * to disambiguate state files associated with different backup transports. */ - String transportDirName(); + void transportDirName(in AndroidFuture<String> resultFuture); /** * Verify that this is a suitable time for a backup pass. This should return zero @@ -110,10 +118,11 @@ interface IBackupTransport { * <p>If this is not a suitable time for a backup, the transport should return a * backoff delay, in milliseconds, after which the Backup Manager should try again. * - * @return Zero if this is a suitable time for a backup pass, or a positive time delay - * in milliseconds to suggest deferring the backup pass for a while. + * @param resultFuture an {@link AndroidFuture} that is completed with {@code int}: zero if + * this is a suitable time for a backup pass, or a positive time delay in milliseconds + * to suggest deferring the backup pass for a while. */ - long requestBackupTime(); + void requestBackupTime(in AndroidFuture<long> resultFuture); /** * Initialize the server side storage for this device, erasing all stored data. @@ -121,10 +130,11 @@ interface IBackupTransport { * this is called, {@link #finishBackup} must be called to ensure the request * is sent and received successfully. * - * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far) or - * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure). + * @param callback a callback that is completed with a {@code int} which is one + * of {@link BackupConstants#TRANSPORT_OK} (OK so far) or + * {@link BackupConstants#TRANSPORT_ERROR} (on network error or other failure). */ - int initializeDevice(); + void initializeDevice(in ITransportStatusCallback callback); /** * Send one application's data to the backup destination. The transport may send @@ -137,12 +147,14 @@ interface IBackupTransport { * BackupService.doBackup() method. This may be a pipe rather than a file on * persistent media, so it may not be seekable. * @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) + * @param callback a callback that is completed with a {@code int} which is 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 flags); + void performBackup(in PackageInfo packageInfo, in ParcelFileDescriptor inFd, int flags, + in ITransportStatusCallback callback); /** * Erase the give application's data from the backup destination. This clears @@ -150,9 +162,10 @@ interface IBackupTransport { * the app had never yet been backed up. After this is called, {@link finishBackup} * must be called to ensure that the operation is recorded successfully. * - * @return the same error codes as {@link #performBackup}. + * @param callback a callback that is completed with the same error codes as + * {@link #performBackup}. */ - int clearBackupData(in PackageInfo packageInfo); + void clearBackupData(in PackageInfo packageInfo, in ITransportStatusCallback callback); /** * Finish sending application data to the backup destination. This must be @@ -160,27 +173,30 @@ interface IBackupTransport { * all data is sent. Only when this method returns true can a backup be assumed * to have succeeded. * - * @return the same error codes as {@link #performBackup}. + * @param callback a callback that is completed with the same error codes as + * {@link #performBackup}. */ - int finishBackup(); + void finishBackup(in ITransportStatusCallback callback); /** * Get the set of all backups currently available over this transport. * - * @return Descriptions of the set of restore images available for this device, - * or null if an error occurred (the attempt should be rescheduled). + * @param resultFuture an {@link AndroidFuture} that is completed with {@code List<RestoreSet>}: + * the descriptions of a set of restore images available for this device, or null if an + * error occurred (the attempt should be rescheduled). **/ - RestoreSet[] getAvailableRestoreSets(); + void getAvailableRestoreSets(in AndroidFuture<List<RestoreSet>> resultFuture); /** * Get the identifying token of the backup set currently being stored from * this device. This is used in the case of applications wishing to restore * their last-known-good data. * - * @return A token that can be passed to {@link #startRestore}, or 0 if there - * is no backup set available corresponding to the current device state. + * @param resultFuture an {@link AndroidFuture} that is completed with a {@code long}: a token + * that can be passed to {@link #startRestore}, or 0 if there is no backup set available + * corresponding to the current device state. */ - long getCurrentRestoreSet(); + void getCurrentRestoreSet(in AndroidFuture<long> resultFuture); /** * Start restoring application data from backup. After calling this function, @@ -191,11 +207,12 @@ interface IBackupTransport { * or {@link #getCurrentRestoreSet}. * @param packages List of applications to restore (if data is available). * Application data will be restored in the order given. - * @return One of {@link BackupConstants#TRANSPORT_OK} (OK so far, call - * {@link #nextRestorePackage}) or {@link BackupConstants#TRANSPORT_ERROR} - * (an error occurred, the restore should be aborted and rescheduled). + * @param callback a callback that is completed with one of + * {@link BackupConstants#TRANSPORT_OK} (OK so far, call {@link #nextRestorePackage}) or + * {@link BackupConstants#TRANSPORT_ERROR} (an error occurred, the restore should be aborted + * and rescheduled). */ - int startRestore(long token, in PackageInfo[] packages); + void startRestore(long token, in PackageInfo[] packages, in ITransportStatusCallback callback); /** * Get the package name of the next application with data in the backup store, plus @@ -210,34 +227,95 @@ interface IBackupTransport { * <p>If this method returns {@code null}, it means that a transport-level error has * occurred and the entire restore operation should be abandoned. * - * @return A RestoreDescription object containing the name of one of the packages - * supplied to {@link #startRestore} plus an indicator of the data type of that - * restore data; or {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that - * no more packages can be restored in this session; or {@code null} to indicate - * a transport-level error. + * @param resultFuture an {@link AndroidFuture} that is completed with a + * {@link RestoreDescription} object containing the name of one of the packages supplied to + * {@link #startRestore} plus an indicator of the data type of that restore data; or + * {@link RestoreDescription#NO_MORE_PACKAGES} to indicate that no more packages can be + * restored in this session; or {@code null} to indicate a transport-level error. */ - RestoreDescription nextRestorePackage(); + void nextRestorePackage(in AndroidFuture<RestoreDescription> resultFuture); /** * Get the data for the application returned by {@link #nextRestorePackage}. * @param data An open, writable file into which the backup data should be stored. - * @return the same error codes as {@link #startRestore}. + * + * @param callback a callback that is completed with the same error codes as + * {@link #startRestore}. */ - int getRestoreData(in ParcelFileDescriptor outFd); + void getRestoreData(in ParcelFileDescriptor outFd, in ITransportStatusCallback callback); /** * End a restore session (aborting any in-process data transfer as necessary), * freeing any resources and connections used during the restore process. + * + * @param callback a callback to signal that restore has been finished on transport side. */ - void finishRestore(); + void finishRestore(in ITransportStatusCallback callback); // full backup stuff - long requestFullBackupTime(); - int performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket, int flags); - int checkFullBackupSize(long size); - int sendBackupData(int numBytes); - void cancelFullBackup(); + /** + * Verify that this is a suitable time for a full-data backup pass. + * + * @param resultFuture an {@link AndroidFuture} that is completed with {@code long}: 0 if this + * is a suitable time for a backup pass, or a positive time delay in milliseconds to + * suggest deferring the backup pass for a while. + */ + void requestFullBackupTime(in AndroidFuture<long> resultFuture); + + /** + * Begin the process of sending an application's full-data archive to the backend. + * + * @param targetPackage The package whose data is to follow. + * @param socket The socket file descriptor through which the data will be provided. + * @param flags {@link BackupTransport#FLAG_USER_INITIATED} or 0. + * @param callback callback to return a {@code int} which is one of: + * {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} to indicate that the stated + * application is not to be backed up; {@link BackupTransport#TRANSPORT_OK} to indicate + * that the OS may proceed with delivering backup data; + * {@link BackupTransport#TRANSPORT_ERROR to indicate a fatal error condition that + * precludes performing a backup at this time. + */ + void performFullBackup(in PackageInfo targetPackage, in ParcelFileDescriptor socket, int flags, + in ITransportStatusCallback callback); + + /** + * Called after {@link #performFullBackup} to make sure that the transport is willing to + * handle a full-data backup operation of the specified size on the current package. + * + * @param size The estimated size of the full-data payload for this app. This includes + * manifest and archive format overhead, but is not guaranteed to be precise. + * @param callback a callback that is completed with a {@code int} which is + * one of: {@link BackupTransport#TRANSPORT_OK} if the platform is to proceed with the + * full-data {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED} if the proposed payload + * size is backup, too large for the transport to handle, or + * {@link BackupTransport#TRANSPORT_ERROR} to indicate a fatal error condition that + * means the platform cannot perform a backup at this time. + */ + void checkFullBackupSize(long size, in ITransportStatusCallback callback); + + /** + * Tells the transport to read {@code numBytes} bytes of data from the socket file + * descriptor provided in the {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)} + * call, and deliver those bytes to the datastore. + * + * @param numBytes The number of bytes of tarball data available to be read from the + * socket. + * @param callback a callback that is completed with a {@code int} which is + * one of: {@link BackupTransport#TRANSPORT_OK} on successful processing of the data, + * {@link BackupTransport#TRANSPORT_ERROR} to indicate a fatal error situation. If an + * error is returned, the system will call finishBackup() and stop attempting backups + * until after a backoff and retry interval. + */ + void sendBackupData(int numBytes, in ITransportStatusCallback callback); + + /** + * Tells the transport to cancel the currently-ongoing full backup operation. + * + * @param callback a callback to indicate that transport has cancelled the operation, + * does not return any value (see {@link ITransportCallback#onVoidReceived}). + */ + void cancelFullBackup(in ITransportStatusCallback callback); /** * Ask the transport whether this app is eligible for backup. @@ -245,9 +323,11 @@ interface IBackupTransport { * @param targetPackage The identity of the application. * @param isFullBackup If set, transport should check if app is eligible for full data backup, * otherwise to check if eligible for key-value backup. - * @return Whether this app is eligible for backup. + * @param resultFuture an {@link AndroidFuture} that is completed with a {@code boolean} + * indicating whether this app is eligible for backup. */ - boolean isAppEligibleForBackup(in PackageInfo targetPackage, boolean isFullBackup); + void isAppEligibleForBackup(in PackageInfo targetPackage, boolean isFullBackup, + in AndroidFuture<boolean> resultFuture); /** * Ask the transport about current quota for backup size of the package. @@ -255,9 +335,11 @@ interface IBackupTransport { * @param packageName ID of package to provide the quota. * @param isFullBackup If set, transport should return limit for full data backup, otherwise * for key-value backup. - * @return Current limit on full data backup size in bytes. + * @param resultFuture an {@link AndroidFuture} that is completed with a {@code long}: current + * limit on full data backup size in bytes. */ - long getBackupQuota(String packageName, boolean isFullBackup); + void getBackupQuota(String packageName, boolean isFullBackup, + in AndroidFuture<long> resultFuture); // full restore stuff @@ -284,13 +366,14 @@ interface IBackupTransport { * @param socket The file descriptor that the transport will use for delivering the * streamed archive. The transport must close this socket in all cases when returning * from this method. - * @return 0 when no more data for the current package is available. A positive value - * indicates the presence of that many bytes to be delivered to the app. Any negative - * return value is treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR}, - * indicating a fatal error condition that precludes further restore operations - * on the current dataset. + * @param callback a callback that is completed with an {@code int}: 0 when + * no more data for the current package is available. A positive value indicates the + * presence of that many bytes to be delivered to the app. Any negative return value is + * treated as equivalent to {@link BackupTransport#TRANSPORT_ERROR}, indicating a fatal error + * condition that precludes further restore operations on the current dataset. */ - int getNextFullRestoreDataChunk(in ParcelFileDescriptor socket); + void getNextFullRestoreDataChunk(in ParcelFileDescriptor socket, + in ITransportStatusCallback callback); /** * If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM} @@ -300,19 +383,21 @@ interface IBackupTransport { * set being iterated over, or will call {@link #finishRestore()} to shut down the restore * operation. * - * @return {@link #TRANSPORT_OK} if the transport was successful in shutting down the - * current stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious - * transport-level failure. If the transport reports an error here, the entire restore - * operation will immediately be finished with no further attempts to restore app data. + * @param callback a callback that is completed with {@code int}, which is + * one of: {@link #TRANSPORT_OK} if the transport was successful in shutting down the current + * stream cleanly, or {@link #TRANSPORT_ERROR} to indicate a serious transport-level failure. + * If the transport reports an error here, the entire restore operation will immediately be + * finished with no further attempts to restore app data. */ - int abortFullRestore(); + void abortFullRestore(in ITransportStatusCallback callback); /** - * Returns flags with additional information about the transport, which is accessible to the + * @param resultFuture an {@link AndroidFuture} that is completed with an {@code int}: flags + * with additional information about the transport, which is accessible to the * {@link android.app.backup.BackupAgent}. This allows the agent to decide what to backup or * restore based on properties of the transport. * * <p>For supported flags see {@link android.app.backup.BackupAgent}. */ - int getTransportFlags(); + void getTransportFlags(in AndroidFuture<int> resultFuture); } diff --git a/core/java/com/android/internal/backup/ITransportStatusCallback.aidl b/core/java/com/android/internal/backup/ITransportStatusCallback.aidl new file mode 100644 index 000000000000..a731480b4c6f --- /dev/null +++ b/core/java/com/android/internal/backup/ITransportStatusCallback.aidl @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2021 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 com.android.internal.backup; + +/** +* A callback class for {@link IBackupTransport} +* +* {@hide} +*/ +oneway interface ITransportStatusCallback { + /** + * Callback for methods that complete with an {@code int} status. + */ + void onOperationCompleteWithStatus(int status); + + /** + * Callback for methods that complete without a value. + */ + void onOperationComplete(); +} diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java index 85ab48c5f7fb..89633373b152 100644 --- a/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java +++ b/services/backup/backuplib/java/com/android/server/backup/transport/BackupTransportClient.java @@ -17,6 +17,7 @@ package com.android.server.backup.transport; import android.annotation.Nullable; +import android.app.backup.BackupTransport; import android.app.backup.RestoreDescription; import android.app.backup.RestoreSet; import android.content.Intent; @@ -24,50 +25,70 @@ import android.content.pm.PackageInfo; import android.os.Binder; import android.os.ParcelFileDescriptor; import android.os.RemoteException; +import android.util.Slog; +import com.android.internal.annotations.GuardedBy; import com.android.internal.backup.IBackupTransport; +import com.android.internal.infra.AndroidFuture; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import java.util.Queue; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * Client to {@link com.android.internal.backup.IBackupTransport}. Manages the call to the remote * transport service and delivers the results. */ public class BackupTransportClient { + private static final String TAG = "BackupTransportClient"; + private final IBackupTransport mTransportBinder; + private final TransportStatusCallbackPool mCallbackPool; BackupTransportClient(IBackupTransport transportBinder) { mTransportBinder = transportBinder; - - // This is a temporary fix to allow blocking calls. - // TODO: b/147702043. Redesign IBackupTransport so as to make the calls non-blocking. - Binder.allowBlocking(mTransportBinder.asBinder()); + mCallbackPool = new TransportStatusCallbackPool(); } /** * See {@link IBackupTransport#name()}. */ public String name() throws RemoteException { - return mTransportBinder.name(); + AndroidFuture<String> resultFuture = new AndroidFuture<>(); + mTransportBinder.name(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#configurationIntent()} */ public Intent configurationIntent() throws RemoteException { - return mTransportBinder.configurationIntent(); + AndroidFuture<Intent> resultFuture = new AndroidFuture<>(); + mTransportBinder.configurationIntent(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#currentDestinationString()} */ public String currentDestinationString() throws RemoteException { - return mTransportBinder.currentDestinationString(); + AndroidFuture<String> resultFuture = new AndroidFuture<>(); + mTransportBinder.currentDestinationString(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#dataManagementIntent()} */ public Intent dataManagementIntent() throws RemoteException { - return mTransportBinder.dataManagementIntent(); + AndroidFuture<Intent> resultFuture = new AndroidFuture<>(); + mTransportBinder.dataManagementIntent(resultFuture); + return getFutureResult(resultFuture); } /** @@ -75,42 +96,67 @@ public class BackupTransportClient { */ @Nullable public CharSequence dataManagementIntentLabel() throws RemoteException { - return mTransportBinder.dataManagementIntentLabel(); + AndroidFuture<CharSequence> resultFuture = new AndroidFuture<>(); + mTransportBinder.dataManagementIntentLabel(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#transportDirName()} */ public String transportDirName() throws RemoteException { - return mTransportBinder.transportDirName(); + AndroidFuture<String> resultFuture = new AndroidFuture<>(); + mTransportBinder.transportDirName(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#initializeDevice()} */ public int initializeDevice() throws RemoteException { - return mTransportBinder.initializeDevice(); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.initializeDevice(callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#clearBackupData(PackageInfo)} */ public int clearBackupData(PackageInfo packageInfo) throws RemoteException { - return mTransportBinder.clearBackupData(packageInfo); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.clearBackupData(packageInfo, callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#finishBackup()} */ public int finishBackup() throws RemoteException { - return mTransportBinder.finishBackup(); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.finishBackup(callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#requestBackupTime()} */ public long requestBackupTime() throws RemoteException { - return mTransportBinder.requestBackupTime(); + AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + mTransportBinder.requestBackupTime(resultFuture); + Long result = getFutureResult(resultFuture); + return result == null ? BackupTransport.TRANSPORT_ERROR : result; } /** @@ -118,56 +164,91 @@ public class BackupTransportClient { */ public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd, int flags) throws RemoteException { - return mTransportBinder.performBackup(packageInfo, inFd, flags); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.performBackup(packageInfo, inFd, flags, callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#getAvailableRestoreSets()} */ public RestoreSet[] getAvailableRestoreSets() throws RemoteException { - return mTransportBinder.getAvailableRestoreSets(); + AndroidFuture<List<RestoreSet>> resultFuture = new AndroidFuture<>(); + mTransportBinder.getAvailableRestoreSets(resultFuture); + List<RestoreSet> result = getFutureResult(resultFuture); + return result == null ? null : result.toArray(new RestoreSet[] {}); } /** * See {@link IBackupTransport#getCurrentRestoreSet()} */ public long getCurrentRestoreSet() throws RemoteException { - return mTransportBinder.getCurrentRestoreSet(); + AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + mTransportBinder.getCurrentRestoreSet(resultFuture); + Long result = getFutureResult(resultFuture); + return result == null ? BackupTransport.TRANSPORT_ERROR : result; } /** * See {@link IBackupTransport#startRestore(long, PackageInfo[])} */ public int startRestore(long token, PackageInfo[] packages) throws RemoteException { - return mTransportBinder.startRestore(token, packages); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.startRestore(token, packages, callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#nextRestorePackage()} */ public RestoreDescription nextRestorePackage() throws RemoteException { - return mTransportBinder.nextRestorePackage(); + AndroidFuture<RestoreDescription> resultFuture = new AndroidFuture<>(); + mTransportBinder.nextRestorePackage(resultFuture); + return getFutureResult(resultFuture); } /** * See {@link IBackupTransport#getRestoreData(ParcelFileDescriptor)} */ public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException { - return mTransportBinder.getRestoreData(outFd); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.getRestoreData(outFd, callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#finishRestore()} */ public void finishRestore() throws RemoteException { - mTransportBinder.finishRestore(); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.finishRestore(callback); + callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#requestFullBackupTime()} */ public long requestFullBackupTime() throws RemoteException { - return mTransportBinder.requestFullBackupTime(); + AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + mTransportBinder.requestFullBackupTime(resultFuture); + Long result = getFutureResult(resultFuture); + return result == null ? BackupTransport.TRANSPORT_ERROR : result; } /** @@ -175,28 +256,52 @@ public class BackupTransportClient { */ public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket, int flags) throws RemoteException { - return mTransportBinder.performFullBackup(targetPackage, socket, flags); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.performFullBackup(targetPackage, socket, flags, callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#checkFullBackupSize(long)} */ public int checkFullBackupSize(long size) throws RemoteException { - return mTransportBinder.checkFullBackupSize(size); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.checkFullBackupSize(size, callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#sendBackupData(int)} */ public int sendBackupData(int numBytes) throws RemoteException { - return mTransportBinder.sendBackupData(numBytes); + TransportStatusCallback callback = mCallbackPool.acquire(); + mTransportBinder.sendBackupData(numBytes, callback); + try { + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#cancelFullBackup()} */ public void cancelFullBackup() throws RemoteException { - mTransportBinder.cancelFullBackup(); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.cancelFullBackup(callback); + callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** @@ -204,34 +309,93 @@ public class BackupTransportClient { */ public boolean isAppEligibleForBackup(PackageInfo targetPackage, boolean isFullBackup) throws RemoteException { - return mTransportBinder.isAppEligibleForBackup(targetPackage, isFullBackup); + AndroidFuture<Boolean> resultFuture = new AndroidFuture<>(); + mTransportBinder.isAppEligibleForBackup(targetPackage, isFullBackup, resultFuture); + Boolean result = getFutureResult(resultFuture); + return result != null && result; } /** * See {@link IBackupTransport#getBackupQuota(String, boolean)} */ public long getBackupQuota(String packageName, boolean isFullBackup) throws RemoteException { - return mTransportBinder.getBackupQuota(packageName, isFullBackup); + AndroidFuture<Long> resultFuture = new AndroidFuture<>(); + mTransportBinder.getBackupQuota(packageName, isFullBackup, resultFuture); + Long result = getFutureResult(resultFuture); + return result == null ? BackupTransport.TRANSPORT_ERROR : result; } /** * See {@link IBackupTransport#getNextFullRestoreDataChunk(ParcelFileDescriptor)} */ public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) throws RemoteException { - return mTransportBinder.getNextFullRestoreDataChunk(socket); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.getNextFullRestoreDataChunk(socket, callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#abortFullRestore()} */ public int abortFullRestore() throws RemoteException { - return mTransportBinder.abortFullRestore(); + TransportStatusCallback callback = mCallbackPool.acquire(); + try { + mTransportBinder.abortFullRestore(callback); + return callback.getOperationStatus(); + } finally { + mCallbackPool.recycle(callback); + } } /** * See {@link IBackupTransport#getTransportFlags()} */ public int getTransportFlags() throws RemoteException { - return mTransportBinder.getTransportFlags(); + AndroidFuture<Integer> resultFuture = new AndroidFuture<>(); + mTransportBinder.getTransportFlags(resultFuture); + Integer result = getFutureResult(resultFuture); + return result == null ? BackupTransport.TRANSPORT_ERROR : result; + } + + private <T> T getFutureResult(AndroidFuture<T> future) { + try { + return future.get(600, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + Slog.w(TAG, "Failed to get result from transport:", e); + return null; + } + } + + private static class TransportStatusCallbackPool { + private static final int MAX_POOL_SIZE = 100; + + private final Object mPoolLock = new Object(); + private final Queue<TransportStatusCallback> mCallbackPool = new ArrayDeque<>(); + + TransportStatusCallback acquire() { + synchronized (mPoolLock) { + if (mCallbackPool.isEmpty()) { + return new TransportStatusCallback(); + } else { + return mCallbackPool.poll(); + } + } + } + + void recycle(TransportStatusCallback callback) { + synchronized (mPoolLock) { + if (mCallbackPool.size() > MAX_POOL_SIZE) { + Slog.d(TAG, "TransportStatusCallback pool size exceeded"); + return; + } + + callback.reset(); + mCallbackPool.add(callback); + } + } } } diff --git a/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java new file mode 100644 index 000000000000..a55178c27eef --- /dev/null +++ b/services/backup/backuplib/java/com/android/server/backup/transport/TransportStatusCallback.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2021 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 com.android.server.backup.transport; + +import android.app.backup.BackupTransport; +import android.os.RemoteException; +import android.util.Slog; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.backup.ITransportStatusCallback; + +public class TransportStatusCallback extends ITransportStatusCallback.Stub { + private static final String TAG = "TransportStatusCallback"; + private static final int TIMEOUT_MILLIS = 600 * 1000; // 10 minutes. + private static final int OPERATION_STATUS_DEFAULT = 0; + + private final int mOperationTimeout; + + @GuardedBy("this") + private int mOperationStatus = OPERATION_STATUS_DEFAULT; + @GuardedBy("this") + private boolean mHasCompletedOperation = false; + + public TransportStatusCallback() { + mOperationTimeout = TIMEOUT_MILLIS; + } + + @VisibleForTesting + TransportStatusCallback(int operationTimeout) { + mOperationTimeout = operationTimeout; + } + + @Override + public synchronized void onOperationCompleteWithStatus(int status) throws RemoteException { + mHasCompletedOperation = true; + mOperationStatus = status; + + notifyAll(); + } + + @Override + public synchronized void onOperationComplete() throws RemoteException { + onOperationCompleteWithStatus(OPERATION_STATUS_DEFAULT); + } + + synchronized int getOperationStatus() { + if (mHasCompletedOperation) { + return mOperationStatus; + } + + long timeoutLeft = mOperationTimeout; + try { + while (!mHasCompletedOperation && timeoutLeft > 0) { + long waitStartTime = System.currentTimeMillis(); + wait(timeoutLeft); + if (mHasCompletedOperation) { + return mOperationStatus; + } + timeoutLeft -= System.currentTimeMillis() - waitStartTime; + } + + Slog.w(TAG, "Couldn't get operation status from transport"); + return BackupTransport.TRANSPORT_ERROR; + } catch (InterruptedException e) { + Slog.w(TAG, "Couldn't get operation status from transport: ", e); + return BackupTransport.TRANSPORT_ERROR; + } finally { + reset(); + } + } + + synchronized void reset() { + mHasCompletedOperation = false; + mOperationStatus = OPERATION_STATUS_DEFAULT; + } +} diff --git a/services/tests/servicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java b/services/tests/servicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java new file mode 100644 index 000000000000..7f7901f893a3 --- /dev/null +++ b/services/tests/servicestests/src/com/android/server/backup/transport/TransportStatusCallbackTest.java @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2021 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 com.android.server.backup.transport; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.backup.BackupTransport; +import android.platform.test.annotations.Presubmit; + +import androidx.test.runner.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@Presubmit +@RunWith(AndroidJUnit4.class) +public class TransportStatusCallbackTest { + private static final int OPERATION_TIMEOUT_MILLIS = 10; + private static final int OPERATION_COMPLETE_STATUS = 123; + + private TransportStatusCallback mTransportStatusCallback; + + @Before + public void setUp() { + mTransportStatusCallback = new TransportStatusCallback(); + } + + @Test + public void testGetOperationStatus_withPreCompletedOperation_returnsStatus() throws Exception { + mTransportStatusCallback.onOperationCompleteWithStatus(OPERATION_COMPLETE_STATUS); + + int result = mTransportStatusCallback.getOperationStatus(); + + assertThat(result).isEqualTo(OPERATION_COMPLETE_STATUS); + } + + @Test + public void testGetOperationStatus_completeOperation_returnsStatus() throws Exception { + Thread thread = new Thread(() -> { + int result = mTransportStatusCallback.getOperationStatus(); + assertThat(result).isEqualTo(OPERATION_COMPLETE_STATUS); + }); + thread.start(); + + mTransportStatusCallback.onOperationCompleteWithStatus(OPERATION_COMPLETE_STATUS); + + thread.join(); + } + + @Test + public void testGetOperationStatus_operationTimesOut_returnsError() throws Exception { + TransportStatusCallback callback = new TransportStatusCallback(OPERATION_TIMEOUT_MILLIS); + + int result = callback.getOperationStatus(); + + assertThat(result).isEqualTo(BackupTransport.TRANSPORT_ERROR); + } +} |