/*
* Copyright (C) 2014 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.content.Intent;
import android.content.pm.PackageInfo;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import com.android.internal.backup.IBackupTransport;
/**
* Concrete class that provides a stable-API bridge between IBackupTransport
* and its implementations.
*
* @hide
*/
@SystemApi
public class BackupTransport {
// Zero return always means things are okay. If returned from
// getNextFullRestoreDataChunk(), it means that no data could be delivered at
// this time, but the restore is still running and the caller should simply
// retry.
public static final int TRANSPORT_OK = 0;
// -1 is special; it is used in getNextFullRestoreDataChunk() to indicate that
// we've delivered the entire data stream for the current restore target.
public static final int NO_MORE_DATA = -1;
// Result codes that indicate real errors are negative and not -1
public static final int TRANSPORT_ERROR = -1000;
public static final int TRANSPORT_NOT_INITIALIZED = -1001;
public static final int TRANSPORT_PACKAGE_REJECTED = -1002;
public static final int AGENT_ERROR = -1003;
public static final int AGENT_UNKNOWN = -1004;
IBackupTransport mBinderImpl = new TransportImpl();
public IBinder getBinder() {
return mBinderImpl.asBinder();
}
// ------------------------------------------------------------------------------------
// Transport self-description and general configuration interfaces
//
/**
* Ask the transport for the name under which it should be registered. This will
* typically be its host service's component name, but need not be.
*/
public String name() {
throw new UnsupportedOperationException("Transport name() not implemented");
}
/**
* 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.
*
* <p>If the transport does not supply any user-facing configuration UI, 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 configuration UI. This method will return {@code null}
* if the transport does not offer any user-facing configuration UI.
*/
public Intent configurationIntent() {
return null;
}
/**
* 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.
*/
public String currentDestinationString() {
throw new UnsupportedOperationException(
"Transport currentDestinationString() not implemented");
}
/**
* Ask the transport for an Intent that can be used to launch a more detailed
* secondary data management activity. For example, the configuration intent might
* be one for allowing the user to select which account they wish to associate
* their backups with, and the management intent might be one which presents a
* UI for managing the data on the backend.
*
* <p>In the Settings UI, the configuration intent will typically be invoked
* when the user taps on the preferences item labeled with the current
* destination string, and the management intent will be placed in an overflow
* menu labelled with the management label string.
*
* <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.
*/
public Intent dataManagementIntent() {
return null;
}
/**
* On demand, supply a short string that can be shown to the user as the label
* on an overflow menu item used to invoked the data management UI.
*
* @return A string 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}.
*/
public String dataManagementLabel() {
throw new UnsupportedOperationException(
"Transport dataManagementLabel() not implemented");
}
/**
* Ask the transport where, on local device storage, to keep backup state blobs.
* This is per-transport so that mock transports used for testing can coexist with
* "live" backup services without interfering with the live bookkeeping. The
* returned string should be a name that is expected to be unambiguous among all
* available backup transports; the name of the class implementing the transport
* is a good choice.
*
* @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.
*/
public String transportDirName() {
throw new UnsupportedOperationException(
"Transport transportDirName() not implemented");
}
// ------------------------------------------------------------------------------------
// Device-level operations common to both key/value and full-data storage
/**
* Initialize the server side storage for this device, erasing all stored data.
* The transport may send the request immediately, or may buffer it. After
* this is called, {@link #finishBackup} will be called to ensure the request
* is sent and received successfully.
*
* <p>If the transport returns anything other than TRANSPORT_OK from this method,
* the OS will halt the current initialize operation and schedule a retry in the
* near future. Even if the transport is in a state such that attempting to
* "initialize" the backend storage is meaningless -- for example, if there is
* no current live dataset at all, or there is no authenticated account under which
* to store the data remotely -- the transport should return TRANSPORT_OK here
* and treat the initializeDevice() / finishBackup() pair as a graceful no-op.
*
* @return One of {@link BackupTransport#TRANSPORT_OK} (OK so far) or
* {@link BackupTransport#TRANSPORT_ERROR} (to retry following network error
* or other failure).
*/
public int initializeDevice() {
return BackupTransport.TRANSPORT_ERROR;
}
/**
* Erase the given application's data from the backup destination. This clears
* out the given package's data from the current backup set, making it as though
* 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}.
*/
public int clearBackupData(PackageInfo packageInfo) {
return BackupTransport.TRANSPORT_ERROR;
}
/**
* Finish sending application data to the backup destination. This must be
* called after {@link #performBackup}, {@link #performFullBackup}, or {@link clearBackupData}
* to ensure that all data is sent and the operation properly finalized. Only when this
* method returns true can a backup be assumed to have succeeded.
*
* @return the same error codes as {@link #performBackup} or {@link #performFullBackup}.
*/
public int finishBackup() {
return BackupTransport.TRANSPORT_ERROR;
}
// ------------------------------------------------------------------------------------
// Key/value incremental backup support interfaces
/**
* Verify that this is a suitable time for a key/value backup pass. This should return zero
* if a backup is reasonable right now, some positive value otherwise. This method
* will be called outside of the {@link #performBackup}/{@link #finishBackup} pair.
*
* <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.
*/
public long requestBackupTime() {
return 0;
}
/**
* Send one application's key/value data update to the backup destination. The
* transport may send the data immediately, or may buffer it. If this method returns
* {@link #TRANSPORT_OK}, {@link #finishBackup} will then be called to ensure the data
* is sent and recorded successfully.
*
* @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
* 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.
* @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),
* {@link BackupTransport#TRANSPORT_ERROR} (on network error or other failure), or
* {@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) {
return BackupTransport.TRANSPORT_ERROR;
}
// ------------------------------------------------------------------------------------
// Key/value dataset restore interfaces
/**
* 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).
**/
public RestoreSet[] getAvailableRestoreSets() {
return null;
}
/**
* 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.
*/
public long getCurrentRestoreSet() {
return 0;
}
/**
* Start restoring application data from backup. After calling this function,
* alternate calls to {@link #nextRestorePackage} and {@link #nextRestoreData}
* to walk through the actual application data.
*
* @param token A backup token as returned by {@link #getAvailableRestoreSets}
* 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 BackupTransport#TRANSPORT_OK} (OK so far, call
* {@link #nextRestorePackage}) or {@link BackupTransport#TRANSPORT_ERROR}
* (an error occurred, the restore should be aborted and rescheduled).
*/
public int startRestore(long token, PackageInfo[] packages) {
return BackupTransport.TRANSPORT_ERROR;
}
/**
* Get the package name of the next application with data in the backup store, plus
* a description of the structure of the restored archive: either TYPE_KEY_VALUE for
* an original-API key/value dataset, or TYPE_FULL_STREAM for a tarball-type archive stream.
*
* <p>If the package name in the returned RestoreDescription object is the singleton
* {@link RestoreDescription#NO_MORE_PACKAGES}, it indicates that no further data is available
* in the current restore session: all packages described in startRestore() have been
* processed.
*
* <p>If this method returns {@code null}, it means that a transport-level error has
* occurred and the entire restore operation should be abandoned.
*
* <p class="note">The OS may call {@link #nextRestorePackage()} multiple times
* before calling either {@link #getRestoreData(ParcelFileDescriptor) getRestoreData()}
* or {@link #getNextFullRestoreDataChunk(ParcelFileDescriptor) getNextFullRestoreDataChunk()}.
* It does this when it has determined that it needs to skip restore of one or more
* packages. The transport should not actually transfer any restore data for
* the given package in response to {@link #nextRestorePackage()}, but rather wait
* for an explicit request before doing so.
*
* @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.
*/
public RestoreDescription nextRestorePackage() {
return null;
}
/**
* Get the data for the application returned by {@link #nextRestorePackage}, if that
* method reported {@link RestoreDescription#TYPE_KEY_VALUE} as its delivery type.
* If the package has only TYPE_FULL_STREAM data, then this method will return an
* error.
*
* @param data An open, writable file into which the key/value backup data should be stored.
* @return the same error codes as {@link #startRestore}.
*/
public int getRestoreData(ParcelFileDescriptor outFd) {
return BackupTransport.TRANSPORT_ERROR;
}
/**
* End a restore session (aborting any in-process data transfer as necessary),
* freeing any resources and connections used during the restore process.
*/
public void finishRestore() {
throw new UnsupportedOperationException(
"Transport finishRestore() not implemented");
}
// ------------------------------------------------------------------------------------
// Full backup interfaces
/**
* Verify that this is a suitable time for a full-data backup pass. This should return zero
* if a backup is reasonable right now, some positive value otherwise. This method
* will be called outside of the {@link #performFullBackup}/{@link #finishBackup} pair.
*
* <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.
*
* @see #requestBackupTime()
*/
public long requestFullBackupTime() {
return 0;
}
/**
* Begin the process of sending an application's full-data archive to the backend.
* The description of the package whose data will be delivered is provided, as well as
* the socket file descriptor on which the transport will receive the data itself.
*
* <p>If the package is not eligible for backup, the transport should return
* {@link BackupTransport#TRANSPORT_PACKAGE_REJECTED}. In this case the system will
* simply proceed with the next candidate if any, or finish the full backup operation
* if all apps have been processed.
*
* <p>After the transport returns {@link BackupTransport#TRANSPORT_OK} from this
* method, the OS will proceed to call {@link #sendBackupData()} one or more times
* to deliver the application's data as a streamed tarball. The transport should not
* read() from the socket except as instructed to via the {@link #sendBackupData(int)}
* method.
*
* <p>After all data has been delivered to the transport, the system will call
* {@link #finishBackup()}. At this point the transport should commit the data to
* its datastore, if appropriate, and close the socket that had been provided in
* {@link #performFullBackup(PackageInfo, ParcelFileDescriptor)}.
*
* <p class="note">If the transport returns TRANSPORT_OK from this method, then the
* OS will always provide a matching call to {@link #finishBackup()} even if sending
* data via {@link #sendBackupData(int)} failed at some point.
*
* @param targetPackage The package whose data is to follow.
* @param socket The socket file descriptor through which the data will be provided.
* If the transport returns {@link #TRANSPORT_PACKAGE_REJECTED} here, it must still
* 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()}.
* @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) {
return BackupTransport.TRANSPORT_PACKAGE_REJECTED;
}
/**
* 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.
* If the transport returns anything other than TRANSPORT_OK, the package's backup
* operation will be skipped (and {@link #finishBackup() invoked} with no data for that
* package being passed to {@link #sendBackupData}.
*
* <p class="note">The platform does no size-based rejection of full backup attempts on
* its own: it is always the responsibility of the transport to implement its own policy.
* In particular, even if the preflighted payload size is zero, the platform will still call
* this method and will proceed to back up an archive metadata header with no file content
* if this method returns TRANSPORT_OK. To avoid storing such payloads the transport
* must recognize this case and return TRANSPORT_PACKAGE_REJECTED.
*
* Added in {@link android.os.Build.VERSION_CODES#M}.
*
* @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.
* @return TRANSPORT_OK if the platform is to proceed with the full-data backup,
* TRANSPORT_PACKAGE_REJECTED if the proposed payload size is too large for
* the transport to handle, or TRANSPORT_ERROR to indicate a fatal error
* condition that means the platform cannot perform a backup at this time.
*/
public int checkFullBackupSize(long size) {
return BackupTransport.TRANSPORT_OK;
}
/**
* 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.
* @return TRANSPORT_OK on successful processing of the data; 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.
*/
public int sendBackupData(int numBytes) {
return BackupTransport.TRANSPORT_ERROR;
}
/**
* Tells the transport to cancel the currently-ongoing full backup operation. This
* will happen between {@link #performFullBackup()} and {@link #finishBackup()}
* if the OS needs to abort the backup operation for any reason, such as a crash in
* the application undergoing backup.
*
* <p>When it receives this call, the transport should discard any partial archive
* that it has stored so far. If possible it should also roll back to the previous
* known-good archive in its datastore.
*
* <p>If the transport receives this callback, it will <em>not</em> receive a
* call to {@link #finishBackup()}. It needs to tear down any ongoing backup state
* here.
*/
public void cancelFullBackup() {
throw new UnsupportedOperationException(
"Transport cancelFullBackup() not implemented");
}
// ------------------------------------------------------------------------------------
// Full restore interfaces
/**
* Ask the transport to provide data for the "current" package being restored. This
* is the package that was just reported by {@link #nextRestorePackage()} as having
* {@link RestoreDescription#TYPE_FULL_STREAM} data.
*
* The transport writes some data to the socket supplied to this call, and returns
* the number of bytes written. The system will then read that many bytes and
* stream them to the application's agent for restore, then will call this method again
* to receive the next chunk of the archive. This sequence will be repeated until the
* transport returns zero indicating that all of the package's data has been delivered
* (or returns a negative value indicating some sort of hard error condition at the
* transport level).
*
* <p>After this method returns zero, the system will then call
* {@link #nextRestorePackage()} to begin the restore process for the next
* application, and the sequence begins again.
*
* <p>The transport should always close this socket when returning from this method.
* Do not cache this socket across multiple calls or you may leak file descriptors.
*
* @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 {@link #NO_MORE_DATA} 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.
* A value of zero indicates that no data was deliverable at this time, but the restore
* is still running and the caller should retry. {@link #TRANSPORT_PACKAGE_REJECTED}
* means that the current package's restore operation should be aborted, but that
* the transport itself is still in a good state and so a multiple-package restore
* sequence can still be continued. Any other negative return value is treated as a
* fatal error condition that aborts all further restore operations on the current dataset.
*/
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
return 0;
}
/**
* If the OS encounters an error while processing {@link RestoreDescription#TYPE_FULL_STREAM}
* data for restore, it will invoke this method to tell the transport that it should
* abandon the data download for the current package. The OS will then either call
* {@link #nextRestorePackage()} again to move on to restoring the next package in the
* 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.
*/
public int abortFullRestore() {
return BackupTransport.TRANSPORT_OK;
}
/**
* Bridge between the actual IBackupTransport implementation and the stable API. If the
* binder interface needs to change, we use this layer to translate so that we can
* (if appropriate) decouple those framework-side changes from the BackupTransport
* implementations.
*/
class TransportImpl extends IBackupTransport.Stub {
@Override
public String name() throws RemoteException {
return BackupTransport.this.name();
}
@Override
public Intent configurationIntent() throws RemoteException {
return BackupTransport.this.configurationIntent();
}
@Override
public String currentDestinationString() throws RemoteException {
return BackupTransport.this.currentDestinationString();
}
@Override
public Intent dataManagementIntent() {
return BackupTransport.this.dataManagementIntent();
}
@Override
public String dataManagementLabel() {
return BackupTransport.this.dataManagementLabel();
}
@Override
public String transportDirName() throws RemoteException {
return BackupTransport.this.transportDirName();
}
@Override
public long requestBackupTime() throws RemoteException {
return BackupTransport.this.requestBackupTime();
}
@Override
public int initializeDevice() throws RemoteException {
return BackupTransport.this.initializeDevice();
}
@Override
public int performBackup(PackageInfo packageInfo, ParcelFileDescriptor inFd)
throws RemoteException {
return BackupTransport.this.performBackup(packageInfo, inFd);
}
@Override
public int clearBackupData(PackageInfo packageInfo) throws RemoteException {
return BackupTransport.this.clearBackupData(packageInfo);
}
@Override
public int finishBackup() throws RemoteException {
return BackupTransport.this.finishBackup();
}
@Override
public RestoreSet[] getAvailableRestoreSets() throws RemoteException {
return BackupTransport.this.getAvailableRestoreSets();
}
@Override
public long getCurrentRestoreSet() throws RemoteException {
return BackupTransport.this.getCurrentRestoreSet();
}
@Override
public int startRestore(long token, PackageInfo[] packages) throws RemoteException {
return BackupTransport.this.startRestore(token, packages);
}
@Override
public RestoreDescription nextRestorePackage() throws RemoteException {
return BackupTransport.this.nextRestorePackage();
}
@Override
public int getRestoreData(ParcelFileDescriptor outFd) throws RemoteException {
return BackupTransport.this.getRestoreData(outFd);
}
@Override
public void finishRestore() throws RemoteException {
BackupTransport.this.finishRestore();
}
@Override
public long requestFullBackupTime() throws RemoteException {
return BackupTransport.this.requestFullBackupTime();
}
@Override
public int performFullBackup(PackageInfo targetPackage, ParcelFileDescriptor socket) throws RemoteException {
return BackupTransport.this.performFullBackup(targetPackage, socket);
}
@Override
public int checkFullBackupSize(long size) {
return BackupTransport.this.checkFullBackupSize(size);
}
@Override
public int sendBackupData(int numBytes) throws RemoteException {
return BackupTransport.this.sendBackupData(numBytes);
}
@Override
public void cancelFullBackup() throws RemoteException {
BackupTransport.this.cancelFullBackup();
}
@Override
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
return BackupTransport.this.getNextFullRestoreDataChunk(socket);
}
@Override
public int abortFullRestore() {
return BackupTransport.this.abortFullRestore();
}
}
}