diff options
| author | 2018-01-25 12:17:45 +0000 | |
|---|---|---|
| committer | 2018-01-25 12:17:45 +0000 | |
| commit | bb8a385fab19974ab5a01cef18bf7c89dbf6ae9d (patch) | |
| tree | e145f144dd7f8c439cdd5cc72b877d9cb75dbae7 | |
| parent | 9543d6eb658cb6c1a2c1d6f3df09cb305d5631af (diff) | |
| parent | 39194c0582463be17513b9ba82802a703b10c934 (diff) | |
Merge "Add #getTransportFlags to BackupDataOutput"
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; + } +} |