diff options
10 files changed, 705 insertions, 155 deletions
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java index cd1bd67a5387..574288af58b4 100644 --- a/services/backup/java/com/android/server/backup/BackupManagerService.java +++ b/services/backup/java/com/android/server/backup/BackupManagerService.java @@ -38,6 +38,7 @@ import android.app.AppGlobals; import android.app.IActivityManager; import android.app.IBackupAgent; import android.app.PendingIntent; +import android.app.backup.BackupAgent; import android.app.backup.BackupManager; import android.app.backup.BackupManagerMonitor; import android.app.backup.FullBackup; @@ -704,7 +705,7 @@ public class BackupManagerService implements BackupManagerServiceInterface { * process-local non-lifecycle agent instance, so we manually set up the context * topology for it. */ - public PackageManagerBackupAgent makeMetadataAgent() { + public BackupAgent makeMetadataAgent() { PackageManagerBackupAgent pmAgent = new PackageManagerBackupAgent(mPackageManager); pmAgent.attach(mContext); pmAgent.onCreate(); @@ -784,7 +785,7 @@ public class BackupManagerService implements BackupManagerServiceInterface { } @VisibleForTesting - BackupManagerService( + public BackupManagerService( Context context, Trampoline parent, HandlerThread backupThread, @@ -1749,6 +1750,16 @@ public class BackupManagerService implements BackupManagerServiceInterface { } } + public void putOperation(int token, Operation operation) { + if (MORE_DEBUG) { + Slog.d(TAG, "Adding operation token=" + Integer.toHexString(token) + ", operation type=" + + operation.type); + } + synchronized (mCurrentOpLock) { + mCurrentOperations.put(token, operation); + } + } + public void removeOperation(int token) { if (MORE_DEBUG) { Slog.d(TAG, "Removing operation token=" + Integer.toHexString(token)); 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 ae43299f7752..90b4bbd6f5f2 100644 --- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java +++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java @@ -30,6 +30,7 @@ import static com.android.server.backup.internal.BackupHandler.MSG_BACKUP_RESTOR import android.annotation.Nullable; import android.app.ApplicationThreadConstants; import android.app.IBackupAgent; +import android.app.backup.BackupAgent; import android.app.backup.BackupDataInput; import android.app.backup.BackupDataOutput; import android.app.backup.BackupManager; @@ -205,10 +206,8 @@ public class PerformBackupTask implements BackupRestoreTask { * Put this task in the repository of running tasks. */ private void registerTask() { - synchronized (backupManagerService.getCurrentOpLock()) { - backupManagerService.getCurrentOperations().put( - mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP)); - } + backupManagerService.putOperation( + mCurrentOpToken, new Operation(OP_PENDING, this, OP_TYPE_BACKUP)); } /** @@ -358,7 +357,7 @@ public class PerformBackupTask implements BackupRestoreTask { // The package manager doesn't have a proper <application> etc, but since it's running // here in the system process we can just set up its agent directly and use a synthetic // BackupRequest. - PackageManagerBackupAgent pmAgent = backupManagerService.makeMetadataAgent(); + BackupAgent pmAgent = backupManagerService.makeMetadataAgent(); mStatus = invokeAgentForBackup( PACKAGE_MANAGER_SENTINEL, IBackupAgent.Stub.asInterface(pmAgent.onBind())); diff --git a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java index 78b000d49f38..32dbad9f92db 100644 --- a/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java +++ b/services/backup/java/com/android/server/backup/restore/PerformAdbRestoreTask.java @@ -30,6 +30,7 @@ import static com.android.server.backup.internal.BackupHandler.MSG_RESTORE_OPERA import android.app.IBackupAgent; +import android.app.backup.BackupAgent; import android.app.backup.IFullBackupRestoreObserver; import android.content.pm.ApplicationInfo; import android.content.pm.Signature; @@ -76,7 +77,7 @@ public class PerformAdbRestoreTask implements Runnable { private final String mCurrentPassword; private final String mDecryptPassword; private final AtomicBoolean mLatchObject; - private final PackageManagerBackupAgent mPackageManagerBackupAgent; + private final BackupAgent mPackageManagerBackupAgent; private final RestoreDeleteObserver mDeleteObserver = new RestoreDeleteObserver(); private IFullBackupRestoreObserver mObserver; diff --git a/services/robotests/src/android/app/backup/ForwardingBackupAgent.java b/services/robotests/src/android/app/backup/ForwardingBackupAgent.java new file mode 100644 index 000000000000..4ff5b7c03a8f --- /dev/null +++ b/services/robotests/src/android/app/backup/ForwardingBackupAgent.java @@ -0,0 +1,113 @@ +/* + * 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 android.app.backup; + +import android.content.Context; +import android.os.ParcelFileDescriptor; + +import java.io.File; +import java.io.IOException; + +/** + * Useful for spying in {@link BackupAgent} instances since their {@link BackupAgent#onBind()} is + * final and always points to the original instance, instead of the spy. + * + * <p>To use, construct a spy of the desired {@link BackupAgent}, spying on the methods of interest. + * Then, where you need to pass the agent, use {@link ForwardingBackupAgent#forward(BackupAgent)} + * with the spy. + */ +public class ForwardingBackupAgent extends BackupAgent { + /** Returns a {@link BackupAgent} that forwards method calls to {@code backupAgent}. */ + public static BackupAgent forward(BackupAgent backupAgent) { + return new ForwardingBackupAgent(backupAgent); + } + + private final BackupAgent mBackupAgent; + + private ForwardingBackupAgent(BackupAgent backupAgent) { + mBackupAgent = backupAgent; + } + + @Override + public void onCreate() { + mBackupAgent.onCreate(); + } + + @Override + public void onDestroy() { + mBackupAgent.onDestroy(); + } + + @Override + public void onBackup( + ParcelFileDescriptor oldState, BackupDataOutput data, ParcelFileDescriptor newState) + throws IOException { + mBackupAgent.onBackup(oldState, data, newState); + } + + @Override + public void onRestore(BackupDataInput data, int appVersionCode, ParcelFileDescriptor newState) + throws IOException { + mBackupAgent.onRestore(data, appVersionCode, newState); + } + + @Override + public void onRestore(BackupDataInput data, long appVersionCode, ParcelFileDescriptor newState) + throws IOException { + mBackupAgent.onRestore(data, appVersionCode, newState); + } + + @Override + public void onFullBackup(FullBackupDataOutput data) throws IOException { + mBackupAgent.onFullBackup(data); + } + + @Override + public void onQuotaExceeded(long backupDataBytes, long quotaBytes) { + mBackupAgent.onQuotaExceeded(backupDataBytes, quotaBytes); + } + + @Override + public void onRestoreFile( + ParcelFileDescriptor data, long size, File destination, int type, long mode, long mtime) + throws IOException { + mBackupAgent.onRestoreFile(data, size, destination, type, mode, mtime); + } + + @Override + protected void onRestoreFile( + ParcelFileDescriptor data, + long size, + int type, + String domain, + String path, + long mode, + long mtime) + throws IOException { + mBackupAgent.onRestoreFile(data, size, type, domain, path, mode, mtime); + } + + @Override + public void onRestoreFinished() { + mBackupAgent.onRestoreFinished(); + } + + @Override + public void attach(Context context) { + mBackupAgent.attach(context); + } +} diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java index d16bc269786b..abaed7c2b6d4 100644 --- a/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java +++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceTest.java @@ -16,7 +16,7 @@ package com.android.server.backup; -import static com.android.server.backup.testing.BackupManagerServiceTestUtils.startBackupThread; +import static com.android.server.backup.testing.BackupManagerServiceTestUtils.startSilentBackupThread; import static com.android.server.backup.testing.TransportData.backupTransport; import static com.android.server.backup.testing.TransportData.d2dTransport; import static com.android.server.backup.testing.TransportData.localTransport; @@ -46,6 +46,7 @@ import android.os.PowerSaveState; import android.platform.test.annotations.Presubmit; import android.provider.Settings; import com.android.server.backup.internal.BackupRequest; +import com.android.server.backup.testing.BackupManagerServiceTestUtils; import com.android.server.backup.testing.TransportData; import com.android.server.backup.testing.TransportTestUtils.TransportMock; import com.android.server.backup.transport.TransportNotRegisteredException; @@ -68,7 +69,6 @@ import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.annotation.Implements; import org.robolectric.shadows.ShadowContextWrapper; -import org.robolectric.shadows.ShadowLog; import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowSettings; @@ -104,7 +104,10 @@ public class BackupManagerServiceTest { mTransport = backupTransport(); mTransportName = mTransport.transportName; - mBackupThread = startBackupThread(this::uncaughtException); + // Unrelated exceptions are thrown in the backup thread. Until we mock everything properly + // we should not fail tests because of this. This is not flakiness, the exceptions thrown + // don't interfere with the tests. + mBackupThread = startSilentBackupThread(TAG); mShadowBackupLooper = shadowOf(mBackupThread.getLooper()); ContextWrapper context = RuntimeEnvironment.application; @@ -113,8 +116,10 @@ public class BackupManagerServiceTest { mShadowContext = shadowOf(context); File cacheDir = mContext.getCacheDir(); - mBaseStateDir = new File(cacheDir, "base_state_dir"); - mDataDir = new File(cacheDir, "data_dir"); + // Corresponds to /data/backup + mBaseStateDir = new File(cacheDir, "base_state"); + // Corresponds to /cache/backup_stage + mDataDir = new File(cacheDir, "data"); ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(null); } @@ -126,13 +131,6 @@ public class BackupManagerServiceTest { ShadowBackupPolicyEnforcer.setMandatoryBackupTransport(null); } - private void uncaughtException(Thread thread, Throwable e) { - // Unrelated exceptions are thrown in the backup thread. Until we mock everything properly - // we should not fail tests because of this. This is not flakiness, the exceptions thrown - // don't interfere with the tests. - ShadowLog.e(TAG, "Uncaught exception in test thread " + thread.getName(), e); - } - /* Tests for destination string */ @Test @@ -864,22 +862,8 @@ public class BackupManagerServiceTest { } private BackupManagerService createInitializedBackupManagerService() { - BackupManagerService backupManagerService = - new BackupManagerService( - mContext, - new Trampoline(mContext), - mBackupThread, - mBaseStateDir, - mDataDir, - mTransportManager); - mShadowBackupLooper.runToEndOfTasks(); - // Handler instances have their own clock, so advancing looper (with runToEndOfTasks()) - // above does NOT advance the handlers' clock, hence whenever a handler post messages with - // specific time to the looper the time of those messages will be before the looper's time. - // To fix this we advance SystemClock as well since that is from where the handlers read - // time. - ShadowSystemClock.setCurrentTimeMillis(mShadowBackupLooper.getScheduler().getCurrentTime()); - return backupManagerService; + return BackupManagerServiceTestUtils.createInitializedBackupManagerService( + mContext, mBackupThread, mBaseStateDir, mDataDir, mTransportManager); } private void setUpPowerManager(BackupManagerService backupManagerService) { diff --git a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java index 88a51a51a7a1..2d6dd4d80406 100644 --- a/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java +++ b/services/robotests/src/com/android/server/backup/PerformBackupTaskTest.java @@ -16,12 +16,17 @@ package com.android.server.backup; +import static android.app.backup.ForwardingBackupAgent.forward; + +import static com.android.server.backup.BackupManagerService.PACKAGE_MANAGER_SENTINEL; import static com.android.server.backup.testing.BackupManagerServiceTestUtils.createBackupWakeLock; +import static com.android.server.backup.testing.BackupManagerServiceTestUtils.createInitializedBackupManagerService; import static com.android.server.backup.testing.BackupManagerServiceTestUtils.setUpBackupManagerServiceBasics; -import static com.android.server.backup.testing.BackupManagerServiceTestUtils.startBackupThreadAndGetLooper; import static com.android.server.backup.testing.TestUtils.uncheck; import static com.android.server.backup.testing.TransportData.backupTransport; import static com.google.common.truth.Truth.assertThat; + +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.util.Collections.emptyList; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toList; @@ -34,6 +39,7 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.intThat; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; @@ -51,12 +57,12 @@ import android.app.backup.BackupTransport; 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.DeadObjectException; import android.os.Handler; -import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; import android.os.PowerManager; @@ -77,13 +83,23 @@ import com.android.server.testing.SystemLoaderClasses; import com.android.server.testing.SystemLoaderPackages; import com.android.server.testing.shadows.ShadowBackupDataInput; import com.android.server.testing.shadows.ShadowBackupDataOutput; + +import com.google.common.truth.IterableSubject; + +import java.io.ByteArrayOutputStream; import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Files; -import java.nio.file.Paths; +import java.nio.file.Path; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; import java.util.stream.Stream; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -98,6 +114,19 @@ import org.robolectric.shadows.ShadowLooper; import org.robolectric.shadows.ShadowPackageManager; import org.robolectric.shadows.ShadowQueuedWork; +// TODO: Don't do backup for full-backup +// TODO: Don't do backup for stopped +// TODO: Don't do backup for non-eligible +// TODO: (performBackup() => SUCCESS, finishBackup() => SUCCESS) => delete stage file, renames +// state file +// TODO: Check agent writes state file => check file content +// TODO: Check agent writes new state file => next agent reads it correctly +// TODO: Check non-incremental has empty state file +// TODO: Check queue of 2, transport rejecting package but other package proceeds +// TODO: Check queue in general, behavior w/ multiple packages +// TODO: Check quota is passed from transport to agent +// TODO: Check non-incremental and transport requests PM in queue +// TODO: Verify initialization @RunWith(FrameworkRobolectricTestRunner.class) @Config( manifest = Config.NONE, @@ -114,20 +143,22 @@ public class PerformBackupTaskTest { private static final String PACKAGE_1 = "com.example.package1"; private static final String PACKAGE_2 = "com.example.package2"; - @Mock private BackupManagerService mBackupManagerService; @Mock private TransportManager mTransportManager; @Mock private DataChangedJournal mDataChangedJournal; @Mock private IBackupObserver mObserver; @Mock private IBackupManagerMonitor mMonitor; @Mock private OnTaskFinishedListener mListener; + private BackupManagerService mBackupManagerService; private TransportData mTransport; private ShadowLooper mShadowBackupLooper; - private BackupHandler mBackupHandler; + private Handler mBackupHandler; private PowerManager.WakeLock mWakeLock; private ShadowPackageManager mShadowPackageManager; private FakeIBackupManager mBackupManager; private File mBaseStateDir; + private File mDataDir; private Application mApplication; + private Context mContext; @Before public void setUp() throws Exception { @@ -136,49 +167,269 @@ public class PerformBackupTaskTest { mTransport = backupTransport(); mApplication = RuntimeEnvironment.application; + mContext = mApplication; + File cacheDir = mApplication.getCacheDir(); - mBaseStateDir = new File(cacheDir, "base_state_dir"); - File dataDir = new File(cacheDir, "data_dir"); - assertThat(mBaseStateDir.mkdir()).isTrue(); - assertThat(dataDir.mkdir()).isTrue(); + // Corresponds to /data/backup + mBaseStateDir = new File(cacheDir, "base_state"); + // Corresponds to /cache/backup_stage + mDataDir = new File(cacheDir, "data"); + // We create here simulating init.rc + mDataDir.mkdirs(); + assertThat(mDataDir.isDirectory()).isTrue(); PackageManager packageManager = mApplication.getPackageManager(); mShadowPackageManager = shadowOf(packageManager); mWakeLock = createBackupWakeLock(mApplication); - Looper backupLooper = startBackupThreadAndGetLooper(); - mShadowBackupLooper = shadowOf(backupLooper); - - Handler mainHandler = new Handler(Looper.getMainLooper()); - BackupAgentTimeoutParameters agentTimeoutParameters = - new BackupAgentTimeoutParameters(mainHandler, mApplication.getContentResolver()); - agentTimeoutParameters.start(); - - // We need to mock BMS timeout parameters before initializing the BackupHandler since - // the constructor of BackupHandler relies on the timeout parameters. - when(mBackupManagerService.getAgentTimeoutParameters()).thenReturn(agentTimeoutParameters); - mBackupHandler = new BackupHandler(mBackupManagerService, backupLooper); - mBackupManager = spy(FakeIBackupManager.class); - BackupManagerConstants constants = - new BackupManagerConstants(mainHandler, mApplication.getContentResolver()); - constants.start(); - + mBackupManagerService = + spy( + createInitializedBackupManagerService( + mContext, mBaseStateDir, mDataDir, mTransportManager)); setUpBackupManagerServiceBasics( mBackupManagerService, mApplication, mTransportManager, packageManager, - mBackupHandler, + mBackupManagerService.getBackupHandler(), mWakeLock, - agentTimeoutParameters); + mBackupManagerService.getAgentTimeoutParameters()); when(mBackupManagerService.getBaseStateDir()).thenReturn(mBaseStateDir); - when(mBackupManagerService.getDataDir()).thenReturn(dataDir); + when(mBackupManagerService.getDataDir()).thenReturn(mDataDir); when(mBackupManagerService.getBackupManagerBinder()).thenReturn(mBackupManager); - when(mBackupManagerService.getConstants()).thenReturn(constants); - when(mBackupManagerService.makeMetadataAgent()).thenAnswer(invocation -> createPmAgent()); + + mBackupHandler = mBackupManagerService.getBackupHandler(); + mShadowBackupLooper = shadowOf(mBackupHandler.getLooper()); + } + + @Test + public void testRunTask_whenQueueEmpty() throws Exception { + when(mBackupManagerService.getCurrentToken()).thenReturn(0L); + TransportMock transportMock = setUpTransport(mTransport); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, mTransport.transportDirName, true); + + runTask(task); + + assertThat(mBackupManagerService.getPendingInits()).isEmpty(); + assertThat(mBackupManagerService.isBackupRunning()).isFalse(); + assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0); + assertThat(mWakeLock.isHeld()).isFalse(); + assertDirectory(getStateDirectory(mTransport)).isEmpty(); + assertDirectory(mDataDir.toPath()).isEmpty(); + verify(transportMock.transport, never()).initializeDevice(); + verify(transportMock.transport, never()).performBackup(any(), any(), anyInt()); + verify(transportMock.transport, never()).finishBackup(); + verify(mDataChangedJournal).delete(); + verify(mListener).onFinished(any()); + verify(mObserver, never()).onResult(any(), anyInt()); + verify(mObserver).backupFinished(BackupManager.SUCCESS); + // TODO: Verify set current token? + } + + @Test + public void testRunTask_whenQueueEmpty_doesNotChangeStateFiles() throws Exception { + TransportMock transportMock = setUpTransport(mTransport); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, mTransport.transportDirName, true); + createPmStateFile(); + Files.write(getStateFile(mTransport, PACKAGE_1), "packageState".getBytes()); + + runTask(task); + + assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_MANAGER_SENTINEL))).isEqualTo("pmState".getBytes()); + assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1))).isEqualTo("packageState".getBytes()); + } + + @Test + public void testRunTask_whenSinglePackage_aboutAgent() throws Exception { + TransportMock transportMock = setUpTransport(mTransport); + AgentMock agentMock = setUpAgent(PACKAGE_1); + agentOnBackupDo( + agentMock, + (oldState, dataOutput, newState) -> { + writeData(dataOutput, "key", "data".getBytes()); + writeState(newState, "newState".getBytes()); + }); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); + + runTask(task); + + verify(agentMock.agent).onBackup(any(), any(), any()); + assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1))) + .isEqualTo("newState".getBytes()); + } + + @Test + public void testRunTask_whenSinglePackage_notifiesCorrectly() throws Exception { + TransportMock transportMock = setUpTransport(mTransport); + setUpAgentWithData(PACKAGE_1); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); + + runTask(task); + + verify(mBackupManagerService).logBackupComplete(PACKAGE_1); + verify(mObserver).onResult(PACKAGE_1, BackupManager.SUCCESS); + verify(mListener).onFinished(any()); + verify(mObserver).backupFinished(BackupManager.SUCCESS); + } + + @Test + public void testRunTask_whenSinglePackage_releasesWakeLock() throws Exception { + TransportMock transportMock = setUpTransport(mTransport); + setUpAgentWithData(PACKAGE_1); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); + + runTask(task); + + assertThat(mWakeLock.isHeld()).isFalse(); + } + + @Test + public void testRunTask_whenSinglePackage_updatesBookkeeping() throws Exception { + TransportMock transportMock = setUpTransport(mTransport); + setUpAgentWithData(PACKAGE_1); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); + + runTask(task); + + assertThat(mBackupManagerService.getPendingInits()).isEmpty(); + assertThat(mBackupManagerService.isBackupRunning()).isFalse(); + assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0); + verify(mDataChangedJournal).delete(); + } + + @Test + public void testRunTask_whenSinglePackageIncremental_passesOldStateToAgent() throws Exception { + TransportMock transportMock = setUpTransport(mTransport); + AgentMock agentMock = setUpAgentWithData(PACKAGE_1); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, + mTransport.transportDirName, + false, + PACKAGE_1); + createPmStateFile(); + Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes()); + + runTask(task); + + assertThat(agentMock.oldState).isEqualTo("oldState".getBytes()); + } + + @Test + public void testRunTask_whenSinglePackageNonIncremental_passesEmptyOldStateToAgent() throws Exception { + TransportMock transportMock = setUpTransport(mTransport); + AgentMock agentMock = setUpAgentWithData(PACKAGE_1); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, + mTransport.transportDirName, + true, + PACKAGE_1); + createPmStateFile(); + Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes()); + + runTask(task); + + assertThat(agentMock.oldState).isEqualTo(new byte[0]); + } + + @Test + public void testRunTask_whenSinglePackageNonIncremental_doesNotBackUpPm() throws Exception { + PackageManagerBackupAgent pmAgent = spy(createPmAgent()); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); + TransportMock transportMock = setUpTransport(mTransport); + setUpAgentWithData(PACKAGE_1); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, + mTransport.transportDirName, + true, + PACKAGE_1); + + runTask(task); + + verify(pmAgent, never()).onBackup(any(), any(), any()); + } + + @Test + public void testRunTask_whenPackageAndPmNonIncremental_backsUpPm() throws Exception { + PackageManagerBackupAgent pmAgent = spy(createPmAgent()); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); + TransportMock transportMock = setUpTransport(mTransport); + setUpAgentWithData(PACKAGE_1); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, + mTransport.transportDirName, + true, + PACKAGE_1, + PACKAGE_MANAGER_SENTINEL); + + runTask(task); + + verify(pmAgent).onBackup(any(), any(), any()); + } + + @Test + public void testRunTask_whenSinglePackageIncremental_backsUpPm() throws Exception { + PackageManagerBackupAgent pmAgent = spy(createPmAgent()); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(forward(pmAgent)); + TransportMock transportMock = setUpTransport(mTransport); + setUpAgentWithData(PACKAGE_1); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, + mTransport.transportDirName, + false, + PACKAGE_1); + + runTask(task); + + verify(pmAgent).onBackup(any(), any(), any()); + } + + @Test + public void testRunTask_whenSinglePackageNoPmState_initializesTransport() throws Exception { + TransportMock transportMock = setUpTransport(mTransport); + setUpAgentWithData(PACKAGE_1); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); + Files.deleteIfExists(getStateFile(mTransport, PACKAGE_MANAGER_SENTINEL)); + + runTask(task); + + verify(transportMock.transport).initializeDevice(); + } + + @Test + public void testRunTask_whenSinglePackageWithPmState_doesNotInitializeTransport() + throws Exception { + TransportMock transportMock = setUpTransport(mTransport); + setUpAgentWithData(PACKAGE_1); + PerformBackupTask task = + createPerformBackupTask( + transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); + createPmStateFile(); + + runTask(task); + + verify(transportMock.transport, never()).initializeDevice(); } @Test @@ -249,20 +500,6 @@ public class PerformBackupTaskTest { } @Test - public void testRunTask_callsListenerAndObserver() throws Exception { - TransportMock transportMock = setUpTransport(mTransport); - setUpAgent(PACKAGE_1); - PerformBackupTask task = - createPerformBackupTask( - transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); - - runTask(task); - - verify(mListener).onFinished(any()); - verify(mObserver).backupFinished(eq(BackupManager.SUCCESS)); - } - - @Test public void testRunTask_releasesWakeLock() throws Exception { TransportMock transportMock = setUpTransport(mTransport); setUpAgent(PACKAGE_1); @@ -281,7 +518,7 @@ public class PerformBackupTaskTest { IBackupTransport transportBinder = transportMock.transport; AgentMock agentMock = setUpAgent(PACKAGE_1); agentOnBackupDo( - agentMock.agent, + agentMock, (oldState, dataOutput, newState) -> { writeData(dataOutput, "key1", "foo".getBytes()); writeData(dataOutput, "key2", "bar".getBytes()); @@ -289,43 +526,48 @@ public class PerformBackupTaskTest { PerformBackupTask task = createPerformBackupTask( transportMock.transportClient, mTransport.transportDirName, PACKAGE_1); - // We need to verify at call time because the file is deleted right after + Path backupDataPath = + Files.createTempFile(mContext.getCacheDir().toPath(), "backup", ".tmp"); when(transportBinder.performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt())) - .then(this::mockAndVerifyTransportPerformBackupData); + .then( + invocation -> { + ParcelFileDescriptor backupDataParcelFd = invocation.getArgument(1); + FileDescriptor backupDataFd = backupDataParcelFd.getFileDescriptor(); + Files.copy( + new FileInputStream(backupDataFd), + backupDataPath, + REPLACE_EXISTING); + backupDataParcelFd.close(); + return BackupTransport.TRANSPORT_OK; + }); runTask(task); - // Already verified data in mockAndVerifyPerformBackupData verify(transportBinder).performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt()); - } - - private int mockAndVerifyTransportPerformBackupData(InvocationOnMock invocation) - throws IOException { - ParcelFileDescriptor data = invocation.getArgument(1); - // Verifying that what we passed to the transport is what the agent wrote - BackupDataInput dataInput = new BackupDataInput(data.getFileDescriptor()); + // Now verify data sent + FileInputStream inputStream = new FileInputStream(backupDataPath.toFile()); + BackupDataInput backupData = new BackupDataInput(inputStream.getFD()); // "key1" => "foo" - assertThat(dataInput.readNextHeader()).isTrue(); - assertThat(dataInput.getKey()).isEqualTo("key1"); - int size1 = dataInput.getDataSize(); + assertThat(backupData.readNextHeader()).isTrue(); + assertThat(backupData.getKey()).isEqualTo("key1"); + int size1 = backupData.getDataSize(); byte[] data1 = new byte[size1]; - dataInput.readEntityData(data1, 0, size1); + backupData.readEntityData(data1, 0, size1); assertThat(data1).isEqualTo("foo".getBytes()); // "key2" => "bar" - assertThat(dataInput.readNextHeader()).isTrue(); - assertThat(dataInput.getKey()).isEqualTo("key2"); - int size2 = dataInput.getDataSize(); + assertThat(backupData.readNextHeader()).isTrue(); + assertThat(backupData.getKey()).isEqualTo("key2"); + int size2 = backupData.getDataSize(); byte[] data2 = new byte[size2]; - dataInput.readEntityData(data2, 0, size2); + backupData.readEntityData(data2, 0, size2); assertThat(data2).isEqualTo("bar".getBytes()); // No more - assertThat(dataInput.readNextHeader()).isFalse(); - - return BackupTransport.TRANSPORT_OK; + assertThat(backupData.readNextHeader()).isFalse(); + inputStream.close(); } @Test @@ -350,7 +592,7 @@ public class PerformBackupTaskTest { TransportMock transportMock = setUpTransport(mTransport); AgentMock agentMock = setUpAgent(PACKAGE_1); agentOnBackupDo( - agentMock.agent, + agentMock, (oldState, dataOutput, newState) -> { char prohibitedChar = 0xff00; writeData(dataOutput, prohibitedChar + "key", "foo".getBytes()); @@ -374,13 +616,13 @@ public class PerformBackupTaskTest { AgentMock agentMock1 = agentMocks.get(0); AgentMock agentMock2 = agentMocks.get(1); agentOnBackupDo( - agentMock1.agent, + agentMock1, (oldState, dataOutput, newState) -> { char prohibitedChar = 0xff00; writeData(dataOutput, prohibitedChar + "key", "foo".getBytes()); }); agentOnBackupDo( - agentMock2.agent, + agentMock2, (oldState, dataOutput, newState) -> { writeData(dataOutput, "key", "bar".getBytes()); }); @@ -528,6 +770,7 @@ public class PerformBackupTaskTest { runTask(task); + // Error because it was non-incremental already, so transport can't request it verify(mObserver).onResult(PACKAGE_1, BackupManager.ERROR_TRANSPORT_ABORTED); verify(mObserver).backupFinished(BackupManager.ERROR_TRANSPORT_ABORTED); } @@ -553,10 +796,9 @@ public class PerformBackupTaskTest { mTransport.transportDirName, false, PACKAGE_1); - // Write content to be incremental - Files.write( - Paths.get(mBaseStateDir.getAbsolutePath(), mTransport.transportDirName, PACKAGE_1), - "existent".getBytes()); + createPmStateFile(); + // Write state to be incremental + Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes()); runTask(task); @@ -566,25 +808,12 @@ public class PerformBackupTaskTest { } @Test - public void testRunTask_whenQueueEmpty() throws Exception { - TransportMock transportMock = setUpTransport(mTransport); - PerformBackupTask task = - createPerformBackupTask( - transportMock.transportClient, mTransport.transportDirName, true); - - runTask(task); - - verify(mObserver).backupFinished(eq(BackupManager.SUCCESS)); - } - - @Test public void testRunTask_whenIncrementalAndTransportUnavailableDuringPmBackup() throws Exception { TransportMock transportMock = setUpTransport(mTransport); IBackupTransport transportBinder = transportMock.transport; setUpAgent(PACKAGE_1); - when(transportBinder.getBackupQuota( - eq(BackupManagerService.PACKAGE_MANAGER_SENTINEL), anyBoolean())) + when(transportBinder.getBackupQuota(eq(PACKAGE_MANAGER_SENTINEL), anyBoolean())) .thenThrow(DeadObjectException.class); PerformBackupTask task = createPerformBackupTask( @@ -600,9 +829,10 @@ public class PerformBackupTaskTest { } @Test - public void testRunTask_whenIncrementalAndAgentFails() throws Exception { + public void testRunTask_whenIncrementalAndPmAgentFails() throws Exception { TransportMock transportMock = setUpTransport(mTransport); - createFakePmAgent(); + PackageManagerBackupAgent pmAgent = createThrowingPmAgent(); + when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent); PerformBackupTask task = createPerformBackupTask( transportMock.transportClient, @@ -627,11 +857,18 @@ public class PerformBackupTaskTest { private TransportMock setUpTransport(TransportData transport) throws Exception { TransportMock transportMock = TransportTestUtils.setUpTransport(mTransportManager, transport); - File stateDir = new File(mBaseStateDir, transport.transportDirName); - assertThat(stateDir.mkdir()).isTrue(); + Files.createDirectories(getStateDirectory(transport)); return transportMock; } + private Path getStateDirectory(TransportData transport) { + return mBaseStateDir.toPath().resolve(transport.transportDirName); + } + + private Path getStateFile(TransportData transport, String packageName) { + return getStateDirectory(transport).resolve(packageName); + } + private List<AgentMock> setUpAgents(String... packageNames) { return Stream.of(packageNames).map(this::setUpAgent).collect(toList()); } @@ -652,9 +889,9 @@ public class PerformBackupTaskTest { spy(IBackupAgent.Stub.asInterface(backupAgent.onBind())); // Don't crash our only process (in production code this would crash the app, not us) doNothing().when(backupAgentBinder).fail(any()); - when(mBackupManagerService.bindToAgentSynchronous( - eq(packageInfo.applicationInfo), anyInt())) - .thenReturn(backupAgentBinder); + doReturn(backupAgentBinder) + .when(mBackupManagerService) + .bindToAgentSynchronous(eq(packageInfo.applicationInfo), anyInt()); return new AgentMock(backupAgentBinder, backupAgent); } catch (RemoteException e) { // Never happens, compiler happy @@ -668,12 +905,15 @@ public class PerformBackupTaskTest { private AgentMock setUpAgentWithData(String packageName) { AgentMock agentMock = setUpAgent(packageName); + uncheck( () -> agentOnBackupDo( - agentMock.agent, - (oldState, dataOutput, newState) -> - writeData(dataOutput, "key", packageName.getBytes()))); + agentMock, + (oldState, dataOutput, newState) -> { + writeData(dataOutput, "key", ("data" + packageName).getBytes()); + writeState(newState, ("state" + packageName).getBytes()); + })); return agentMock; } @@ -719,13 +959,12 @@ public class PerformBackupTaskTest { * Returns an implementation of PackageManagerBackupAgent that throws RuntimeException in {@link * BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)} */ - private PackageManagerBackupAgent createFakePmAgent() { - PackageManagerBackupAgent fakePmAgent = - new FakePackageManagerBackupAgent(mApplication.getPackageManager()); - fakePmAgent.attach(mApplication); - fakePmAgent.onCreate(); - when(mBackupManagerService.makeMetadataAgent()).thenReturn(fakePmAgent); - return fakePmAgent; + private PackageManagerBackupAgent createThrowingPmAgent() { + PackageManagerBackupAgent pmAgent = + new ThrowingPackageManagerBackupAgent(mApplication.getPackageManager()); + pmAgent.attach(mApplication); + pmAgent.onCreate(); + return pmAgent; } /** Matches {@link PackageInfo} whose package name is {@code packageName}. */ @@ -751,9 +990,49 @@ public class PerformBackupTaskTest { dataOutput.writeEntityData(data, data.length); } - private static void agentOnBackupDo(BackupAgent agent, BackupAgentOnBackup function) + private static void writeState(ParcelFileDescriptor newState, byte[] state) throws IOException { + OutputStream outputStream = new FileOutputStream(newState.getFileDescriptor()); + outputStream.write(state); + outputStream.flush(); + } + + /** Prevents the states from being reset and transport initialization. */ + private void createPmStateFile() throws IOException { + Files.write(getStateFile(mTransport, PACKAGE_MANAGER_SENTINEL), "pmState".getBytes()); + } + + /** + * Implements {@code function} for {@link BackupAgent#onBackup(ParcelFileDescriptor, + * BackupDataOutput, ParcelFileDescriptor)} of {@code agentMock} and populates {@link + * AgentMock#oldState}. + */ + private static void agentOnBackupDo(AgentMock agentMock, BackupAgentOnBackup function) throws Exception { - doAnswer(function).when(agent).onBackup(any(), any(), any()); + doAnswer( + (BackupAgentOnBackup) + (oldState, dataOutput, newState) -> { + ByteArrayOutputStream outputStream = + new ByteArrayOutputStream(); + Utils.transferStreamedData( + new FileInputStream(oldState.getFileDescriptor()), + outputStream); + agentMock.oldState = outputStream.toByteArray(); + function.onBackup(oldState, dataOutput, newState); + }) + .when(agentMock.agent) + .onBackup(any(), any(), any()); + } + + // TODO: Find some implementation? Extract? + private static <T> Iterable<T> oneTimeIterable(Iterator<T> iterator) { + return () -> iterator; + } + + private static IterableSubject< + ? extends IterableSubject<?, Path, Iterable<Path>>, Path, Iterable<Path>> + assertDirectory(Path directory) throws IOException { + return assertThat(oneTimeIterable(Files.newDirectoryStream(directory).iterator())) + .named("directory " + directory); } @FunctionalInterface @@ -777,6 +1056,7 @@ public class PerformBackupTaskTest { private static class AgentMock { private final IBackupAgent agentBinder; private final BackupAgent agent; + private byte[] oldState; private AgentMock(IBackupAgent agentBinder, BackupAgent agent) { this.agentBinder = agentBinder; @@ -805,8 +1085,8 @@ public class PerformBackupTaskTest { } } - private static class FakePackageManagerBackupAgent extends PackageManagerBackupAgent { - public FakePackageManagerBackupAgent(PackageManager packageMgr) { + private static class ThrowingPackageManagerBackupAgent extends PackageManagerBackupAgent { + ThrowingPackageManagerBackupAgent(PackageManager packageMgr) { super(packageMgr); } diff --git a/services/robotests/src/com/android/server/backup/Utils.java b/services/robotests/src/com/android/server/backup/Utils.java new file mode 100644 index 000000000000..7cdca17e50fc --- /dev/null +++ b/services/robotests/src/com/android/server/backup/Utils.java @@ -0,0 +1,40 @@ +/* + * 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 java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; + +public class Utils { + public static final int BUFFER_SIZE = 8192; + + public static void transferStreamedData(InputStream in, OutputStream out) throws IOException { + transferStreamedData(in, out, BUFFER_SIZE); + } + + public static void transferStreamedData(InputStream in, OutputStream out, int bufferSize) + throws IOException { + byte[] buffer = new byte[bufferSize]; + int read; + while ((read = in.read(buffer)) != -1) { + out.write(buffer, 0, read); + } + } + + private Utils() {} +} diff --git a/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java b/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java index 92d6bbd54fab..c51b75b748c9 100644 --- a/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java +++ b/services/robotests/src/com/android/server/backup/restore/ActiveRestoreSessionTest.java @@ -77,10 +77,9 @@ import java.util.ArrayDeque; @RunWith(FrameworkRobolectricTestRunner.class) @Config( - manifest = Config.NONE, - sdk = 26, - shadows = {ShadowEventLog.class, ShadowPerformUnifiedRestoreTask.class, ShadowBinder.class} -) + manifest = Config.NONE, + sdk = 26, + shadows = {ShadowEventLog.class, ShadowPerformUnifiedRestoreTask.class, ShadowBinder.class}) @SystemLoaderPackages({"com.android.server.backup"}) @Presubmit public class ActiveRestoreSessionTest { @@ -130,6 +129,7 @@ public class ActiveRestoreSessionTest { mWakeLock = createBackupWakeLock(application); + // TODO: Migrate to use spy(createInitializedBackupManagerService()) setUpBackupManagerServiceBasics( mBackupManagerService, application, diff --git a/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java b/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java index 5a886e33622f..522578fa4eae 100644 --- a/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java +++ b/services/robotests/src/com/android/server/backup/testing/BackupManagerServiceTestUtils.java @@ -16,13 +16,20 @@ package com.android.server.backup.testing; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +import static org.robolectric.Shadows.shadowOf; +import android.annotation.Nullable; import android.app.Application; import android.app.IActivityManager; import android.content.Context; import android.content.pm.PackageManager; +import android.os.Handler; import android.os.HandlerThread; import android.os.Looper; import android.os.PowerManager; @@ -30,32 +37,118 @@ import android.util.SparseArray; import com.android.server.backup.BackupAgentTimeoutParameters; import com.android.server.backup.BackupManagerService; +import com.android.server.backup.Trampoline; import com.android.server.backup.TransportManager; -import com.android.server.backup.internal.BackupHandler; +import com.android.server.backup.internal.Operation; +import org.mockito.stubbing.Answer; +import org.robolectric.shadows.ShadowLog; +import org.robolectric.shadows.ShadowLooper; +import org.robolectric.shadows.ShadowSystemClock; + +import java.io.File; import java.lang.Thread.UncaughtExceptionHandler; +import java.util.concurrent.atomic.AtomicReference; /** Test utils for {@link BackupManagerService} and friends. */ public class BackupManagerServiceTestUtils { - /** Sets up basic mocks for {@link BackupManagerService}. */ + public static BackupManagerService createInitializedBackupManagerService( + Context context, File baseStateDir, File dataDir, TransportManager transportManager) { + return createInitializedBackupManagerService( + context, + startBackupThread(null), + baseStateDir, + dataDir, + transportManager); + } + + public static BackupManagerService createInitializedBackupManagerService( + Context context, + HandlerThread backupThread, + File baseStateDir, + File dataDir, + TransportManager transportManager) { + BackupManagerService backupManagerService = + new BackupManagerService( + context, + new Trampoline(context), + backupThread, + baseStateDir, + dataDir, + transportManager); + ShadowLooper shadowBackupLooper = shadowOf(backupThread.getLooper()); + shadowBackupLooper.runToEndOfTasks(); + // Handler instances have their own clock, so advancing looper (with runToEndOfTasks()) + // above does NOT advance the handlers' clock, hence whenever a handler post messages with + // specific time to the looper the time of those messages will be before the looper's time. + // To fix this we advance SystemClock as well since that is from where the handlers read + // time. + ShadowSystemClock.setCurrentTimeMillis(shadowBackupLooper.getScheduler().getCurrentTime()); + return backupManagerService; + } + + /** Sets up basic mocks for {@link BackupManagerService} mock. */ + @SuppressWarnings("ResultOfMethodCallIgnored") public static void setUpBackupManagerServiceBasics( BackupManagerService backupManagerService, Context context, TransportManager transportManager, PackageManager packageManager, - BackupHandler backupHandler, + Handler backupHandler, PowerManager.WakeLock wakeLock, BackupAgentTimeoutParameters agentTimeoutParameters) { + SparseArray<Operation> operations = new SparseArray<>(); + when(backupManagerService.getContext()).thenReturn(context); when(backupManagerService.getTransportManager()).thenReturn(transportManager); when(backupManagerService.getPackageManager()).thenReturn(packageManager); when(backupManagerService.getBackupHandler()).thenReturn(backupHandler); when(backupManagerService.getCurrentOpLock()).thenReturn(new Object()); when(backupManagerService.getQueueLock()).thenReturn(new Object()); - when(backupManagerService.getCurrentOperations()).thenReturn(new SparseArray<>()); + when(backupManagerService.getCurrentOperations()).thenReturn(operations); when(backupManagerService.getActivityManager()).thenReturn(mock(IActivityManager.class)); when(backupManagerService.getWakelock()).thenReturn(wakeLock); when(backupManagerService.getAgentTimeoutParameters()).thenReturn(agentTimeoutParameters); + + AccessorMock backupEnabled = mockAccessor(false); + doAnswer(backupEnabled.getter).when(backupManagerService).isBackupEnabled(); + doAnswer(backupEnabled.setter).when(backupManagerService).setBackupEnabled(anyBoolean()); + + AccessorMock backupRunning = mockAccessor(false); + doAnswer(backupEnabled.getter).when(backupManagerService).isBackupRunning(); + doAnswer(backupRunning.setter).when(backupManagerService).setBackupRunning(anyBoolean()); + + doAnswer( + invocation -> { + operations.put(invocation.getArgument(0), invocation.getArgument(1)); + return null; + }) + .when(backupManagerService) + .putOperation(anyInt(), any()); + doAnswer( + invocation -> { + int token = invocation.getArgument(0); + operations.remove(token); + return null; + }) + .when(backupManagerService) + .removeOperation(anyInt()); + } + + /** + * Returns one getter {@link Answer<T>} and one setter {@link Answer<T>} to be easily passed to + * Mockito mocking facilities. + * + * @param defaultValue Value returned by the getter if there was no setter call until then. + */ + public static <T> AccessorMock<T> mockAccessor(T defaultValue) { + AtomicReference<T> holder = new AtomicReference<>(defaultValue); + return new AccessorMock<>( + invocation -> holder.get(), + invocation -> { + holder.set(invocation.getArgument(0)); + return null; + }); } public static PowerManager.WakeLock createBackupWakeLock(Application application) { @@ -88,12 +181,38 @@ public class BackupManagerServiceTestUtils { * @return The backup thread. * @see #startBackupThreadAndGetLooper() */ - public static HandlerThread startBackupThread(UncaughtExceptionHandler exceptionHandler) { + public static HandlerThread startBackupThread( + @Nullable UncaughtExceptionHandler exceptionHandler) { HandlerThread backupThread = new HandlerThread("backup"); backupThread.setUncaughtExceptionHandler(exceptionHandler); backupThread.start(); return backupThread; } + /** + * Similar to {@link #startBackupThread(UncaughtExceptionHandler)} but logging uncaught + * exceptions to logcat. + * + * @param tag Tag used for logging exceptions. + * @return The backup thread. + * @see #startBackupThread(UncaughtExceptionHandler) + */ + public static HandlerThread startSilentBackupThread(String tag) { + return startBackupThread( + (thread, e) -> + ShadowLog.e( + tag, "Uncaught exception in test thread " + thread.getName(), e)); + } + private BackupManagerServiceTestUtils() {} + + public static class AccessorMock<T> { + public Answer<T> getter; + public Answer<T> setter; + + private AccessorMock(Answer<T> getter, Answer<T> setter) { + this.getter = getter; + this.setter = setter; + } + } } 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 584413157dac..6625443f9b8e 100644 --- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java +++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java @@ -26,13 +26,13 @@ import static org.mockito.Mockito.when; import static java.util.stream.Collectors.toList; +import android.annotation.IntDef; import android.annotation.Nullable; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; import android.os.RemoteException; -import android.support.annotation.IntDef; import com.android.internal.backup.IBackupTransport; import com.android.server.backup.TransportManager; @@ -42,6 +42,8 @@ import com.android.server.backup.transport.TransportNotRegisteredException; import org.robolectric.shadows.ShadowPackageManager; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; import java.util.List; import java.util.stream.Stream; @@ -209,6 +211,7 @@ public class TransportTestUtils { TransportStatus.REGISTERED_UNAVAILABLE, TransportStatus.UNREGISTERED }) + @Retention(RetentionPolicy.SOURCE) public @interface TransportStatus { int REGISTERED_AVAILABLE = 0; int REGISTERED_UNAVAILABLE = 1; |