Binding on-demand #10: Remove permanent binding and add registration

This CL is the last of the P1 CLs for binding on-demand. During
transport-usage migration to binding on-demand the permanent-bound
code was still there and we piggybacked on it to register the
transports. Now that everything is migrated we removed the permanent
binding and used the registration code created in the CL #9
(selectTransport) for registering all the transports.

I put a 3s delay on registration after bring-up. Any operation that uses
the transport before that will gracefully fail.

The TransportBoundListener does not return a boolean anymore because it
can't fail anymore (we pass it the data it needs so that it doesn't need
to be exposed to TransportNotRegistered exceptions). It's now called
OnTransportRegisteredListener.

Eligible transports were a similar thing to valid transports from the
permanent binding code, whose only need came from rebinding from what I
could tell. I saw no need for it anymore and removed it. If I missed
something please shout :)

I shuffled methods a bit in TransportManager.

There were a lot of changes to robo test infra to bring together
TransportManager tests and the rest and re-use mocking infra.

Change-Id: If61268228dd0bb724b718abd3dcafdad50e8b3dc
Ref: http://go/br-binding-on-demand
Bug: 17140907
Test: m -j RunFrameworksServicesRoboTest
Test: runtest -p com.android.server.backup frameworks-services
Test: gts-tradefed run commandAndExit gts-dev -m GtsBackupTestCases
Test: gts-tradefed run commandAndExit gts-dev -m GtsBackupHostTestCases
Test: cts-tradefed run commandAndExit cts-dev -m CtsBackupTestCases
Test: adb shell bmgr enable true/false
Test: adb shell bmgr list transports [-c]
Test: adb shell bmgr transport <transport_name>/-c <transport_component>
Test: adb shell bmgr restore <token>/<token> <package>/<package>
Test: adb shell bmgr backup <pacakge>
Test: adb shell bmgr run
Test: adb shell bmgr wipe <transport> <package>
Test: adb shell bmgr fullbackup <package>
Test: adb shell bmgr backupnow --all/<packages>
Test: adb shell cmd jobscheduler run -f android <job_id>
Test: adb shell dumpsys backup
Test: D2D scenario
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 03591a8..350d7af 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -207,6 +207,10 @@
     public static final String BACKUP_FINISHED_ACTION = "android.intent.action.BACKUP_FINISHED";
     public static final String BACKUP_FINISHED_PACKAGE_EXTRA = "packageName";
 
+    // Time delay for initialization operations that can be delayed so as not to consume too much CPU
+    // on bring-up and increase time-to-UI.
+    private static final long INITIALIZATION_DELAY_MILLIS = 3000;
+
     // Timeout interval for deciding that a bind or clear-data has taken too long
     private static final long TIMEOUT_INTERVAL = 10 * 1000;
 
@@ -282,6 +286,9 @@
 
     private final BackupPasswordManager mBackupPasswordManager;
 
+    // Time when we post the transport registration operation
+    private final long mRegisterTransportsRequestedTime;
+
     @GuardedBy("mPendingRestores")
     private boolean mIsRestoreInProgress;
     @GuardedBy("mPendingRestores")
@@ -735,6 +742,9 @@
         // Set up our transport options and initialize the default transport
         SystemConfig systemConfig = SystemConfig.getInstance();
         Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
+        if (transportWhitelist == null) {
+            transportWhitelist = Collections.emptySet();
+        }
 
         String transport =
                 Settings.Secure.getString(
@@ -749,8 +759,7 @@
                 new TransportManager(
                         context,
                         transportWhitelist,
-                        transport,
-                        backupThread.getLooper());
+                        transport);
 
         // If encrypted file systems is enabled or disabled, this call will return the
         // correct directory.
@@ -852,12 +861,14 @@
         }
 
         mTransportManager = transportManager;
-        mTransportManager.setTransportBoundListener(mTransportBoundListener);
-        mTransportManager.registerAllTransports();
+        mTransportManager.setOnTransportRegisteredListener(this::onTransportRegistered);
+        mRegisterTransportsRequestedTime = SystemClock.elapsedRealtime();
+        mBackupHandler.postDelayed(
+                mTransportManager::registerTransports, INITIALIZATION_DELAY_MILLIS);
 
-        // Now that we know about valid backup participants, parse any
-        // leftover journal files into the pending backup set
-        mBackupHandler.post(this::parseLeftoverJournals);
+        // Now that we know about valid backup participants, parse any leftover journal files into
+        // the pending backup set
+        mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);
 
         // Power management
         mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
@@ -1151,39 +1162,28 @@
         }
     }
 
-    private TransportManager.TransportBoundListener mTransportBoundListener =
-            new TransportManager.TransportBoundListener() {
-                @Override
-                public boolean onTransportBound(IBackupTransport transport) {
-                    // If the init sentinel file exists, we need to be sure to perform the init
-                    // as soon as practical.  We also create the state directory at registration
-                    // time to ensure it's present from the outset.
-                    String name = null;
-                    try {
-                        name = transport.name();
-                        String transportDirName = transport.transportDirName();
-                        File stateDir = new File(mBaseStateDir, transportDirName);
-                        stateDir.mkdirs();
+    private void onTransportRegistered(String transportName, String transportDirName) {
+        if (DEBUG) {
+            long timeMs = SystemClock.elapsedRealtime() - mRegisterTransportsRequestedTime;
+            Slog.d(TAG, "Transport " + transportName + " registered " + timeMs
+                    + "ms after first request (delay = " + INITIALIZATION_DELAY_MILLIS + "ms)");
+        }
 
-                        File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
-                        if (initSentinel.exists()) {
-                            synchronized (mQueueLock) {
-                                mPendingInits.add(name);
+        File stateDir = new File(mBaseStateDir, transportDirName);
+        stateDir.mkdirs();
 
-                                // TODO: pick a better starting time than now + 1 minute
-                                long delay = 1000 * 60; // one minute, in milliseconds
-                                mAlarmManager.set(AlarmManager.RTC_WAKEUP,
-                                        System.currentTimeMillis() + delay, mRunInitIntent);
-                            }
-                        }
-                        return true;
-                    } catch (Exception e) {
-                        // the transport threw when asked its file naming prefs; declare it invalid
-                        Slog.w(TAG, "Failed to regiser transport: " + name);
-                        return false;
-                    }
-                }
-            };
+        File initSentinel = new File(stateDir, INIT_SENTINEL_FILE_NAME);
+        if (initSentinel.exists()) {
+            synchronized (mQueueLock) {
+                mPendingInits.add(transportName);
+
+                // TODO: pick a better starting time than now + 1 minute
+                long delay = 1000 * 60; // one minute, in milliseconds
+                mAlarmManager.set(AlarmManager.RTC_WAKEUP,
+                        System.currentTimeMillis() + delay, mRunInitIntent);
+            }
+        }
+    }
 
     // ----- Track installation/removal of packages -----
     private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
@@ -2891,14 +2891,14 @@
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "listAllTransports");
 
-        return mTransportManager.getBoundTransportNames();
+        return mTransportManager.getRegisteredTransportNames();
     }
 
     @Override
     public ComponentName[] listAllTransportComponents() {
         mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP,
                 "listAllTransportComponents");
-        return mTransportManager.getAllTransportComponents();
+        return mTransportManager.getRegisteredTransportComponents();
     }
 
     @Override
@@ -3003,6 +3003,8 @@
 
     /** Selects transport {@code transportName} and returns previous selected transport. */
     @Override
+    @Deprecated
+    @Nullable
     public String selectBackupTransport(String transportName) {
         mContext.enforceCallingOrSelfPermission(
                 android.Manifest.permission.BACKUP, "selectBackupTransport");
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 34b8935..09456b68 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -16,93 +16,56 @@
 
 package com.android.server.backup;
 
-import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
-
 import android.annotation.Nullable;
+import android.annotation.WorkerThread;
 import android.app.backup.BackupManager;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.Handler;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
 import android.os.RemoteException;
 import android.os.UserHandle;
-import android.provider.Settings;
 import android.util.ArrayMap;
-import android.util.ArraySet;
 import android.util.EventLog;
-import android.util.Log;
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.IBackupTransport;
+import com.android.internal.util.Preconditions;
 import com.android.server.EventLogTags;
+import com.android.server.backup.transport.OnTransportRegisteredListener;
 import com.android.server.backup.transport.TransportClient;
 import com.android.server.backup.transport.TransportClientManager;
 import com.android.server.backup.transport.TransportConnectionListener;
 import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 
-import java.util.ArrayList;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
-/**
- * Handles in-memory bookkeeping of all BackupTransport objects.
- */
+/** Handles in-memory bookkeeping of all BackupTransport objects. */
 public class TransportManager {
-
     private static final String TAG = "BackupTransportManager";
 
     @VisibleForTesting
     public static final String SERVICE_ACTION_TRANSPORT_HOST = "android.backup.TRANSPORT_HOST";
 
-    private static final long REBINDING_TIMEOUT_UNPROVISIONED_MS = 30 * 1000; // 30 sec
-    private static final long REBINDING_TIMEOUT_PROVISIONED_MS = 5 * 60 * 1000; // 5 mins
-    private static final int REBINDING_TIMEOUT_MSG = 1;
-
     private final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
     private final Context mContext;
     private final PackageManager mPackageManager;
     private final Set<ComponentName> mTransportWhitelist;
-    private final Handler mHandler;
     private final TransportClientManager mTransportClientManager;
-
-    /**
-     * This listener is called after we bind to any transport. If it returns true, this is a valid
-     * transport.
-     */
-    private TransportBoundListener mTransportBoundListener;
-
     private final Object mTransportLock = new Object();
-
-    /**
-     * We have detected these transports on the device. Unless in exceptional cases, we are also
-     * bound to all of these.
-     */
-    @GuardedBy("mTransportLock")
-    private final Map<ComponentName, TransportConnection> mValidTransports = new ArrayMap<>();
-
-    /** We are currently bound to these transports. */
-    @GuardedBy("mTransportLock")
-    private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
-
-    /** @see #getEligibleTransportComponents() */
-    @GuardedBy("mTransportLock")
-    private final Set<ComponentName> mEligibleTransports = new ArraySet<>();
+    private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {};
 
     /** @see #getRegisteredTransportNames() */
     @GuardedBy("mTransportLock")
@@ -110,120 +73,98 @@
             new ArrayMap<>();
 
     @GuardedBy("mTransportLock")
+    @Nullable
     private volatile String mCurrentTransportName;
 
-    TransportManager(
-            Context context,
-            Set<ComponentName> whitelist,
-            String defaultTransport,
-            TransportBoundListener listener,
-            Looper looper) {
-        this(context, whitelist, defaultTransport, looper);
-        mTransportBoundListener = listener;
+    TransportManager(Context context, Set<ComponentName> whitelist, String selectedTransport) {
+        this(context, whitelist, selectedTransport, new TransportClientManager(context));
     }
 
+    @VisibleForTesting
     TransportManager(
             Context context,
             Set<ComponentName> whitelist,
-            String defaultTransport,
-            Looper looper) {
+            String selectedTransport,
+            TransportClientManager transportClientManager) {
         mContext = context;
         mPackageManager = context.getPackageManager();
-        if (whitelist != null) {
-            mTransportWhitelist = whitelist;
-        } else {
-            mTransportWhitelist = new ArraySet<>();
-        }
-        mCurrentTransportName = defaultTransport;
-        mHandler = new RebindOnTimeoutHandler(looper);
-        mTransportClientManager = new TransportClientManager(context);
+        mTransportWhitelist = Preconditions.checkNotNull(whitelist);
+        mCurrentTransportName = selectedTransport;
+        mTransportClientManager = transportClientManager;
     }
 
-    public void setTransportBoundListener(TransportBoundListener transportBoundListener) {
-        mTransportBoundListener = transportBoundListener;
+    /* Sets a listener to be called whenever a transport is registered. */
+    public void setOnTransportRegisteredListener(OnTransportRegisteredListener listener) {
+        mOnTransportRegisteredListener = listener;
     }
 
+    @WorkerThread
     void onPackageAdded(String packageName) {
-        // New package added. Bind to all transports it contains.
-        synchronized (mTransportLock) {
-            log_verbose("Package added. Binding to all transports. " + packageName);
-            bindToAllInternal(packageName, null /* all components */);
-        }
+        registerTransportsFromPackage(packageName, transportComponent -> true);
     }
 
     void onPackageRemoved(String packageName) {
-        // Package removed. Remove all its transports from our list. These transports have already
-        // been removed from mBoundTransports because onServiceDisconnected would already been
-        // called on TransportConnection objects.
         synchronized (mTransportLock) {
-            Iterator<Map.Entry<ComponentName, TransportConnection>> iter =
-                    mValidTransports.entrySet().iterator();
-            while (iter.hasNext()) {
-                Map.Entry<ComponentName, TransportConnection> validTransport = iter.next();
-                ComponentName componentName = validTransport.getKey();
-                if (componentName.getPackageName().equals(packageName)) {
-                    TransportConnection transportConnection = validTransport.getValue();
-                    iter.remove();
-                    if (transportConnection != null) {
-                        mContext.unbindService(transportConnection);
-                        log_verbose("Package removed, removing transport: "
-                                + componentName.flattenToShortString());
-                    }
-                }
-            }
-            removeTransportsIfLocked(
-                    componentName -> packageName.equals(componentName.getPackageName()));
+            mRegisteredTransportsDescriptionMap.keySet().removeIf(fromPackageFilter(packageName));
         }
     }
 
-    void onPackageChanged(String packageName, String[] components) {
+    @WorkerThread
+    void onPackageChanged(String packageName, String... components) {
         synchronized (mTransportLock) {
-            // Remove all changed components from mValidTransports. We'll bind to them again
-            // and re-add them if still valid.
-            Set<ComponentName> transportsToBeRemoved = new ArraySet<>();
-            for (String component : components) {
-                ComponentName componentName = new ComponentName(packageName, component);
-                transportsToBeRemoved.add(componentName);
-                TransportConnection removed = mValidTransports.remove(componentName);
-                if (removed != null) {
-                    mContext.unbindService(removed);
-                    log_verbose("Package changed. Removing transport: " +
-                            componentName.flattenToShortString());
-                }
-            }
-            removeTransportsIfLocked(transportsToBeRemoved::contains);
-            bindToAllInternal(packageName, components);
+            Set<ComponentName> transportComponents =
+                    Stream.of(components)
+                            .map(component -> new ComponentName(packageName, component))
+                            .collect(Collectors.toSet());
+
+            mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains);
+            registerTransportsFromPackage(packageName, transportComponents::contains);
         }
     }
 
-    @GuardedBy("mTransportLock")
-    private void removeTransportsIfLocked(Predicate<ComponentName> filter) {
-        mEligibleTransports.removeIf(filter);
-        mRegisteredTransportsDescriptionMap.keySet().removeIf(filter);
-    }
-
-    public IBackupTransport getTransportBinder(String transportName) {
+    /**
+     * Returns the {@link ComponentName}s of the registered transports.
+     *
+     * <p>A *registered* transport is a transport that satisfies intent with action
+     * android.backup.TRANSPORT_HOST, returns true for {@link #isTransportTrusted(ComponentName)}
+     * and that we have successfully connected to once.
+     */
+    ComponentName[] getRegisteredTransportComponents() {
         synchronized (mTransportLock) {
-            ComponentName component = mBoundTransports.get(transportName);
-            if (component == null) {
-                Slog.w(TAG, "Transport " + transportName + " not bound.");
-                return null;
-            }
-            TransportConnection conn = mValidTransports.get(component);
-            if (conn == null) {
-                Slog.w(TAG, "Transport " + transportName + " not valid.");
-                return null;
-            }
-            return conn.getBinder();
+            return mRegisteredTransportsDescriptionMap
+                    .keySet()
+                    .toArray(new ComponentName[mRegisteredTransportsDescriptionMap.size()]);
         }
     }
 
-    public IBackupTransport getCurrentTransportBinder() {
-        return getTransportBinder(mCurrentTransportName);
+    /**
+     * Returns the names of the registered transports.
+     *
+     * @see #getRegisteredTransportComponents()
+     */
+    String[] getRegisteredTransportNames() {
+        synchronized (mTransportLock) {
+            return mRegisteredTransportsDescriptionMap
+                    .values()
+                    .stream()
+                    .map(transportDescription -> transportDescription.name)
+                    .toArray(String[]::new);
+        }
+    }
+
+    /** Returns a set with the whitelisted transports. */
+    Set<ComponentName> getTransportWhitelist() {
+        return mTransportWhitelist;
+    }
+
+    @Nullable
+    String getCurrentTransportName() {
+        return mCurrentTransportName;
     }
 
     /**
      * Returns the transport name associated with {@code transportComponent}.
+     *
      * @throws TransportNotRegisteredException if the transport is not registered.
      */
     public String getTransportName(ComponentName transportComponent)
@@ -234,7 +175,32 @@
     }
 
     /**
-     * Retrieve the configuration intent of {@code transportName}.
+     * Retrieves the transport dir name of {@code transportComponent}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportDirName(ComponentName transportComponent)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportComponent)
+                    .transportDirName;
+        }
+    }
+
+    /**
+     * Retrieves the transport dir name of {@code transportName}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportDirName(String transportName) throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportName).transportDirName;
+        }
+    }
+
+    /**
+     * Retrieves the configuration intent of {@code transportName}.
+     *
      * @throws TransportNotRegisteredException if the transport is not registered.
      */
     @Nullable
@@ -247,7 +213,21 @@
     }
 
     /**
-     * Retrieve the data management intent of {@code transportName}.
+     * Retrieves the current destination string of {@code transportName}.
+     *
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
+    public String getTransportCurrentDestinationString(String transportName)
+            throws TransportNotRegisteredException {
+        synchronized (mTransportLock) {
+            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
+                    .currentDestinationString;
+        }
+    }
+
+    /**
+     * Retrieves the data management intent of {@code transportName}.
+     *
      * @throws TransportNotRegisteredException if the transport is not registered.
      */
     @Nullable
@@ -260,19 +240,8 @@
     }
 
     /**
-     * Retrieve the current destination string of {@code transportName}.
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    public String getTransportCurrentDestinationString(String transportName)
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
-                    .currentDestinationString;
-        }
-    }
-
-    /**
-     * Retrieve the data management label of {@code transportName}.
+     * Retrieves the data management label of {@code transportName}.
+     *
      * @throws TransportNotRegisteredException if the transport is not registered.
      */
     @Nullable
@@ -284,54 +253,74 @@
         }
     }
 
-    /**
-     * Retrieve the transport dir name of {@code transportName}.
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    public String getTransportDirName(String transportName)
-            throws TransportNotRegisteredException {
+    /* Returns true if the transport identified by {@code transportName} is registered. */
+    public boolean isTransportRegistered(String transportName) {
         synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
-                    .transportDirName;
-        }
-    }
-
-    /**
-     * Retrieve the transport dir name of {@code transportComponent}.
-     * @throws TransportNotRegisteredException if the transport is not registered.
-     */
-    public String getTransportDirName(ComponentName transportComponent)
-            throws TransportNotRegisteredException {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportDescriptionOrThrowLocked(transportComponent)
-                    .transportDirName;
+            return getRegisteredTransportEntryLocked(transportName) != null;
         }
     }
 
     /**
      * Execute {@code transportConsumer} for each registered transport passing the transport name.
      * This is called with an internal lock held, ensuring that the transport will remain registered
-     * while {@code transportConsumer} is being executed. Don't do heavy operations in
-     * {@code transportConsumer}.
+     * while {@code transportConsumer} is being executed. Don't do heavy operations in {@code
+     * transportConsumer}.
      */
     public void forEachRegisteredTransport(Consumer<String> transportConsumer) {
         synchronized (mTransportLock) {
-            for (TransportDescription transportDescription
-                    : mRegisteredTransportsDescriptionMap.values()) {
+            for (TransportDescription transportDescription :
+                    mRegisteredTransportsDescriptionMap.values()) {
                 transportConsumer.accept(transportDescription.name);
             }
         }
     }
 
-    public String getTransportName(IBackupTransport binder) {
+    /**
+     * Updates given values for the transport already registered and identified with {@param
+     * transportComponent}. If the transport is not registered it will log and return.
+     */
+    public void updateTransportAttributes(
+            ComponentName transportComponent,
+            String name,
+            @Nullable Intent configurationIntent,
+            String currentDestinationString,
+            @Nullable Intent dataManagementIntent,
+            @Nullable String dataManagementLabel) {
         synchronized (mTransportLock) {
-            for (TransportConnection conn : mValidTransports.values()) {
-                if (conn.getBinder() == binder) {
-                    return conn.getName();
-                }
+            TransportDescription description =
+                    mRegisteredTransportsDescriptionMap.get(transportComponent);
+            if (description == null) {
+                Slog.e(TAG, "Transport " + name + " not registered tried to change description");
+                return;
             }
+            description.name = name;
+            description.configurationIntent = configurationIntent;
+            description.currentDestinationString = currentDestinationString;
+            description.dataManagementIntent = dataManagementIntent;
+            description.dataManagementLabel = dataManagementLabel;
+            Slog.d(TAG, "Transport " + name + " updated its attributes");
         }
-        return null;
+    }
+
+    @GuardedBy("mTransportLock")
+    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
+            ComponentName transportComponent) throws TransportNotRegisteredException {
+        TransportDescription description =
+                mRegisteredTransportsDescriptionMap.get(transportComponent);
+        if (description == null) {
+            throw new TransportNotRegisteredException(transportComponent);
+        }
+        return description;
+    }
+
+    @GuardedBy("mTransportLock")
+    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
+            String transportName) throws TransportNotRegisteredException {
+        TransportDescription description = getRegisteredTransportDescriptionLocked(transportName);
+        if (description == null) {
+            throw new TransportNotRegisteredException(transportName);
+        }
+        return description;
     }
 
     @GuardedBy("mTransportLock")
@@ -351,21 +340,11 @@
     }
 
     @GuardedBy("mTransportLock")
-    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
-            String transportName) throws TransportNotRegisteredException {
-        TransportDescription description = getRegisteredTransportDescriptionLocked(transportName);
-        if (description == null) {
-            throw new TransportNotRegisteredException(transportName);
-        }
-        return description;
-    }
-
-    @GuardedBy("mTransportLock")
     @Nullable
     private Map.Entry<ComponentName, TransportDescription> getRegisteredTransportEntryLocked(
             String transportName) {
-        for (Map.Entry<ComponentName, TransportDescription> entry
-                : mRegisteredTransportsDescriptionMap.entrySet()) {
+        for (Map.Entry<ComponentName, TransportDescription> entry :
+                mRegisteredTransportsDescriptionMap.entrySet()) {
             TransportDescription description = entry.getValue();
             if (transportName.equals(description.name)) {
                 return entry;
@@ -374,17 +353,16 @@
         return null;
     }
 
-    @GuardedBy("mTransportLock")
-    private TransportDescription getRegisteredTransportDescriptionOrThrowLocked(
-            ComponentName transportComponent) throws TransportNotRegisteredException {
-        TransportDescription description =
-                mRegisteredTransportsDescriptionMap.get(transportComponent);
-        if (description == null) {
-            throw new TransportNotRegisteredException(transportComponent);
-        }
-        return description;
-    }
-
+    /**
+     * Returns a {@link TransportClient} for {@code transportName} or {@code null} if not
+     * registered.
+     *
+     * @param transportName The name of the transport.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient} or null if not registered.
+     */
     @Nullable
     public TransportClient getTransportClient(String transportName, String caller) {
         try {
@@ -395,6 +373,16 @@
         }
     }
 
+    /**
+     * Returns a {@link TransportClient} for {@code transportName} or throws if not registered.
+     *
+     * @param transportName The name of the transport.
+     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
+     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
+     *     details.
+     * @return A {@link TransportClient}.
+     * @throws TransportNotRegisteredException if the transport is not registered.
+     */
     public TransportClient getTransportClientOrThrow(String transportName, String caller)
             throws TransportNotRegisteredException {
         synchronized (mTransportLock) {
@@ -406,19 +394,14 @@
         }
     }
 
-    public boolean isTransportRegistered(String transportName) {
-        synchronized (mTransportLock) {
-            return getRegisteredTransportEntryLocked(transportName) != null;
-        }
-    }
-
     /**
-     * Returns a {@link TransportClient} for the current transport or null if not found.
+     * Returns a {@link TransportClient} for the current transport or {@code null} if not
+     * registered.
      *
      * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
      *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
      *     details.
-     * @return A {@link TransportClient} or null if not found.
+     * @return A {@link TransportClient} or null if not registered.
      */
     @Nullable
     public TransportClient getCurrentTransportClient(String caller) {
@@ -455,130 +438,88 @@
         mTransportClientManager.disposeOfTransportClient(transportClient, caller);
     }
 
-    String[] getBoundTransportNames() {
-        synchronized (mTransportLock) {
-            return mBoundTransports.keySet().toArray(new String[mBoundTransports.size()]);
-        }
-    }
-
-    ComponentName[] getAllTransportComponents() {
-        synchronized (mTransportLock) {
-            return mValidTransports.keySet().toArray(new ComponentName[mValidTransports.size()]);
-        }
-    }
-
     /**
-     * An *eligible* transport is a service component that satisfies intent with action
-     * android.backup.TRANSPORT_HOST and returns true for
-     * {@link #isTransportTrusted(ComponentName)}. It may be registered or not registered.
-     * This method returns the {@link ComponentName}s of those transports.
-     */
-    ComponentName[] getEligibleTransportComponents() {
-        synchronized (mTransportLock) {
-            return mEligibleTransports.toArray(new ComponentName[mEligibleTransports.size()]);
-        }
-    }
-
-    Set<ComponentName> getTransportWhitelist() {
-        return mTransportWhitelist;
-    }
-
-    /**
-     * A *registered* transport is an eligible transport that has been successfully connected and
-     * that returned true for method
-     * {@link TransportBoundListener#onTransportBound(IBackupTransport)} of TransportBoundListener
-     * provided in the constructor. This method returns the names of the registered transports.
-     */
-    String[] getRegisteredTransportNames() {
-        synchronized (mTransportLock) {
-            return mRegisteredTransportsDescriptionMap.values().stream()
-                    .map(transportDescription -> transportDescription.name)
-                    .toArray(String[]::new);
-        }
-    }
-
-    /**
-     * Updates given values for the transport already registered and identified with
-     * {@param transportComponent}. If the transport is not registered it will log and return.
-     */
-    public void updateTransportAttributes(
-            ComponentName transportComponent,
-            String name,
-            @Nullable Intent configurationIntent,
-            String currentDestinationString,
-            @Nullable Intent dataManagementIntent,
-            @Nullable String dataManagementLabel) {
-        synchronized (mTransportLock) {
-            TransportDescription description =
-                    mRegisteredTransportsDescriptionMap.get(transportComponent);
-            if (description == null) {
-                Slog.e(TAG, "Transport " + name + " not registered tried to change description");
-                return;
-            }
-            description.name = name;
-            description.configurationIntent = configurationIntent;
-            description.currentDestinationString = currentDestinationString;
-            description.dataManagementIntent = dataManagementIntent;
-            description.dataManagementLabel = dataManagementLabel;
-            Slog.d(TAG, "Transport " + name + " updated its attributes");
-        }
-    }
-
-    @Nullable
-    String getCurrentTransportName() {
-        return mCurrentTransportName;
-    }
-
-    // This is for mocking, Mockito can't mock if package-protected and in the same package but
-    // different class loaders. Checked with the debugger and class loaders are different
-    // See https://github.com/mockito/mockito/issues/796
-    @VisibleForTesting(visibility = PACKAGE)
-    public void registerAllTransports() {
-        bindToAllInternal(null /* all packages */, null /* all components */);
-    }
-
-    /**
-     * Bind to all transports belonging to the given package and the given component list.
-     * null acts a wildcard.
+     * Sets {@code transportName} as selected transport and returns previously selected transport
+     * name. If there was no previous transport it returns null.
      *
-     * If packageName is null, bind to all transports in all packages.
-     * If components is null, bind to all transports in the given package.
+     * <p>You should NOT call this method in new code. This won't make any checks against {@code
+     * transportName}, putting any operation at risk of a {@link TransportNotRegisteredException} or
+     * another error at the time it's being executed.
+     *
+     * <p>{@link Deprecated} as public, this method can be used as private.
      */
-    private void bindToAllInternal(String packageName, String[] components) {
-        PackageInfo pkgInfo = null;
-        if (packageName != null) {
+    @Deprecated
+    @Nullable
+    String selectTransport(String transportName) {
+        synchronized (mTransportLock) {
+            String prevTransport = mCurrentTransportName;
+            mCurrentTransportName = transportName;
+            return prevTransport;
+        }
+    }
+
+    /**
+     * Tries to register the transport if not registered. If successful also selects the transport.
+     *
+     * @param transportComponent Host of the transport.
+     * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
+     *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
+     */
+    @WorkerThread
+    public int registerAndSelectTransport(ComponentName transportComponent) {
+        synchronized (mTransportLock) {
+            if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) {
+                int result = registerTransport(transportComponent);
+                if (result != BackupManager.SUCCESS) {
+                    return result;
+                }
+            }
+
             try {
-                pkgInfo = mPackageManager.getPackageInfo(packageName, 0);
-            } catch (PackageManager.NameNotFoundException e) {
-                Slog.w(TAG, "Package not found: " + packageName);
-                return;
+                selectTransport(getTransportName(transportComponent));
+                return BackupManager.SUCCESS;
+            } catch (TransportNotRegisteredException e) {
+                // Shouldn't happen because we are holding the lock
+                Slog.wtf(TAG, "Transport unexpectedly not registered");
+                return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
             }
         }
+    }
 
-        Intent intent = new Intent(mTransportServiceIntent);
-        if (packageName != null) {
-            intent.setPackage(packageName);
+    @WorkerThread
+    public void registerTransports() {
+        registerTransportsForIntent(mTransportServiceIntent, transportComponent -> true);
+    }
+
+    @WorkerThread
+    private void registerTransportsFromPackage(
+            String packageName, Predicate<ComponentName> transportComponentFilter) {
+        try {
+            mPackageManager.getPackageInfo(packageName, 0);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "Trying to register transports from package not found " + packageName);
+            return;
         }
 
-        List<ResolveInfo> hosts = mPackageManager.queryIntentServicesAsUser(
-                intent, 0, UserHandle.USER_SYSTEM);
-        if (hosts != null) {
+        registerTransportsForIntent(
+                new Intent(mTransportServiceIntent).setPackage(packageName),
+                transportComponentFilter.and(fromPackageFilter(packageName)));
+    }
+
+    @WorkerThread
+    private void registerTransportsForIntent(
+            Intent intent, Predicate<ComponentName> transportComponentFilter) {
+        List<ResolveInfo> hosts =
+                mPackageManager.queryIntentServicesAsUser(intent, 0, UserHandle.USER_SYSTEM);
+        if (hosts == null) {
+            return;
+        }
+        synchronized (mTransportLock) {
             for (ResolveInfo host : hosts) {
-                final ComponentName infoComponentName = getComponentName(host.serviceInfo);
-                boolean shouldBind = false;
-                if (components != null && packageName != null) {
-                    for (String component : components) {
-                        ComponentName cn = new ComponentName(pkgInfo.packageName, component);
-                        if (infoComponentName.equals(cn)) {
-                            shouldBind = true;
-                            break;
-                        }
-                    }
-                } else {
-                    shouldBind = true;
-                }
-                if (shouldBind && isTransportTrusted(infoComponentName)) {
-                    tryBindTransport(infoComponentName);
+                ComponentName transportComponent = host.serviceInfo.getComponentName();
+                if (transportComponentFilter.test(transportComponent)
+                        && isTransportTrusted(transportComponent)) {
+                    registerTransport(transportComponent);
                 }
             }
         }
@@ -605,64 +546,6 @@
         return true;
     }
 
-    private void tryBindTransport(ComponentName transportComponentName) {
-        Slog.d(TAG, "Binding to transport: " + transportComponentName.flattenToShortString());
-        // TODO: b/22388012 (Multi user backup and restore)
-        TransportConnection connection = new TransportConnection(transportComponentName);
-        synchronized (mTransportLock) {
-            mEligibleTransports.add(transportComponentName);
-        }
-        if (bindToTransport(transportComponentName, connection)) {
-            synchronized (mTransportLock) {
-                mValidTransports.put(transportComponentName, connection);
-            }
-        } else {
-            Slog.w(TAG, "Couldn't bind to transport " + transportComponentName);
-        }
-    }
-
-    private boolean bindToTransport(ComponentName componentName, ServiceConnection connection) {
-        Intent intent = new Intent(mTransportServiceIntent)
-                .setComponent(componentName);
-        return mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE,
-                createSystemUserHandle());
-    }
-
-    String selectTransport(String transportName) {
-        synchronized (mTransportLock) {
-            String prevTransport = mCurrentTransportName;
-            mCurrentTransportName = transportName;
-            return prevTransport;
-        }
-    }
-
-    /**
-     * Tries to register the transport if not registered. If successful also selects the transport.
-     *
-     * @param transportComponent Host of the transport.
-     * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
-     *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
-     */
-    public int registerAndSelectTransport(ComponentName transportComponent) {
-        synchronized (mTransportLock) {
-            if (!mRegisteredTransportsDescriptionMap.containsKey(transportComponent)) {
-                int result = registerTransport(transportComponent);
-                if (result != BackupManager.SUCCESS) {
-                    return result;
-                }
-            }
-
-            try {
-                selectTransport(getTransportName(transportComponent));
-                return BackupManager.SUCCESS;
-            } catch (TransportNotRegisteredException e) {
-                // Shouldn't happen because we are holding the lock
-                Slog.wtf(TAG, "Transport unexpectedly not registered");
-                return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
-            }
-        }
-    }
-
     /**
      * Tries to register transport represented by {@code transportComponent}.
      *
@@ -670,7 +553,12 @@
      * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
      *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
      */
+    @WorkerThread
     private int registerTransport(ComponentName transportComponent) {
+        if (!isTransportTrusted(transportComponent)) {
+            return BackupManager.ERROR_TRANSPORT_INVALID;
+        }
+
         String transportString = transportComponent.flattenToShortString();
 
         String callerLogString = "TransportManager.registerTransport()";
@@ -689,26 +577,21 @@
         EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 1);
 
         int result;
-        if (isTransportValid(transport)) {
-            try {
-                registerTransport(transportComponent, transport);
-                // If registerTransport() hasn't thrown...
-                result = BackupManager.SUCCESS;
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Transport " + transportString + " died while registering");
-                result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
-            }
-        } else {
-            Slog.w(TAG, "Can't register invalid transport " + transportString);
-            result = BackupManager.ERROR_TRANSPORT_INVALID;
+        try {
+            String transportName = transport.name();
+            String transportDirName = transport.transportDirName();
+            registerTransport(transportComponent, transport);
+            // If registerTransport() hasn't thrown...
+            Slog.d(TAG, "Transport " + transportString + " registered");
+            mOnTransportRegisteredListener.onTransportRegistered(transportName, transportDirName);
+            result = BackupManager.SUCCESS;
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Transport " + transportString + " died while registering");
+            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0);
+            result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
         }
 
         mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
-        if (result == BackupManager.SUCCESS) {
-            Slog.d(TAG, "Transport " + transportString + " registered");
-        } else {
-            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transportString, 0);
-        }
         return result;
     }
 
@@ -717,204 +600,20 @@
             throws RemoteException {
         synchronized (mTransportLock) {
             String name = transport.name();
-            TransportDescription description = new TransportDescription(
-                    name,
-                    transport.transportDirName(),
-                    transport.configurationIntent(),
-                    transport.currentDestinationString(),
-                    transport.dataManagementIntent(),
-                    transport.dataManagementLabel());
+            TransportDescription description =
+                    new TransportDescription(
+                            name,
+                            transport.transportDirName(),
+                            transport.configurationIntent(),
+                            transport.currentDestinationString(),
+                            transport.dataManagementIntent(),
+                            transport.dataManagementLabel());
             mRegisteredTransportsDescriptionMap.put(transportComponent, description);
         }
     }
 
-    private boolean isTransportValid(IBackupTransport transport) {
-        if (mTransportBoundListener == null) {
-            Slog.w(TAG, "setTransportBoundListener() not called, assuming transport invalid");
-            return false;
-        }
-        return mTransportBoundListener.onTransportBound(transport);
-    }
-
-    private class TransportConnection implements ServiceConnection {
-
-        // Hold mTransportLock to access these fields so as to provide a consistent view of them.
-        private volatile IBackupTransport mBinder;
-        private volatile String mTransportName;
-
-        private final ComponentName mTransportComponent;
-
-        private TransportConnection(ComponentName transportComponent) {
-            mTransportComponent = transportComponent;
-        }
-
-        @Override
-        public void onServiceConnected(ComponentName component, IBinder binder) {
-            synchronized (mTransportLock) {
-                mBinder = IBackupTransport.Stub.asInterface(binder);
-                boolean success = false;
-
-                EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
-                        component.flattenToShortString(), 1);
-
-                try {
-                    mTransportName = mBinder.name();
-                    // BackupManager requests some fields from the transport. If they are
-                    // invalid, throw away this transport.
-                    if (isTransportValid(mBinder)) {
-                        // We're now using the always-bound connection to do the registration but
-                        // when we remove the always-bound code this will be in the first binding
-                        // TODO: Move registration to first binding
-                        registerTransport(component, mBinder);
-                        // If registerTransport() hasn't thrown...
-                        success = true;
-                    }
-                } catch (RemoteException e) {
-                    success = false;
-                    Slog.e(TAG, "Couldn't get transport name.", e);
-                } finally {
-                    // we need to intern() the String of the component, so that we can use it with
-                    // Handler's removeMessages(), which uses == operator to compare the tokens
-                    String componentShortString = component.flattenToShortString().intern();
-                    if (success) {
-                        Slog.d(TAG, "Bound to transport: " + componentShortString);
-                        mBoundTransports.put(mTransportName, component);
-                        // cancel rebinding on timeout for this component as we've already connected
-                        mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
-                    } else {
-                        Slog.w(TAG, "Bound to transport " + componentShortString +
-                                " but it is invalid");
-                        EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE,
-                                componentShortString, 0);
-                        mContext.unbindService(this);
-                        mValidTransports.remove(component);
-                        mEligibleTransports.remove(component);
-                        mBinder = null;
-                    }
-                }
-            }
-        }
-
-        @Override
-        public void onServiceDisconnected(ComponentName component) {
-            synchronized (mTransportLock) {
-                mBinder = null;
-                mBoundTransports.remove(mTransportName);
-            }
-            String componentShortString = component.flattenToShortString();
-            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, componentShortString, 0);
-            Slog.w(TAG, "Disconnected from transport " + componentShortString);
-            scheduleRebindTimeout(component);
-        }
-
-        /**
-         * We'll attempt to explicitly rebind to a transport if it hasn't happened automatically
-         * for a few minutes after the binding went away.
-         */
-        private void scheduleRebindTimeout(ComponentName component) {
-            // we need to intern() the String of the component, so that we can use it with Handler's
-            // removeMessages(), which uses == operator to compare the tokens
-            final String componentShortString = component.flattenToShortString().intern();
-            final long rebindTimeout = getRebindTimeout();
-            mHandler.removeMessages(REBINDING_TIMEOUT_MSG, componentShortString);
-            Message msg = mHandler.obtainMessage(REBINDING_TIMEOUT_MSG);
-            msg.obj = componentShortString;
-            mHandler.sendMessageDelayed(msg, rebindTimeout);
-            Slog.d(TAG, "Scheduled explicit rebinding for " + componentShortString + " in "
-                    + rebindTimeout + "ms");
-        }
-
-        // Intentionally not synchronized -- the variable is volatile and changes to its value
-        // are inside synchronized blocks, providing a memory sync barrier; and this method
-        // does not touch any other state protected by that lock.
-        private IBackupTransport getBinder() {
-            return mBinder;
-        }
-
-        // Intentionally not synchronized; same as getBinder()
-        private String getName() {
-            return mTransportName;
-        }
-
-        // Intentionally not synchronized; same as getBinder()
-        private void bindIfUnbound() {
-            if (mBinder == null) {
-                Slog.d(TAG,
-                        "Rebinding to transport " + mTransportComponent.flattenToShortString());
-                bindToTransport(mTransportComponent, this);
-            }
-        }
-
-        private long getRebindTimeout() {
-            final boolean isDeviceProvisioned = Settings.Global.getInt(
-                    mContext.getContentResolver(),
-                    Settings.Global.DEVICE_PROVISIONED, 0) != 0;
-            return isDeviceProvisioned
-                    ? REBINDING_TIMEOUT_PROVISIONED_MS
-                    : REBINDING_TIMEOUT_UNPROVISIONED_MS;
-        }
-    }
-
-    public interface TransportBoundListener {
-        /** Should return true if this is a valid transport. */
-        boolean onTransportBound(IBackupTransport binder);
-    }
-
-    private class RebindOnTimeoutHandler extends Handler {
-
-        RebindOnTimeoutHandler(Looper looper) {
-            super(looper);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            if (msg.what == REBINDING_TIMEOUT_MSG) {
-                String componentShortString = (String) msg.obj;
-                ComponentName transportComponent =
-                        ComponentName.unflattenFromString(componentShortString);
-                synchronized (mTransportLock) {
-                    if (mBoundTransports.containsValue(transportComponent)) {
-                        Slog.d(TAG, "Explicit rebinding timeout passed, but already bound to "
-                                + componentShortString + " so not attempting to rebind");
-                        return;
-                    }
-                    Slog.d(TAG, "Explicit rebinding timeout passed, attempting rebinding to: "
-                            + componentShortString);
-                    // unbind the existing (broken) connection
-                    TransportConnection conn = mValidTransports.get(transportComponent);
-                    if (conn != null) {
-                        mContext.unbindService(conn);
-                        Slog.d(TAG, "Unbinding the existing (broken) connection to transport: "
-                                + componentShortString);
-                    }
-                }
-                // rebind to transport
-                tryBindTransport(transportComponent);
-            } else {
-                Slog.e(TAG, "Unknown message sent to RebindOnTimeoutHandler, msg.what: "
-                        + msg.what);
-            }
-        }
-    }
-
-    private static void log_verbose(String message) {
-        if (Log.isLoggable(TAG, Log.VERBOSE)) {
-            Slog.v(TAG, message);
-        }
-    }
-
-    // These only exists to make it testable with Robolectric, which is not updated to API level 24
-    // yet.
-    // TODO: Get rid of this once Robolectric is updated.
-    private static ComponentName getComponentName(ServiceInfo serviceInfo) {
-        return new ComponentName(serviceInfo.packageName, serviceInfo.name);
-    }
-
-    // These only exists to make it testable with Robolectric, which is not updated to API level 24
-    // yet.
-    // TODO: Get rid of this once Robolectric is updated.
-    public static UserHandle createSystemUserHandle() {
-        return new UserHandle(UserHandle.USER_SYSTEM);
+    private static Predicate<ComponentName> fromPackageFilter(String packageName) {
+        return transportComponent -> packageName.equals(transportComponent.getPackageName());
     }
 
     private static class TransportDescription {
diff --git a/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
new file mode 100644
index 0000000..391ec2d
--- /dev/null
+++ b/services/backup/java/com/android/server/backup/transport/OnTransportRegisteredListener.java
@@ -0,0 +1,33 @@
+/*
+ * 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.transport;
+
+import com.android.server.backup.TransportManager;
+
+/**
+ * Listener called when a transport is registered with the {@link TransportManager}. Can be set
+ * using {@link TransportManager#setOnTransportRegisteredListener(OnTransportRegisteredListener)}.
+ */
+@FunctionalInterface
+public interface OnTransportRegisteredListener {
+    /**
+     * Called when a transport is successfully registered.
+     * @param transportName The name of the transport.
+     * @param transportDirName The dir name of the transport.
+     */
+    public void onTransportRegistered(String transportName, String transportDirName);
+}
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClient.java b/services/backup/java/com/android/server/backup/transport/TransportClient.java
index 7bd9111..bd4a0bb 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -236,7 +236,7 @@
                                     mBindIntent,
                                     mConnection,
                                     Context.BIND_AUTO_CREATE,
-                                    TransportManager.createSystemUserHandle());
+                                    UserHandle.SYSTEM);
                     if (hasBound) {
                         // We don't need to set a time-out because we are guaranteed to get a call
                         // back in ServiceConnection, either an onServiceConnected() or
diff --git a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
index f9ebd28..bf58224 100644
--- a/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
+++ b/services/robotests/src/com/android/server/backup/BackupManagerServiceRoboTest.java
@@ -16,7 +16,11 @@
 
 package com.android.server.backup;
 
-import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES;
+import static com.android.server.backup.testing.TransportData.backupTransport;
+import static com.android.server.backup.testing.TransportData.d2dTransport;
+import static com.android.server.backup.testing.TransportData.localTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpCurrentTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransports;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -24,6 +28,7 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.robolectric.Shadows.shadowOf;
@@ -39,8 +44,8 @@
 import android.provider.Settings;
 
 import com.android.server.backup.testing.ShadowAppBackupUtils;
-import com.android.server.backup.testing.TransportTestUtils;
-import com.android.server.backup.testing.TransportTestUtils.TransportData;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 import com.android.server.testing.FrameworkRobolectricTestRunner;
 import com.android.server.testing.SystemLoaderClasses;
@@ -57,10 +62,10 @@
 import org.robolectric.shadows.ShadowLog;
 import org.robolectric.shadows.ShadowLooper;
 import org.robolectric.shadows.ShadowSettings;
+import org.robolectric.shadows.ShadowSystemClock;
 
 import java.io.File;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
 @RunWith(FrameworkRobolectricTestRunner.class)
@@ -73,22 +78,24 @@
 @Presubmit
 public class BackupManagerServiceRoboTest {
     private static final String TAG = "BMSTest";
-    private static final String TRANSPORT_NAME =
-            "com.google.android.gms/.backup.BackupTransportService";
 
     @Mock private TransportManager mTransportManager;
     private HandlerThread mBackupThread;
     private ShadowLooper mShadowBackupLooper;
     private File mBaseStateDir;
     private File mDataDir;
-    private RefactoredBackupManagerService mBackupManagerService;
     private ShadowContextWrapper mShadowContext;
     private Context mContext;
+    private TransportData mTransport;
+    private String mTransportName;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mTransport = backupTransport();
+        mTransportName = mTransport.transportName;
+
         mBackupThread = new HandlerThread("backup-test");
         mBackupThread.setUncaughtExceptionHandler(
                 (t, e) -> ShadowLog.e(TAG, "Uncaught exception in test thread " + t.getName(), e));
@@ -102,15 +109,6 @@
         File cacheDir = mContext.getCacheDir();
         mBaseStateDir = new File(cacheDir, "base_state_dir");
         mDataDir = new File(cacheDir, "data_dir");
-
-        mBackupManagerService =
-                new RefactoredBackupManagerService(
-                        mContext,
-                        new Trampoline(mContext),
-                        mBackupThread,
-                        mBaseStateDir,
-                        mDataDir,
-                        mTransportManager);
     }
 
     @After
@@ -124,10 +122,12 @@
     @Test
     public void testDestinationString() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
-        when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+        when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName)))
                 .thenReturn("destinationString");
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
-        String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME);
+        String destination = backupManagerService.getDestinationString(mTransportName);
 
         assertThat(destination).isEqualTo("destinationString");
     }
@@ -135,10 +135,12 @@
     @Test
     public void testDestinationString_whenTransportNotRegistered() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
-        when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+        when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName)))
                 .thenThrow(TransportNotRegisteredException.class);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
-        String destination = mBackupManagerService.getDestinationString(TRANSPORT_NAME);
+        String destination = backupManagerService.getDestinationString(mTransportName);
 
         assertThat(destination).isNull();
     }
@@ -146,12 +148,14 @@
     @Test
     public void testDestinationString_withoutPermission() throws Exception {
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
-        when(mTransportManager.getTransportCurrentDestinationString(eq(TRANSPORT_NAME)))
+        when(mTransportManager.getTransportCurrentDestinationString(eq(mTransportName)))
                 .thenThrow(TransportNotRegisteredException.class);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         expectThrows(
                 SecurityException.class,
-                () -> mBackupManagerService.getDestinationString(TRANSPORT_NAME));
+                () -> backupManagerService.getDestinationString(mTransportName));
     }
 
     /* Tests for app eligibility */
@@ -159,24 +163,28 @@
     @Test
     public void testIsAppEligibleForBackup_whenAppEligible() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
-        TransportData transport =
-                TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        TransportMock transportMock = setUpCurrentTransport(mTransportManager, backupTransport());
         ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> true;
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
-        boolean result = mBackupManagerService.isAppEligibleForBackup("app.package");
+        boolean result = backupManagerService.isAppEligibleForBackup("app.package");
 
         assertThat(result).isTrue();
+
         verify(mTransportManager)
-                .disposeOfTransportClient(eq(transport.transportClientMock), any());
+                .disposeOfTransportClient(eq(transportMock.transportClient), any());
     }
 
     @Test
     public void testIsAppEligibleForBackup_whenAppNotEligible() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
-        TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        setUpCurrentTransport(mTransportManager, mTransport);
         ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false;
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
-        boolean result = mBackupManagerService.isAppEligibleForBackup("app.package");
+        boolean result = backupManagerService.isAppEligibleForBackup("app.package");
 
         assertThat(result).isFalse();
     }
@@ -184,38 +192,43 @@
     @Test
     public void testIsAppEligibleForBackup_withoutPermission() throws Exception {
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
-        TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        setUpCurrentTransport(mTransportManager, mTransport);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         expectThrows(
                 SecurityException.class,
-                () -> mBackupManagerService.isAppEligibleForBackup("app.package"));
+                () -> backupManagerService.isAppEligibleForBackup("app.package"));
     }
 
     @Test
     public void testFilterAppsEligibleForBackup() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
-        TransportData transport =
-                TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        TransportMock transportMock = setUpCurrentTransport(mTransportManager, mTransport);
         Map<String, Boolean> packagesMap = new HashMap<>();
         packagesMap.put("package.a", true);
         packagesMap.put("package.b", false);
         ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = packagesMap::get;
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
         String[] packages = packagesMap.keySet().toArray(new String[packagesMap.size()]);
 
-        String[] filtered = mBackupManagerService.filterAppsEligibleForBackup(packages);
+        String[] filtered = backupManagerService.filterAppsEligibleForBackup(packages);
 
         assertThat(filtered).asList().containsExactly("package.a");
         verify(mTransportManager)
-                .disposeOfTransportClient(eq(transport.transportClientMock), any());
+                .disposeOfTransportClient(eq(transportMock.transportClient), any());
     }
 
     @Test
     public void testFilterAppsEligibleForBackup_whenNoneIsEligible() throws Exception {
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
         ShadowAppBackupUtils.sAppIsRunningAndEligibleForBackupWithTransport = p -> false;
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         String[] filtered =
-                mBackupManagerService.filterAppsEligibleForBackup(
+                backupManagerService.filterAppsEligibleForBackup(
                         new String[] {"package.a", "package.b"});
 
         assertThat(filtered).isEmpty();
@@ -224,12 +237,14 @@
     @Test
     public void testFilterAppsEligibleForBackup_withoutPermission() throws Exception {
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
-        TransportTestUtils.setUpCurrentTransport(mTransportManager, TRANSPORT_NAME);
+        setUpCurrentTransport(mTransportManager, mTransport);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         expectThrows(
                 SecurityException.class,
                 () ->
-                        mBackupManagerService.filterAppsEligibleForBackup(
+                        backupManagerService.filterAppsEligibleForBackup(
                                 new String[] {"package.a", "package.b"}));
     }
 
@@ -238,14 +253,12 @@
     private TransportData mNewTransport;
     private TransportData mOldTransport;
     private ComponentName mNewTransportComponent;
-    private ISelectBackupTransportCallback mCallback;
 
     private void setUpForSelectTransport() throws Exception {
-        List<TransportData> transports =
-                TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES);
-        mNewTransport = transports.get(0);
-        mNewTransportComponent = mNewTransport.transportClientMock.getTransportComponent();
-        mOldTransport = transports.get(1);
+        mNewTransport = backupTransport();
+        mNewTransportComponent = mNewTransport.getTransportComponent();
+        mOldTransport = d2dTransport();
+        setUpTransports(mTransportManager, mNewTransport, mOldTransport, localTransport());
         when(mTransportManager.selectTransport(eq(mNewTransport.transportName)))
                 .thenReturn(mOldTransport.transportName);
     }
@@ -254,9 +267,11 @@
     public void testSelectBackupTransport() throws Exception {
         setUpForSelectTransport();
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         String oldTransport =
-                mBackupManagerService.selectBackupTransport(mNewTransport.transportName);
+                backupManagerService.selectBackupTransport(mNewTransport.transportName);
 
         assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
         assertThat(oldTransport).isEqualTo(mOldTransport.transportName);
@@ -266,10 +281,12 @@
     public void testSelectBackupTransport_withoutPermission() throws Exception {
         setUpForSelectTransport();
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
 
         expectThrows(
                 SecurityException.class,
-                () -> mBackupManagerService.selectBackupTransport(mNewTransport.transportName));
+                () -> backupManagerService.selectBackupTransport(mNewTransport.transportName));
     }
 
     @Test
@@ -278,9 +295,11 @@
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
         when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
                 .thenReturn(BackupManager.SUCCESS);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
         ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
 
-        mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+        backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
 
         mShadowBackupLooper.runToEndOfTasks();
         assertThat(getSettingsTransport()).isEqualTo(mNewTransport.transportName);
@@ -293,9 +312,11 @@
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
         when(mTransportManager.registerAndSelectTransport(eq(mNewTransportComponent)))
                 .thenReturn(BackupManager.ERROR_TRANSPORT_UNAVAILABLE);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
         ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
 
-        mBackupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
+        backupManagerService.selectBackupTransportAsync(mNewTransportComponent, callback);
 
         mShadowBackupLooper.runToEndOfTasks();
         assertThat(getSettingsTransport()).isNotEqualTo(mNewTransport.transportName);
@@ -304,19 +325,19 @@
 
     @Test
     public void testSelectBackupTransportAsync_whenTransportGetsUnregistered() throws Exception {
-        TransportTestUtils.setUpTransports(
-                mTransportManager, new TransportData(TRANSPORT_NAME, null, null));
-        ComponentName newTransportComponent =
-                TransportTestUtils.transportComponentName(TRANSPORT_NAME);
+        setUpTransports(mTransportManager, mTransport.unregistered());
+        ComponentName newTransportComponent = mTransport.getTransportComponent();
         mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
         when(mTransportManager.registerAndSelectTransport(eq(newTransportComponent)))
                 .thenReturn(BackupManager.SUCCESS);
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
         ISelectBackupTransportCallback callback = mock(ISelectBackupTransportCallback.class);
 
-        mBackupManagerService.selectBackupTransportAsync(newTransportComponent, callback);
+        backupManagerService.selectBackupTransportAsync(newTransportComponent, callback);
 
         mShadowBackupLooper.runToEndOfTasks();
-        assertThat(getSettingsTransport()).isNotEqualTo(TRANSPORT_NAME);
+        assertThat(getSettingsTransport()).isNotEqualTo(mTransportName);
         verify(callback).onFailure(anyInt());
     }
 
@@ -324,13 +345,14 @@
     public void testSelectBackupTransportAsync_withoutPermission() throws Exception {
         setUpForSelectTransport();
         mShadowContext.denyPermissions(android.Manifest.permission.BACKUP);
-        ComponentName newTransportComponent =
-                mNewTransport.transportClientMock.getTransportComponent();
+        RefactoredBackupManagerService backupManagerService =
+                createInitializedBackupManagerService();
+        ComponentName newTransportComponent = mNewTransport.getTransportComponent();
 
         expectThrows(
                 SecurityException.class,
                 () ->
-                        mBackupManagerService.selectBackupTransportAsync(
+                        backupManagerService.selectBackupTransportAsync(
                                 newTransportComponent, mock(ISelectBackupTransportCallback.class)));
     }
 
@@ -338,4 +360,55 @@
         return ShadowSettings.ShadowSecure.getString(
                 mContext.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
     }
+
+    /* Miscellaneous tests */
+
+    @Test
+    public void testConstructor_postRegisterTransports() {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+
+        createBackupManagerService();
+
+        mShadowBackupLooper.runToEndOfTasks();
+        verify(mTransportManager).registerTransports();
+    }
+
+    @Test
+    public void testConstructor_doesNotRegisterTransportsSynchronously() {
+        mShadowContext.grantPermissions(android.Manifest.permission.BACKUP);
+
+        createBackupManagerService();
+
+        // Operations posted to mBackupThread only run with mShadowBackupLooper.runToEndOfTasks()
+        verify(mTransportManager, never()).registerTransports();
+    }
+
+    private RefactoredBackupManagerService createBackupManagerService() {
+        return new RefactoredBackupManagerService(
+                mContext,
+                new Trampoline(mContext),
+                mBackupThread,
+                mBaseStateDir,
+                mDataDir,
+                mTransportManager);
+    }
+
+    private RefactoredBackupManagerService createInitializedBackupManagerService() {
+        RefactoredBackupManagerService backupManagerService =
+                new RefactoredBackupManagerService(
+                        mContext,
+                        new Trampoline(mContext),
+                        mBackupThread,
+                        mBaseStateDir,
+                        mDataDir,
+                        mTransportManager);
+        mShadowBackupLooper.runToEndOfTasks();
+        // Handler instances have their own clock, so advancing looper (with runToEndOfTasks())
+        // above does NOT advance the handlers' clock, hence whenever a handler post messages with
+        // specific time to the looper the time of those messages will be before the looper's time.
+        // To fix this we advance SystemClock as well since that is from where the handlers read
+        // time.
+        ShadowSystemClock.setCurrentTimeMillis(mShadowBackupLooper.getScheduler().getCurrentTime());
+        return backupManagerService;
+    }
 }
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index acd670f..cf0bc23 100644
--- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -16,108 +16,97 @@
 
 package com.android.server.backup;
 
+import static com.android.server.backup.testing.TransportData.genericTransport;
+import static com.android.server.backup.testing.TransportTestUtils.mockTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransportsForTransportManager;
+
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.Mockito.mock;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.robolectric.shadow.api.Shadow.extract;
 import static org.testng.Assert.expectThrows;
 
+import static java.util.Arrays.asList;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
+import static java.util.stream.Collectors.toList;
+import static java.util.stream.Collectors.toSet;
+import static java.util.stream.Stream.concat;
+
 import android.annotation.Nullable;
 import android.content.ComponentName;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
-import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.os.IBinder;
-import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
 
-import com.android.internal.backup.IBackupTransport;
-import com.android.server.backup.testing.ShadowBackupTransportStub;
 import com.android.server.backup.testing.ShadowContextImplForBackup;
-import com.android.server.backup.testing.ShadowPackageManagerForBackup;
-import com.android.server.backup.testing.TransportBoundListenerStub;
+import com.android.server.testing.shadows.FrameworkShadowPackageManager;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
+import com.android.server.backup.transport.OnTransportRegisteredListener;
 import com.android.server.backup.transport.TransportClient;
+import com.android.server.backup.transport.TransportClientManager;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 import com.android.server.testing.FrameworkRobolectricTestRunner;
 import com.android.server.testing.SystemLoaderClasses;
+import com.android.server.testing.shadows.FrameworkShadowContextImpl;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
-import org.robolectric.shadows.ShadowLog;
-import org.robolectric.shadows.ShadowLooper;
 import org.robolectric.shadows.ShadowPackageManager;
 
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
 import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
 
 @RunWith(FrameworkRobolectricTestRunner.class)
 @Config(
-        manifest = Config.NONE,
-        sdk = 26,
-        shadows = {
-                ShadowContextImplForBackup.class,
-                ShadowBackupTransportStub.class,
-                ShadowPackageManagerForBackup.class
-        }
+    manifest = Config.NONE,
+    sdk = 26,
+    shadows = {FrameworkShadowPackageManager.class, FrameworkShadowContextImpl.class}
 )
 @SystemLoaderClasses({TransportManager.class})
 @Presubmit
 public class TransportManagerTest {
-    private static final String PACKAGE_NAME = "some.package.name";
-    private static final String ANOTHER_PACKAGE_NAME = "another.package.name";
+    private static final String PACKAGE_A = "some.package.a";
+    private static final String PACKAGE_B = "some.package.b";
 
-    private TransportInfo mTransport1;
-    private TransportInfo mTransport2;
+    @Mock private OnTransportRegisteredListener mListener;
+    @Mock private TransportClientManager mTransportClientManager;
+    private TransportData mTransportA1;
+    private TransportData mTransportA2;
+    private TransportData mTransportB1;
 
-    private ShadowPackageManager mPackageManagerShadow;
-
-    private final TransportBoundListenerStub mTransportBoundListenerStub =
-            new TransportBoundListenerStub(true);
+    private ShadowPackageManager mShadowPackageManager;
+    private Context mContext;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
-        ShadowLog.stream = System.out;
-
-        mPackageManagerShadow =
-                (ShadowPackageManagerForBackup)
+        mShadowPackageManager =
+                (FrameworkShadowPackageManager)
                         extract(RuntimeEnvironment.application.getPackageManager());
+        mContext = RuntimeEnvironment.application.getApplicationContext();
 
-        mTransport1 = new TransportInfo(
-                PACKAGE_NAME,
-                "transport1.name",
-                new Intent(),
-                "currentDestinationString",
-                new Intent(),
-                "dataManagementLabel");
-        mTransport2 = new TransportInfo(
-                PACKAGE_NAME,
-                "transport2.name",
-                new Intent(),
-                "currentDestinationString",
-                new Intent(),
-                "dataManagementLabel");
-
-        ShadowContextImplForBackup.sComponentBinderMap.put(mTransport1.componentName,
-                mTransport1.binder);
-        ShadowContextImplForBackup.sComponentBinderMap.put(mTransport2.componentName,
-                mTransport2.binder);
-        ShadowBackupTransportStub.sBinderTransportMap.put(
-                mTransport1.binder, mTransport1.binderInterface);
-        ShadowBackupTransportStub.sBinderTransportMap.put(
-                mTransport2.binder, mTransport2.binderInterface);
+        mTransportA1 = genericTransport(PACKAGE_A, "TransportFoo");
+        mTransportA2 = genericTransport(PACKAGE_A, "TransportBar");
+        mTransportB1 = genericTransport(PACKAGE_B, "TransportBaz");
     }
 
     @After
@@ -126,560 +115,441 @@
     }
 
     @Test
-    public void onPackageAdded_bindsToAllTransports() throws Exception {
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(Arrays.asList(
-                        mTransport1.componentName, mTransport2.componentName)),
-                null /* defaultTransport */,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isTrue();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isTrue();
-    }
-
-    @Test
-    public void onPackageAdded_oneTransportUnavailable_bindsToOnlyOneTransport() throws Exception {
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
-        ShadowContextImplForBackup.sUnbindableComponents.add(mTransport1.componentName);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(Arrays.asList(
-                        mTransport1.componentName, mTransport2.componentName)),
-                null /* defaultTransport */,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Collections.singleton(mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Collections.singleton(mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isTrue();
-    }
-
-    @Test
-    public void onPackageAdded_whitelistIsNull_doesNotBindToTransports() throws Exception {
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                null /* whitelist */,
-                null /* defaultTransport */,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).isEmpty();
-        assertThat(transportManager.getBoundTransportNames()).isEmpty();
-        assertThat(mTransportBoundListenerStub.isCalled()).isFalse();
-    }
-
-    @Test
-    public void onPackageAdded_onlyOneTransportWhitelisted_onlyConnectsToWhitelistedTransport()
-            throws Exception {
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(Collections.singleton(mTransport2.componentName)),
-                null /* defaultTransport */,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Collections.singleton(mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Collections.singleton(mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isTrue();
-    }
-
-    @Test
-    public void onPackageAdded_appIsNotPrivileged_doesNotBindToTransports() throws Exception {
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2), 0);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(Arrays.asList(
-                        mTransport1.componentName, mTransport2.componentName)),
-                null /* defaultTransport */,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).isEmpty();
-        assertThat(transportManager.getBoundTransportNames()).isEmpty();
-        assertThat(mTransportBoundListenerStub.isCalled()).isFalse();
-    }
-
-    @Test
-    public void onPackageRemoved_transportsUnbound() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageRemoved(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).isEmpty();
-        assertThat(transportManager.getBoundTransportNames()).isEmpty();
-    }
-
-    @Test
-    public void onPackageRemoved_incorrectPackageName_nothingHappens() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageRemoved(ANOTHER_PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-    }
-
-    @Test
-    public void onPackageChanged_oneComponentChanged_onlyOneTransportRebound() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name});
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isTrue();
-    }
-
-    @Test
-    public void onPackageChanged_nothingChanged_noTransportsRebound() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageChanged(PACKAGE_NAME, new String[0]);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isFalse();
-    }
-
-    @Test
-    public void onPackageChanged_unexpectedComponentChanged_noTransportsRebound() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageChanged(PACKAGE_NAME, new String[]{"unexpected.component"});
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isFalse();
-    }
-
-    @Test
-    public void onPackageChanged_transportsRebound() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        transportManager.onPackageChanged(PACKAGE_NAME, new String[]{mTransport2.name});
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                Arrays.asList(mTransport1.name, mTransport2.name));
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport1.binderInterface))
-                .isFalse();
-        assertThat(mTransportBoundListenerStub.isCalledForTransport(mTransport2.binderInterface))
-                .isTrue();
-    }
-
-    @Test
-    public void getTransportBinder_returnsCorrectBinder() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        assertThat(transportManager.getTransportBinder(mTransport1.name)).isEqualTo(
-                mTransport1.binderInterface);
-        assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo(
-                mTransport2.binderInterface);
-    }
-
-    @Test
-    public void getTransportBinder_incorrectTransportName_returnsNull() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        assertThat(transportManager.getTransportBinder("incorrect.transport")).isNull();
-    }
-
-    @Test
-    public void getTransportBinder_oneTransportUnavailable_returnsCorrectBinder() throws Exception {
+    public void testRegisterTransports() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2, mTransportB1);
         TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
-                        Collections.singletonList(mTransport1), mTransport1.name);
+                createTransportManager(mTransportA1, mTransportA2, mTransportB1);
 
-        assertThat(transportManager.getTransportBinder(mTransport1.name)).isNull();
-        assertThat(transportManager.getTransportBinder(mTransport2.name)).isEqualTo(
-                mTransport2.binderInterface);
+        transportManager.registerTransports();
+
+        assertRegisteredTransports(
+                transportManager, asList(mTransportA1, mTransportA2, mTransportB1));
+
+        verify(mListener)
+                .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+        verify(mListener)
+                .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
+        verify(mListener)
+                .onTransportRegistered(mTransportB1.transportName, mTransportB1.transportDirName);
     }
 
     @Test
-    public void getCurrentTransport_selectTransportNotCalled_returnsDefaultTransport()
+    public void
+            testRegisterTransports_whenOneTransportUnavailable_doesNotRegisterUnavailableTransport()
+                    throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        TransportData transport1 = mTransportA1.unavailable();
+        TransportData transport2 = mTransportA2;
+        setUpTransports(transport1, transport2);
+        TransportManager transportManager = createTransportManager(transport1, transport2);
+
+        transportManager.registerTransports();
+
+        assertRegisteredTransports(transportManager, singletonList(transport2));
+        verify(mListener, never())
+                .onTransportRegistered(transport1.transportName, transport1.transportDirName);
+        verify(mListener)
+                .onTransportRegistered(transport2.transportName, transport2.transportDirName);
+    }
+
+    @Test
+    public void testRegisterTransports_whenWhitelistIsEmpty_doesNotRegisterTransports()
             throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(null);
 
-        assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name);
+        transportManager.registerTransports();
+
+        assertRegisteredTransports(transportManager, emptyList());
+        verify(mListener, never()).onTransportRegistered(any(), any());
     }
 
     @Test
-    public void getCurrentTransport_selectTransportCalled_returnsCorrectTransport()
+    public void
+            testRegisterTransports_whenOnlyOneTransportWhitelisted_onlyRegistersWhitelistedTransport()
+                    throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(null, mTransportA1);
+
+        transportManager.registerTransports();
+
+        assertRegisteredTransports(transportManager, singletonList(mTransportA1));
+        verify(mListener)
+                .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+        verify(mListener, never())
+                .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
+    }
+
+    @Test
+    public void testRegisterTransports_whenAppIsNotPrivileged_doesNotRegisterTransports()
             throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport1.name);
-
-        transportManager.selectTransport(mTransport2.name);
-
-        assertThat(transportManager.getCurrentTransportName()).isEqualTo(mTransport2.name);
-    }
-
-    @Test
-    public void getCurrentTransportBinder_returnsCorrectBinder() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
-
-        assertThat(transportManager.getCurrentTransportBinder())
-                .isEqualTo(mTransport1.binderInterface);
-    }
-
-    @Test
-    public void getCurrentTransportBinder_transportNotBound_returnsNull() throws Exception {
+        // Note ApplicationInfo.PRIVATE_FLAG_PRIVILEGED is missing from flags
+        setUpPackage(PACKAGE_A, 0);
+        setUpTransports(mTransportA1, mTransportA2);
         TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
-                        Collections.singletonList(mTransport1), mTransport2.name);
+                createTransportManager(null, mTransportA1, mTransportA2);
 
-        transportManager.selectTransport(mTransport1.name);
+        transportManager.registerTransports();
 
-        assertThat(transportManager.getCurrentTransportBinder()).isNull();
+        assertRegisteredTransports(transportManager, emptyList());
+        verify(mListener, never()).onTransportRegistered(any(), any());
     }
 
     @Test
-    public void getTransportName_returnsCorrectTransportName() throws Exception {
-        TransportManager transportManager = createTransportManagerAndSetUpTransports(
-                Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+    public void testOnPackageAdded_registerTransports() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
 
-        assertThat(transportManager.getTransportName(mTransport1.binderInterface))
-                .isEqualTo(mTransport1.name);
-        assertThat(transportManager.getTransportName(mTransport2.binderInterface))
-                .isEqualTo(mTransport2.name);
+        transportManager.onPackageAdded(PACKAGE_A);
+
+        assertRegisteredTransports(transportManager, asList(mTransportA1));
+        verify(mListener)
+                .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
     }
 
     @Test
-    public void getTransportName_transportNotBound_returnsNull() throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(Collections.singletonList(mTransport2),
-                        Collections.singletonList(mTransport1), mTransport1.name);
+    public void testOnPackageRemoved_unregisterTransports() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpPackage(PACKAGE_B, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportB1);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportB1);
+        transportManager.registerTransports();
 
-        assertThat(transportManager.getTransportName(mTransport1.binderInterface)).isNull();
-        assertThat(transportManager.getTransportName(mTransport2.binderInterface))
-                .isEqualTo(mTransport2.name);
+        transportManager.onPackageRemoved(PACKAGE_A);
+
+        assertRegisteredTransports(transportManager, singletonList(mTransportB1));
     }
 
     @Test
-    public void getTransportWhitelist_returnsCorrectWhiteList() throws Exception {
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(Arrays.asList(mTransport1.componentName, mTransport2.componentName)),
-                mTransport1.name,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
+    public void testOnPackageRemoved_whenUnknownPackage_nothingHappens() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
+        transportManager.registerTransports();
 
-        assertThat(transportManager.getTransportWhitelist()).containsExactlyElementsIn(
-                Arrays.asList(mTransport1.componentName, mTransport2.componentName));
+        transportManager.onPackageRemoved(PACKAGE_A + "unknown");
+
+        assertRegisteredTransports(transportManager, singletonList(mTransportA1));
     }
 
     @Test
-    public void getTransportWhitelist_whiteListIsNull_returnsEmptyArray() throws Exception {
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                null /* whitelist */,
-                mTransport1.name,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-
-        assertThat(transportManager.getTransportWhitelist()).isEmpty();
-    }
-
-    @Test
-    public void selectTransport_setsTransportCorrectlyAndReturnsPreviousTransport()
+    public void testOnPackageChanged_whenOneComponentChanged_onlyOneTransportReRegistered()
             throws Exception {
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                null /* whitelist */,
-                mTransport1.name,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
+        // Reset listener to verify calls after registerTransports() above
+        reset(mListener);
 
-        assertThat(transportManager.selectTransport(mTransport2.name)).isEqualTo(mTransport1.name);
-        assertThat(transportManager.selectTransport(mTransport1.name)).isEqualTo(mTransport2.name);
+        transportManager.onPackageChanged(
+                PACKAGE_A, mTransportA1.getTransportComponent().getClassName());
+
+        assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2));
+        verify(mListener)
+                .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+        verify(mListener, never())
+                .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
     }
 
     @Test
-    public void getTransportClient_forRegisteredTransport_returnCorrectly() throws Exception {
+    public void testOnPackageChanged_whenNoComponentsChanged_doesNotRegisterTransports()
+            throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
+        transportManager.registerTransports();
+        reset(mListener);
+
+        transportManager.onPackageChanged(PACKAGE_A);
+
+        assertRegisteredTransports(transportManager, singletonList(mTransportA1));
+        verify(mListener, never()).onTransportRegistered(any(), any());
+    }
+
+    @Test
+    public void testOnPackageChanged_whenUnknownComponentChanged_noTransportsRegistered()
+            throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
+        transportManager.registerTransports();
+        reset(mListener);
+
+        transportManager.onPackageChanged(PACKAGE_A, PACKAGE_A + ".UnknownComponent");
+
+        assertRegisteredTransports(transportManager, singletonList(mTransportA1));
+        verify(mListener, never()).onTransportRegistered(any(), any());
+    }
+
+    @Test
+    public void testOnPackageChanged_reRegisterTransports() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
+        reset(mListener);
+
+        transportManager.onPackageChanged(
+                PACKAGE_A,
+                mTransportA1.getTransportComponent().getClassName(),
+                mTransportA2.getTransportComponent().getClassName());
+
+        assertRegisteredTransports(transportManager, asList(mTransportA1, mTransportA2));
+        verify(mListener)
+                .onTransportRegistered(mTransportA1.transportName, mTransportA1.transportDirName);
+        verify(mListener)
+                .onTransportRegistered(mTransportA2.transportName, mTransportA2.transportDirName);
+    }
+
+    @Test
+    public void testGetCurrentTransportName_whenSelectTransportNotCalled_returnsDefaultTransport()
+            throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
+
+        String currentTransportName = transportManager.getCurrentTransportName();
+
+        assertThat(currentTransportName).isEqualTo(mTransportA1.transportName);
+    }
+
+    @Test
+    public void testGetCurrentTransport_whenSelectTransportCalled_returnsSelectedTransport()
+            throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
+        transportManager.selectTransport(mTransportA2.transportName);
+
+        String currentTransportName = transportManager.getCurrentTransportName();
+
+        assertThat(currentTransportName).isEqualTo(mTransportA2.transportName);
+    }
+
+    @Test
+    public void testGetTransportWhitelist() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+
+        Set<ComponentName> transportWhitelist = transportManager.getTransportWhitelist();
+
+        assertThat(transportWhitelist)
+                .containsExactlyElementsIn(
+                        asList(
+                                mTransportA1.getTransportComponent(),
+                                mTransportA2.getTransportComponent()));
+    }
+
+    @Test
+    public void testSelectTransport() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
         TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+                createTransportManager(null, mTransportA1, mTransportA2);
+
+        String transport1 = transportManager.selectTransport(mTransportA1.transportName);
+        String transport2 = transportManager.selectTransport(mTransportA2.transportName);
+
+        assertThat(transport1).isNull();
+        assertThat(transport2).isEqualTo(mTransportA1.transportName);
+    }
+
+    @Test
+    public void testGetTransportClient_forRegisteredTransport() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
 
         TransportClient transportClient =
-                transportManager.getTransportClient(mTransport1.name, "caller");
+                transportManager.getTransportClient(mTransportA1.transportName, "caller");
 
-        assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName);
+        assertThat(transportClient.getTransportComponent())
+                .isEqualTo(mTransportA1.getTransportComponent());
     }
 
     @Test
-    public void getTransportClient_forOldNameOfTransportThatChangedName_returnsNull()
+    public void testGetTransportClient_forOldNameOfTransportThatChangedName_returnsNull()
             throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
         transportManager.updateTransportAttributes(
-                mTransport1.componentName, "newName", null, "destinationString", null, null);
+                mTransportA1.getTransportComponent(),
+                "newName",
+                null,
+                "destinationString",
+                null,
+                null);
 
         TransportClient transportClient =
-                transportManager.getTransportClient(mTransport1.name, "caller");
+                transportManager.getTransportClient(mTransportA1.transportName, "caller");
 
         assertThat(transportClient).isNull();
     }
 
     @Test
-    public void getTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly()
+    public void testGetTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly()
             throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
         transportManager.updateTransportAttributes(
-                mTransport1.componentName, "newName", null, "destinationString", null, null);
+                mTransportA1.getTransportComponent(),
+                "newName",
+                null,
+                "destinationString",
+                null,
+                null);
 
-        TransportClient transportClient =
-                transportManager.getTransportClient("newName", "caller");
+        TransportClient transportClient = transportManager.getTransportClient("newName", "caller");
 
-        assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName);
+        assertThat(transportClient.getTransportComponent())
+                .isEqualTo(mTransportA1.getTransportComponent());
     }
 
     @Test
-    public void getTransportName_forTransportThatChangedName_returnsNewName()
-            throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+    public void testGetTransportName_forTransportThatChangedName_returnsNewName() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1, mTransportA2);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
         transportManager.updateTransportAttributes(
-                mTransport1.componentName, "newName", null, "destinationString", null, null);
+                mTransportA1.getTransportComponent(),
+                "newName",
+                null,
+                "destinationString",
+                null,
+                null);
 
-        String transportName = transportManager.getTransportName(mTransport1.componentName);
+        String transportName =
+                transportManager.getTransportName(mTransportA1.getTransportComponent());
 
         assertThat(transportName).isEqualTo("newName");
     }
 
     @Test
-    public void isTransportRegistered_returnsCorrectly() throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Collections.singletonList(mTransport1),
-                        Collections.singletonList(mTransport2),
-                        mTransport1.name);
+    public void testIsTransportRegistered() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1, mTransportA2);
+        transportManager.registerTransports();
 
-        assertThat(transportManager.isTransportRegistered(mTransport1.name)).isTrue();
-        assertThat(transportManager.isTransportRegistered(mTransport2.name)).isFalse();
+        boolean isTransportA1Registered =
+                transportManager.isTransportRegistered(mTransportA1.transportName);
+        boolean isTransportA2Registered =
+                transportManager.isTransportRegistered(mTransportA2.transportName);
+
+        assertThat(isTransportA1Registered).isTrue();
+        assertThat(isTransportA2Registered).isFalse();
     }
 
     @Test
-    public void getTransportAttributes_forRegisteredTransport_returnsCorrectValues()
+    public void testGetTransportAttributes_forRegisteredTransport_returnsCorrectValues()
             throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Collections.singletonList(mTransport1),
-                        mTransport1.name);
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
+        transportManager.registerTransports();
 
-        assertThat(transportManager.getTransportConfigurationIntent(mTransport1.name))
-                .isEqualTo(mTransport1.binderInterface.configurationIntent());
-        assertThat(transportManager.getTransportDataManagementIntent(mTransport1.name))
-                .isEqualTo(mTransport1.binderInterface.dataManagementIntent());
-        assertThat(transportManager.getTransportDataManagementLabel(mTransport1.name))
-                .isEqualTo(mTransport1.binderInterface.dataManagementLabel());
-        assertThat(transportManager.getTransportDirName(mTransport1.name))
-                .isEqualTo(mTransport1.binderInterface.transportDirName());
+        Intent configurationIntent =
+                transportManager.getTransportConfigurationIntent(mTransportA1.transportName);
+        Intent dataManagementIntent =
+                transportManager.getTransportDataManagementIntent(mTransportA1.transportName);
+        String dataManagementLabel =
+                transportManager.getTransportDataManagementLabel(mTransportA1.transportName);
+        String transportDirName = transportManager.getTransportDirName(mTransportA1.transportName);
+
+        assertThat(configurationIntent).isEqualTo(mTransportA1.configurationIntent);
+        assertThat(dataManagementIntent).isEqualTo(mTransportA1.dataManagementIntent);
+        assertThat(dataManagementLabel).isEqualTo(mTransportA1.dataManagementLabel);
+        assertThat(transportDirName).isEqualTo(mTransportA1.transportDirName);
     }
 
     @Test
-    public void getTransportAttributes_forUnregisteredTransport_throws()
-            throws Exception {
-        TransportManager transportManager =
-                createTransportManagerAndSetUpTransports(
-                        Collections.singletonList(mTransport1),
-                        Collections.singletonList(mTransport2),
-                        mTransport1.name);
+    public void testGetTransportAttributes_forUnregisteredTransport_throws() throws Exception {
+        setUpPackage(PACKAGE_A, ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
+        setUpTransports(mTransportA1);
+        TransportManager transportManager = createTransportManager(mTransportA1);
+        transportManager.registerTransports();
 
         expectThrows(
                 TransportNotRegisteredException.class,
-                () -> transportManager.getTransportConfigurationIntent(mTransport2.name));
+                () -> transportManager.getTransportConfigurationIntent(mTransportA2.transportName));
         expectThrows(
                 TransportNotRegisteredException.class,
-                () -> transportManager.getTransportDataManagementIntent(
-                        mTransport2.name));
+                () ->
+                        transportManager.getTransportDataManagementIntent(
+                                mTransportA2.transportName));
         expectThrows(
                 TransportNotRegisteredException.class,
-                () -> transportManager.getTransportDataManagementLabel(mTransport2.name));
+                () -> transportManager.getTransportDataManagementLabel(mTransportA2.transportName));
         expectThrows(
                 TransportNotRegisteredException.class,
-                () -> transportManager.getTransportDirName(mTransport2.name));
+                () -> transportManager.getTransportDirName(mTransportA2.transportName));
     }
 
-    private void setUpPackageWithTransports(String packageName, List<TransportInfo> transports,
-            int flags) throws Exception {
+    private List<TransportMock> setUpTransports(TransportData... transports) throws Exception {
+        setUpTransportsForTransportManager(mShadowPackageManager, transports);
+        List<TransportMock> transportMocks = new ArrayList<>(transports.length);
+        for (TransportData transport : transports) {
+            TransportMock transportMock = mockTransport(transport);
+            when(mTransportClientManager.getTransportClient(
+                            eq(transport.getTransportComponent()), any()))
+                    .thenReturn(transportMock.transportClient);
+            transportMocks.add(transportMock);
+        }
+        return transportMocks;
+    }
+
+    private void setUpPackage(String packageName, int flags) {
         PackageInfo packageInfo = new PackageInfo();
         packageInfo.packageName = packageName;
         packageInfo.applicationInfo = new ApplicationInfo();
         packageInfo.applicationInfo.privateFlags = flags;
-
-        mPackageManagerShadow.addPackage(packageInfo);
-
-        List<ResolveInfo> transportsInfo = new ArrayList<>();
-        for (TransportInfo transport : transports) {
-            ResolveInfo info = new ResolveInfo();
-            info.serviceInfo = new ServiceInfo();
-            info.serviceInfo.packageName = packageName;
-            info.serviceInfo.name = transport.name;
-            transportsInfo.add(info);
-        }
-
-        Intent intent = new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST);
-        intent.setPackage(packageName);
-
-        mPackageManagerShadow.addResolveInfoForIntent(intent, transportsInfo);
+        mShadowPackageManager.addPackage(packageInfo);
     }
 
-    private TransportManager createTransportManagerAndSetUpTransports(
-            List<TransportInfo> availableTransports, String defaultTransportName) throws Exception {
-        return createTransportManagerAndSetUpTransports(availableTransports,
-                Collections.<TransportInfo>emptyList(), defaultTransportName);
-    }
-
-    private TransportManager createTransportManagerAndSetUpTransports(
-            List<TransportInfo> availableTransports, List<TransportInfo> unavailableTransports,
-            String defaultTransportName)
-            throws Exception {
-        List<String> availableTransportsNames = new ArrayList<>();
-        List<ComponentName> availableTransportsComponentNames = new ArrayList<>();
-        for (TransportInfo transport : availableTransports) {
-            availableTransportsNames.add(transport.name);
-            availableTransportsComponentNames.add(transport.componentName);
-        }
-
-        List<ComponentName> allTransportsComponentNames = new ArrayList<>();
-        allTransportsComponentNames.addAll(availableTransportsComponentNames);
-        for (TransportInfo transport : unavailableTransports) {
-            allTransportsComponentNames.add(transport.componentName);
-        }
-
-        for (TransportInfo transport : unavailableTransports) {
-            ShadowContextImplForBackup.sUnbindableComponents.add(transport.componentName);
-        }
-
-        setUpPackageWithTransports(PACKAGE_NAME, Arrays.asList(mTransport1, mTransport2),
-                ApplicationInfo.PRIVATE_FLAG_PRIVILEGED);
-
-        TransportManager transportManager = new TransportManager(
-                RuntimeEnvironment.application.getApplicationContext(),
-                new HashSet<>(allTransportsComponentNames),
-                defaultTransportName,
-                mTransportBoundListenerStub,
-                ShadowLooper.getMainLooper());
-        transportManager.onPackageAdded(PACKAGE_NAME);
-
-        assertThat(transportManager.getAllTransportComponents()).asList().containsExactlyElementsIn(
-                availableTransportsComponentNames);
-        assertThat(transportManager.getBoundTransportNames()).asList().containsExactlyElementsIn(
-                availableTransportsNames);
-        for (TransportInfo transport : availableTransports) {
-            assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface))
-                    .isTrue();
-        }
-        for (TransportInfo transport : unavailableTransports) {
-            assertThat(mTransportBoundListenerStub.isCalledForTransport(transport.binderInterface))
-                    .isFalse();
-        }
-
-        mTransportBoundListenerStub.resetState();
-
+    private TransportManager createTransportManager(
+            @Nullable TransportData selectedTransport, TransportData... transports) {
+        Set<ComponentName> whitelist =
+                concat(Stream.of(selectedTransport), Stream.of(transports))
+                        .filter(Objects::nonNull)
+                        .map(TransportData::getTransportComponent)
+                        .collect(toSet());
+        TransportManager transportManager =
+                new TransportManager(
+                        mContext,
+                        whitelist,
+                        selectedTransport != null ? selectedTransport.transportName : null,
+                        mTransportClientManager);
+        transportManager.setOnTransportRegisteredListener(mListener);
         return transportManager;
     }
 
-    private static class TransportInfo {
-        public final String packageName;
-        public final String name;
-        public final ComponentName componentName;
-        public final IBackupTransport binderInterface;
-        public final IBinder binder;
-
-        TransportInfo(
-                String packageName,
-                String name,
-                @Nullable Intent configurationIntent,
-                String currentDestinationString,
-                @Nullable Intent dataManagementIntent,
-                String dataManagementLabel) {
-            this.packageName = packageName;
-            this.name = name;
-            this.componentName = new ComponentName(packageName, name);
-            this.binder = mock(IBinder.class);
-            IBackupTransport transport = mock(IBackupTransport.class);
-            try {
-                when(transport.name()).thenReturn(name);
-                when(transport.configurationIntent()).thenReturn(configurationIntent);
-                when(transport.currentDestinationString()).thenReturn(currentDestinationString);
-                when(transport.dataManagementIntent()).thenReturn(dataManagementIntent);
-                when(transport.dataManagementLabel()).thenReturn(dataManagementLabel);
-            } catch (RemoteException e) {
-                // Only here to mock methods that throw RemoteException
-            }
-            this.binderInterface = transport;
-        }
+    private void assertRegisteredTransports(
+            TransportManager transportManager, List<TransportData> transports) {
+        assertThat(transportManager.getRegisteredTransportComponents())
+                .asList()
+                .containsExactlyElementsIn(
+                        transports
+                                .stream()
+                                .map(TransportData::getTransportComponent)
+                                .collect(toList()));
+        assertThat(transportManager.getRegisteredTransportNames())
+                .asList()
+                .containsExactlyElementsIn(
+                        transports.stream().map(t -> t.transportName).collect(toList()));
     }
-
 }
diff --git a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
index dfca901..ace0441 100644
--- a/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
+++ b/services/robotests/src/com/android/server/backup/internal/PerformInitializeTaskTest.java
@@ -19,8 +19,10 @@
 import static android.app.backup.BackupTransport.TRANSPORT_ERROR;
 import static android.app.backup.BackupTransport.TRANSPORT_OK;
 
-import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAME;
-import static com.android.server.backup.testing.TransportTestUtils.TRANSPORT_NAMES;
+import static com.android.server.backup.testing.TransportData.backupTransport;
+import static com.android.server.backup.testing.TransportData.d2dTransport;
+import static com.android.server.backup.testing.TransportData.localTransport;
+import static com.android.server.backup.testing.TransportTestUtils.setUpTransports;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -28,7 +30,6 @@
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -44,7 +45,8 @@
 import com.android.server.backup.RefactoredBackupManagerService;
 import com.android.server.backup.TransportManager;
 import com.android.server.backup.testing.TransportTestUtils;
-import com.android.server.backup.testing.TransportTestUtils.TransportData;
+import com.android.server.backup.testing.TransportData;
+import com.android.server.backup.testing.TransportTestUtils.TransportMock;
 import com.android.server.backup.transport.TransportClient;
 import com.android.server.testing.FrameworkRobolectricTestRunner;
 import com.android.server.testing.SystemLoaderClasses;
@@ -58,7 +60,10 @@
 import org.robolectric.annotation.Config;
 
 import java.io.File;
+import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
+import java.util.stream.Stream;
 
 @RunWith(FrameworkRobolectricTestRunner.class)
 @Config(manifest = Config.NONE, sdk = 26)
@@ -68,16 +73,21 @@
     @Mock private RefactoredBackupManagerService mBackupManagerService;
     @Mock private TransportManager mTransportManager;
     @Mock private OnTaskFinishedListener mListener;
-    @Mock private IBackupTransport mTransport;
+    @Mock private IBackupTransport mTransportBinder;
     @Mock private IBackupObserver mObserver;
     @Mock private AlarmManager mAlarmManager;
     @Mock private PendingIntent mRunInitIntent;
     private File mBaseStateDir;
+    private TransportData mTransport;
+    private String mTransportName;
 
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
 
+        mTransport = backupTransport();
+        mTransportName = mTransport.transportName;
+
         Application context = RuntimeEnvironment.application;
         mBaseStateDir = new File(context.getCacheDir(), "base_state_dir");
         assertThat(mBaseStateDir.mkdir()).isTrue();
@@ -88,82 +98,76 @@
 
     @Test
     public void testRun_callsTransportCorrectly() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mTransport).initializeDevice();
-        verify(mTransport).finishBackup();
+        verify(mTransportBinder).initializeDevice();
+        verify(mTransportBinder).finishBackup();
     }
 
     @Test
     public void testRun_callsBackupManagerCorrectly() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
         verify(mBackupManagerService)
-                .recordInitPending(
-                        false, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+                .recordInitPending(false, mTransportName, mTransport.transportDirName);
         verify(mBackupManagerService)
-                .resetBackupState(
-                        eq(
-                                new File(
-                                        mBaseStateDir,
-                                        TransportTestUtils.transportDirName(TRANSPORT_NAME))));
+                .resetBackupState(eq(new File(mBaseStateDir, mTransport.transportDirName)));
     }
 
     @Test
     public void testRun_callsObserverAndListenerCorrectly() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_OK);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_OK);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_OK));
+        verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_OK));
         verify(mObserver).backupFinished(eq(TRANSPORT_OK));
         verify(mListener).onFinished(any());
     }
 
     @Test
     public void testRun_whenInitializeDeviceFails() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_ERROR, 0);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mTransport).initializeDevice();
-        verify(mTransport, never()).finishBackup();
+        verify(mTransportBinder).initializeDevice();
+        verify(mTransportBinder, never()).finishBackup();
         verify(mBackupManagerService)
-                .recordInitPending(
-                        true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+                .recordInitPending(true, mTransportName, mTransport.transportDirName);
     }
 
     @Test
     public void testRun_whenInitializeDeviceFails_callsObserverAndListenerCorrectly()
             throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_ERROR, 0);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR));
+        verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR));
         verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
         verify(mListener).onFinished(any());
     }
 
     @Test
     public void testRun_whenInitializeDeviceFails_schedulesAlarm() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_ERROR, 0);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_ERROR, 0);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
@@ -172,37 +176,36 @@
 
     @Test
     public void testRun_whenFinishBackupFails() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mTransport).initializeDevice();
-        verify(mTransport).finishBackup();
+        verify(mTransportBinder).initializeDevice();
+        verify(mTransportBinder).finishBackup();
         verify(mBackupManagerService)
-                .recordInitPending(
-                        true, TRANSPORT_NAME, TransportTestUtils.transportDirName(TRANSPORT_NAME));
+                .recordInitPending(true, mTransportName, mTransport.transportDirName);
     }
 
     @Test
     public void testRun_whenFinishBackupFails_callsObserverAndListenerCorrectly() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mObserver).onResult(eq(TRANSPORT_NAME), eq(TRANSPORT_ERROR));
+        verify(mObserver).onResult(eq(mTransportName), eq(TRANSPORT_ERROR));
         verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
         verify(mListener).onFinished(any());
     }
 
     @Test
     public void testRun_whenFinishBackupFails_schedulesAlarm() throws Exception {
-        setUpTransport(TRANSPORT_NAME);
-        configureTransport(mTransport, TRANSPORT_OK, TRANSPORT_ERROR);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransport(mTransport);
+        configureTransport(mTransportBinder, TRANSPORT_OK, TRANSPORT_ERROR);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
@@ -211,64 +214,76 @@
 
     @Test
     public void testRun_whenOnlyOneTransportFails() throws Exception {
-        List<TransportData> transports =
-                TransportTestUtils.setUpTransports(
-                        mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
-        configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
-        configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+        TransportData transport1 = backupTransport();
+        TransportData transport2 = d2dTransport();
+        List<TransportMock> transportMocks =
+                setUpTransports(mTransportManager, transport1, transport2);
+        configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0);
+        configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK);
         PerformInitializeTask performInitializeTask =
-                createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+                createPerformInitializeTask(transport1.transportName, transport2.transportName);
 
         performInitializeTask.run();
 
-        verify(transports.get(1).transportMock).initializeDevice();
-        verify(mObserver).onResult(eq(TRANSPORT_NAMES[0]), eq(TRANSPORT_ERROR));
-        verify(mObserver).onResult(eq(TRANSPORT_NAMES[1]), eq(TRANSPORT_OK));
+        verify(transportMocks.get(1).transport).initializeDevice();
+        verify(mObserver).onResult(eq(transport1.transportName), eq(TRANSPORT_ERROR));
+        verify(mObserver).onResult(eq(transport2.transportName), eq(TRANSPORT_OK));
         verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
     }
 
     @Test
     public void testRun_withMultipleTransports() throws Exception {
-        List<TransportData> transports =
-                TransportTestUtils.setUpTransports(mTransportManager, TRANSPORT_NAMES);
-        configureTransport(transports.get(0).transportMock, TRANSPORT_OK, TRANSPORT_OK);
-        configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
-        configureTransport(transports.get(2).transportMock, TRANSPORT_OK, TRANSPORT_OK);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAMES);
+        List<TransportMock> transportMocks =
+                setUpTransports(
+                        mTransportManager, backupTransport(), d2dTransport(), localTransport());
+        configureTransport(transportMocks.get(0).transport, TRANSPORT_OK, TRANSPORT_OK);
+        configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK);
+        configureTransport(transportMocks.get(2).transport, TRANSPORT_OK, TRANSPORT_OK);
+        String[] transportNames =
+                Stream.of(new TransportData[] {backupTransport(), d2dTransport(), localTransport()})
+                        .map(t -> t.transportName)
+                        .toArray(String[]::new);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(transportNames);
 
         performInitializeTask.run();
 
-        for (TransportData transport : transports) {
+        Iterator<TransportData> transportsIterator =
+                Arrays.asList(
+                                new TransportData[] {
+                                    backupTransport(), d2dTransport(), localTransport()
+                                })
+                        .iterator();
+        for (TransportMock transportMock : transportMocks) {
+            TransportData transport = transportsIterator.next();
             verify(mTransportManager).getTransportClient(eq(transport.transportName), any());
             verify(mTransportManager)
-                    .disposeOfTransportClient(eq(transport.transportClientMock), any());
+                    .disposeOfTransportClient(eq(transportMock.transportClient), any());
         }
     }
 
     @Test
     public void testRun_whenOnlyOneTransportFails_disposesAllTransports() throws Exception {
-        List<TransportData> transports =
-                TransportTestUtils.setUpTransports(
-                        mTransportManager, TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
-        configureTransport(transports.get(0).transportMock, TRANSPORT_ERROR, 0);
-        configureTransport(transports.get(1).transportMock, TRANSPORT_OK, TRANSPORT_OK);
+        TransportData transport1 = backupTransport();
+        TransportData transport2 = d2dTransport();
+        List<TransportMock> transportMocks =
+                setUpTransports(mTransportManager, transport1, transport2);
+        configureTransport(transportMocks.get(0).transport, TRANSPORT_ERROR, 0);
+        configureTransport(transportMocks.get(1).transport, TRANSPORT_OK, TRANSPORT_OK);
         PerformInitializeTask performInitializeTask =
-                createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+                createPerformInitializeTask(transport1.transportName, transport2.transportName);
 
         performInitializeTask.run();
 
         verify(mTransportManager)
-                .disposeOfTransportClient(eq(transports.get(0).transportClientMock), any());
+                .disposeOfTransportClient(eq(transportMocks.get(0).transportClient), any());
         verify(mTransportManager)
-                .disposeOfTransportClient(eq(transports.get(1).transportClientMock), any());
+                .disposeOfTransportClient(eq(transportMocks.get(1).transportClient), any());
     }
 
     @Test
     public void testRun_whenTransportNotRegistered() throws Exception {
-        TransportTestUtils.setUpTransports(
-                mTransportManager, new TransportData(TRANSPORT_NAME, null, null));
-
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        setUpTransports(mTransportManager, mTransport.unregistered());
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
@@ -279,16 +294,15 @@
 
     @Test
     public void testRun_whenOnlyOneTransportNotRegistered() throws Exception {
-        List<TransportData> transports =
-                TransportTestUtils.setUpTransports(
-                        mTransportManager,
-                        new TransportData(TRANSPORT_NAMES[0], null, null),
-                        new TransportData(TRANSPORT_NAMES[1]));
-        String registeredTransportName = transports.get(1).transportName;
-        IBackupTransport registeredTransport = transports.get(1).transportMock;
-        TransportClient registeredTransportClient = transports.get(1).transportClientMock;
+        TransportData transport1 = backupTransport().unregistered();
+        TransportData transport2 = d2dTransport();
+        List<TransportMock> transportMocks =
+                setUpTransports(mTransportManager, transport1, transport2);
+        String registeredTransportName = transport2.transportName;
+        IBackupTransport registeredTransport = transportMocks.get(1).transport;
+        TransportClient registeredTransportClient = transportMocks.get(1).transportClient;
         PerformInitializeTask performInitializeTask =
-                createPerformInitializeTask(TRANSPORT_NAMES[0], TRANSPORT_NAMES[1]);
+                createPerformInitializeTask(transport1.transportName, transport2.transportName);
 
         performInitializeTask.run();
 
@@ -299,25 +313,24 @@
 
     @Test
     public void testRun_whenTransportNotAvailable() throws Exception {
-        TransportClient transportClient = mock(TransportClient.class);
-        TransportTestUtils.setUpTransports(
-                mTransportManager, new TransportData(TRANSPORT_NAME, null, transportClient));
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        TransportMock transportMock = setUpTransport(mTransport.unavailable());
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
-        verify(mTransportManager).disposeOfTransportClient(eq(transportClient), any());
+        verify(mTransportManager)
+                .disposeOfTransportClient(eq(transportMock.transportClient), any());
         verify(mObserver).backupFinished(eq(TRANSPORT_ERROR));
         verify(mListener).onFinished(any());
     }
 
     @Test
     public void testRun_whenTransportThrowsDeadObjectException() throws Exception {
-        TransportClient transportClient = mock(TransportClient.class);
-        TransportTestUtils.setUpTransports(
-                mTransportManager, new TransportData(TRANSPORT_NAME, mTransport, transportClient));
-        when(mTransport.initializeDevice()).thenThrow(DeadObjectException.class);
-        PerformInitializeTask performInitializeTask = createPerformInitializeTask(TRANSPORT_NAME);
+        TransportMock transportMock = setUpTransport(mTransport);
+        IBackupTransport transport = transportMock.transport;
+        TransportClient transportClient = transportMock.transportClient;
+        when(transport.initializeDevice()).thenThrow(DeadObjectException.class);
+        PerformInitializeTask performInitializeTask = createPerformInitializeTask(mTransportName);
 
         performInitializeTask.run();
 
@@ -343,9 +356,10 @@
         when(transportMock.finishBackup()).thenReturn(finishBackupStatus);
     }
 
-    private void setUpTransport(String transportName) throws Exception {
-        TransportTestUtils.setUpTransport(
-                mTransportManager,
-                new TransportData(transportName, mTransport, mock(TransportClient.class)));
+    private TransportMock setUpTransport(TransportData transport) throws Exception {
+        TransportMock transportMock =
+                TransportTestUtils.setUpTransport(mTransportManager, transport);
+        mTransportBinder = transportMock.transport;
+        return transportMock;
     }
 }
diff --git a/services/robotests/src/com/android/server/backup/testing/TestUtils.java b/services/robotests/src/com/android/server/backup/testing/TestUtils.java
new file mode 100644
index 0000000..1be298d
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/testing/TestUtils.java
@@ -0,0 +1,68 @@
+/*
+ * 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.testing;
+
+import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
+
+import java.util.concurrent.Callable;
+
+public class TestUtils {
+    /**
+     * Calls {@link Runnable#run()} and returns if no exception is thrown. Otherwise, if the
+     * exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException} and
+     * throw.
+     *
+     * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure
+     * in a test.
+     */
+    public static void uncheck(ThrowingRunnable runnable) {
+        try {
+            runnable.runOrThrow();
+        } catch (Exception e) {
+            throw wrapIfChecked(e);
+        }
+    }
+
+    /**
+     * Calls {@link Callable#call()} and returns the value if no exception is thrown. Otherwise, if
+     * the exception is unchecked, rethrow it; if it's checked wrap in a {@link RuntimeException}
+     * and throw.
+     *
+     * <p><b>Warning:</b>DON'T use this outside tests. A wrapped checked exception is just a failure
+     * in a test.
+     */
+    public static <T> T uncheck(Callable<T> callable) {
+        try {
+            return callable.call();
+        } catch (Exception e) {
+            throw wrapIfChecked(e);
+        }
+    }
+
+    /**
+     * Wrap {@code e} in a {@link RuntimeException} only if it's not one already, in which case it's
+     * returned.
+     */
+    public static RuntimeException wrapIfChecked(Exception e) {
+        if (e instanceof RuntimeException) {
+            return (RuntimeException) e;
+        }
+        return new RuntimeException(e);
+    }
+
+    private TestUtils() {}
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java b/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java
deleted file mode 100644
index 84ac2c2..0000000
--- a/services/robotests/src/com/android/server/backup/testing/TransportBoundListenerStub.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * Copyright (C) 2017 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.testing;
-
-import com.android.internal.backup.IBackupTransport;
-import com.android.server.backup.TransportManager;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Stub implementation of TransportBoundListener, which returns given result and can tell whether
- * it was called for given transport.
- */
-public class TransportBoundListenerStub implements
-        TransportManager.TransportBoundListener {
-    private boolean mAlwaysReturnSuccess;
-    private Set<IBackupTransport> mTransportsCalledFor = new HashSet<>();
-
-    public TransportBoundListenerStub(boolean alwaysReturnSuccess) {
-        this.mAlwaysReturnSuccess = alwaysReturnSuccess;
-    }
-
-    @Override
-    public boolean onTransportBound(IBackupTransport binder) {
-        mTransportsCalledFor.add(binder);
-        return mAlwaysReturnSuccess;
-    }
-
-    /**
-     * Returns whether the listener was called for the specified transport at least once.
-     */
-    public boolean isCalledForTransport(IBackupTransport binder) {
-        return mTransportsCalledFor.contains(binder);
-    }
-
-    /**
-     * Returns whether the listener was called at least once.
-     */
-    public boolean isCalled() {
-        return !mTransportsCalledFor.isEmpty();
-    }
-
-    /**
-     * Resets listener calls.
-     */
-    public void resetState() {
-        mTransportsCalledFor.clear();
-    }
-}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportData.java b/services/robotests/src/com/android/server/backup/testing/TransportData.java
new file mode 100644
index 0000000..9feaa8e
--- /dev/null
+++ b/services/robotests/src/com/android/server/backup/testing/TransportData.java
@@ -0,0 +1,149 @@
+/*
+ * 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.testing;
+
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Intent;
+
+public class TransportData {
+    // No constants since new Intent() can't be called in static context because of Robolectric
+    public static TransportData backupTransport() {
+        return new TransportData(
+                "com.google.android.gms/.backup.BackupTransportService",
+                "com.google.android.gms/.backup.BackupTransportService",
+                "com.google.android.gms.backup.BackupTransportService",
+                new Intent(),
+                "user@gmail.com",
+                new Intent(),
+                "Google Account");
+    }
+
+    public static TransportData d2dTransport() {
+        return new TransportData(
+                "com.google.android.gms/.backup.migrate.service.D2dTransport",
+                "com.google.android.gms/.backup.component.D2dTransportService",
+                "d2dMigrateTransport",
+                null,
+                "Moving data to new device",
+                null,
+                "");
+    }
+
+    public static TransportData localTransport() {
+        return new TransportData(
+                "android/com.android.internal.backup.LocalTransport",
+                "android/com.android.internal.backup.LocalTransportService",
+                "com.android.internal.backup.LocalTransport",
+                null,
+                "Backing up to debug-only private cache",
+                null,
+                "");
+    }
+
+    public static TransportData genericTransport(String packageName, String className) {
+        return new TransportData(
+                packageName + "/." + className,
+                packageName + "/." + className + "Service",
+                packageName + "." + className,
+                new Intent(),
+                "currentDestinationString",
+                new Intent(),
+                "dataManagementLabel");
+    }
+
+    @TransportTestUtils.TransportStatus
+    public int transportStatus;
+    public final String transportName;
+    private final String transportComponentShort;
+    @Nullable
+    public String transportDirName;
+    @Nullable public Intent configurationIntent;
+    @Nullable public String currentDestinationString;
+    @Nullable public Intent dataManagementIntent;
+    @Nullable public String dataManagementLabel;
+
+    private TransportData(
+            @TransportTestUtils.TransportStatus int transportStatus,
+            String transportName,
+            String transportComponentShort,
+            String transportDirName,
+            Intent configurationIntent,
+            String currentDestinationString,
+            Intent dataManagementIntent,
+            String dataManagementLabel) {
+        this.transportStatus = transportStatus;
+        this.transportName = transportName;
+        this.transportComponentShort = transportComponentShort;
+        this.transportDirName = transportDirName;
+        this.configurationIntent = configurationIntent;
+        this.currentDestinationString = currentDestinationString;
+        this.dataManagementIntent = dataManagementIntent;
+        this.dataManagementLabel = dataManagementLabel;
+    }
+
+    public TransportData(
+            String transportName,
+            String transportComponentShort,
+            String transportDirName,
+            Intent configurationIntent,
+            String currentDestinationString,
+            Intent dataManagementIntent,
+            String dataManagementLabel) {
+        this(
+                TransportTestUtils.TransportStatus.REGISTERED_AVAILABLE,
+                transportName,
+                transportComponentShort,
+                transportDirName,
+                configurationIntent,
+                currentDestinationString,
+                dataManagementIntent,
+                dataManagementLabel);
+    }
+
+    /**
+     * Not field because otherwise we'd have to call ComponentName::new in static context and
+     * Robolectric does not like this.
+     */
+    public ComponentName getTransportComponent() {
+        return ComponentName.unflattenFromString(transportComponentShort);
+    }
+
+    public TransportData unavailable() {
+        return new TransportData(
+                TransportTestUtils.TransportStatus.REGISTERED_UNAVAILABLE,
+                transportName,
+                transportComponentShort,
+                transportDirName,
+                configurationIntent,
+                currentDestinationString,
+                dataManagementIntent,
+                dataManagementLabel);
+    }
+
+    public TransportData unregistered() {
+        return new TransportData(
+                TransportTestUtils.TransportStatus.UNREGISTERED,
+                transportName,
+                transportComponentShort,
+                transportDirName,
+                configurationIntent,
+                currentDestinationString,
+                dataManagementIntent,
+                dataManagementLabel);
+    }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
index 9770e40..e1dc7b5 100644
--- a/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
+++ b/services/robotests/src/com/android/server/backup/testing/TransportTestUtils.java
@@ -16,13 +16,23 @@
 
 package com.android.server.backup.testing;
 
+import static com.android.server.backup.testing.TestUtils.uncheck;
+
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import static java.util.stream.Collectors.toList;
+
 import android.annotation.Nullable;
 import android.content.ComponentName;
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.os.RemoteException;
+import android.support.annotation.IntDef;
 
 import com.android.internal.backup.IBackupTransport;
 import com.android.server.backup.TransportManager;
@@ -30,85 +40,82 @@
 import com.android.server.backup.transport.TransportNotAvailableException;
 import com.android.server.backup.transport.TransportNotRegisteredException;
 
-import java.util.Arrays;
+import org.robolectric.shadows.ShadowPackageManager;
+
 import java.util.List;
+import java.util.stream.Stream;
 
 public class TransportTestUtils {
-    public static final String[] TRANSPORT_NAMES = {
-        "android/com.android.internal.backup.LocalTransport",
-        "com.google.android.gms/.backup.migrate.service.D2dTransport",
-        "com.google.android.gms/.backup.BackupTransportService"
-    };
+    /**
+     * Differently from {@link #setUpTransports(TransportManager, TransportData...)}, which
+     * configures {@link TransportManager}, this is meant to mock the environment for a real
+     * TransportManager.
+     */
+    public static void setUpTransportsForTransportManager(
+            ShadowPackageManager shadowPackageManager, TransportData... transports)
+            throws Exception {
+        for (TransportData transport : transports) {
+            ComponentName transportComponent = transport.getTransportComponent();
+            String packageName = transportComponent.getPackageName();
+            ResolveInfo resolveInfo = resolveInfo(transportComponent);
+            shadowPackageManager.addResolveInfoForIntent(transportIntent(), resolveInfo);
+            shadowPackageManager.addResolveInfoForIntent(
+                    transportIntent().setPackage(packageName), resolveInfo);
+        }
+    }
 
-    public static final String TRANSPORT_NAME = TRANSPORT_NAMES[0];
+    private static Intent transportIntent() {
+        return new Intent(TransportManager.SERVICE_ACTION_TRANSPORT_HOST);
+    }
 
-    /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
-    public static TransportData setUpCurrentTransport(
-            TransportManager transportManager, String transportName) throws Exception {
-        TransportData transport = setUpTransports(transportManager, transportName).get(0);
-        when(transportManager.getCurrentTransportClient(any()))
-                .thenReturn(transport.transportClientMock);
-        return transport;
+    private static ResolveInfo resolveInfo(ComponentName transportComponent) {
+        ResolveInfo resolveInfo = new ResolveInfo();
+        resolveInfo.serviceInfo = new ServiceInfo();
+        resolveInfo.serviceInfo.packageName = transportComponent.getPackageName();
+        resolveInfo.serviceInfo.name = transportComponent.getClassName();
+        return resolveInfo;
     }
 
     /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
-    public static List<TransportData> setUpTransports(
-            TransportManager transportManager, String... transportNames) throws Exception {
-        return setUpTransports(
-                transportManager,
-                Arrays.stream(transportNames)
-                        .map(TransportData::new)
-                        .toArray(TransportData[]::new));
+    public static TransportMock setUpCurrentTransport(
+            TransportManager transportManager, TransportData transport) throws Exception {
+        TransportMock transportMock = setUpTransports(transportManager, transport).get(0);
+        if (transportMock.transportClient != null) {
+            when(transportManager.getCurrentTransportClient(any()))
+                    .thenReturn(transportMock.transportClient);
+        }
+        return transportMock;
     }
 
     /** @see #setUpTransport(TransportManager, TransportData) */
-    public static List<TransportData> setUpTransports(
+    public static List<TransportMock> setUpTransports(
             TransportManager transportManager, TransportData... transports) throws Exception {
-        for (TransportData transport : transports) {
-            setUpTransport(transportManager, transport);
-        }
-        return Arrays.asList(transports);
+        return Stream.of(transports)
+                .map(transport -> uncheck(() -> setUpTransport(transportManager, transport)))
+                .collect(toList());
     }
 
-    /**
-     * Configures transport according to {@link TransportData}:
-     *
-     * <ul>
-     *   <li>{@link TransportData#transportMock} {@code null} means transport not available.
-     *   <li>{@link TransportData#transportClientMock} {@code null} means transport not registered.
-     * </ul>
-     */
-    public static void setUpTransport(TransportManager transportManager, TransportData transport)
-            throws Exception {
+    public static TransportMock setUpTransport(
+            TransportManager transportManager, TransportData transport) throws Exception {
+        int status = transport.transportStatus;
         String transportName = transport.transportName;
-        String transportDirName = transportDirName(transportName);
-        ComponentName transportComponent = transportComponentName(transportName);
-        IBackupTransport transportMock = transport.transportMock;
-        TransportClient transportClientMock = transport.transportClientMock;
+        ComponentName transportComponent = transport.getTransportComponent();
+        String transportDirName = transport.transportDirName;
 
-        if (transportClientMock != null) {
+        TransportMock transportMock = mockTransport(transport);
+        if (status == TransportStatus.REGISTERED_AVAILABLE
+                || status == TransportStatus.REGISTERED_UNAVAILABLE) {
             // Transport registered
             when(transportManager.getTransportClient(eq(transportName), any()))
-                    .thenReturn(transportClientMock);
+                    .thenReturn(transportMock.transportClient);
             when(transportManager.getTransportClientOrThrow(eq(transportName), any()))
-                    .thenReturn(transportClientMock);
+                    .thenReturn(transportMock.transportClient);
             when(transportManager.getTransportName(transportComponent)).thenReturn(transportName);
             when(transportManager.getTransportDirName(eq(transportName)))
                     .thenReturn(transportDirName);
             when(transportManager.getTransportDirName(eq(transportComponent)))
                     .thenReturn(transportDirName);
-            when(transportClientMock.getTransportComponent()).thenReturn(transportComponent);
-
-            if (transportMock != null) {
-                // Transport registered and available
-                when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
-                when(transportMock.name()).thenReturn(transportName);
-                when(transportMock.transportDirName()).thenReturn(transportDirName);
-            } else {
-                // Transport registered but unavailable
-                when(transportClientMock.connectOrThrow(any()))
-                        .thenThrow(TransportNotAvailableException.class);
-            }
+            // TODO: Mock rest of description methods
         } else {
             // Transport not registered
             when(transportManager.getTransportClient(eq(transportName), any())).thenReturn(null);
@@ -121,34 +128,73 @@
             when(transportManager.getTransportDirName(eq(transportComponent)))
                     .thenThrow(TransportNotRegisteredException.class);
         }
+        return transportMock;
     }
 
-    /** {@code transportName} has to be in the {@link ComponentName} format (with '/') */
-    public static ComponentName transportComponentName(String transportName) {
-        return ComponentName.unflattenFromString(transportName);
-    }
+    public static TransportMock mockTransport(TransportData transport) throws Exception {
+        final TransportClient transportClientMock;
+        int status = transport.transportStatus;
+        ComponentName transportComponent = transport.getTransportComponent();
+        if (status == TransportStatus.REGISTERED_AVAILABLE
+                || status == TransportStatus.REGISTERED_UNAVAILABLE) {
+            // Transport registered
+            transportClientMock = mock(TransportClient.class);
+            when(transportClientMock.getTransportComponent()).thenReturn(transportComponent);
+            if (status == TransportStatus.REGISTERED_AVAILABLE) {
+                // Transport registered and available
+                IBackupTransport transportMock = mockTransportBinder(transport);
+                when(transportClientMock.connectOrThrow(any())).thenReturn(transportMock);
 
-    public static String transportDirName(String transportName) {
-        return transportName + "_dir_name";
-    }
+                return new TransportMock(transportClientMock, transportMock);
+            } else {
+                // Transport registered but unavailable
+                when(transportClientMock.connectOrThrow(any()))
+                        .thenThrow(TransportNotAvailableException.class);
 
-    public static class TransportData {
-        public final String transportName;
-        @Nullable public final IBackupTransport transportMock;
-        @Nullable public final TransportClient transportClientMock;
-
-        public TransportData(
-                String transportName,
-                @Nullable IBackupTransport transportMock,
-                @Nullable TransportClient transportClientMock) {
-            this.transportName = transportName;
-            this.transportMock = transportMock;
-            this.transportClientMock = transportClientMock;
+                return new TransportMock(transportClientMock, null);
+            }
+        } else {
+            // Transport not registered
+            return new TransportMock(null, null);
         }
+    }
 
-        public TransportData(String transportName) {
-            this(transportName, mock(IBackupTransport.class), mock(TransportClient.class));
+    private static IBackupTransport mockTransportBinder(TransportData transport) throws Exception {
+        IBackupTransport transportBinder = mock(IBackupTransport.class);
+        try {
+            when(transportBinder.name()).thenReturn(transport.transportName);
+            when(transportBinder.transportDirName()).thenReturn(transport.transportDirName);
+            when(transportBinder.configurationIntent()).thenReturn(transport.configurationIntent);
+            when(transportBinder.currentDestinationString())
+                    .thenReturn(transport.currentDestinationString);
+            when(transportBinder.dataManagementIntent()).thenReturn(transport.dataManagementIntent);
+            when(transportBinder.dataManagementLabel()).thenReturn(transport.dataManagementLabel);
+        } catch (RemoteException e) {
+            fail("RemoteException?");
         }
+        return transportBinder;
+    }
+
+    public static class TransportMock {
+        @Nullable public final TransportClient transportClient;
+        @Nullable public final IBackupTransport transport;
+
+        private TransportMock(
+                @Nullable TransportClient transportClient, @Nullable IBackupTransport transport) {
+            this.transportClient = transportClient;
+            this.transport = transport;
+        }
+    }
+
+    @IntDef({
+        TransportStatus.REGISTERED_AVAILABLE,
+        TransportStatus.REGISTERED_UNAVAILABLE,
+        TransportStatus.UNREGISTERED
+    })
+    public @interface TransportStatus {
+        int REGISTERED_AVAILABLE = 0;
+        int REGISTERED_UNAVAILABLE = 1;
+        int UNREGISTERED = 2;
     }
 
     private TransportTestUtils() {}
diff --git a/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java
new file mode 100644
index 0000000..6d22073
--- /dev/null
+++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowContextImpl.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package com.android.server.testing.shadows;
+
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.UserHandle;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+import org.robolectric.shadows.ShadowContextImpl;
+
+@Implements(className = ShadowContextImpl.CLASS_NAME, inheritImplementationMethods = true)
+public class FrameworkShadowContextImpl extends ShadowContextImpl {
+    @Implementation
+    public boolean bindServiceAsUser(
+            Intent service,
+            ServiceConnection connection,
+            int flags,
+            UserHandle user) {
+        return bindService(service, connection, flags);
+    }
+}
diff --git a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java
similarity index 83%
rename from services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java
rename to services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java
index b64b59d..5cdbe7f 100644
--- a/services/robotests/src/com/android/server/backup/testing/ShadowPackageManagerForBackup.java
+++ b/services/robotests/src/com/android/server/testing/shadows/FrameworkShadowPackageManager.java
@@ -14,22 +14,18 @@
  * limitations under the License
  */
 
-package com.android.server.backup.testing;
+package com.android.server.testing.shadows;
 
 import android.app.ApplicationPackageManager;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
-
+import java.util.List;
 import org.robolectric.annotation.Implements;
 import org.robolectric.shadows.ShadowApplicationPackageManager;
 
-import java.util.List;
-
-/**
- * Implementation of PackageManager for Robolectric which handles queryIntentServicesAsUser().
- */
+/** Extension of ShadowApplicationPackageManager */
 @Implements(value = ApplicationPackageManager.class, inheritImplementationMethods = true)
-public class ShadowPackageManagerForBackup extends ShadowApplicationPackageManager {
+public class FrameworkShadowPackageManager extends ShadowApplicationPackageManager {
     @Override
     public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
         return queryIntentServices(intent, flags);
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
index 766d30d..7b4441a 100644
--- a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -96,7 +96,7 @@
         createBackupManagerService();
 
         verify(mTransportManager)
-                .setTransportBoundListener(any(TransportManager.TransportBoundListener.class));
+                .setOnTransportRegisteredListener(any());
     }
 
     @Test