/*
 * 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.keyvalue;

import static android.app.backup.BackupManager.ERROR_AGENT_FAILURE;
import static android.app.backup.BackupManager.ERROR_BACKUP_NOT_ALLOWED;
import static android.app.backup.BackupManager.ERROR_PACKAGE_NOT_FOUND;
import static android.app.backup.BackupManager.ERROR_TRANSPORT_ABORTED;
import static android.app.backup.BackupManager.ERROR_TRANSPORT_PACKAGE_REJECTED;
import static android.app.backup.BackupManager.SUCCESS;
import static android.app.backup.ForwardingBackupAgent.forward;

import static com.android.server.backup.testing.BackupManagerServiceTestUtils.createBackupWakeLock;
import static com.android.server.backup.testing.BackupManagerServiceTestUtils.createUserBackupManagerServiceAndRunTasks;
import static com.android.server.backup.testing.BackupManagerServiceTestUtils.setUpBackupManagerServiceBasics;
import static com.android.server.backup.testing.BackupManagerServiceTestUtils.setUpBinderCallerAndApplicationAsSystem;
import static com.android.server.backup.testing.PackageData.PM_PACKAGE;
import static com.android.server.backup.testing.PackageData.fullBackupPackage;
import static com.android.server.backup.testing.PackageData.keyValuePackage;
import static com.android.server.backup.testing.TestUtils.assertEventLogged;
import static com.android.server.backup.testing.TestUtils.messagesInLooper;
import static com.android.server.backup.testing.TestUtils.uncheck;
import static com.android.server.backup.testing.TestUtils.waitUntil;
import static com.android.server.backup.testing.TransportData.backupTransport;
import static com.android.server.backup.testing.Utils.isFileNonEmpty;
import static com.android.server.backup.testing.Utils.oneTimeIterable;
import static com.android.server.backup.testing.Utils.transferStreamedData;

import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.argThat;
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.doThrow;
import static org.mockito.Mockito.inOrder;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.robolectric.Shadows.shadowOf;
import static org.robolectric.shadow.api.Shadow.extract;
import static org.testng.Assert.expectThrows;
import static org.testng.Assert.fail;

import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.toList;

import android.annotation.Nullable;
import android.app.Application;
import android.app.IBackupAgent;
import android.app.backup.BackupAgent;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupManager;
import android.app.backup.BackupTransport;
import android.app.backup.IBackupCallback;
import android.app.backup.IBackupManager;
import android.app.backup.IBackupManagerMonitor;
import android.app.backup.IBackupObserver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.net.Uri;
import android.os.ConditionVariable;
import android.os.DeadObjectException;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.platform.test.annotations.Presubmit;
import android.util.Pair;

import com.android.internal.backup.IBackupTransport;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.DataChangedJournal;
import com.android.server.backup.KeyValueBackupJob;
import com.android.server.backup.PackageManagerBackupAgent;
import com.android.server.backup.TransportManager;
import com.android.server.backup.UserBackupManagerService;
import com.android.server.backup.internal.BackupHandler;
import com.android.server.backup.internal.OnTaskFinishedListener;
import com.android.server.backup.remote.RemoteCall;
import com.android.server.backup.testing.PackageData;
import com.android.server.backup.testing.TestUtils.ThrowingRunnable;
import com.android.server.backup.testing.TransportData;
import com.android.server.backup.testing.TransportTestUtils;
import com.android.server.backup.testing.TransportTestUtils.TransportMock;
import com.android.server.testing.shadows.FrameworkShadowLooper;
import com.android.server.testing.shadows.ShadowApplicationPackageManager;
import com.android.server.testing.shadows.ShadowBackupDataInput;
import com.android.server.testing.shadows.ShadowBackupDataOutput;
import com.android.server.testing.shadows.ShadowEventLog;
import com.android.server.testing.shadows.ShadowSystemServiceRegistry;

import com.google.common.truth.IterableSubject;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentMatcher;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
import org.robolectric.shadows.ShadowLooper;
import org.robolectric.shadows.ShadowPackageManager;
import org.robolectric.shadows.ShadowQueuedWork;

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.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.stream.Stream;

// TODO: Test agents timing out
@RunWith(RobolectricTestRunner.class)
@Config(
        shadows = {
            FrameworkShadowLooper.class,
            ShadowApplicationPackageManager.class,
            ShadowBackupDataInput.class,
            ShadowBackupDataOutput.class,
            ShadowEventLog.class,
            ShadowQueuedWork.class,
            ShadowSystemServiceRegistry.class
        })
@Presubmit
public class KeyValueBackupTaskTest  {
    private static final PackageData PACKAGE_1 = keyValuePackage(1);
    private static final PackageData PACKAGE_2 = keyValuePackage(2);
    private static final String BACKUP_AGENT_SHARED_PREFS_SYNCHRONIZER_CLASS =
            "android.app.backup.BackupAgent$SharedPrefsSynchronizer";
    private static final int USER_ID = 10;

    @Mock private TransportManager mTransportManager;
    @Mock private DataChangedJournal mOldJournal;
    @Mock private IBackupObserver mObserver;
    @Mock private IBackupManagerMonitor mMonitor;
    @Mock private OnTaskFinishedListener mListener;
    @Mock private PackageManagerInternal mPackageManagerInternal;
    private UserBackupManagerService mBackupManagerService;
    private TransportData mTransport;
    private ShadowLooper mShadowBackupLooper;
    private Handler mBackupHandler;
    private UserBackupManagerService.BackupWakeLock mWakeLock;
    private KeyValueBackupReporter mReporter;
    private PackageManager mPackageManager;
    private ShadowPackageManager mShadowPackageManager;
    private FakeIBackupManager mBackupManager;
    private File mBaseStateDir;
    private File mDataDir;
    private Application mApplication;
    private Looper mMainLooper;
    private FrameworkShadowLooper mShadowMainLooper;
    private Context mContext;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);

        mTransport = backupTransport();

        mApplication = RuntimeEnvironment.application;
        mContext = mApplication;

        mMainLooper = Looper.getMainLooper();
        mShadowMainLooper = extract(mMainLooper);

        File cacheDir = mApplication.getCacheDir();
        // 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();

        mPackageManager = mApplication.getPackageManager();
        mShadowPackageManager = shadowOf(mPackageManager);

        mWakeLock = createBackupWakeLock(mApplication);
        mBackupManager = spy(FakeIBackupManager.class);

        // Needed to be able to use a real BMS instead of a mock
        setUpBinderCallerAndApplicationAsSystem(mApplication);
        mBackupManagerService =
                spy(
                        createUserBackupManagerServiceAndRunTasks(
                                USER_ID, mContext, mBaseStateDir, mDataDir, mTransportManager));
        setUpBackupManagerServiceBasics(
                mBackupManagerService,
                mApplication,
                mTransportManager,
                mPackageManager,
                mBackupManagerService.getBackupHandler(),
                mWakeLock,
                mBackupManagerService.getAgentTimeoutParameters());
        when(mBackupManagerService.getBaseStateDir()).thenReturn(mBaseStateDir);
        when(mBackupManagerService.getDataDir()).thenReturn(mDataDir);
        when(mBackupManagerService.getBackupManagerBinder()).thenReturn(mBackupManager);

        mBackupHandler = mBackupManagerService.getBackupHandler();
        mShadowBackupLooper = shadowOf(mBackupHandler.getLooper());
        ShadowEventLog.setUp();
        mReporter = spy(new KeyValueBackupReporter(mBackupManagerService, mObserver, mMonitor));

        when(mPackageManagerInternal.getApplicationEnabledState(any(), anyInt()))
                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED);
        LocalServices.removeServiceForTest(PackageManagerInternal.class);
        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
    }

    @After
    public void tearDown() throws Exception {
        ShadowBackupDataInput.reset();
        ShadowApplicationPackageManager.reset();
    }

    @Test
    public void testRunTask_whenQueueEmpty_updatesBookkeeping() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(mBackupManagerService.getCurrentToken()).thenReturn(0L);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true);

        runTask(task);

        assertThat(mBackupManagerService.getPendingInits()).isEmpty();
        assertThat(mBackupManagerService.isBackupRunning()).isFalse();
        assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0);
        verify(mOldJournal).delete();
    }

    @Test
    public void testRunTask_whenQueueEmpty_releasesWakeLock() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(mBackupManagerService.getCurrentToken()).thenReturn(0L);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true);

        runTask(task);

        assertThat(mWakeLock.isHeld()).isFalse();
    }

    @Test
    public void testRunTask_whenQueueEmpty_doesNotProduceData() throws Exception {
        TransportMock transportMock = setUpTransport(mTransport);
        when(mBackupManagerService.getCurrentToken()).thenReturn(0L);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true);

        runTask(task);

        assertDirectory(getStateDirectory(mTransport)).isEmpty();
        assertDirectory(mDataDir.toPath()).isEmpty();
    }

    @Test
    public void testRunTask_whenQueueEmpty_doesNotCallTransport() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(mBackupManagerService.getCurrentToken()).thenReturn(0L);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true);

        runTask(task);

        verify(transportMock.transport, never()).initializeDevice();
        verify(transportMock.transport, never()).performBackup(any(), any(), anyInt());
        verify(transportMock.transport, never()).finishBackup();
    }

    @Test
    public void testRunTask_whenQueueEmpty_notifiesCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(mBackupManagerService.getCurrentToken()).thenReturn(0L);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true);

        runTask(task);

        verify(mListener).onFinished(any());
        verify(mObserver, never()).onResult(any(), anyInt());
        verify(mObserver).backupFinished(SUCCESS);
    }

    @Test
    public void testRunTask_whenQueueEmpty_doesNotChangeStateFiles() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true);
        Files.write(getStateFile(mTransport, PM_PACKAGE), "pmState".getBytes());
        Files.write(getStateFile(mTransport, PACKAGE_1), "packageState".getBytes());

        runTask(task);

        assertThat(Files.readAllBytes(getStateFile(mTransport, PM_PACKAGE)))
                .isEqualTo("pmState".getBytes());
        assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1)))
                .isEqualTo("packageState".getBytes());
    }

    /** Do not update backup token if the backup queue was empty */
    @Test
    public void testRunTask_whenQueueEmptyOnFirstBackup_doesNotUpdateCurrentToken()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true);
        mBackupManagerService.setCurrentToken(0L);
        when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L);

        runTask(task);

        assertThat(mBackupManagerService.getCurrentToken()).isEqualTo(0L);
    }

    @Test
    public void testRunTask_whenOnePackageAndTransportUnavailable() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport.unavailable());
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mListener).onFinished(any());
        verify(mObserver).backupFinished(ERROR_TRANSPORT_ABORTED);
        assertBackupPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenOnePackage_logsBackupStartEvent() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertEventLogged(EventLogTags.BACKUP_START, mTransport.transportName);
    }

    @Test
    public void testRunTask_whenOnePackage_releasesWakeLock() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertThat(mWakeLock.isHeld()).isFalse();
    }

    @Test
    public void testRunTask_whenOnePackage_cleansUpPmFiles() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertCleansUpFiles(mTransport, PM_PACKAGE);
    }

    @Test
    public void testRunTask_whenTransportReturnsTransportErrorForPm_cleansUpPmFiles()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PM_PACKAGE)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
        setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertCleansUpFiles(mTransport, PM_PACKAGE);
    }

    @Test
    public void testRunTask_whenTransportReturnsTransportErrorForPm_resetsBackupState()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PM_PACKAGE)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
        setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mBackupManagerService).resetBackupState(getStateDirectory(mTransport).toFile());
    }

    @Test
    public void testRunTask_whenOnePackage_updatesBookkeeping() throws Exception {
        // Transport has to be initialized to not reset current token
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        mBackupManagerService.setCurrentToken(0L);
        when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertThat(mBackupManagerService.getPendingInits()).isEmpty();
        assertThat(mBackupManagerService.isBackupRunning()).isFalse();
        assertThat(mBackupManagerService.getCurrentOperations().size()).isEqualTo(0);
        assertThat(mBackupManagerService.getCurrentToken()).isEqualTo(1234L);
        verify(mBackupManagerService).writeRestoreTokens();
        verify(mOldJournal).delete();
    }

    @Test
    public void testRunTask_whenPackageWithOldStateAndIncremental_passesOldStateToAgent()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, false, PACKAGE_1);
        Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());

        runTask(task);

        assertThat(agentMock.oldState).isEqualTo("oldState".getBytes());
    }

    @Test
    public void testRunTask_whenPackageWithOldStateAndNonIncremental_passesEmptyOldStateToAgent()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);
        Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());

        runTask(task);

        assertThat(agentMock.oldState).isEqualTo(new byte[0]);
    }

    @Test
    public void testRunTask_whenNonPmPackageAndNonIncremental_doesNotBackUpPm() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        BackupAgent pmAgent = spy(createPmAgent());
        doReturn(forward(pmAgent)).when(mBackupManagerService).makeMetadataAgent();
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);

        runTask(task);

        verify(pmAgent, never()).onBackup(any(), any(), any());
    }

    @Test
    public void testRunTask_whenNonPmPackageAndPmAndNonIncremental_backsUpPm() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        BackupAgent pmAgent = spy(createPmAgent());
        doReturn(forward(pmAgent)).when(mBackupManagerService).makeMetadataAgent();
        KeyValueBackupTask task =
                createKeyValueBackupTask(transportMock, true, PACKAGE_1, PM_PACKAGE);

        runTask(task);

        verify(pmAgent).onBackup(any(), any(), any());
    }

    @Test
    public void testRunTask_whenNonPmPackageAndIncremental_backsUpPm() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        BackupAgent pmAgent = spy(createPmAgent());
        doReturn(forward(pmAgent)).when(mBackupManagerService).makeMetadataAgent();
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, false, PACKAGE_1);

        runTask(task);

        verify(pmAgent).onBackup(any(), any(), any());
    }

    @Test
    public void testRunTask_whenOnePackageAndNoPmState_initializesTransportAndResetsState()
            throws Exception {
        TransportMock transportMock = setUpTransport(mTransport);
        // Need 2 packages to be able to verify state of package not involved in the task
        setUpAgentsWithData(PACKAGE_1, PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        deletePmStateFile();
        Files.write(getStateFile(mTransport, PACKAGE_2), "package2State".getBytes());

        runTask(task);

        verify(transportMock.transport).initializeDevice();
        verify(mBackupManagerService).resetBackupState(getStateDirectory(mTransport).toFile());
        // Verifying that it deleted all the states (can't verify package 1 because it generated a
        // new state in this task execution)
        assertThat(Files.exists(getStateFile(mTransport, PACKAGE_2))).isFalse();
        assertEventLogged(EventLogTags.BACKUP_INITIALIZE);
    }

    @Test
    public void testRunTask_whenOnePackageAndWithPmState_doesNotInitializeTransportOrResetState()
            throws Exception {
        TransportMock transportMock = setUpTransport(mTransport);
        setUpAgentsWithData(PACKAGE_1, PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        createPmStateFile();
        Files.write(getStateFile(mTransport, PACKAGE_2), "package2State".getBytes());

        runTask(task);

        verify(transportMock.transport, never()).initializeDevice();
        assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_2)))
                .isEqualTo("package2State".getBytes());
    }

    @Test
    public void testRunTask_whenTransportReturnsErrorForInitialization() throws Exception {
        TransportMock transportMock = setUpTransport(mTransport);
        when(transportMock.transport.initializeDevice())
                .thenReturn(BackupTransport.TRANSPORT_ERROR);
        AgentMock agentMock = setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        deletePmStateFile();

        runTask(task);

        // First for initialization and second because of the transport failure
        verify(mBackupManagerService, times(2))
                .resetBackupState(getStateDirectory(mTransport).toFile());
        verify(agentMock.agent, never()).onBackup(any(), any(), any());
        verify(transportMock.transport, never()).performBackup(any(), any(), anyInt());
        assertBackupPendingFor(PACKAGE_1);
        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_FAILURE, "(initialize)");
    }

    @Test
    public void testRunTask_whenTransportThrowsDuringInitialization() throws Exception {
        TransportMock transportMock = setUpTransport(mTransport);
        when(transportMock.transport.initializeDevice()).thenThrow(RemoteException.class);
        AgentMock agentMock = setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        deletePmStateFile();

        runTask(task);

        // First for initialization and second because of the transport failure
        verify(mBackupManagerService, times(2))
                .resetBackupState(getStateDirectory(mTransport).toFile());
        verify(agentMock.agent, never()).onBackup(any(), any(), any());
        verify(transportMock.transport, never()).performBackup(any(), any(), anyInt());
        assertBackupPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenPackageUnknown() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        // Not calling setUpAgent() for PACKAGE_1
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(transportMock.transport, never())
                .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_PACKAGE_NOT_FOUND);
        verify(mObserver).backupFinished(SUCCESS);
        assertBackupNotPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenFirstPackageUnknown_callsTransportForSecondPackage()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        // Not calling setUpAgent() for PACKAGE_1
        setUpAgentWithData(PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);

        runTask(task);

        verify(transportMock.transport)
                .performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt());
    }

    @Test
    public void testRunTask_whenPackageNotEligibleForBackup() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgentWithData(PACKAGE_1.backupNotAllowed());
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(agentMock.agent, never()).onBackup(any(), any(), any());
        verify(transportMock.transport, never())
                .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_BACKUP_NOT_ALLOWED);
        verify(mObserver).backupFinished(SUCCESS);
        assertBackupNotPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenFirstPackageNotEligibleForBackup_callsTransportForSecondPackage()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentsWithData(PACKAGE_1.backupNotAllowed(), PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);

        runTask(task);

        verify(transportMock.transport)
                .performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt());
    }

    @Test
    public void testRunTask_whenPackageDoesFullBackup() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        PackageData packageData = fullBackupPackage(1);
        AgentMock agentMock = setUpAgentWithData(packageData);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, packageData);

        runTask(task);

        verify(agentMock.agent, never()).onBackup(any(), any(), any());
        verify(agentMock.agent, never()).onFullBackup(any());
        verify(mObserver).onResult(packageData.packageName, ERROR_BACKUP_NOT_ALLOWED);
        verify(mObserver).backupFinished(SUCCESS);
        assertBackupNotPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenFirstPackageDoesFullBackup_callsTransportForSecondPackage()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        PackageData packageData = fullBackupPackage(1);
        setUpAgentsWithData(packageData, PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, packageData, PACKAGE_2);

        runTask(task);

        verify(transportMock.transport)
                .performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt());
    }

    @Test
    public void testRunTask_whenPackageIsStopped() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgentWithData(PACKAGE_1.stopped());
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(agentMock.agent, never()).onBackup(any(), any(), any());
        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_BACKUP_NOT_ALLOWED);
        verify(mObserver).backupFinished(SUCCESS);
        assertBackupNotPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenFirstPackageIsStopped_callsTransportForSecondPackage()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentsWithData(PACKAGE_1.stopped(), PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);

        runTask(task);

        verify(transportMock.transport)
                .performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt());
    }

    @Test
    public void testRunTask_whenCallingAgent_setsWakeLockWorkSource() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    // In production (for non-system agents) the call is asynchronous, but here is
                    // synchronous, so it's fine to verify here.
                    // Verify has set work source and hasn't unset yet.
                    verify(mBackupManagerService)
                            .setWorkSource(
                                    argThat(workSource -> workSource.getUid(0) == PACKAGE_1.uid));
                    verify(mBackupManagerService, never()).setWorkSource(null);
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        // More verifications inside agent call above
        verify(mBackupManagerService).setWorkSource(null);
    }

    /**
     * Agent unavailable means {@link
     * UserBackupManagerService#bindToAgentSynchronous(ApplicationInfo, int)} returns {@code null}.
     *
     * @see #setUpAgent(PackageData)
     */
    @Test
    public void testRunTask_whenAgentUnavailable() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1.unavailable());
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mBackupManagerService).setWorkSource(null);
        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_AGENT_FAILURE);
        verify(mObserver).backupFinished(BackupManager.SUCCESS);
        assertBackupPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenSecondAgentUnavailable_commitsFirstAgentState() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        setUpAgent(PACKAGE_2.unavailable());
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);

        runTask(task);

        assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1)))
                .isEqualTo("newState".getBytes());
    }

    @Test
    public void testRunTask_whenNonIncrementalAndAgentUnavailable() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1.unavailable());
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);

        runTask(task);

        verify(mBackupManagerService).setWorkSource(null);
        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_AGENT_FAILURE);
        verify(mObserver).backupFinished(BackupManager.SUCCESS);
        assertBackupPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenBindToAgentThrowsSecurityException() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1);
        doThrow(SecurityException.class)
                .when(mBackupManagerService)
                .bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt());
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mBackupManagerService).setWorkSource(null);
        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_AGENT_FAILURE);
        verify(mObserver).backupFinished(BackupManager.SUCCESS);
        assertBackupPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenNonIncrementalAndBindToAgentThrowsSecurityException()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1);
        doThrow(SecurityException.class)
                .when(mBackupManagerService)
                .bindToAgentSynchronous(argThat(applicationInfo(PACKAGE_1)), anyInt());
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);

        runTask(task);

        verify(mBackupManagerService).setWorkSource(null);
        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_AGENT_FAILURE);
        verify(mObserver).backupFinished(BackupManager.SUCCESS);
        assertBackupPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenTransportGetBackupQuotaThrows_notifiesCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.getBackupQuota(PACKAGE_1.packageName, false))
                .thenThrow(DeadObjectException.class);
        setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mObserver, never()).onResult(eq(PACKAGE_1.packageName), anyInt());
        verify(mObserver).backupFinished(ERROR_TRANSPORT_ABORTED);
        verify(mListener).onFinished(any());
    }

    @Test
    public void testRunTask_whenTransportGetBackupQuotaThrows_cleansUp() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.getBackupQuota(PACKAGE_1.packageName, false))
                .thenThrow(DeadObjectException.class);
        setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mBackupManagerService).setWorkSource(null);
        verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
    }

    @Test
    public void testRunTask_whenTransportGetBackupQuotaThrows_doesNotTouchFiles() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.getBackupQuota(PACKAGE_1.packageName, false))
                .thenThrow(DeadObjectException.class);
        setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        Files.write(getStateFile(mTransport, PACKAGE_1), "packageState".getBytes());

        runTask(task);

        assertThat(Files.exists(getTemporaryStateFile(mTransport, PACKAGE_1))).isFalse();
        assertThat(Files.exists(getStagingFile(PACKAGE_1))).isFalse();
        assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1)))
                .isEqualTo("packageState".getBytes());
    }

    @Test
    public void testRunTask_whenTransportGetBackupQuotaThrows_revertsTask() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.getBackupQuota(PACKAGE_1.packageName, false))
                .thenThrow(DeadObjectException.class);
        setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertTaskReverted(transportMock, PACKAGE_1);
    }

    /**
     * For local agents the exception is thrown in our stack, before {@link RemoteCall} has a chance
     * to complete cleanly.
     */
    // TODO: When RemoteCall spins up a new thread the assertions on this method should be the same
    // as the methods below (non-local call).
    @Test
    public void testRunTask_whenLocalAgentOnBackupThrows_setsNullWorkSource() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    throw new RuntimeException();
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mBackupManagerService).setWorkSource(null);
    }

    @Test
    public void testRunTask_whenLocalAgentOnBackupThrows_reportsCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    throw new RuntimeException();
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_AGENT_FAILURE);
        verify(mObserver).backupFinished(SUCCESS);
        verify(mReporter)
                .onCallAgentDoBackupError(
                        eq(PACKAGE_1.packageName), eq(true), any(RuntimeException.class));
        assertEventLogged(
                EventLogTags.BACKUP_AGENT_FAILURE,
                PACKAGE_1.packageName,
                new RuntimeException().toString());
    }

    @Test
    public void testRunTask_whenLocalAgentOnBackupThrows_doesNotUpdateBookkeping()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    throw new RuntimeException();
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertBackupPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenAgentOnBackupThrows_reportsCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        remoteAgentOnBackupThrows(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    throw new RuntimeException();
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mReporter).onAgentResultError(argThat(packageInfo(PACKAGE_1)));
    }

    @Test
    public void testRunTask_whenAgentOnBackupThrows_updatesBookkeeping() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        remoteAgentOnBackupThrows(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    throw new RuntimeException();
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertBackupPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenAgentOnBackupThrows_doesNotCallTransport() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        remoteAgentOnBackupThrows(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    throw new RuntimeException();
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(transportMock.transport, never())
                .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
    }

    @Test
    public void testRunTask_whenAgentOnBackupThrows_updatesFilesAndCleansUp() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        remoteAgentOnBackupThrows(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    throw new RuntimeException();
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());

        runTask(task);

        assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1)))
                .isEqualTo("oldState".getBytes());
        assertCleansUpFilesAndAgent(mTransport, PACKAGE_1);
    }

    @Test
    public void testRunTask_whenTransportProvidesFlags_passesThemToTheAgent() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
        when(transportMock.transport.getTransportFlags()).thenReturn(flags);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(agentMock.agent)
                .onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
    }

    @Test
    public void testRunTask_whenTransportDoesNotProvidesFlags() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(agentMock.agent).onBackup(any(), argThat(dataOutputWithTransportFlags(0)), any());
    }

    @Test
    public void testRunTask_whenTransportProvidesFlagsAndMultipleAgents_passesToAll()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
        when(transportMock.transport.getTransportFlags()).thenReturn(flags);
        List<AgentMock> agentMocks = setUpAgents(PACKAGE_1, PACKAGE_2);
        BackupAgent agent1 = agentMocks.get(0).agent;
        BackupAgent agent2 = agentMocks.get(1).agent;
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, 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 {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        int flags = BackupAgent.FLAG_CLIENT_SIDE_ENCRYPTION_ENABLED;
        when(transportMock.transport.getTransportFlags()).thenReturn(flags);

        runTask(task);

        verify(agentMock.agent)
                .onBackup(any(), argThat(dataOutputWithTransportFlags(flags)), any());
    }

    @Test
    public void testRunTask_whenAgentUsesProhibitedKey_failsAgent() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    char prohibitedChar = 0xff00;
                    writeData(dataOutput, prohibitedChar + "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(agentMock.agentBinder).fail(any());
        verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
    }

    @Test
    public void testRunTask_whenAgentUsesProhibitedKey_updatesFilesAndCleansUp() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    char prohibitedChar = 0xff00;
                    writeData(dataOutput, prohibitedChar + "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());

        runTask(task);

        assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1)))
                .isEqualTo("oldState".getBytes());
        assertCleansUpFilesAndAgent(mTransport, PACKAGE_1);
    }

    @Test
    public void testRunTask_whenAgentUsesProhibitedKey_doesNotCallTransport() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    char prohibitedChar = 0xff00;
                    writeData(dataOutput, prohibitedChar + "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());

        runTask(task);

        verify(transportMock.transport, never())
                .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
    }

    @Test
    public void testRunTask_whenAgentUsesProhibitedKey_notifiesCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    char prohibitedChar = 0xff00;
                    writeData(dataOutput, prohibitedChar + "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());

        runTask(task);

        verify(mListener).onFinished(any());
        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_AGENT_FAILURE);
        verify(mObserver).backupFinished(SUCCESS);
    }

    @Test
    public void testRunTask_whenAgentUsesProhibitedKey_logsAgentFailureEvent() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    char prohibitedChar = 0xff00;
                    writeData(dataOutput, prohibitedChar + "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());

        runTask(task);

        assertEventLogged(EventLogTags.BACKUP_AGENT_FAILURE, PACKAGE_1.packageName, "bad key");
    }

    @Test
    public void testRunTask_whenFirstAgentUsesProhibitedKeyButLastAgentUsesPermittedKey()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        List<AgentMock> agentMocks = setUpAgents(PACKAGE_1, PACKAGE_2);
        AgentMock agentMock1 = agentMocks.get(0);
        AgentMock agentMock2 = agentMocks.get(1);
        agentOnBackupDo(
                agentMock1,
                (oldState, dataOutput, newState) -> {
                    char prohibitedChar = 0xff00;
                    writeData(dataOutput, prohibitedChar + "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        agentOnBackupDo(
                agentMock2,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);

        runTask(task);

        verify(mListener).onFinished(any());
        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_AGENT_FAILURE);
        verify(agentMock1.agentBinder).fail(any());
        verify(mObserver).onResult(PACKAGE_2.packageName, SUCCESS);
        verify(mObserver).backupFinished(SUCCESS);
    }

    @Test
    public void testRunTask_whenAgentDoesNotWriteData_doesNotCallTransport() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    // No-op
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(transportMock.transport, never())
                .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
    }

    @Test
    public void testRunTask_whenAgentDoesNotWriteData_logsEvents() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    // No-op
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertEventLogged(EventLogTags.BACKUP_PACKAGE, PACKAGE_1.packageName, 0L);
        verify(mBackupManagerService).logBackupComplete(PACKAGE_1.packageName);
    }

    @Test
    public void testRunTask_whenAgentDoesNotWriteData_notifiesCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    // No-op
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mObserver).onResult(PACKAGE_1.packageName, SUCCESS);
        verify(mObserver).backupFinished(SUCCESS);
    }

    @Test
    public void testRunTask_whenAgentDoesNotWriteData_updatesBookkeeping() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    // No-op
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertBackupNotPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenAgentDoesNotWriteData_updatesFilesAndCleansUp() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    // No-op
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1))).isEqualTo(new byte[0]);
        assertCleansUpFilesAndAgent(mTransport, PACKAGE_1);
    }

    @Test
    public void testRunTask_whenAgentWritesData_callsTransportPerformBackupWithAgentData()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        Path backupDataPath = createTemporaryFile();
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .then(copyBackupDataTo(backupDataPath));
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key1", "data1".getBytes());
                    writeData(dataOutput, "key2", "data2".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(transportMock.transport)
                .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
        // Now verify data sent
        try (FileInputStream inputStream = new FileInputStream(backupDataPath.toFile())) {
            BackupDataInput backupData = new BackupDataInput(inputStream.getFD());
            assertDataHasKeyValue(backupData, "key1", "data1".getBytes());
            assertDataHasKeyValue(backupData, "key2", "data2".getBytes());
            assertThat(backupData.readNextHeader()).isFalse();
        }
    }

    @Test
    public void testRunTask_whenPmAgentWritesData_callsTransportPerformBackupWithAgentData()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1);
        Path backupDataPath = createTemporaryFile();
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PM_PACKAGE)), any(), anyInt()))
                .then(copyBackupDataTo(backupDataPath));
        BackupAgent pmAgent = spy(createPmAgent());
        doReturn(forward(pmAgent)).when(mBackupManagerService).makeMetadataAgent();
        agentOnBackupDo(
                pmAgent,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key1", "data1".getBytes());
                    writeData(dataOutput, "key2", "data2".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(transportMock.transport)
                .performBackup(argThat(packageInfo(PM_PACKAGE)), any(), anyInt());
        try (FileInputStream inputStream = new FileInputStream(backupDataPath.toFile())) {
            BackupDataInput backupData = new BackupDataInput(inputStream.getFD());
            assertDataHasKeyValue(backupData, "key1", "data1".getBytes());
            assertDataHasKeyValue(backupData, "key2", "data2".getBytes());
            assertThat(backupData.readNextHeader()).isFalse();
        }
    }

    @Test
    public void testRunTask_whenPerformBackupSucceeds_callsTransportFinishBackup()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_OK);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        // First for PM, then for the package
        verify(transportMock.transport, times(2)).finishBackup();
    }

    @Test
    public void testRunTask_whenFinishBackupSucceeds_updatesFilesAndCleansUp() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.finishBackup()).thenReturn(BackupTransport.TRANSPORT_OK);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1)))
                .isEqualTo("newState".getBytes());
        assertCleansUpFilesAndAgent(mTransport, PACKAGE_1);
    }

    @Test
    public void testRunTask_whenFinishBackupSucceedsForPm_cleansUp() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1);
        when(transportMock.transport.finishBackup()).thenReturn(BackupTransport.TRANSPORT_OK);
        BackupAgent pmAgent = spy(createPmAgent());
        doReturn(forward(pmAgent)).when(mBackupManagerService).makeMetadataAgent();
        agentOnBackupDo(
                pmAgent,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertThat(Files.readAllBytes(getStateFile(mTransport, PM_PACKAGE)))
                .isEqualTo("newState".getBytes());
        assertCleansUpFiles(mTransport, PM_PACKAGE);
        // We don't unbind PM
        verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE)));
    }

    @Test
    public void testRunTask_whenFinishBackupSucceedsForPm_doesNotUnbindPm() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1);
        when(transportMock.transport.finishBackup()).thenReturn(BackupTransport.TRANSPORT_OK);
        BackupAgent pmAgent = spy(createPmAgent());
        doReturn(forward(pmAgent)).when(mBackupManagerService).makeMetadataAgent();
        agentOnBackupDo(
                pmAgent,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mBackupManagerService, never()).unbindAgent(argThat(applicationInfo(PM_PACKAGE)));
    }

    @Test
    public void testRunTask_whenFinishBackupSucceeds_logsBackupPackageEvent() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        Path backupData = createTemporaryFile();
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .then(copyBackupDataTo(backupData));
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertEventLogged(
                EventLogTags.BACKUP_PACKAGE, PACKAGE_1.packageName, Files.size(backupData));
    }

    @Test
    public void testRunTask_whenFinishBackupSucceeds_notifiesCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mBackupManagerService).logBackupComplete(PACKAGE_1.packageName);
        verify(mObserver).onResult(PACKAGE_1.packageName, SUCCESS);
        verify(mObserver).backupFinished(SUCCESS);
        verify(mListener).onFinished(any());
    }

    @Test
    public void testRunTask_whenFinishBackupSucceeds_updatesBookkeeping() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertBackupNotPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenTransportRejectsPackage_doesNotCallFinishBackup() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        // Called only for PM
        verify(transportMock.transport, times(1)).finishBackup();
    }

    @Test
    public void testRunTask_whenTransportRejectsPackage_updatesFilesAndCleansUp() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());

        runTask(task);

        assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1)))
                .isEqualTo("oldState".getBytes());
        assertCleansUpFilesAndAgent(mTransport, PACKAGE_1);
    }

    @Test
    public void testRunTask_whenTransportRejectsPackage_logsAgentFailureEvent() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertEventLogged(
                EventLogTags.BACKUP_AGENT_FAILURE, PACKAGE_1.packageName, "Transport rejected");
    }

    @Test
    public void testRunTask_whenTransportRejectsPackage_notifiesCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_TRANSPORT_PACKAGE_REJECTED);
        verify(mObserver).backupFinished(SUCCESS);
        verify(mListener).onFinished(any());
    }

    @Test
    public void testRunTask_whenTransportRejectsPackage_updatesBookkeeping() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertBackupNotPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenTransportRejectsFirstPackageButLastSucceeds() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_2)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_OK);
        setUpAgentsWithData(PACKAGE_1, PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);

        runTask(task);

        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_TRANSPORT_PACKAGE_REJECTED);
        verify(mObserver).onResult(PACKAGE_2.packageName, SUCCESS);
        verify(mObserver).backupFinished(SUCCESS);
    }

    @Test
    public void testRunTask_whenTransportRejectsLastPackageButFirstSucceeds() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_OK);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_2)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_PACKAGE_REJECTED);
        setUpAgentsWithData(PACKAGE_1, PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);

        runTask(task);

        verify(mObserver).onResult(PACKAGE_1.packageName, SUCCESS);
        verify(mObserver).onResult(PACKAGE_2.packageName, ERROR_TRANSPORT_PACKAGE_REJECTED);
        verify(mObserver).backupFinished(SUCCESS);
    }

    @Test
    public void testRunTask_whenTransportReturnsQuotaExceeded_callsAgentOnQuotaExceeded()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.getBackupQuota(PACKAGE_1.packageName, false))
                .thenReturn(1234L);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_QUOTA_EXCEEDED);
        AgentMock agentMock = setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        InOrder inOrder = inOrder(agentMock.agent, mBackupManagerService);
        inOrder.verify(agentMock.agent).onQuotaExceeded(anyLong(), eq(1234L));
        inOrder.verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(PACKAGE_1)));
    }

    @Test
    public void testRunTask_whenTransportReturnsQuotaExceeded_updatesBookkeeping()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_QUOTA_EXCEEDED);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertBackupNotPendingFor(PACKAGE_1);
    }

    @Test
    public void testRunTask_whenTransportReturnsQuotaExceeded_notifiesAndLogs() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.getBackupQuota(PACKAGE_1.packageName, false))
                .thenReturn(1234L);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_QUOTA_EXCEEDED);
        AgentMock agentMock = setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mObserver)
                .onResult(PACKAGE_1.packageName, BackupManager.ERROR_TRANSPORT_QUOTA_EXCEEDED);
        verify(mObserver).backupFinished(SUCCESS);
        assertEventLogged(EventLogTags.BACKUP_QUOTA_EXCEEDED, PACKAGE_1.packageName);
    }

    @Test
    public void testRunTask_whenTransportReturnsQuotaExceeded_cleansUpFiles() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_QUOTA_EXCEEDED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertCleansUpFilesAndAgent(mTransport, PACKAGE_1);
    }

    @Test
    public void testRunTask_whenTransportReturnsNotInitialized_cleansUpFiles() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_NOT_INITIALIZED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertThat(isFileNonEmpty(getStateFile(mTransport, PACKAGE_1))).isFalse();
        assertCleansUpFilesAndAgent(mTransport, PACKAGE_1);
    }

    @Test
    public void testRunTask_whenTransportReturnsNotInitialized_reportsCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_NOT_INITIALIZED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mReporter).onPackageBackupTransportFailure(PACKAGE_1.packageName);
        verify(mReporter).onTransportNotInitialized(mTransport.transportName);
        verify(mReporter).onBackupFinished(BackupManager.ERROR_TRANSPORT_ABORTED);
    }

    @Test
    public void testRunTask_whenTransportReturnsNotInitializedForPm_reportsCorrectly()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PM_PACKAGE)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_NOT_INITIALIZED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mReporter).onPackageBackupTransportFailure(PM_PACKAGE.packageName);
        verify(mReporter).onTransportNotInitialized(mTransport.transportName);
        verify(mReporter).onBackupFinished(BackupManager.ERROR_TRANSPORT_ABORTED);
    }

    @Test
    public void testRunTask_whenTransportReturnsNotInitialized_doesNotCallSecondAgent()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_NOT_INITIALIZED);
        setUpAgentWithData(PACKAGE_1);
        AgentMock agentMock = setUpAgentWithData(PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);

        runTask(task);

        verify(agentMock.agent, never()).onBackup(any(), any(), any());
    }

    @Test
    public void testRunTask_whenTransportReturnsNotInitialized_revertsTask() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_NOT_INITIALIZED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertTaskReverted(transportMock, PACKAGE_1);
    }

    @Test
    public void testRunTask_whenTransportReturnsNotInitialized_triggersTransportInitialization()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_NOT_INITIALIZED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertThat(mBackupManagerService.getPendingInits()).contains(mTransport.transportName);
        verify(mBackupManagerService).backupNow();
    }

    @Test
    public void testRunTask_whenTransportReturnsNotInitialized_cleansUpPmStateFile()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_NOT_INITIALIZED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        Files.write(getStateFile(mTransport, PM_PACKAGE), "pmState".getBytes());

        runTask(task);

        assertThat(Files.exists(getStateFile(mTransport, PM_PACKAGE))).isFalse();
    }

    @Test
    public void testRunTask_whenTransportReturnsNotInitializedForPm_cleansUpPmStateFile()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PM_PACKAGE)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_NOT_INITIALIZED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        Files.write(getStateFile(mTransport, PM_PACKAGE), "pmState".getBytes());

        runTask(task);

        assertThat(Files.exists(getStateFile(mTransport, PM_PACKAGE))).isFalse();
    }

    @Test
    public void
            testRunTask_whenTransportReturnsNotInitializedAndThrowsWhenQueryingName_reportsCorrectly()
                    throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(any(), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_NOT_INITIALIZED);
        // First one is in startTask(), second is in finishTask(), the third is the one we want.
        when(transportMock.transport.name())
                .thenReturn(mTransport.transportName)
                .thenReturn(mTransport.transportName)
                .thenThrow(DeadObjectException.class);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mReporter).onPendingInitializeTransportError(any(DeadObjectException.class));
        verify(mReporter).onBackupFinished(ERROR_TRANSPORT_ABORTED);
    }

    @Test
    public void testRunTask_whenNonIncrementalAndTransportRequestsNonIncremental()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);
        // Delete to be non-incremental
        Files.deleteIfExists(getStateFile(mTransport, PACKAGE_1));

        runTask(task);

        // Error because it was non-incremental already, so transport can't request it
        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_TRANSPORT_ABORTED);
        verify(mObserver).backupFinished(ERROR_TRANSPORT_ABORTED);
    }

    @Test
    public void testRunTask_whenIncrementalAndTransportRequestsNonIncremental() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        Path incrementalData = createTemporaryFile();
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)),
                        any(),
                        intThat(flags -> (flags & BackupTransport.FLAG_INCREMENTAL) != 0)))
                .thenAnswer(
                        copyBackupDataAndReturn(
                                incrementalData,
                                BackupTransport.TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED));
        Path nonIncrementalData = createTemporaryFile();
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)),
                        any(),
                        intThat(flags -> (flags & BackupTransport.FLAG_NON_INCREMENTAL) != 0)))
                .thenAnswer(
                        copyBackupDataAndReturn(nonIncrementalData, BackupTransport.TRANSPORT_OK));
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    // agentMock.oldState has already been updated by now.
                    if (agentMock.oldState.length > 0) {
                        writeData(dataOutput, "key", "dataForIncremental".getBytes());
                        writeState(newState, "stateForIncremental".getBytes());
                    } else {
                        writeData(dataOutput, "key", "dataForNonIncremental".getBytes());
                        writeState(newState, "stateForNonIncremental".getBytes());
                    }
                });
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, false, PACKAGE_1);
        // Write state to be incremental
        Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());

        runTask(task);

        verify(agentMock.agent, times(2)).onBackup(any(), any(), any());
        byte[] oldStateDuringIncremental = agentMock.oldStateHistory.get(0);
        byte[] oldStateDuringNonIncremental = agentMock.oldStateHistory.get(1);
        assertThat(oldStateDuringIncremental).isEqualTo("oldState".getBytes());
        assertThat(oldStateDuringNonIncremental).isEqualTo(new byte[0]);
        assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1)))
                .isEqualTo("stateForNonIncremental".getBytes());
        try (FileInputStream inputStream = new FileInputStream(incrementalData.toFile())) {
            BackupDataInput backupData = new BackupDataInput(inputStream.getFD());
            assertDataHasKeyValue(backupData, "key", "dataForIncremental".getBytes());
            assertThat(backupData.readNextHeader()).isFalse();
        }
        try (FileInputStream inputStream = new FileInputStream(nonIncrementalData.toFile())) {
            BackupDataInput backupData = new BackupDataInput(inputStream.getFD());
            assertDataHasKeyValue(backupData, "key", "dataForNonIncremental".getBytes());
            assertThat(backupData.readNextHeader()).isFalse();
        }
        verify(mObserver).onResult(PACKAGE_1.packageName, SUCCESS);
        verify(mObserver).backupFinished(SUCCESS);
    }

    @Test
    public void testRunTask_whenTransportReturnsError_notifiesCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_ERROR);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mObserver).onResult(PACKAGE_1.packageName, ERROR_TRANSPORT_ABORTED);
        verify(mObserver).backupFinished(ERROR_TRANSPORT_ABORTED);
    }

    @Test
    public void testRunTask_whenTransportReturnsError_logsBackupTransportFailureEvent()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_ERROR);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertEventLogged(EventLogTags.BACKUP_TRANSPORT_FAILURE, PACKAGE_1.packageName);
    }

    @Test
    public void testRunTask_whenTransportReturnsError_revertsTask() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_ERROR);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertTaskReverted(transportMock, PACKAGE_1);
    }

    @Test
    public void testRunTask_whenTransportReturnsError_updatesFilesAndCleansUp() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenReturn(BackupTransport.TRANSPORT_ERROR);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        Files.write(getStateFile(mTransport, PACKAGE_1), "oldState".getBytes());

        runTask(task);

        assertThat(Files.readAllBytes(getStateFile(mTransport, PACKAGE_1)))
                .isEqualTo("oldState".getBytes());
        assertCleansUpFilesAndAgent(mTransport, PACKAGE_1);
    }

    @Test
    public void testRunTask_whenTransportGetBackupQuotaThrowsForPm() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.getBackupQuota(PM_PACKAGE.packageName, false))
                .thenThrow(DeadObjectException.class);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mListener).onFinished(any());
        verify(mObserver).backupFinished(ERROR_TRANSPORT_ABORTED);
        assertEventLogged(
                EventLogTags.BACKUP_AGENT_FAILURE,
                PM_PACKAGE.packageName,
                new DeadObjectException().toString());
    }

    @Test
    public void testRunTask_whenPmAgentFails_reportsCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException());
        when(mBackupManagerService.makeMetadataAgent()).thenReturn(pmAgent);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mListener).onFinished(any());
        verify(mObserver).backupFinished(eq(ERROR_TRANSPORT_ABORTED));
        assertEventLogged(
                EventLogTags.BACKUP_AGENT_FAILURE,
                PM_PACKAGE.packageName,
                new RuntimeException().toString());
    }

    @Test
    public void testRunTask_whenPmAgentFails_revertsTask() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1);
        BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException());
        doReturn(pmAgent).when(mBackupManagerService).makeMetadataAgent();
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertTaskReverted(transportMock, PACKAGE_1);
    }

    @Test
    public void testRunTask_whenPmAgentFails_cleansUpFiles() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1);
        BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException());
        doReturn(pmAgent).when(mBackupManagerService).makeMetadataAgent();
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        assertCleansUpFiles(mTransport, PM_PACKAGE);
    }

    @Test
    public void testRunTask_whenPmAgentFails_resetsBackupState() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1);
        BackupAgent pmAgent = createThrowingPmAgent(new RuntimeException());
        doReturn(pmAgent).when(mBackupManagerService).makeMetadataAgent();
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        runTask(task);

        verify(mBackupManagerService).resetBackupState(getStateDirectory(mTransport).toFile());
    }

    @Test
    public void testRunTask_whenMarkCancelDuringPmOnBackup_resetsBackupState() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1);
        BackupAgent pmAgent = spy(createPmAgent());
        doReturn(forward(pmAgent)).when(mBackupManagerService).makeMetadataAgent();
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        agentOnBackupDo(
                pmAgent, (oldState, dataOutput, newState) -> runInWorkerThread(task::markCancel));

        runTask(task);

        verify(mBackupManagerService).resetBackupState(getStateDirectory(mTransport).toFile());
    }

    @Test
    public void testRunTask_whenMarkCancelDuringPmOnBackup_cleansUpFiles() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgent(PACKAGE_1);
        BackupAgent pmAgent = spy(createPmAgent());
        doReturn(forward(pmAgent)).when(mBackupManagerService).makeMetadataAgent();
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        agentOnBackupDo(
                pmAgent, (oldState, dataOutput, newState) -> runInWorkerThread(task::markCancel));

        runTask(task);

        assertCleansUpFiles(mTransport, PM_PACKAGE);
    }

    @Test
    public void testRunTask_whenBackupRunning_doesNotThrow() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(mBackupManagerService.isBackupOperationInProgress()).thenReturn(true);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock);

        runTask(task);
    }

    @Test
    public void testRunTask_whenReadingBackupDataThrows_reportsCorrectly() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        // We don't validate PM's data, so it will only throw in PACKAGE_1
        ShadowBackupDataInput.throwInNextHeaderRead();

        runTask(task);

        verify(mReporter).onAgentDataError(eq(PACKAGE_1.packageName), any());
    }

    @Test
    public void testRunTask_whenReadingBackupDataThrows_doesNotCallTransport() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        ShadowBackupDataInput.throwInNextHeaderRead();

        runTask(task);

        verify(transportMock.transport, never())
                .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
    }

    @Test
    public void testRunTask_whenReadingBackupDataThrows_doesNotCallSecondAgent() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        AgentMock agentMock = setUpAgentWithData(PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);
        ShadowBackupDataInput.throwInNextHeaderRead();

        runTask(task);

        verify(agentMock.agent, never()).onBackup(any(), any(), any());
    }

    @Test
    public void testRunTask_whenReadingBackupDataThrows_cleansUpAndRevertsTask() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentsWithData(PACKAGE_1, PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);
        ShadowBackupDataInput.throwInNextHeaderRead();

        runTask(task);

        assertCleansUpFiles(mTransport, PACKAGE_2);
        assertTaskReverted(transportMock, PACKAGE_1, PACKAGE_2);
    }

    @Test
    public void testRunTask_whenMarkCancelDuringAgentOnBackup_cleansUpFiles() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                    runInWorkerThread(task::markCancel);
                });

        runTask(task);

        assertCleansUpFiles(mTransport, PACKAGE_1);
    }

    @Test
    public void
            testRunTask_whenMarkCancelDuringFirstAgentOnBackup_doesNotCallTransportAfterWaitCancel()
                    throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        setUpAgentsWithData(PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                    runInWorkerThread(task::markCancel);
                });

        ConditionVariable taskFinished = runTaskAsync(task);

        verifyAndUnblockAgentCalls(2);
        task.waitCancel();
        reset(transportMock.transport);
        taskFinished.block();
        verifyZeroInteractions(transportMock.transport);
    }

    @Test
    public void testRunTask_whenMarkCancelDuringAgentOnBackup_doesNotCallTransportForPackage()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                    runInWorkerThread(task::markCancel);
                });

        ConditionVariable taskFinished = runTaskAsync(task);

        verifyAndUnblockAgentCalls(2);
        taskFinished.block();
        // For PM
        verify(transportMock.transport, times(1)).finishBackup();
        verify(transportMock.transport, never())
                .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
    }

    @Test
    public void testRunTask_whenMarkCancelDuringTransportPerformBackup_callsTransportForPackage()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenAnswer(
                        invocation -> {
                            runInWorkerThread(task::markCancel);
                            return BackupTransport.TRANSPORT_OK;
                        });

        ConditionVariable taskFinished = runTaskAsync(task);

        verifyAndUnblockAgentCalls(2);
        taskFinished.block();
        InOrder inOrder = inOrder(transportMock.transport);
        inOrder.verify(transportMock.transport)
                .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
        inOrder.verify(transportMock.transport).finishBackup();
    }

    @Test
    public void
            testRunTask_whenMarkCancelDuringSecondAgentOnBackup_callsTransportForFirstPackageButNotForSecond()
                    throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        AgentMock agentMock = setUpAgent(PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                    runInWorkerThread(task::markCancel);
                });

        ConditionVariable taskFinished = runTaskAsync(task);

        verifyAndUnblockAgentCalls(3);
        taskFinished.block();
        InOrder inOrder = inOrder(transportMock.transport);
        inOrder.verify(transportMock.transport)
                .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
        inOrder.verify(transportMock.transport).finishBackup();
        verify(transportMock.transport, never())
                .performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt());
    }

    @Test
    public void
            testRunTask_whenMarkCancelDuringTransportPerformBackupForFirstPackage_callsTransportForFirstPackageButNotForSecond()
                    throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentsWithData(PACKAGE_1, PACKAGE_2);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);
        when(transportMock.transport.performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(), anyInt()))
                .thenAnswer(
                        invocation -> {
                            runInWorkerThread(task::markCancel);
                            return BackupTransport.TRANSPORT_OK;
                        });

        ConditionVariable taskFinished = runTaskAsync(task);

        verifyAndUnblockAgentCalls(2);
        taskFinished.block();
        InOrder inOrder = inOrder(transportMock.transport);
        inOrder.verify(transportMock.transport)
                .performBackup(argThat(packageInfo(PACKAGE_1)), any(), anyInt());
        inOrder.verify(transportMock.transport).finishBackup();
        verify(transportMock.transport, never())
                .performBackup(argThat(packageInfo(PACKAGE_2)), any(), anyInt());
    }

    @Test
    public void testRunTask_afterMarkCancel_doesNotCallAgentOrTransport() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        AgentMock agentMock = setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        task.markCancel();

        runTask(task);

        verify(agentMock.agent, never()).onBackup(any(), any(), any());
        verify(transportMock.transport, never()).performBackup(any(), any(), anyInt());
        verify(transportMock.transport, never()).finishBackup();
    }

    @Test
    public void testWaitCancel_afterCancelledTaskFinished_returns() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        task.markCancel();
        runTask(task);

        task.waitCancel();
    }

    @Test
    public void testWaitCancel_whenMarkCancelDuringAgentOnBackup_unregistersTask()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        AgentMock agentMock = setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        agentOnBackupDo(
                agentMock,
                (oldState, dataOutput, newState) -> {
                    writeData(dataOutput, "key", "data".getBytes());
                    writeState(newState, "newState".getBytes());
                    runInWorkerThread(task::markCancel);
                });
        ConditionVariable taskFinished = runTaskAsync(task);
        verifyAndUnblockAgentCalls(1);
        boolean backupInProgressDuringBackup = mBackupManagerService.isBackupOperationInProgress();
        assertThat(backupInProgressDuringBackup).isTrue();
        verifyAndUnblockAgentCalls(1);

        task.waitCancel();

        boolean backupInProgressAfterWaitCancel =
                mBackupManagerService.isBackupOperationInProgress();
        assertThat(backupInProgressDuringBackup).isTrue();
        assertThat(backupInProgressAfterWaitCancel).isFalse();
        taskFinished.block();
    }

    @Test
    public void testMarkCancel_afterTaskFinished_returns() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        runTask(task);

        task.markCancel();
    }

    @Test
    public void testHandleCancel_callsMarkCancelAndWaitCancel() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = spy(createKeyValueBackupTask(transportMock, PACKAGE_1));
        doNothing().when(task).waitCancel();

        task.handleCancel(true);

        InOrder inOrder = inOrder(task);
        inOrder.verify(task).markCancel();
        inOrder.verify(task).waitCancel();
    }

    @Test
    public void testHandleCancel_whenCancelAllFalse_throws() throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        setUpAgentWithData(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1);

        expectThrows(IllegalArgumentException.class, () -> task.handleCancel(false));
    }

    /** Do not update backup token if no data was moved. */
    @Test
    public void testRunTask_whenNoDataToBackupOnFirstBackup_doesNotUpdateCurrentToken()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        mBackupManagerService.setCurrentToken(0L);
        when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L);
        // Set up agent with no data.
        setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);

        runTask(task);

        assertThat(mBackupManagerService.getCurrentToken()).isEqualTo(0L);
    }

    /** Do not inform transport of an empty backup if the app hasn't backed up before */
    @Test
    public void testRunTask_whenNoDataToBackupOnFirstBackup_doesNotTellTransportOfBackup()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        mBackupManagerService.setCurrentToken(0L);
        when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L);
        setUpAgent(PACKAGE_1);
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, true, PACKAGE_1);

        runTask(task);

        verify(transportMock.transport, never())
                .performBackup(
                        argThat(packageInfo(PACKAGE_1)), any(ParcelFileDescriptor.class), anyInt());
    }

    /** Let the transport know if there are no changes for a KV backed-up package. */
    @Test
    public void testRunTask_whenBackupHasCompletedAndThenNoDataChanges_transportGetsNotified()
            throws Exception {
        TransportMock transportMock = setUpInitializedTransport(mTransport);
        when(transportMock.transport.getCurrentRestoreSet()).thenReturn(1234L);
        when(transportMock.transport.isAppEligibleForBackup(
                        argThat(packageInfo(PACKAGE_1)), eq(false)))
                .thenReturn(true);
        when(transportMock.transport.isAppEligibleForBackup(
                        argThat(packageInfo(PACKAGE_2)), eq(false)))
                .thenReturn(true);
        setUpAgentWithData(PACKAGE_1);
        setUpAgentWithData(PACKAGE_2);

        PackageInfo endSentinel = new PackageInfo();
        endSentinel.packageName = KeyValueBackupTask.NO_DATA_END_SENTINEL;

        // Perform First Backup run, which should backup both packages
        KeyValueBackupTask task = createKeyValueBackupTask(transportMock, PACKAGE_1, PACKAGE_2);
        runTask(task);
        InOrder order = Mockito.inOrder(transportMock.transport);
        order.verify(transportMock.transport)
                .performBackup(
                        argThat(packageInfo(PACKAGE_1)),
                        any(),
                        eq(BackupTransport.FLAG_NON_INCREMENTAL));
        order.verify(transportMock.transport).finishBackup();
        order.verify(transportMock.transport)
                .performBackup(
                        argThat(packageInfo(PACKAGE_2)),
                        any(),
                        eq(BackupTransport.FLAG_NON_INCREMENTAL));
        order.verify(transportMock.transport).finishBackup();

        // Run again with new data for package 1, but nothing new for package 2
        task = createKeyValueBackupTask(transportMock, PACKAGE_1);
        runTask(task);

        // Now for the second run we performed one incremental backup (package 1) and
        // made one "no change" call (package 2) before sending the end sentinel.
        order.verify(transportMock.transport)
                .performBackup(
                        argThat(packageInfo(PACKAGE_1)),
                        any(),
                        eq(BackupTransport.FLAG_INCREMENTAL));
        order.verify(transportMock.transport).finishBackup();
        order.verify(transportMock.transport)
                .performBackup(
                        argThat(packageInfo(PACKAGE_2)),
                        any(),
                        eq(BackupTransport.FLAG_DATA_NOT_CHANGED));
        order.verify(transportMock.transport).finishBackup();
        order.verify(transportMock.transport)
                .performBackup(
                        argThat(packageInfo(endSentinel)),
                        any(),
                        eq(BackupTransport.FLAG_DATA_NOT_CHANGED));
        order.verify(transportMock.transport).finishBackup();
        order.verifyNoMoreInteractions();
    }

    private void runTask(KeyValueBackupTask task) {
        // Pretend we are not on the main-thread to prevent RemoteCall from complaining
        mShadowMainLooper.setCurrentThread(false);
        task.run();
        mShadowMainLooper.reset();
        assertTaskPostConditions();
    }

    private ConditionVariable runTaskAsync(KeyValueBackupTask task) {
        return runInWorkerThreadAsync(task::run);
    }

    private static ConditionVariable runInWorkerThreadAsync(ThrowingRunnable runnable) {
        ConditionVariable finished = new ConditionVariable(false);
        new Thread(
                        () -> {
                            uncheck(runnable);
                            finished.open();
                        },
                        "test-worker-thread")
                .start();
        return finished;
    }

    private static void runInWorkerThread(ThrowingRunnable runnable) {
        runInWorkerThreadAsync(runnable).block();
    }

    /**
     * If you have kicked-off the task with {@link #runTaskAsync(KeyValueBackupTask)}, call this to
     * unblock the task thread that will be waiting for the agent's {@link
     * IBackupAgent#doBackup(ParcelFileDescriptor, ParcelFileDescriptor, ParcelFileDescriptor, long,
     * IBackupCallback, int)}.
     *
     * @param times The number of {@link IBackupAgent#doBackup(ParcelFileDescriptor,
     *     ParcelFileDescriptor, ParcelFileDescriptor, long, IBackupCallback, int)} calls. Remember
     *     to count PM calls.
     */
    private void verifyAndUnblockAgentCalls(int times)
            throws InterruptedException, TimeoutException {
        // HACK: IBackupAgent.doBackup() posts a runnable to the front of the main-thread queue and
        // immediately waits for its execution. In Robolectric, if we are in the main-thread this
        // runnable is executed inline (this is called unpaused looper), that's why when we run the
        // task in the main-thread (runTask() as opposed to runTaskAsync()) we don't need to call
        // this method. However, if we are not in the main-thread nobody executes the runnable for
        // us, thus IBackupAgent code will be stuck waiting for someone to execute the runnable.
        // This method waits for that *specific* runnable, identifying it via class name, and then
        // idles the main looper (for 0 seconds because it's posted at the front of the queue),
        // which executes the method.
        for (int i = 0; i < times; i++) {
            waitUntil(() -> messagesInLooper(mMainLooper, this::isSharedPrefsSynchronizer) > 0);
            mShadowMainLooper.idle();
        }
    }

    private boolean isSharedPrefsSynchronizer(@Nullable Message message) {
        String className = BACKUP_AGENT_SHARED_PREFS_SYNCHRONIZER_CLASS;
        return message != null
                && message.getCallback() != null
                && className.equals(message.getCallback().getClass().getName());
    }

    private TransportMock setUpTransport(TransportData transport) throws Exception {
        TransportMock transportMock =
                TransportTestUtils.setUpTransport(mTransportManager, transport);
        Files.createDirectories(getStateDirectory(transport));
        return transportMock;
    }

    /** Sets up the transport and writes a PM state file in the transport state directory. */
    private TransportMock setUpInitializedTransport(TransportData transport) throws Exception {
        TransportMock transportMock = setUpTransport(transport);
        createPmStateFile(transport);
        return transportMock;
    }

    private Path getStateDirectory(TransportData transport) {
        return mBaseStateDir.toPath().resolve(transport.transportDirName);
    }

    private Path getStateFile(TransportData transport, PackageData packageData) {
        return getStateDirectory(transport).resolve(packageData.packageName);
    }

    private Path getTemporaryStateFile(TransportData transport, PackageData packageData) {
        return getStateDirectory(transport)
                .resolve(packageData.packageName + KeyValueBackupTask.NEW_STATE_FILE_SUFFIX);
    }

    private Path getStagingDirectory() {
        return mDataDir.toPath();
    }

    private Path getStagingFile(PackageData packageData) {
        return getStagingDirectory()
                .resolve(packageData.packageName + KeyValueBackupTask.STAGING_FILE_SUFFIX);
    }

    private List<AgentMock> setUpAgents(PackageData... packageNames) {
        return Stream.of(packageNames).map(this::setUpAgent).collect(toList());
    }

    private AgentMock setUpAgent(PackageData packageData) {
        try {
            String packageName = packageData.packageName;
            mPackageManager.setApplicationEnabledSetting(
                    packageName, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 0);
            PackageInfo packageInfo = getPackageInfo(packageData);
            mShadowPackageManager.installPackage(packageInfo);
            ShadowApplicationPackageManager.addInstalledPackage(packageName, packageInfo);
            mContext.sendBroadcast(getPackageAddedIntent(packageData));
            // Run the backup looper because on the receiver we post MSG_SCHEDULE_BACKUP_PACKAGE
            mShadowBackupLooper.runToEndOfTasks();
            BackupAgent backupAgent = spy(BackupAgent.class);
            IBackupAgent backupAgentBinder =
                    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());
            if (packageData.available) {
                doReturn(backupAgentBinder)
                        .when(mBackupManagerService)
                        .bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt());
            } else {
                doReturn(null)
                        .when(mBackupManagerService)
                        .bindToAgentSynchronous(argThat(applicationInfo(packageData)), anyInt());
            }
            return new AgentMock(backupAgentBinder, backupAgent);
        } catch (RemoteException e) {
            // Never happens, compiler happy
            throw new AssertionError(e);
        }
    }

    private PackageInfo getPackageInfo(PackageData packageData) {
        PackageInfo packageInfo = new PackageInfo();
        packageInfo.packageName = packageData.packageName;
        packageInfo.applicationInfo = new ApplicationInfo();
        packageInfo.applicationInfo.uid = packageData.uid;
        packageInfo.applicationInfo.flags = packageData.flags();
        packageInfo.applicationInfo.backupAgentName = packageData.agentName;
        packageInfo.applicationInfo.packageName = packageData.packageName;
        return packageInfo;
    }

    private Intent getPackageAddedIntent(PackageData packageData) {
        Intent intent =
                new Intent(
                        Intent.ACTION_PACKAGE_ADDED,
                        Uri.parse("package:" + packageData.packageName));
        intent.putExtra(Intent.EXTRA_UID, packageData.uid);
        intent.putExtra(Intent.EXTRA_REPLACING, false);
        intent.putExtra(Intent.EXTRA_USER_HANDLE, 0);
        return intent;
    }

    private List<AgentMock> setUpAgentsWithData(PackageData... packages) {
        return Stream.of(packages).map(this::setUpAgentWithData).collect(toList());
    }

    private AgentMock setUpAgentWithData(PackageData packageData) {
        AgentMock agentMock = setUpAgent(packageData);
        String packageName = packageData.packageName;
        uncheck(
                () ->
                        agentOnBackupDo(
                                agentMock,
                                (oldState, dataOutput, newState) -> {
                                    writeData(dataOutput, "key", ("data" + packageName).getBytes());
                                    writeState(newState, ("state" + packageName).getBytes());
                                }));
        return agentMock;
    }

    private KeyValueBackupTask createKeyValueBackupTask(
            TransportMock transportMock, PackageData... packages) {
        return createKeyValueBackupTask(transportMock, false, packages);
    }

    private KeyValueBackupTask createKeyValueBackupTask(
            TransportMock transportMock, boolean nonIncremental, PackageData... packages) {
        List<String> queue =
                Stream.of(packages).map(packageData -> packageData.packageName).collect(toList());
        mBackupManagerService.getPendingBackups().clear();
        // mOldJournal is a mock, but it would be the value returned by BMS.getJournal() now
        mBackupManagerService.setJournal(null);
        mWakeLock.acquire();
        KeyValueBackupTask task =
                new KeyValueBackupTask(
                        mBackupManagerService,
                        transportMock.transportClient,
                        transportMock.transportData.transportDirName,
                        queue,
                        mOldJournal,
                        mReporter,
                        mListener,
                        emptyList(),
                        /* userInitiated */ false,
                        nonIncremental);
        mBackupManager.setUp(mBackupHandler, task);
        return task;
    }

    private PackageManagerBackupAgent createPmAgent() {
        PackageManagerBackupAgent pmAgent =
                new PackageManagerBackupAgent(mApplication.getPackageManager(), USER_ID);
        pmAgent.attach(mApplication);
        pmAgent.onCreate();
        return pmAgent;
    }

    /**
     * Returns an implementation of PackageManagerBackupAgent that throws RuntimeException in {@link
     * BackupAgent#onBackup(ParcelFileDescriptor, BackupDataOutput, ParcelFileDescriptor)}
     */
    private PackageManagerBackupAgent createThrowingPmAgent(RuntimeException exception) {
        PackageManagerBackupAgent pmAgent =
                new ThrowingPackageManagerBackupAgent(mApplication.getPackageManager(), exception);
        pmAgent.attach(mApplication);
        pmAgent.onCreate();
        return pmAgent;
    }

    /** Matches {@link PackageInfo} whose package name is {@code packageData.packageName}. */
    private static ArgumentMatcher<PackageInfo> packageInfo(PackageData packageData) {
        // We have to test for packageInfo nulity because of Mockito's own stubbing with argThat().
        // E.g. if you do:
        //
        //   1. when(object.method(argThat(str -> str.equals("foo")))).thenReturn(0)
        //   2. when(object.method(argThat(str -> str.equals("bar")))).thenReturn(2)
        //
        // The second line will throw NPE because it will call lambda 1 with null, since argThat()
        // returns null. So we guard against that by checking for null.
        return packageInfo ->
                packageInfo != null && packageData.packageName.equals(packageInfo.packageName);
    }

    /** Matches {@link PackageInfo} whose package name is {@code packageData.packageName}. */
    private static ArgumentMatcher<PackageInfo> packageInfo(PackageInfo packageData) {
        // We have to test for packageInfo nulity because of Mockito's own stubbing with argThat().
        // E.g. if you do:
        //
        //   1. when(object.method(argThat(str -> str.equals("foo")))).thenReturn(0)
        //   2. when(object.method(argThat(str -> str.equals("bar")))).thenReturn(2)
        //
        // The second line will throw NPE because it will call lambda 1 with null, since argThat()
        // returns null. So we guard against that by checking for null.
        return packageInfo ->
                packageInfo != null && packageInfo.packageName.equals(packageInfo.packageName);
    }

    /** Matches {@link ApplicationInfo} whose package name is {@code packageData.packageName}. */
    private static ArgumentMatcher<ApplicationInfo> applicationInfo(PackageData packageData) {
        return applicationInfo ->
                applicationInfo != null
                        && packageData.packageName.equals(applicationInfo.packageName);
    }

    private static ArgumentMatcher<BackupDataOutput> dataOutputWithTransportFlags(int flags) {
        return dataOutput -> dataOutput.getTransportFlags() == flags;
    }

    private static void writeData(BackupDataOutput dataOutput, String key, byte[] data)
            throws IOException {
        dataOutput.writeEntityHeader(key, data.length);
        dataOutput.writeEntityData(data, data.length);
    }

    private static void writeState(ParcelFileDescriptor newState, byte[] state) throws IOException {
        OutputStream outputStream = new FileOutputStream(newState.getFileDescriptor());
        outputStream.write(state);
        outputStream.flush();
    }

    /**
     * This is to prevent the following:
     *
     * <ul>
     *   <li>The transport being initialized with {@link IBackupTransport#initializeDevice()}
     *   <li>{@link UserBackupManagerService#resetBackupState(File)} being called, which will:
     *       <ul>
     *         <li>Reset processed packages journal.
     *         <li>Reset current token to 0.
     *         <li>Delete state files.
     *         <li>Mark data changed for every key-value participant.
     *       </ul>
     * </ul>
     */
    private void createPmStateFile() throws IOException {
        createPmStateFile(mTransport);
    }

    /** @see #createPmStateFile() */
    private void createPmStateFile(TransportData transport) throws IOException {
        Files.write(getStateFile(transport, PM_PACKAGE), "pmState".getBytes());
    }

    /**
     * Forces transport initialization and call to {@link
     * UserBackupManagerService#resetBackupState(File)}
     */
    private void deletePmStateFile() throws IOException {
        Files.deleteIfExists(getStateFile(mTransport, PM_PACKAGE));
    }

    /**
     * Implements {@code function} for {@link BackupAgent#onBackup(ParcelFileDescriptor,
     * BackupDataOutput, ParcelFileDescriptor)} of {@code agentMock} and populates {@link
     * AgentMock#oldState}.
     *
     * <p>Note that for throwing agents this will simulate a local agent (the exception will be
     * thrown in our stack), use {@link #remoteAgentOnBackupThrows(AgentMock, BackupAgentOnBackup)}
     * if you want to simulate a remote agent.
     */
    private static void agentOnBackupDo(AgentMock agentMock, BackupAgentOnBackup function)
            throws Exception {
        agentOnBackupDo(
                agentMock.agent,
                (oldState, dataOutput, newState) -> {
                    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
                    transferStreamedData(
                            new FileInputStream(oldState.getFileDescriptor()), outputStream);
                    agentMock.oldState = outputStream.toByteArray();
                    agentMock.oldStateHistory.add(agentMock.oldState);
                    function.onBackup(oldState, dataOutput, newState);
                });
    }

    /**
     * Implements {@code function} for {@link BackupAgent#onBackup(ParcelFileDescriptor,
     * BackupDataOutput, ParcelFileDescriptor)} of {@code agentMock}.
     *
     * @see #agentOnBackupDo(AgentMock, BackupAgentOnBackup)
     * @see #remoteAgentOnBackupThrows(AgentMock, BackupAgentOnBackup)
     */
    private static void agentOnBackupDo(BackupAgent backupAgent, BackupAgentOnBackup function)
            throws IOException {
        doAnswer(function).when(backupAgent).onBackup(any(), any(), any());
    }

    /**
     * Use this method to simulate a remote agent throwing. We catch the exception thrown, thus
     * simulating a one-way call. It also populates {@link AgentMock#oldState}.
     *
     * @param agentMock The Agent mock.
     * @param function A function that throws, otherwise the test will fail.
     */
    // TODO: Remove when RemoteCall spins up a dedicated thread for calls
    private static void remoteAgentOnBackupThrows(AgentMock agentMock, BackupAgentOnBackup function)
            throws Exception {
        agentOnBackupDo(agentMock, function);
        doAnswer(
                        invocation -> {
                            try {
                                invocation.callRealMethod();
                                fail("Agent method expected to throw");
                            } catch (RuntimeException e) {
                                // This silences the exception just like a one-way call would, the
                                // normal completion via IBackupCallback binder still happens, check
                                // finally() block of IBackupAgent.doBackup().
                            }
                            return null;
                        })
                .when(agentMock.agentBinder)
                .doBackup(any(), any(), any(), anyLong(), any(), anyInt());
    }

    /**
     * Returns an {@link Answer} that can be used for mocking {@link
     * IBackupTransport#performBackup(PackageInfo, ParcelFileDescriptor, int)} that copies the
     * backup data received to {@code backupDataPath} and returns {@code result}.
     */
    private static Answer<Integer> copyBackupDataAndReturn(Path backupDataPath, int result) {
        return invocation -> {
            ParcelFileDescriptor backupDataParcelFd = invocation.getArgument(1);
            FileDescriptor backupDataFd = backupDataParcelFd.getFileDescriptor();
            Files.copy(new FileInputStream(backupDataFd), backupDataPath, REPLACE_EXISTING);
            backupDataParcelFd.close();
            return result;
        };
    }

    /**
     * Same as {@link #copyBackupDataAndReturn(Path, int)}} with {@code result =
     * BackupTransport.TRANSPORT_OK}.
     */
    private static Answer<Integer> copyBackupDataTo(Path backupDataPath) {
        return copyBackupDataAndReturn(backupDataPath, BackupTransport.TRANSPORT_OK);
    }

    private Path createTemporaryFile() throws IOException {
        return Files.createTempFile(mContext.getCacheDir().toPath(), "backup", ".tmp");
    }

    private static IterableSubject assertDirectory(Path directory) throws IOException {
        return assertThat(oneTimeIterable(Files.newDirectoryStream(directory).iterator()))
                .named("directory " + directory);
    }

    private static void assertJournalDoesNotContain(
            @Nullable DataChangedJournal journal, String packageName) throws IOException {
        List<String> packages = (journal == null) ? emptyList() : journal.getPackages();
        assertThat(packages).doesNotContain(packageName);
    }

    private void assertTaskReverted(TransportMock transportMock, PackageData... packages)
            throws RemoteException, IOException {
        verify(transportMock.transport).requestBackupTime();
        assertBackupPendingFor(packages);
        assertThat(KeyValueBackupJob.isScheduled(mBackupManagerService.getUserId())).isTrue();
    }

    private void assertBackupPendingFor(PackageData... packages) throws IOException {
        for (PackageData packageData : packages) {
            String packageName = packageData.packageName;
            // We verify the current journal, NOT the old one passed to KeyValueBackupTask
            // constructor
            assertThat(mBackupManagerService.getJournal().getPackages()).contains(packageName);
            assertThat(mBackupManagerService.getPendingBackups()).containsKey(packageName);
        }
    }

    private void assertBackupNotPendingFor(PackageData... packages) throws IOException {
        for (PackageData packageData : packages) {
            String packageName = packageData.packageName;
            // We verify the current journal, NOT the old one passed to KeyValueBackupTask
            // constructor
            assertJournalDoesNotContain(mBackupManagerService.getJournal(), packageName);
            assertThat(mBackupManagerService.getPendingBackups()).doesNotContainKey(packageName);
            // Also verifying BMS is never called since for some cases the package wouldn't be
            // pending for other reasons (for example it's not eligible for backup). Regardless of
            // these reasons, we shouldn't mark them as pending backup (call dataChangedImpl()).
            verify(mBackupManagerService, never()).dataChangedImpl(packageName);
        }
    }

    private void assertDataHasKeyValue(BackupDataInput backupData, String key, byte[] value)
            throws IOException {
        assertThat(backupData.readNextHeader()).isTrue();
        assertThat(backupData.getKey()).isEqualTo(key);
        int size = backupData.getDataSize();
        byte[] data1 = new byte[size];
        backupData.readEntityData(data1, 0, size);
        assertThat(data1).isEqualTo(value);
    }

    private void assertCleansUpFilesAndAgent(TransportData transport, PackageData packageData) {
        assertCleansUpFiles(transport, packageData);
        verify(mBackupManagerService).unbindAgent(argThat(applicationInfo(packageData)));
    }

    private void assertCleansUpFiles(TransportData transport, PackageData packageData) {
        assertThat(Files.exists(getTemporaryStateFile(transport, packageData))).isFalse();
        assertThat(Files.exists(getStagingFile(packageData))).isFalse();
    }

    /**
     * Put conditions that should *always* be true after task execution.
     *
     * <p>Note: We should generally NOT do this. For every different set of pre-conditions that
     * result in different code-paths being executed there should be one test method verifying these
     * post-conditions. Since there were a couple of methods here already and these post-conditions
     * are pretty serious to be neglected it was decided to over-verify in this case.
     */
    private void assertTaskPostConditions() {
        assertThat(mWakeLock.isHeld()).isFalse();
    }

    @FunctionalInterface
    private interface BackupAgentOnBackup extends Answer<Void> {
        void onBackup(
                ParcelFileDescriptor oldState,
                BackupDataOutput dataOutput,
                ParcelFileDescriptor newState)
                throws IOException;

        @Override
        default Void answer(InvocationOnMock invocation) throws Throwable {
            onBackup(
                    invocation.getArgument(0),
                    invocation.getArgument(1),
                    invocation.getArgument(2));
            return null;
        }
    }

    private static class AgentMock {
        private final IBackupAgent agentBinder;
        private final BackupAgent agent;
        private final List<byte[]> oldStateHistory = new ArrayList<>();
        private byte[] oldState;

        private AgentMock(IBackupAgent agentBinder, BackupAgent agent) {
            this.agentBinder = agentBinder;
            this.agent = agent;
        }
    }

    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);
        }
    }

    private static class ThrowingPackageManagerBackupAgent extends PackageManagerBackupAgent {
        private final RuntimeException mException;

        ThrowingPackageManagerBackupAgent(
                PackageManager packageManager, RuntimeException exception) {
            super(packageManager, USER_ID);
            mException = exception;
        }

        @Override
        public void onBackup(
                ParcelFileDescriptor oldState,
                BackupDataOutput data,
                ParcelFileDescriptor newState) {
            throw mException;
        }
    }
}
