summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Robert Berry <robertberry@google.com> 2018-01-25 12:17:45 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2018-01-25 12:17:45 +0000
commitbb8a385fab19974ab5a01cef18bf7c89dbf6ae9d (patch)
treee145f144dd7f8c439cdd5cc72b877d9cb75dbae7
parent9543d6eb658cb6c1a2c1d6f3df09cb305d5631af (diff)
parent39194c0582463be17513b9ba82802a703b10c934 (diff)
Merge "Add #getTransportFlags to BackupDataOutput"
-rw-r--r--api/current.txt3
-rw-r--r--api/system-current.txt1
-rw-r--r--core/java/android/app/IBackupAgent.aidl12
-rw-r--r--core/java/android/app/backup/BackupAgent.java28
-rw-r--r--core/java/android/app/backup/BackupDataOutput.java24
-rw-r--r--core/java/android/app/backup/BackupTransport.java14
-rw-r--r--core/java/android/app/backup/FullBackupDataOutput.java32
-rw-r--r--core/java/com/android/internal/backup/IBackupTransport.aidl9
-rw-r--r--services/backup/java/com/android/server/backup/DataChangedJournal.java2
-rw-r--r--services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java5
-rw-r--r--services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java3
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java10
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java3
-rw-r--r--services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java21
-rw-r--r--services/backup/java/com/android/server/backup/internal/PerformBackupTask.java5
-rw-r--r--services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java3
-rw-r--r--services/robotests/Android.mk51
-rw-r--r--services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java315
-rw-r--r--services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java2
-rw-r--r--services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java27
-rw-r--r--services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java61
-rw-r--r--services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java57
22 files changed, 647 insertions, 41 deletions
diff --git a/api/current.txt b/api/current.txt
index 3e0f560d553e..c38690c4ba8d 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6893,6 +6893,7 @@ package android.app.backup {
method public void onRestore(android.app.backup.BackupDataInput, long, android.os.ParcelFileDescriptor) throws java.io.IOException;
method public void onRestoreFile(android.os.ParcelFileDescriptor, long, java.io.File, int, long, long) throws java.io.IOException;
method public void onRestoreFinished();
+ field public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1; // 0x1
field public static final int TYPE_DIRECTORY = 2; // 0x2
field public static final int TYPE_FILE = 1; // 0x1
}
@@ -6920,6 +6921,7 @@ package android.app.backup {
public class BackupDataOutput {
method public long getQuota();
+ method public int getTransportFlags();
method public int writeEntityData(byte[], int) throws java.io.IOException;
method public int writeEntityHeader(java.lang.String, int) throws java.io.IOException;
}
@@ -6945,6 +6947,7 @@ package android.app.backup {
public class FullBackupDataOutput {
method public long getQuota();
+ method public int getTransportFlags();
}
public abstract class RestoreObserver {
diff --git a/api/system-current.txt b/api/system-current.txt
index 6b0366b75e30..10ce02803f0c 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -569,6 +569,7 @@ package android.app.backup {
method public long getCurrentRestoreSet();
method public int getNextFullRestoreDataChunk(android.os.ParcelFileDescriptor);
method public int getRestoreData(android.os.ParcelFileDescriptor);
+ method public int getTransportFlags();
method public int initializeDevice();
method public boolean isAppEligibleForBackup(android.content.pm.PackageInfo, boolean);
method public java.lang.String name();
diff --git a/core/java/android/app/IBackupAgent.aidl b/core/java/android/app/IBackupAgent.aidl
index 4a85efd9e4c2..3aeef148b4e7 100644
--- a/core/java/android/app/IBackupAgent.aidl
+++ b/core/java/android/app/IBackupAgent.aidl
@@ -49,11 +49,13 @@ oneway interface IBackupAgent {
*
* @param callbackBinder Binder on which to indicate operation completion,
* passed here as a convenience to the agent.
+ *
+ * @param transportFlags Flags with additional information about the transport.
*/
void doBackup(in ParcelFileDescriptor oldState,
in ParcelFileDescriptor data,
in ParcelFileDescriptor newState,
- long quotaBytes, int token, IBackupManager callbackBinder);
+ long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags);
/**
* Restore an entire data snapshot to the application.
@@ -100,13 +102,17 @@ oneway interface IBackupAgent {
*
* @param callbackBinder Binder on which to indicate operation completion,
* passed here as a convenience to the agent.
+ *
+ * @param transportFlags Flags with additional information about transport.
*/
- void doFullBackup(in ParcelFileDescriptor data, long quotaBytes, int token, IBackupManager callbackBinder);
+ void doFullBackup(in ParcelFileDescriptor data, long quotaBytes, int token,
+ IBackupManager callbackBinder, int transportFlags);
/**
* Estimate how much data a full backup will deliver
*/
- void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder);
+ void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder,
+ int transportFlags);
/**
* Tells the application agent that the backup data size exceeded current transport quota.
diff --git a/core/java/android/app/backup/BackupAgent.java b/core/java/android/app/backup/BackupAgent.java
index 861cb9a8d035..6259d86367c1 100644
--- a/core/java/android/app/backup/BackupAgent.java
+++ b/core/java/android/app/backup/BackupAgent.java
@@ -143,6 +143,17 @@ public abstract class BackupAgent extends ContextWrapper {
/** @hide */
public static final int TYPE_SYMLINK = 3;
+ /**
+ * Flag for {@link BackupDataOutput#getTransportFlags()} and
+ * {@link FullBackupDataOutput#getTransportFlags()} only.
+ *
+ * <p>The transport has client-side encryption enabled. i.e., the user's backup has been
+ * encrypted with a key known only to the device, and not to the remote storage solution. Even
+ * if an attacker had root access to the remote storage provider they should not be able to
+ * decrypt the user's backup data.
+ */
+ public static final int FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED = 1;
+
Handler mHandler = null;
Handler getHandler() {
@@ -920,12 +931,14 @@ public abstract class BackupAgent extends ContextWrapper {
public void doBackup(ParcelFileDescriptor oldState,
ParcelFileDescriptor data,
ParcelFileDescriptor newState,
- long quotaBytes, int token, IBackupManager callbackBinder) throws RemoteException {
+ long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags)
+ throws RemoteException {
// Ensure that we're running with the app's normal permission level
long ident = Binder.clearCallingIdentity();
if (DEBUG) Log.v(TAG, "doBackup() invoked");
- BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor(), quotaBytes);
+ BackupDataOutput output = new BackupDataOutput(
+ data.getFileDescriptor(), quotaBytes, transportFlags);
try {
BackupAgent.this.onBackup(oldState, output, newState);
@@ -999,7 +1012,7 @@ public abstract class BackupAgent extends ContextWrapper {
@Override
public void doFullBackup(ParcelFileDescriptor data,
- long quotaBytes, int token, IBackupManager callbackBinder) {
+ long quotaBytes, int token, IBackupManager callbackBinder, int transportFlags) {
// Ensure that we're running with the app's normal permission level
long ident = Binder.clearCallingIdentity();
@@ -1010,7 +1023,8 @@ public abstract class BackupAgent extends ContextWrapper {
waitForSharedPrefs();
try {
- BackupAgent.this.onFullBackup(new FullBackupDataOutput(data, quotaBytes));
+ BackupAgent.this.onFullBackup(new FullBackupDataOutput(
+ data, quotaBytes, transportFlags));
} catch (IOException ex) {
Log.d(TAG, "onFullBackup (" + BackupAgent.this.getClass().getName() + ") threw", ex);
throw new RuntimeException(ex);
@@ -1044,10 +1058,12 @@ public abstract class BackupAgent extends ContextWrapper {
}
}
- public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder) {
+ public void doMeasureFullBackup(long quotaBytes, int token, IBackupManager callbackBinder,
+ int transportFlags) {
// Ensure that we're running with the app's normal permission level
final long ident = Binder.clearCallingIdentity();
- FullBackupDataOutput measureOutput = new FullBackupDataOutput(quotaBytes);
+ FullBackupDataOutput measureOutput =
+ new FullBackupDataOutput(quotaBytes, transportFlags);
waitForSharedPrefs();
try {
diff --git a/core/java/android/app/backup/BackupDataOutput.java b/core/java/android/app/backup/BackupDataOutput.java
index c7586a299dd8..5a66f3407417 100644
--- a/core/java/android/app/backup/BackupDataOutput.java
+++ b/core/java/android/app/backup/BackupDataOutput.java
@@ -18,6 +18,7 @@ package android.app.backup;
import android.annotation.SystemApi;
import android.os.ParcelFileDescriptor;
+
import java.io.FileDescriptor;
import java.io.IOException;
@@ -62,7 +63,10 @@ import java.io.IOException;
* @see BackupAgent
*/
public class BackupDataOutput {
- final long mQuota;
+
+ private final long mQuota;
+ private final int mTransportFlags;
+
long mBackupWriter;
/**
@@ -71,14 +75,20 @@ public class BackupDataOutput {
* @hide */
@SystemApi
public BackupDataOutput(FileDescriptor fd) {
- this(fd, -1);
+ this(fd, /*quota=*/ -1, /*transportFlags=*/ 0);
}
/** @hide */
@SystemApi
public BackupDataOutput(FileDescriptor fd, long quota) {
+ this(fd, quota, /*transportFlags=*/ 0);
+ }
+
+ /** @hide */
+ public BackupDataOutput(FileDescriptor fd, long quota, int transportFlags) {
if (fd == null) throw new NullPointerException();
mQuota = quota;
+ mTransportFlags = transportFlags;
mBackupWriter = ctor(fd);
if (mBackupWriter == 0) {
throw new RuntimeException("Native initialization failed with fd=" + fd);
@@ -96,6 +106,16 @@ public class BackupDataOutput {
}
/**
+ * Returns flags with additional information about the backup transport. For supported flags see
+ * {@link android.app.backup.BackupAgent}
+ *
+ * @see FullBackupDataOutput#getTransportFlags()
+ */
+ public int getTransportFlags() {
+ return mTransportFlags;
+ }
+
+ /**
* Mark the beginning of one record in the backup data stream. This must be called before
* {@link #writeEntityData}.
* @param key A string key that uniquely identifies the data record within the application.
diff --git a/core/java/android/app/backup/BackupTransport.java b/core/java/android/app/backup/BackupTransport.java
index 52ed0c192bed..f456395aa2b5 100644
--- a/core/java/android/app/backup/BackupTransport.java
+++ b/core/java/android/app/backup/BackupTransport.java
@@ -607,6 +607,15 @@ public class BackupTransport {
}
/**
+ * Returns 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 do based on
+ * properties of the transport.
+ */
+ public int getTransportFlags() {
+ return 0;
+ }
+
+ /**
* 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
@@ -738,6 +747,11 @@ public class BackupTransport {
}
@Override
+ public int getTransportFlags() {
+ return BackupTransport.this.getTransportFlags();
+ }
+
+ @Override
public int getNextFullRestoreDataChunk(ParcelFileDescriptor socket) {
return BackupTransport.this.getNextFullRestoreDataChunk(socket);
}
diff --git a/core/java/android/app/backup/FullBackupDataOutput.java b/core/java/android/app/backup/FullBackupDataOutput.java
index 5deedd035d0b..18f428339941 100644
--- a/core/java/android/app/backup/FullBackupDataOutput.java
+++ b/core/java/android/app/backup/FullBackupDataOutput.java
@@ -11,6 +11,7 @@ public class FullBackupDataOutput {
// Currently a name-scoping shim around BackupDataOutput
private final BackupDataOutput mData;
private final long mQuota;
+ private final int mTransportFlags;
private long mSize;
/**
@@ -23,22 +24,49 @@ public class FullBackupDataOutput {
return mQuota;
}
+ /**
+ * Returns flags with additional information about the backup transport. For supported flags see
+ * {@link android.app.backup.BackupAgent}
+ *
+ * @see BackupDataOutput#getTransportFlags()
+ */
+ public int getTransportFlags() {
+ return mTransportFlags;
+ }
+
/** @hide - used only in measure operation */
public FullBackupDataOutput(long quota) {
mData = null;
mQuota = quota;
mSize = 0;
+ mTransportFlags = 0;
+ }
+
+ /** @hide - used only in measure operation */
+ public FullBackupDataOutput(long quota, int transportFlags) {
+ mData = null;
+ mQuota = quota;
+ mSize = 0;
+ mTransportFlags = transportFlags;
}
/** @hide */
public FullBackupDataOutput(ParcelFileDescriptor fd, long quota) {
- mData = new BackupDataOutput(fd.getFileDescriptor(), quota);
+ mData = new BackupDataOutput(fd.getFileDescriptor(), quota, 0);
+ mQuota = quota;
+ mTransportFlags = 0;
+ }
+
+ /** @hide */
+ public FullBackupDataOutput(ParcelFileDescriptor fd, long quota, int transportFlags) {
+ mData = new BackupDataOutput(fd.getFileDescriptor(), quota, transportFlags);
mQuota = quota;
+ mTransportFlags = transportFlags;
}
/** @hide - used only internally to the backup manager service's stream construction */
public FullBackupDataOutput(ParcelFileDescriptor fd) {
- this(fd, -1);
+ this(fd, /*quota=*/ -1, /*transportFlags=*/ 0);
}
/** @hide */
diff --git a/core/java/com/android/internal/backup/IBackupTransport.aidl b/core/java/com/android/internal/backup/IBackupTransport.aidl
index 147438cf47a5..f8117a7e9260 100644
--- a/core/java/com/android/internal/backup/IBackupTransport.aidl
+++ b/core/java/com/android/internal/backup/IBackupTransport.aidl
@@ -306,4 +306,13 @@ interface IBackupTransport {
* operation will immediately be finished with no further attempts to restore app data.
*/
int abortFullRestore();
+
+ /**
+ * Returns 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();
}
diff --git a/services/backup/java/com/android/server/backup/DataChangedJournal.java b/services/backup/java/com/android/server/backup/DataChangedJournal.java
index 9360c85aed33..c2d38290c869 100644
--- a/services/backup/java/com/android/server/backup/DataChangedJournal.java
+++ b/services/backup/java/com/android/server/backup/DataChangedJournal.java
@@ -33,7 +33,7 @@ import java.util.ArrayList;
* <p>This information is persisted to the filesystem so that it is not lost in the event of a
* reboot.
*/
-public final class DataChangedJournal {
+public class DataChangedJournal {
private static final String FILE_NAME_PREFIX = "journal";
/**
diff --git a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
index b38b25a398a1..42785be779de 100644
--- a/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/KeyValueAdbBackupEngine.java
@@ -153,8 +153,9 @@ public class KeyValueAdbBackupEngine {
OP_TYPE_BACKUP_WAIT);
// Start backup and wait for BackupManagerService to get callback for success or timeout
- agent.doBackup(mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
- mBackupManagerService.getBackupManagerBinder());
+ agent.doBackup(
+ mSavedState, mBackupData, mNewState, Long.MAX_VALUE, token,
+ mBackupManagerService.getBackupManagerBinder(), /*transportFlags=*/ 0);
if (!mBackupManagerService.waitUntilOperationComplete(token)) {
Slog.e(TAG, "Key-value backup failed on package " + packageName);
return false;
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 465bb09927a5..dc20d31c2f51 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -65,6 +65,7 @@ import android.net.Uri;
import android.os.Binder;
import android.os.Bundle;
import android.os.Environment;
+import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
@@ -387,7 +388,7 @@ public class RefactoredBackupManagerService implements BackupManagerServiceInter
mWakelock = wakelock;
}
- public BackupHandler getBackupHandler() {
+ public Handler getBackupHandler() {
return mBackupHandler;
}
diff --git a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
index 3bf77e8bd23d..d460f4de4058 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/FullBackupEngine.java
@@ -74,6 +74,7 @@ public class FullBackupEngine {
PackageInfo mPkg;
private final long mQuota;
private final int mOpToken;
+ private final int mTransportFlags;
class FullBackupRunner implements Runnable {
@@ -100,7 +101,8 @@ public class FullBackupEngine {
@Override
public void run() {
try {
- FullBackupDataOutput output = new FullBackupDataOutput(mPipe);
+ FullBackupDataOutput output = new FullBackupDataOutput(
+ mPipe, -1, mTransportFlags);
if (mWriteManifest) {
final boolean writeWidgetData = mWidgetData != null;
@@ -147,7 +149,7 @@ public class FullBackupEngine {
mTimeoutMonitor /* in parent class */,
OP_TYPE_BACKUP_WAIT);
mAgent.doFullBackup(mPipe, mQuota, mToken,
- backupManagerService.getBackupManagerBinder());
+ backupManagerService.getBackupManagerBinder(), mTransportFlags);
} catch (IOException e) {
Slog.e(TAG, "Error running full backup for " + mPackage.packageName);
} catch (RemoteException e) {
@@ -164,7 +166,8 @@ public class FullBackupEngine {
public FullBackupEngine(RefactoredBackupManagerService backupManagerService,
OutputStream output,
FullBackupPreflight preflightHook, PackageInfo pkg,
- boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken) {
+ boolean alsoApks, BackupRestoreTask timeoutMonitor, long quota, int opToken,
+ int transportFlags) {
this.backupManagerService = backupManagerService;
mOutput = output;
mPreflightHook = preflightHook;
@@ -176,6 +179,7 @@ public class FullBackupEngine {
mMetadataFile = new File(mFilesDir, BACKUP_METADATA_FILENAME);
mQuota = quota;
mOpToken = opToken;
+ mTransportFlags = transportFlags;
}
public int preflightCheck() throws RemoteException {
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
index f0b3e4a023f0..19e601be5f80 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformAdbBackupTask.java
@@ -410,7 +410,8 @@ public class PerformAdbBackupTask extends FullBackupTask implements BackupRestor
SHARED_BACKUP_AGENT_PACKAGE);
mBackupEngine = new FullBackupEngine(backupManagerService, out,
- null, pkg, mIncludeApks, this, Long.MAX_VALUE, mCurrentOpToken);
+ null, pkg, mIncludeApks, this, Long.MAX_VALUE,
+ mCurrentOpToken, /*transportFlags=*/ 0);
sendOnBackupPackage(isSharedStorage ? "Shared storage" : pkg.packageName);
// Don't need to check preflight result as there is no preflight hook.
diff --git a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
index d5b3d98f9897..d04be1226e85 100644
--- a/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
+++ b/services/backup/java/com/android/server/backup/fullbackup/PerformFullTransportBackupTask.java
@@ -378,7 +378,8 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
enginePipes = ParcelFileDescriptor.createPipe();
mBackupRunner =
new SinglePackageBackupRunner(enginePipes[1], currentPackage,
- mTransportClient, quota, mBackupRunnerOpToken);
+ mTransportClient, quota, mBackupRunnerOpToken,
+ transport.getTransportFlags());
// The runner dup'd the pipe half, so we close it here
enginePipes[1].close();
enginePipes[1] = null;
@@ -681,12 +682,17 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
final TransportClient mTransportClient;
final long mQuota;
private final int mCurrentOpToken;
+ private final int mTransportFlags;
SinglePackageBackupPreflight(
- TransportClient transportClient, long quota, int currentOpToken) {
+ TransportClient transportClient,
+ long quota,
+ int currentOpToken,
+ int transportFlags) {
mTransportClient = transportClient;
mQuota = quota;
mCurrentOpToken = currentOpToken;
+ mTransportFlags = transportFlags;
}
@Override
@@ -700,7 +706,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
Slog.d(TAG, "Preflighting full payload of " + pkg.packageName);
}
agent.doMeasureFullBackup(mQuota, mCurrentOpToken,
- backupManagerService.getBackupManagerBinder());
+ backupManagerService.getBackupManagerBinder(), mTransportFlags);
// Now wait to get our result back. If this backstop timeout is reached without
// the latch being thrown, flow will continue as though a result or "normal"
@@ -785,20 +791,23 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
private volatile int mBackupResult;
private final long mQuota;
private volatile boolean mIsCancelled;
+ private final int mTransportFlags;
SinglePackageBackupRunner(ParcelFileDescriptor output, PackageInfo target,
- TransportClient transportClient, long quota, int currentOpToken)
+ TransportClient transportClient, long quota, int currentOpToken, int transportFlags)
throws IOException {
mOutput = ParcelFileDescriptor.dup(output.getFileDescriptor());
mTarget = target;
mCurrentOpToken = currentOpToken;
mEphemeralToken = backupManagerService.generateRandomIntegerToken();
- mPreflight = new SinglePackageBackupPreflight(transportClient, quota, mEphemeralToken);
+ mPreflight = new SinglePackageBackupPreflight(
+ transportClient, quota, mEphemeralToken, transportFlags);
mPreflightLatch = new CountDownLatch(1);
mBackupLatch = new CountDownLatch(1);
mPreflightResult = BackupTransport.AGENT_ERROR;
mBackupResult = BackupTransport.AGENT_ERROR;
mQuota = quota;
+ mTransportFlags = transportFlags;
registerTask();
}
@@ -819,7 +828,7 @@ public class PerformFullTransportBackupTask extends FullBackupTask implements Ba
public void run() {
FileOutputStream out = new FileOutputStream(mOutput.getFileDescriptor());
mEngine = new FullBackupEngine(backupManagerService, out, mPreflight, mTarget, false,
- this, mQuota, mCurrentOpToken);
+ this, mQuota, mCurrentOpToken, mTransportFlags);
try {
try {
if (!mIsCancelled) {
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index 99ffa12e3e5f..bacb35791cea 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -714,8 +714,9 @@ public class PerformBackupTask implements BackupRestoreTask {
mEphemeralOpToken, TIMEOUT_BACKUP_INTERVAL, this, OP_TYPE_BACKUP_WAIT);
backupManagerService.addBackupTrace("calling agent doBackup()");
- agent.doBackup(mSavedState, mBackupData, mNewState, quota, mEphemeralOpToken,
- backupManagerService.getBackupManagerBinder());
+ agent.doBackup(
+ mSavedState, mBackupData, mNewState, quota, mEphemeralOpToken,
+ backupManagerService.getBackupManagerBinder(), transport.getTransportFlags());
} catch (Exception e) {
Slog.e(TAG, "Error invoking for backup on " + packageName + ". " + e);
backupManagerService.addBackupTrace("exception: " + e);
diff --git a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
index 7ae5b4389fd4..82106ec51ab8 100644
--- a/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
+++ b/services/backup/java/com/android/server/backup/restore/ActiveRestoreSession.java
@@ -30,6 +30,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Binder;
+import android.os.Handler;
import android.os.Message;
import android.os.PowerManager;
import android.util.Slog;
@@ -374,7 +375,7 @@ public class ActiveRestoreSession extends IRestoreSession.Stub {
}
// Stop the session timeout until we finalize the restore
- BackupHandler backupHandler = mBackupManagerService.getBackupHandler();
+ Handler backupHandler = mBackupManagerService.getBackupHandler();
backupHandler.removeMessages(MSG_RESTORE_SESSION_TIMEOUT);
PowerManager.WakeLock wakelock = mBackupManagerService.getWakelock();
diff --git a/services/robotests/Android.mk b/services/robotests/Android.mk
index ae311f841fd0..d825533e92f5 100644
--- a/services/robotests/Android.mk
+++ b/services/robotests/Android.mk
@@ -13,9 +13,9 @@
# limitations under the License.
-############################################################
-# FrameworksServicesLib app just for Robolectric test target. #
-############################################################
+##############################################################
+# FrameworksServicesLib app just for Robolectric test target #
+##############################################################
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
@@ -31,14 +31,43 @@ LOCAL_STATIC_JAVA_LIBRARIES := \
include $(BUILD_PACKAGE)
-#############################################
-# FrameworksServices Robolectric test target. #
-#############################################
+##############################################
+# FrameworksServices Robolectric test target #
+##############################################
include $(CLEAR_VARS)
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
+# Dependency platform-robolectric-android-all-stubs below contains a bunch of Android classes as
+# stubs that throw RuntimeExceptions when we use them. The goal is to include hidden APIs that
+# weren't included in Robolectric's Android jar files. However, we are testing the framework itself
+# here, so if we write stuff that is being used in the tests and exist in
+# platform-robolectric-android-all-stubs, the class loader is going to pick up the latter, and thus
+# we are going to test what we don't want. To solve this:
+#
+# 1. If the class being used should be visible to bundled apps:
+# => Bypass the stubs target by including them in LOCAL_SRC_FILES and LOCAL_AIDL_INCLUDES
+# (if aidl).
+#
+# 2. If it's not visible:
+# => Remove the class from the stubs jar (common/robolectric/android-all/android-all-stubs.jar)
+# and add the class path to
+# common/robolectric/android-all/android-all-stubs_removed_classes.txt.
+#
+
+INTERNAL_BACKUP := ../../core/java/com/android/internal/backup
+
+LOCAL_SRC_FILES := \
+ $(call all-java-files-under, src) \
+ $(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \
+ ../../core/java/android/content/pm/PackageInfo.java \
+ ../../core/java/android/app/backup/BackupAgent.java \
+ ../../core/java/android/app/backup/BackupDataOutput.java \
+ ../../core/java/android/app/backup/FullBackupDataOutput.java \
+ ../../core/java/android/app/IBackupAgent.aidl
+
+LOCAL_AIDL_INCLUDES := \
+ $(call all-Iaidl-files-under, $(INTERNAL_BACKUP)) \
+ ../../core/java/android/app/IBackupAgent.aidl
-# Include the testing libraries (JUnit4 + Robolectric libs).
LOCAL_STATIC_JAVA_LIBRARIES := \
platform-robolectric-android-all-stubs \
android-support-test \
@@ -58,9 +87,9 @@ LOCAL_MODULE_TAGS := optional
include $(BUILD_STATIC_JAVA_LIBRARY)
-#############################################################
-# FrameworksServices runner target to run the previous target. #
-#############################################################
+###############################################################
+# FrameworksServices runner target to run the previous target #
+###############################################################
include $(CLEAR_VARS)
LOCAL_MODULE := RunFrameworksServicesRoboTests
diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
new file mode 100644
index 000000000000..3668350c6616
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2018 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;
+
+import static com.android.server.backup.testing.TransportData.backupTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransport;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.robolectric.Shadows.shadowOf;
+
+import static java.util.Collections.emptyList;
+import static java.util.stream.Collectors.toCollection;
+import static java.util.stream.Collectors.toList;
+
+import android.app.Application;
+import android.app.IActivityManager;
+import android.app.IBackupAgent;
+import android.app.backup.BackupAgent;
+import android.app.backup.BackupDataOutput;
+import android.app.backup.FullBackupDataOutput;
+import android.app.backup.IBackupManager;
+import android.app.backup.IBackupManagerMonitor;
+import android.app.backup.IBackupObserver;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.platform.test.annotations.Presubmit;
+import android.util.Pair;
+import android.util.SparseArray;
+
+import com.android.internal.backup.IBackupTransport;
+import com.android.server.backup.internal.BackupHandler;
+import com.android.server.backup.internal.BackupRequest;
+import com.android.server.backup.internal.OnTaskFinishedListener;
+import com.android.server.backup.internal.PerformBackupTask;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
+import com.android.server.backup.transport.TransportClient;
+import com.android.server.testing.FrameworkRobolectricTestRunner;
+import com.android.server.testing.SystemLoaderClasses;
+import com.android.server.testing.shadows.FrameworkShadowPackageManager;
+import com.android.server.testing.shadows.ShadowBackupDataInput;
+import com.android.server.testing.shadows.ShadowBackupDataOutput;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+import org.robolectric.shadows.ShadowLooper;
+import org.robolectric.shadows.ShadowPackageManager;
+import org.robolectric.shadows.ShadowQueuedWork;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Stream;
+
+@RunWith(FrameworkRobolectricTestRunner.class)
+@Config(
+ manifest = Config.NONE,
+ sdk = 26,
+ shadows = {
+ FrameworkShadowPackageManager.class,
+ ShadowBackupDataInput.class,
+ ShadowBackupDataOutput.class,
+ ShadowQueuedWork.class
+ }
+)
+@SystemLoaderClasses({
+ PerformBackupTask.class,
+ BackupDataOutput.class,
+ FullBackupDataOutput.class,
+ TransportManager.class,
+ BackupAgent.class,
+ IBackupTransport.class,
+ IBackupAgent.class,
+ PackageInfo.class
+})
+@Presubmit
+public class PerformBackupTaskTest {
+ private static final String PACKAGE_1 = "com.example.package1";
+ private static final String PACKAGE_2 = "com.example.package2";
+
+ @Mock private RefactoredBackupManagerService mBackupManagerService;
+ @Mock private TransportManager mTransportManager;
+ @Mock private DataChangedJournal mDataChangedJournal;
+ @Mock private IBackupObserver mObserver;
+ @Mock private IBackupManagerMonitor mMonitor;
+ @Mock private OnTaskFinishedListener mListener;
+ private TransportData mTransport;
+ private IBackupTransport mTransportBinder;
+ private TransportClient mTransportClient;
+ private ShadowLooper mShadowBackupLooper;
+ private BackupHandler mBackupHandler;
+ private PowerManager.WakeLock mWakeLock;
+ private ShadowPackageManager mShadowPackageManager;
+ private FakeIBackupManager mBackupManager;
+
+ @Before
+ public void setUp() throws Exception {
+ MockitoAnnotations.initMocks(this);
+
+ mTransport = backupTransport();
+ TransportMock transportMock = setUpTransport(mTransportManager, mTransport);
+ mTransportBinder = transportMock.transport;
+ mTransportClient = transportMock.transportClient;
+
+ Application application = RuntimeEnvironment.application;
+ File cacheDir = application.getCacheDir();
+ File baseStateDir = new File(cacheDir, "base_state_dir");
+ File dataDir = new File(cacheDir, "data_dir");
+ File stateDir = new File(baseStateDir, mTransport.transportDirName);
+ assertThat(baseStateDir.mkdir()).isTrue();
+ assertThat(dataDir.mkdir()).isTrue();
+ assertThat(stateDir.mkdir()).isTrue();
+
+ PackageManager packageManager = application.getPackageManager();
+ mShadowPackageManager = Shadow.extract(packageManager);
+
+ PowerManager powerManager =
+ (PowerManager) application.getSystemService(Context.POWER_SERVICE);
+ mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
+
+ // Robolectric simulates multi-thread in a single-thread to avoid flakiness
+ HandlerThread backupThread = new HandlerThread("backup");
+ backupThread.setUncaughtExceptionHandler(
+ (t, e) -> fail("Uncaught exception " + e.getMessage()));
+ backupThread.start();
+ Looper backupLooper = backupThread.getLooper();
+ mShadowBackupLooper = shadowOf(backupLooper);
+ mBackupHandler = new BackupHandler(mBackupManagerService, backupLooper);
+
+ mBackupManager = spy(FakeIBackupManager.class);
+
+ when(mBackupManagerService.getTransportManager()).thenReturn(mTransportManager);
+ when(mBackupManagerService.getContext()).thenReturn(application);
+ when(mBackupManagerService.getPackageManager()).thenReturn(packageManager);
+ when(mBackupManagerService.getWakelock()).thenReturn(mWakeLock);
+ when(mBackupManagerService.getCurrentOpLock()).thenReturn(new Object());
+ when(mBackupManagerService.getQueueLock()).thenReturn(new Object());
+ when(mBackupManagerService.getBaseStateDir()).thenReturn(baseStateDir);
+ when(mBackupManagerService.getDataDir()).thenReturn(dataDir);
+ when(mBackupManagerService.getCurrentOperations()).thenReturn(new SparseArray<>());
+ when(mBackupManagerService.getBackupHandler()).thenReturn(mBackupHandler);
+ when(mBackupManagerService.getBackupManagerBinder()).thenReturn(mBackupManager);
+ when(mBackupManagerService.getActivityManager()).thenReturn(mock(IActivityManager.class));
+ }
+
+ @Test
+ public void testRunTask_whenTransportProvidesFlags_passesThemToTheAgent() throws Exception {
+ BackupAgent agent = setUpAgent(PACKAGE_1);
+ int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+ when(mTransportBinder.getTransportFlags()).thenReturn(flags);
+ PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+
+ runTask(task);
+
+ verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+ }
+
+ @Test
+ public void testRunTask_whenTransportDoesNotProvidesFlags() throws Exception {
+ BackupAgent agent = setUpAgent(PACKAGE_1);
+ PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+
+ runTask(task);
+
+ verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(0)), any());
+ }
+
+ @Test
+ public void testRunTask_whenTransportProvidesFlagsAndMultipleAgents_passesToAll()
+ throws Exception {
+ List<BackupAgent> agents = setUpAgents(PACKAGE_1, PACKAGE_2);
+ BackupAgent agent1 = agents.get(0);
+ BackupAgent agent2 = agents.get(1);
+ int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+ when(mTransportBinder.getTransportFlags()).thenReturn(flags);
+ PerformBackupTask task =
+ createPerformBackupTask(emptyList(), false, true, PACKAGE_1, PACKAGE_2);
+
+ runTask(task);
+
+ verify(agent1).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+ verify(agent2).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+ }
+
+ @Test
+ public void testRunTask_whenTransportChangeFlagsAfterTaskCreation() throws Exception {
+ BackupAgent agent = setUpAgent(PACKAGE_1);
+ PerformBackupTask task = createPerformBackupTask(emptyList(), false, true, PACKAGE_1);
+ int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
+ when(mTransportBinder.getTransportFlags()).thenReturn(flags);
+
+ runTask(task);
+
+ verify(agent).onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
+ }
+
+ private void runTask(PerformBackupTask task) {
+ Message message = mBackupHandler.obtainMessage(BackupHandler.MSG_BACKUP_RESTORE_STEP, task);
+ mBackupHandler.sendMessage(message);
+ while (mShadowBackupLooper.getScheduler().areAnyRunnable()) {
+ mShadowBackupLooper.runToEndOfTasks();
+ }
+ }
+
+ private List<BackupAgent> setUpAgents(String... packageNames) {
+ return Stream.of(packageNames).map(this::setUpAgent).collect(toList());
+ }
+
+ private BackupAgent setUpAgent(String packageName) {
+ PackageInfo packageInfo = new PackageInfo();
+ packageInfo.packageName = packageName;
+ packageInfo.applicationInfo = new ApplicationInfo();
+ packageInfo.applicationInfo.flags = ApplicationInfo.FLAG_ALLOW_BACKUP;
+ packageInfo.applicationInfo.backupAgentName = "BackupAgent" + packageName;
+ packageInfo.applicationInfo.packageName = packageName;
+ mShadowPackageManager.setApplicationEnabledSetting(
+ packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
+ mShadowPackageManager.addPackage(packageInfo);
+ BackupAgent backupAgent = spy(BackupAgent.class);
+ IBackupAgent backupAgentBinder = IBackupAgent.Stub.asInterface(backupAgent.onBind());
+ when(mBackupManagerService.bindToAgentSynchronous(
+ eq(packageInfo.applicationInfo), anyInt()))
+ .thenReturn(backupAgentBinder);
+ return backupAgent;
+ }
+
+ private PerformBackupTask createPerformBackupTask(
+ List<String> pendingFullBackups,
+ boolean userInitiated,
+ boolean nonIncremental,
+ String... packages) {
+ ArrayList<BackupRequest> backupRequests =
+ Stream.of(packages).map(BackupRequest::new).collect(toCollection(ArrayList::new));
+ mWakeLock.acquire();
+ PerformBackupTask task =
+ new PerformBackupTask(
+ mBackupManagerService,
+ mTransportClient,
+ mTransport.transportDirName,
+ backupRequests,
+ mDataChangedJournal,
+ mObserver,
+ mMonitor,
+ mListener,
+ pendingFullBackups,
+ userInitiated,
+ nonIncremental);
+ mBackupManager.setUp(mBackupHandler, task);
+ return task;
+ }
+
+ private ArgumentMatcher<BackupDataOutput> dataOutputWithTransportFlags(int flags) {
+ return dataOutput -> dataOutput.getTransportFlags() == flags;
+ }
+
+ private abstract static class FakeIBackupManager extends IBackupManager.Stub {
+ private Handler mBackupHandler;
+ private BackupRestoreTask mTask;
+
+ public FakeIBackupManager() {}
+
+ private void setUp(Handler backupHandler, BackupRestoreTask task) {
+ mBackupHandler = backupHandler;
+ mTask = task;
+ }
+
+ @Override
+ public void opComplete(int token, long result) throws RemoteException {
+ assertThat(mTask).isNotNull();
+ Message message =
+ mBackupHandler.obtainMessage(
+ BackupHandler.MSG_OP_COMPLETE, Pair.create(mTask, result));
+ mBackupHandler.sendMessage(message);
+ }
+ }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
index e1dc7b5e151e..565c7e638aac 100644
--- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
+++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
@@ -144,12 +144,14 @@ public class TransportTestUtils {
// Transport registered and available
IBackupTransport transportMock = mockTransportBinder(transport);
when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
+ when(transportClientMock.connect(any())).thenReturn(transportMock);
return new TransportMock(transportClientMock, transportMock);
} else {
// Transport registered but unavailable
when(transportClientMock.connectOrThrow(any()))
.thenThrow(TransportNotAvailableException.class);
+ when(transportClientMock.connect(any())).thenReturn(null);
return new TransportMock(transportClientMock, null);
}
diff --git a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
index 6c7313ba639e..c94d5983d2f5 100644
--- a/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
+++ b/services/robotests/src/com/android/server/testing/FrameworkRobolectricTestRunner.java
@@ -16,6 +16,9 @@
package com.android.server.testing;
+import com.android.server.backup.PerformBackupTaskTest;
+import com.android.server.backup.internal.PerformBackupTask;
+
import com.google.common.collect.ImmutableSet;
import org.junit.runners.model.FrameworkMethod;
@@ -30,6 +33,9 @@ import org.robolectric.util.Util;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.util.Enumeration;
import java.util.Set;
import javax.annotation.Nonnull;
@@ -115,6 +121,27 @@ public class FrameworkRobolectricTestRunner extends RobolectricTestRunner {
}
/**
+ * HACK^2
+ * The framework Robolectric run configuration puts a prebuilt in front of us, so we try not
+ * to load the class from there, if possible.
+ */
+ @Override
+ public InputStream getResourceAsStream(String resource) {
+ try {
+ Enumeration<URL> urls = getResources(resource);
+ while (urls.hasMoreElements()) {
+ URL url = urls.nextElement();
+ if (!url.toString().toLowerCase().contains("prebuilt")) {
+ return url.openStream();
+ }
+ }
+ } catch (IOException e) {
+ // Fall through
+ }
+ return super.getResourceAsStream(resource);
+ }
+
+ /**
* Classes like com.package.ClassName$InnerClass should also be loaded from the system class
* loader, so we test if the classes in the annotation are prefixes of the class to load.
*/
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java
new file mode 100644
index 000000000000..28489afce8d5
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataInput.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2018 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.testing.shadows;
+
+import android.app.backup.BackupDataInput;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+@Implements(BackupDataInput.class)
+public class ShadowBackupDataInput {
+ @Implementation
+ public void __constructor__(FileDescriptor fd) {
+ }
+
+ @Implementation
+ protected void finalize() throws Throwable {
+ }
+
+ @Implementation
+ public boolean readNextHeader() throws IOException {
+ return false;
+ }
+
+ @Implementation
+ public String getKey() {
+ throw new AssertionError("Can't call because readNextHeader() returned false");
+ }
+
+ @Implementation
+ public int getDataSize() {
+ throw new AssertionError("Can't call because readNextHeader() returned false");
+ }
+
+ @Implementation
+ public int readEntityData(byte[] data, int offset, int size) throws IOException {
+ throw new AssertionError("Can't call because readNextHeader() returned false");
+ }
+
+ @Implementation
+ public void skipEntityData() throws IOException {
+ throw new AssertionError("Can't call because readNextHeader() returned false");
+ }
+}
diff --git a/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java
new file mode 100644
index 000000000000..c7deada95929
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/ShadowBackupDataOutput.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2018 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.testing.shadows;
+
+import android.app.backup.BackupDataOutput;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+
+@Implements(BackupDataOutput.class)
+public class ShadowBackupDataOutput {
+ private long mQuota;
+ private int mTransportFlags;
+
+ @Implementation
+ public void __constructor__(FileDescriptor fd, long quota, int transportFlags) {
+ mQuota = quota;
+ mTransportFlags = transportFlags;
+ }
+
+ @Implementation
+ public long getQuota() {
+ return mQuota;
+ }
+
+ @Implementation
+ public int getTransportFlags() {
+ return mTransportFlags;
+ }
+
+ @Implementation
+ public int writeEntityHeader(String key, int dataSize) throws IOException {
+ return 0;
+ }
+
+ @Implementation
+ public int writeEntityData(byte[] data, int size) throws IOException {
+ return 0;
+ }
+}