Binding on-demand #3: updateTransportAttributes() API

This CL introduces the updateTransportAttributes() API to be used by the
transport hosts. It doesn't actually use the description attributes yet,
this will go in another CL. This is because I want to test that CL
together with transport usage. Tests are lacking for TransportManager
and BMS, I'll still add them, but I'm trying to migrate Robolectric
first.

Ref: http://go/br-binding-on-demand
Bug: 17140907
Test: make RunFrameworksServicesRoboTests
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
Change-Id: I56f7b5a5026d21d8f11afb371d5560d4913c5f2a
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index e92a564..b2c02b9 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -61,7 +61,6 @@
 import android.app.backup.ISelectBackupTransportCallback;
 import android.app.backup.RestoreDescription;
 import android.app.backup.RestoreSet;
-import android.app.backup.SelectBackupTransportCallback;
 import android.content.ActivityNotFoundException;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -10465,6 +10464,19 @@
         return mEnabled;    // no need to synchronize just to read it
     }
 
+    @Override
+    public void updateTransportAttributes(
+            ComponentName transportComponent,
+            String name,
+            Intent configurationIntent,
+            String currentDestinationString,
+            Intent dataManagementIntent,
+            String dataManagementLabel) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BACKUP, "updateTransportAttributes");
+        Slog.e(TAG, "Unsupported operation");
+    }
+
     // Report the name of the currently active transport
     @Override
     public String getCurrentTransport() {
diff --git a/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java b/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
index 041f9ed..86462d8 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerServiceInterface.java
@@ -120,6 +120,15 @@
   // Report whether the backup mechanism is currently enabled
   boolean isBackupEnabled();
 
+  // Update the transport attributes
+  void updateTransportAttributes(
+          ComponentName transportComponent,
+          String name,
+          Intent configurationIntent,
+          String currentDestinationString,
+          Intent dataManagementIntent,
+          String dataManagementLabel);
+
   // Report the name of the currently active transport
   String getCurrentTransport();
 
diff --git a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
index 2788218..3eaee9a 100644
--- a/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/RefactoredBackupManagerService.java
@@ -91,8 +91,10 @@
 import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.backup.IBackupTransport;
 import com.android.internal.util.DumpUtils;
+import com.android.internal.util.Preconditions;
 import com.android.server.AppWidgetBackupBridge;
 import com.android.server.EventLogTags;
 import com.android.server.SystemConfig;
@@ -723,8 +725,54 @@
 
     // ----- Main service implementation -----
 
-    public RefactoredBackupManagerService(Context context, Trampoline parent,
+    public static RefactoredBackupManagerService create(
+            Context context,
+            Trampoline parent,
             HandlerThread backupThread) {
+        // Set up our transport options and initialize the default transport
+        SystemConfig systemConfig = SystemConfig.getInstance();
+        Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
+
+        String transport =
+                Settings.Secure.getString(
+                        context.getContentResolver(), Settings.Secure.BACKUP_TRANSPORT);
+        if (TextUtils.isEmpty(transport)) {
+            transport = null;
+        }
+        if (DEBUG) {
+            Slog.v(TAG, "Starting with transport " + transport);
+        }
+        TransportManager transportManager =
+                new TransportManager(
+                        context,
+                        transportWhitelist,
+                        transport,
+                        backupThread.getLooper());
+
+        // If encrypted file systems is enabled or disabled, this call will return the
+        // correct directory.
+        File baseStateDir = new File(Environment.getDataDirectory(), "backup");
+
+        // This dir on /cache is managed directly in init.rc
+        File dataDir = new File(Environment.getDownloadCacheDirectory(), "backup_stage");
+
+        return new RefactoredBackupManagerService(
+                context,
+                parent,
+                backupThread,
+                baseStateDir,
+                dataDir,
+                transportManager);
+    }
+
+    @VisibleForTesting
+    RefactoredBackupManagerService(
+            Context context,
+            Trampoline parent,
+            HandlerThread backupThread,
+            File baseStateDir,
+            File dataDir,
+            TransportManager transportManager) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         mPackageManagerBinder = AppGlobals.getPackageManager();
@@ -751,16 +799,13 @@
                 Settings.Global.getUriFor(Settings.Global.DEVICE_PROVISIONED),
                 false, mProvisionedObserver);
 
-        // If Encrypted file systems is enabled or disabled, this call will return the
-        // correct directory.
-        mBaseStateDir = new File(Environment.getDataDirectory(), "backup");
+        mBaseStateDir = baseStateDir;
         mBaseStateDir.mkdirs();
         if (!SELinux.restorecon(mBaseStateDir)) {
             Slog.e(TAG, "SELinux restorecon failed on " + mBaseStateDir);
         }
 
-        // This dir on /cache is managed directly in init.rc
-        mDataDir = new File(Environment.getDownloadCacheDirectory(), "backup_stage");
+        mDataDir = dataDir;
 
         mBackupPasswordManager = new BackupPasswordManager(mContext, mBaseStateDir, mRng);
 
@@ -803,26 +848,13 @@
             addPackageParticipantsLocked(null);
         }
 
-        // Set up our transport options and initialize the default transport
-        // TODO: Don't create transports that we don't need to?
-        SystemConfig systemConfig = SystemConfig.getInstance();
-        Set<ComponentName> transportWhitelist = systemConfig.getBackupTransportWhitelist();
-
-        String transport = Settings.Secure.getString(context.getContentResolver(),
-                Settings.Secure.BACKUP_TRANSPORT);
-        if (TextUtils.isEmpty(transport)) {
-            transport = null;
-        }
-        String currentTransport = transport;
-        if (DEBUG) Slog.v(TAG, "Starting with transport " + currentTransport);
-
-        mTransportManager = new TransportManager(context, transportWhitelist, currentTransport,
-                mTransportBoundListener, backupThread.getLooper());
+        mTransportManager = transportManager;
+        mTransportManager.setTransportBoundListener(mTransportBoundListener);
         mTransportManager.registerAllTransports();
 
         // Now that we know about valid backup participants, parse any
         // leftover journal files into the pending backup set
-        mBackupHandler.post(() -> parseLeftoverJournals());
+        mBackupHandler.post(this::parseLeftoverJournals);
 
         // Power management
         mWakelock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*backup*");
@@ -1438,14 +1470,6 @@
         }
     }
 
-    // What name is this transport registered under...?
-    private String getTransportName(IBackupTransport transport) {
-        if (MORE_DEBUG) {
-            Slog.v(TAG, "Searching for transport name of " + transport);
-        }
-        return mTransportManager.getTransportName(transport);
-    }
-
     // fire off a backup agent, blocking until it attaches or times out
     @Override
     public IBackupAgent bindToAgentSynchronous(ApplicationInfo app, int mode) {
@@ -2885,6 +2909,93 @@
         return whitelistedTransports;
     }
 
+    /**
+     * Update the attributes of the transport identified by {@code transportComponent}. If the
+     * specified transport has not been bound at least once (for registration), this call will be
+     * ignored. Only the host process of the transport can change its description, otherwise a
+     * {@link SecurityException} will be thrown.
+     *
+     * @param transportComponent The identity of the transport being described.
+     * @param name A {@link String} with the new name for the transport. This is NOT for
+     *     identification. MUST NOT be {@code null}.
+     * @param configurationIntent An {@link Intent} that can be passed to
+     *     {@link Context#startActivity} in order to launch the transport's configuration UI. It may
+     *     be {@code null} if the transport does not offer any user-facing configuration UI.
+     * @param currentDestinationString A {@link String} describing the destination to which the
+     *     transport is currently sending data. MUST NOT be {@code null}.
+     * @param dataManagementIntent An {@link Intent} that can be passed to
+     *     {@link Context#startActivity} in order to launch the transport's data-management UI. It
+     *     may be {@code null} if the transport does not offer any user-facing data
+     *     management UI.
+     * @param dataManagementLabel A {@link String} to be used as the label for the transport's data
+     *     management affordance. This MUST be {@code null} when dataManagementIntent is
+     *     {@code null} and MUST NOT be {@code null} when dataManagementIntent is not {@code null}.
+     * @throws SecurityException If the UID of the calling process differs from the package UID of
+     *     {@code transportComponent} or if the caller does NOT have BACKUP permission.
+     */
+    @Override
+    public void updateTransportAttributes(
+            ComponentName transportComponent,
+            String name,
+            @Nullable Intent configurationIntent,
+            String currentDestinationString,
+            @Nullable Intent dataManagementIntent,
+            @Nullable String dataManagementLabel) {
+        updateTransportAttributes(
+                Binder.getCallingUid(),
+                transportComponent,
+                name,
+                configurationIntent,
+                currentDestinationString,
+                dataManagementIntent,
+                dataManagementLabel);
+    }
+
+    @VisibleForTesting
+    void updateTransportAttributes(
+            int callingUid,
+            ComponentName transportComponent,
+            String name,
+            @Nullable Intent configurationIntent,
+            String currentDestinationString,
+            @Nullable Intent dataManagementIntent,
+            @Nullable String dataManagementLabel) {
+        mContext.enforceCallingOrSelfPermission(
+                android.Manifest.permission.BACKUP, "updateTransportAttributes");
+
+        Preconditions.checkNotNull(transportComponent, "transportComponent can't be null");
+        Preconditions.checkNotNull(name, "name can't be null");
+        Preconditions.checkNotNull(
+                currentDestinationString, "currentDestinationString can't be null");
+        Preconditions.checkArgument(
+                (dataManagementIntent == null) == (dataManagementLabel == null),
+                "dataManagementLabel should be null iff dataManagementIntent is null");
+
+        try {
+            int transportUid =
+                    mContext.getPackageManager()
+                            .getPackageUid(transportComponent.getPackageName(), 0);
+            if (callingUid != transportUid) {
+                throw new SecurityException("Only the transport can change its description");
+            }
+        } catch (NameNotFoundException e) {
+            throw new SecurityException("Transport package not found", e);
+        }
+
+        final long oldId = Binder.clearCallingIdentity();
+        try {
+            mTransportManager.describeTransport(
+                    transportComponent,
+                    name,
+                    configurationIntent,
+                    currentDestinationString,
+                    dataManagementIntent,
+                    dataManagementLabel);
+        } finally {
+            Binder.restoreCallingIdentity(oldId);
+        }
+    }
+
     // Select which transport to use for the next backup operation.
     @Override
     public String selectBackupTransport(String transport) {
diff --git a/services/backup/java/com/android/server/backup/Trampoline.java b/services/backup/java/com/android/server/backup/Trampoline.java
index 9847edf..8c0329c 100644
--- a/services/backup/java/com/android/server/backup/Trampoline.java
+++ b/services/backup/java/com/android/server/backup/Trampoline.java
@@ -16,6 +16,7 @@
 
 package com.android.server.backup;
 
+import android.annotation.Nullable;
 import android.app.backup.BackupManager;
 import android.app.backup.IBackupManager;
 import android.app.backup.IBackupObserver;
@@ -31,7 +32,6 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
-import android.os.Looper;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
 import android.os.RemoteException;
@@ -117,7 +117,7 @@
     }
 
     protected BackupManagerServiceInterface createRefactoredBackupManagerService() {
-        return new RefactoredBackupManagerService(mContext, this, mHandlerThread);
+        return RefactoredBackupManagerService.create(mContext, this, mHandlerThread);
     }
 
     protected BackupManagerServiceInterface createBackupManagerService() {
@@ -378,6 +378,26 @@
     }
 
     @Override
+    public void updateTransportAttributes(
+            ComponentName transportComponent,
+            String name,
+            @Nullable Intent configurationIntent,
+            String currentDestinationString,
+            @Nullable Intent dataManagementIntent,
+            String dataManagementLabel) {
+        BackupManagerServiceInterface svc = mService;
+        if (svc != null) {
+            svc.updateTransportAttributes(
+                    transportComponent,
+                    name,
+                    configurationIntent,
+                    currentDestinationString,
+                    dataManagementIntent,
+                    dataManagementLabel);
+        }
+    }
+
+    @Override
     public String selectBackupTransport(String transport) throws RemoteException {
         BackupManagerServiceInterface svc = mService;
         return (svc != null) ? svc.selectBackupTransport(transport) : null;
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index a2b5cb8..f8f1448 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -16,9 +16,10 @@
 
 package com.android.server.backup;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
 import android.annotation.Nullable;
 import android.app.backup.BackupManager;
-import android.app.backup.BackupTransport;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -54,6 +55,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Predicate;
 
 /**
  * Handles in-memory bookkeeping of all BackupTransport objects.
@@ -80,11 +82,8 @@
      * This listener is called after we bind to any transport. If it returns true, this is a valid
      * transport.
      */
-    private final TransportBoundListener mTransportBoundListener;
+    private TransportBoundListener mTransportBoundListener;
 
-    private String mCurrentTransportName;
-
-    /** Lock on this before accessing mValidTransports and mBoundTransports. */
     private final Object mTransportLock = new Object();
 
     /**
@@ -98,9 +97,18 @@
     @GuardedBy("mTransportLock")
     private final Map<String, ComponentName> mBoundTransports = new ArrayMap<>();
 
-    /** Names of transports we've bound to at least once */
+    /** @see #getEligibleTransportComponents() */
     @GuardedBy("mTransportLock")
-    private final Map<String, ComponentName> mTransportsByName = new ArrayMap<>();
+    private final Set<ComponentName> mEligibleTransports = new ArraySet<>();
+
+    /** @see #getRegisteredTransportNames() */
+    @GuardedBy("mTransportLock")
+    private final Map<ComponentName, TransportDescription> mRegisteredTransportsDescriptionMap =
+            new ArrayMap<>();
+
+    @GuardedBy("mTransportLock")
+    private volatile String mCurrentTransportName;
+
 
     /**
      * Callback interface for {@link #ensureTransportReady(ComponentName, TransportReadyCallback)}.
@@ -118,8 +126,21 @@
         void onFailure(int reason);
     }
 
-    TransportManager(Context context, Set<ComponentName> whitelist, String defaultTransport,
-            TransportBoundListener listener, Looper looper) {
+    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 defaultTransport,
+            Looper looper) {
         mContext = context;
         mPackageManager = context.getPackageManager();
         if (whitelist != null) {
@@ -128,11 +149,14 @@
             mTransportWhitelist = new ArraySet<>();
         }
         mCurrentTransportName = defaultTransport;
-        mTransportBoundListener = listener;
         mHandler = new RebindOnTimeoutHandler(looper);
         mTransportClientManager = new TransportClientManager(context);
     }
 
+    public void setTransportBoundListener(TransportBoundListener transportBoundListener) {
+        mTransportBoundListener = transportBoundListener;
+    }
+
     void onPackageAdded(String packageName) {
         // New package added. Bind to all transports it contains.
         synchronized (mTransportLock) {
@@ -161,6 +185,8 @@
                     }
                 }
             }
+            removeTransportsIfLocked(
+                    componentName -> packageName.equals(componentName.getPackageName()));
         }
     }
 
@@ -168,8 +194,10 @@
         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);
@@ -177,10 +205,17 @@
                             componentName.flattenToShortString());
                 }
             }
+            removeTransportsIfLocked(transportsToBeRemoved::contains);
             bindToAllInternal(packageName, components);
         }
     }
 
+    @GuardedBy("mTransportLock")
+    private void removeTransportsIfLocked(Predicate<ComponentName> filter) {
+        mEligibleTransports.removeIf(filter);
+        mRegisteredTransportsDescriptionMap.keySet().removeIf(filter);
+    }
+
     public IBackupTransport getTransportBinder(String transportName) {
         synchronized (mTransportLock) {
             ComponentName component = mBoundTransports.get(transportName);
@@ -213,39 +248,64 @@
     }
 
     /**
-     * Returns the transport name associated with {@param transportClient} or {@code null} if not
+     * Returns the transport name associated with {@param transportComponent} or {@code null} if not
      * found.
      */
     @Nullable
-    public String getTransportName(TransportClient transportClient) {
-        ComponentName transportComponent = transportClient.getTransportComponent();
+    public String getTransportName(ComponentName transportComponent) {
         synchronized (mTransportLock) {
-            for (Map.Entry<String, ComponentName> transportEntry : mTransportsByName.entrySet()) {
-                if (transportEntry.getValue().equals(transportComponent)) {
-                    return transportEntry.getKey();
-                }
+            TransportDescription description =
+                    mRegisteredTransportsDescriptionMap.get(transportComponent);
+            if (description == null) {
+                Slog.e(TAG, "Trying to find name of unregistered transport " + transportComponent);
+                return null;
             }
-            return null;
+            return description.name;
         }
     }
 
-    /**
-     * Returns a {@link TransportClient} for {@param transportName} or {@code null} if not found.
-     *
-     * @param transportName The name of the transport as returned by {@link BackupTransport#name()}.
-     * @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.
-     */
+    @GuardedBy("mTransportLock")
+    @Nullable
+    private ComponentName getRegisteredTransportComponentLocked(String transportName) {
+        Map.Entry<ComponentName, TransportDescription> entry =
+                getRegisteredTransportEntryLocked(transportName);
+        return (entry == null) ? null : entry.getKey();
+    }
+
+    @GuardedBy("mTransportLock")
+    @Nullable
+    private TransportDescription getRegisteredTransportDescriptionLocked(String transportName) {
+        Map.Entry<ComponentName, TransportDescription> entry =
+                getRegisteredTransportEntryLocked(transportName);
+        return (entry == null) ? null : entry.getValue();
+    }
+
+    @GuardedBy("mTransportLock")
+    @Nullable
+    private Map.Entry<ComponentName, TransportDescription> getRegisteredTransportEntryLocked(
+            String transportName) {
+        for (Map.Entry<ComponentName, TransportDescription> entry
+                : mRegisteredTransportsDescriptionMap.entrySet()) {
+            TransportDescription description = entry.getValue();
+            if (transportName.equals(description.name)) {
+                return entry;
+            }
+        }
+        return null;
+    }
+
     @Nullable
     public TransportClient getTransportClient(String transportName, String caller) {
-        ComponentName transportComponent = mTransportsByName.get(transportName);
-        if (transportComponent == null) {
-            Slog.w(TAG, "Transport " + transportName + " not registered");
-            return null;
+        synchronized (mTransportLock) {
+            ComponentName component = getRegisteredTransportComponentLocked(transportName);
+            if (component == null) {
+                Slog.w(TAG, "Transport " + transportName + " not registered");
+                return null;
+            }
+            TransportDescription description = mRegisteredTransportsDescriptionMap.get(component);
+            return mTransportClientManager.getTransportClient(
+                    component, description.transportDirName, caller);
         }
-        return mTransportClientManager.getTransportClient(transportComponent, caller);
     }
 
     /**
@@ -285,14 +345,67 @@
         }
     }
 
-    String getCurrentTransportName() {
-        return mCurrentTransportName;
+    /**
+     * 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 describeTransport(
+            ComponentName transportComponent,
+            String name,
+            @Nullable Intent configurationIntent,
+            String currentDestinationString,
+            @Nullable Intent dataManagementIntent,
+            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;
+        }
+    }
+
+    @Nullable
+    String getCurrentTransportName() {
+        return mCurrentTransportName;
+    }
+
     String selectTransport(String transport) {
         synchronized (mTransportLock) {
             String prevTransport = mCurrentTransportName;
@@ -315,7 +428,12 @@
         }
     }
 
-    void registerAllTransports() {
+
+    // 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 */);
     }
 
@@ -391,6 +509,9 @@
         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);
@@ -407,9 +528,25 @@
                 createSystemUserHandle());
     }
 
+    /** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */
+    private void registerTransport(ComponentName transportComponent, IBackupTransport transport)
+            throws RemoteException {
+        synchronized (mTransportLock) {
+            String name = transport.name();
+            TransportDescription description = new TransportDescription(
+                    name,
+                    transport.transportDirName(),
+                    transport.configurationIntent(),
+                    transport.currentDestinationString(),
+                    transport.dataManagementIntent(),
+                    transport.dataManagementLabel());
+            mRegisteredTransportsDescriptionMap.put(transportComponent, description);
+        }
+    }
+
     private class TransportConnection implements ServiceConnection {
 
-        // Hold mTransportsLock to access these fields so as to provide a consistent view of them.
+        // Hold mTransportLock to access these fields so as to provide a consistent view of them.
         private volatile IBackupTransport mBinder;
         private final List<TransportReadyCallback> mListeners = new ArrayList<>();
         private volatile String mTransportName;
@@ -433,7 +570,22 @@
                     mTransportName = mBinder.name();
                     // BackupManager requests some fields from the transport. If they are
                     // invalid, throw away this transport.
-                    success = mTransportBoundListener.onTransportBound(mBinder);
+                    final boolean valid;
+                    if (mTransportBoundListener != null) {
+                        valid = mTransportBoundListener.onTransportBound(mBinder);
+                    } else {
+                        Slog.w(TAG, "setTransportBoundListener() not called, assuming transport "
+                                + component + " valid");
+                        valid = true;
+                    }
+                    if (valid) {
+                        // 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);
@@ -443,7 +595,6 @@
                     String componentShortString = component.flattenToShortString().intern();
                     if (success) {
                         Slog.d(TAG, "Bound to transport: " + componentShortString);
-                        mTransportsByName.put(mTransportName, component);
                         mBoundTransports.put(mTransportName, component);
                         for (TransportReadyCallback listener : mListeners) {
                             listener.onSuccess(mTransportName);
@@ -457,6 +608,7 @@
                                 componentShortString, 0);
                         mContext.unbindService(this);
                         mValidTransports.remove(component);
+                        mEligibleTransports.remove(component);
                         mBinder = null;
                         for (TransportReadyCallback listener : mListeners) {
                             listener.onFailure(BackupManager.ERROR_TRANSPORT_INVALID);
@@ -601,4 +753,28 @@
     public static UserHandle createSystemUserHandle() {
         return new UserHandle(UserHandle.USER_SYSTEM);
     }
+
+    private static class TransportDescription {
+        private String name;
+        private final String transportDirName;
+        @Nullable private Intent configurationIntent;
+        private String currentDestinationString;
+        @Nullable private Intent dataManagementIntent;
+        private String dataManagementLabel;
+
+        private TransportDescription(
+                String name,
+                String transportDirName,
+                @Nullable Intent configurationIntent,
+                String currentDestinationString,
+                @Nullable Intent dataManagementIntent,
+                String dataManagementLabel) {
+            this.name = name;
+            this.transportDirName = transportDirName;
+            this.configurationIntent = configurationIntent;
+            this.currentDestinationString = currentDestinationString;
+            this.dataManagementIntent = dataManagementIntent;
+            this.dataManagementLabel = dataManagementLabel;
+        }
+    }
 }
diff --git a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
index 5be1b39..e65eb28 100644
--- a/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
+++ b/services/backup/java/com/android/server/backup/internal/PerformBackupTask.java
@@ -560,8 +560,8 @@
                 }
                 backupManagerService.addBackupTrace("init required; rerunning");
                 try {
-                    final String name = backupManagerService.getTransportManager().getTransportName(
-                            mTransportClient);
+                    final String name = backupManagerService.getTransportManager()
+                            .getTransportName(mTransportClient.getTransportComponent());
                     if (name != null) {
                         backupManagerService.getPendingInits().add(name);
                     } else {
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 65f9502..2c7a0eb 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClient.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClient.java
@@ -68,6 +68,7 @@
     private final Intent mBindIntent;
     private final String mIdentifier;
     private final ComponentName mTransportComponent;
+    private final String mTransportDirName;
     private final Handler mListenerHandler;
     private final String mPrefixForLog;
     private final Object mStateLock = new Object();
@@ -86,8 +87,15 @@
             Context context,
             Intent bindIntent,
             ComponentName transportComponent,
+            String transportDirName,
             String identifier) {
-        this(context, bindIntent, transportComponent, identifier, Handler.getMain());
+        this(
+                context,
+                bindIntent,
+                transportComponent,
+                transportDirName,
+                identifier,
+                new Handler(Looper.getMainLooper()));
     }
 
     @VisibleForTesting
@@ -95,10 +103,12 @@
             Context context,
             Intent bindIntent,
             ComponentName transportComponent,
+            String transportDirName,
             String identifier,
             Handler listenerHandler) {
         mContext = context;
         mTransportComponent = transportComponent;
+        mTransportDirName = transportDirName;
         mBindIntent = bindIntent;
         mIdentifier = identifier;
         mListenerHandler = listenerHandler;
@@ -112,6 +122,10 @@
         return mTransportComponent;
     }
 
+    public String getTransportDirName() {
+        return mTransportDirName;
+    }
+
     // Calls to onServiceDisconnected() or onBindingDied() turn TransportClient UNUSABLE. After one
     // of these calls, if a binding happen again the new service can be a different instance. Since
     // transports are stateful, we don't want a new instance responding for an old instance's state.
diff --git a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
index 1cbe7471..bb550f6 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportClientManager.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.util.Log;
 
+import com.android.internal.backup.IBackupTransport;
 import com.android.server.backup.TransportManager;
 
 /**
@@ -47,12 +48,17 @@
      * transportComponent}.
      *
      * @param transportComponent The {@link ComponentName} of the transport.
+     * @param transportDirName The {@link String} returned by
+     *     {@link IBackupTransport#transportDirName()} at registration.
      * @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}.
      */
-    public TransportClient getTransportClient(ComponentName transportComponent, String caller) {
+    public TransportClient getTransportClient(
+            ComponentName transportComponent,
+            String transportDirName,
+            String caller) {
         Intent bindIntent =
                 new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(transportComponent);
         synchronized (mTransportClientsLock) {
@@ -61,6 +67,7 @@
                             mContext,
                             bindIntent,
                             transportComponent,
+                            transportDirName,
                             Integer.toString(mTransportClientsCreated));
             mTransportClientsCreated++;
             TransportUtils.log(Log.DEBUG, TAG, caller, "Retrieving " + transportClient);
diff --git a/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java b/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java
index c4e5a1d..c08eb7f 100644
--- a/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java
+++ b/services/backup/java/com/android/server/backup/transport/TransportNotAvailableException.java
@@ -16,6 +16,8 @@
 
 package com.android.server.backup.transport;
 
+import android.util.AndroidException;
+
 import com.android.internal.backup.IBackupTransport;
 
 /**
@@ -25,7 +27,7 @@
  *
  * @see TransportClient#connectAsync(TransportConnectionListener, String)
  */
-public class TransportNotAvailableException extends Exception {
+public class TransportNotAvailableException extends AndroidException {
     TransportNotAvailableException() {
         super("Transport not available");
     }
diff --git a/services/robotests/src/com/android/server/backup/TransportManagerTest.java b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
index 2824b35..3d5851f 100644
--- a/services/robotests/src/com/android/server/backup/TransportManagerTest.java
+++ b/services/robotests/src/com/android/server/backup/TransportManagerTest.java
@@ -36,6 +36,7 @@
 import com.android.server.backup.testing.ShadowContextImplForBackup;
 import com.android.server.backup.testing.TransportBoundListenerStub;
 import com.android.server.backup.testing.TransportReadyCallbackStub;
+import com.android.server.backup.transport.TransportClient;
 
 import org.junit.After;
 import org.junit.Before;
@@ -475,6 +476,62 @@
         assertThat(mTransportReadyCallbackStub.getFailureCalls()).isEmpty();
     }
 
+    @Test
+    public void getTransportClient_forRegisteredTransport_returnCorrectly() throws Exception {
+        TransportManager transportManager =
+                createTransportManagerAndSetUpTransports(
+                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+
+        TransportClient transportClient =
+                transportManager.getTransportClient(mTransport1.name, "caller");
+
+        assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName);
+    }
+
+    @Test
+    public void getTransportClient_forOldNameOfTransportThatChangedName_returnsNull()
+            throws Exception {
+        TransportManager transportManager =
+                createTransportManagerAndSetUpTransports(
+                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+        transportManager.describeTransport(
+                mTransport1.componentName, "newName", null, "destinationString", null, null);
+
+        TransportClient transportClient =
+                transportManager.getTransportClient(mTransport1.name, "caller");
+
+        assertThat(transportClient).isNull();
+    }
+
+    @Test
+    public void getTransportClient_forNewNameOfTransportThatChangedName_returnsCorrectly()
+            throws Exception {
+        TransportManager transportManager =
+                createTransportManagerAndSetUpTransports(
+                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+        transportManager.describeTransport(
+                mTransport1.componentName, "newName", null, "destinationString", null, null);
+
+        TransportClient transportClient =
+                transportManager.getTransportClient("newName", "caller");
+
+        assertThat(transportClient.getTransportComponent()).isEqualTo(mTransport1.componentName);
+    }
+
+    @Test
+    public void getTransportName_forTransportThatChangedName_returnsNewName()
+            throws Exception {
+        TransportManager transportManager =
+                createTransportManagerAndSetUpTransports(
+                        Arrays.asList(mTransport1, mTransport2), mTransport1.name);
+        transportManager.describeTransport(
+                mTransport1.componentName, "newName", null, "destinationString", null, null);
+
+        String transportName = transportManager.getTransportName(mTransport1.componentName);
+
+        assertThat(transportName).isEqualTo("newName");
+    }
+
     private void setUpPackageWithTransports(String packageName, List<TransportInfo> transports,
             int flags) throws Exception {
         PackageInfo packageInfo = new PackageInfo();
diff --git a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
index 54d233a..4a3277f 100644
--- a/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
+++ b/services/robotests/src/com/android/server/backup/transport/TransportClientTest.java
@@ -17,6 +17,7 @@
 package com.android.server.backup.transport;
 
 import static com.android.server.backup.TransportManager.SERVICE_ACTION_TRANSPORT_HOST;
+import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -54,6 +55,7 @@
     private static final String PACKAGE_NAME = "some.package.name";
     private static final ComponentName TRANSPORT_COMPONENT =
             new ComponentName(PACKAGE_NAME, PACKAGE_NAME + ".transport.Transport");
+    private static final String TRANSPORT_DIR_NAME = TRANSPORT_COMPONENT.toString();
 
     @Mock private Context mContext;
     @Mock private TransportConnectionListener mTransportConnectionListener;
@@ -72,7 +74,12 @@
         mBindIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST).setComponent(TRANSPORT_COMPONENT);
         mTransportClient =
                 new TransportClient(
-                        mContext, mBindIntent, TRANSPORT_COMPONENT, "1", new Handler(mainLooper));
+                        mContext,
+                        mBindIntent,
+                        TRANSPORT_COMPONENT,
+                        TRANSPORT_DIR_NAME,
+                        "1",
+                        new Handler(mainLooper));
 
         when(mContext.bindServiceAsUser(
                         eq(mBindIntent),
@@ -82,7 +89,16 @@
                 .thenReturn(true);
     }
 
-    // TODO: Testing implementation? Remove?
+    @Test
+    public void testGetTransportDirName_returnsTransportDirName() {
+        assertThat(mTransportClient.getTransportDirName()).isEqualTo(TRANSPORT_DIR_NAME);
+    }
+
+    @Test
+    public void testGetTransportComponent_returnsTransportComponent() {
+        assertThat(mTransportClient.getTransportComponent()).isEqualTo(TRANSPORT_COMPONENT);
+    }
+
     @Test
     public void testConnectAsync_callsBindService() throws Exception {
         mTransportClient.connectAsync(mTransportConnectionListener, "caller");
diff --git a/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
new file mode 100644
index 0000000..362856c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/backup/BackupManagerServiceTest.java
@@ -0,0 +1,324 @@
+/*
+ * 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;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doThrow;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.testng.Assert.expectThrows;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.os.HandlerThread;
+import android.platform.test.annotations.Presubmit;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.Log;
+
+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 java.io.File;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupManagerServiceTest {
+    private static final String TAG = "BMSTest";
+    private static final ComponentName TRANSPORT_COMPONENT =
+            new ComponentName(
+                    "com.google.android.gms",
+                    "com.google.android.gms.backup.BackupTransportService");
+    private static final String TRANSPORT_NAME = TRANSPORT_COMPONENT.flattenToShortString();
+
+    @Mock private TransportManager mTransportManager;
+    private Context mContext;
+    private HandlerThread mBackupThread;
+    private int mPackageUid;
+    private File mBaseStateDir;
+    private File mDataDir;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        Context baseContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+        mContext = spy(new ContextWrapper(baseContext));
+
+        mBackupThread = new HandlerThread("backup-test");
+        mBackupThread.setUncaughtExceptionHandler(
+                (t, e) -> Log.e(TAG, "Uncaught exception in test thread " + t.getName(), e));
+        mBackupThread.start();
+
+        File cacheDir = mContext.getCacheDir();
+        mBaseStateDir = new File(cacheDir, "base_state_dir");
+        mDataDir = new File(cacheDir, "data_dir");
+
+        mPackageUid =
+                mContext.getPackageManager().getPackageUid(TRANSPORT_COMPONENT.getPackageName(), 0);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mBackupThread.quit();
+        mBaseStateDir.delete();
+        mDataDir.delete();
+    }
+
+    @Test
+    public void testConstructor_callsTransportManagerSetTransportBoundListener() throws Exception {
+        createBackupManagerService();
+
+        verify(mTransportManager)
+                .setTransportBoundListener(any(TransportManager.TransportBoundListener.class));
+    }
+
+    @Test
+    public void
+            testUpdateTransportAttributes_whenTransportUidEqualsToCallingUid_callsThroughToTransportManager()
+                    throws Exception {
+        grantBackupPermission();
+        RefactoredBackupManagerService backupManagerService = createBackupManagerService();
+        Intent configurationIntent = new Intent();
+        Intent dataManagementIntent = new Intent();
+
+        backupManagerService.updateTransportAttributes(
+                mPackageUid,
+                TRANSPORT_COMPONENT,
+                TRANSPORT_NAME,
+                configurationIntent,
+                "currentDestinationString",
+                dataManagementIntent,
+                "dataManagementLabel");
+
+        verify(mTransportManager)
+                .describeTransport(
+                        eq(TRANSPORT_COMPONENT),
+                        eq(TRANSPORT_NAME),
+                        eq(configurationIntent),
+                        eq("currentDestinationString"),
+                        eq(dataManagementIntent),
+                        eq("dataManagementLabel"));
+    }
+
+    @Test
+    public void testUpdateTransportAttributes_whenTransportUidNotEqualToCallingUid_throwsException()
+            throws Exception {
+        grantBackupPermission();
+        RefactoredBackupManagerService backupManagerService = createBackupManagerService();
+
+        expectThrows(
+                SecurityException.class,
+                () ->
+                        backupManagerService.updateTransportAttributes(
+                                mPackageUid + 1,
+                                TRANSPORT_COMPONENT,
+                                TRANSPORT_NAME,
+                                new Intent(),
+                                "currentDestinationString",
+                                new Intent(),
+                                "dataManagementLabel"));
+    }
+
+    @Test
+    public void testUpdateTransportAttributes_whenTransportComponentNull_throwsException() {
+        grantBackupPermission();
+        RefactoredBackupManagerService backupManagerService = createBackupManagerService();
+
+        expectThrows(
+                RuntimeException.class,
+                () ->
+                        backupManagerService.updateTransportAttributes(
+                                mPackageUid,
+                                null,
+                                TRANSPORT_NAME,
+                                new Intent(),
+                                "currentDestinationString",
+                                new Intent(),
+                                "dataManagementLabel"));
+    }
+
+    @Test
+    public void testUpdateTransportAttributes_whenNameNull_throwsException() {
+        grantBackupPermission();
+        RefactoredBackupManagerService backupManagerService = createBackupManagerService();
+
+        expectThrows(
+                RuntimeException.class,
+                () ->
+                        backupManagerService.updateTransportAttributes(
+                                mPackageUid,
+                                TRANSPORT_COMPONENT,
+                                null,
+                                new Intent(),
+                                "currentDestinationString",
+                                new Intent(),
+                                "dataManagementLabel"));
+    }
+
+    @Test
+    public void testUpdateTransportAttributes_whenCurrentDestinationStringNull_throwsException() {
+        grantBackupPermission();
+        RefactoredBackupManagerService backupManagerService = createBackupManagerService();
+
+        expectThrows(
+                RuntimeException.class,
+                () ->
+                        backupManagerService.updateTransportAttributes(
+                                mPackageUid,
+                                TRANSPORT_COMPONENT,
+                                TRANSPORT_NAME,
+                                new Intent(),
+                                null,
+                                new Intent(),
+                                "dataManagementLabel"));
+    }
+
+    @Test
+    public void
+            testUpdateTransportAttributes_whenDataManagementArgumentsNullityDontMatch_throwsException() {
+        grantBackupPermission();
+        RefactoredBackupManagerService backupManagerService = createBackupManagerService();
+
+        expectThrows(
+                RuntimeException.class,
+                () ->
+                        backupManagerService.updateTransportAttributes(
+                                mPackageUid,
+                                TRANSPORT_COMPONENT,
+                                TRANSPORT_NAME,
+                                new Intent(),
+                                "currentDestinationString",
+                                null,
+                                "dataManagementLabel"));
+
+        expectThrows(
+                RuntimeException.class,
+                () ->
+                        backupManagerService.updateTransportAttributes(
+                                mPackageUid,
+                                TRANSPORT_COMPONENT,
+                                TRANSPORT_NAME,
+                                new Intent(),
+                                "currentDestinationString",
+                                new Intent(),
+                                null));
+    }
+
+    @Test
+    public void
+            testUpdateTransportAttributes_whenDataManagementArgumentsNull_callsThroughToTransportManager() {
+        grantBackupPermission();
+        RefactoredBackupManagerService backupManagerService = createBackupManagerService();
+        Intent configurationIntent = new Intent();
+
+        backupManagerService.updateTransportAttributes(
+                mPackageUid,
+                TRANSPORT_COMPONENT,
+                TRANSPORT_NAME,
+                configurationIntent,
+                "currentDestinationString",
+                null,
+                null);
+
+        verify(mTransportManager)
+                .describeTransport(
+                        eq(TRANSPORT_COMPONENT),
+                        eq(TRANSPORT_NAME),
+                        eq(configurationIntent),
+                        eq("currentDestinationString"),
+                        eq(null),
+                        eq(null));
+    }
+
+    @Test
+    public void
+            testUpdateTransportAttributes_whenPermissionGranted_callsThroughToTransportManager() {
+        grantBackupPermission();
+        RefactoredBackupManagerService backupManagerService = createBackupManagerService();
+        Intent configurationIntent = new Intent();
+        Intent dataManagementIntent = new Intent();
+
+        backupManagerService.updateTransportAttributes(
+                mPackageUid,
+                TRANSPORT_COMPONENT,
+                TRANSPORT_NAME,
+                configurationIntent,
+                "currentDestinationString",
+                dataManagementIntent,
+                "dataManagementLabel");
+
+        verify(mTransportManager)
+                .describeTransport(
+                        eq(TRANSPORT_COMPONENT),
+                        eq(TRANSPORT_NAME),
+                        eq(configurationIntent),
+                        eq("currentDestinationString"),
+                        eq(dataManagementIntent),
+                        eq("dataManagementLabel"));
+    }
+
+    @Test
+    public void testUpdateTransportAttributes_whenPermissionDenied_throwsSecurityException() {
+        denyBackupPermission();
+        RefactoredBackupManagerService backupManagerService = createBackupManagerService();
+
+        expectThrows(
+                SecurityException.class,
+                () ->
+                        backupManagerService.updateTransportAttributes(
+                                mPackageUid,
+                                TRANSPORT_COMPONENT,
+                                TRANSPORT_NAME,
+                                new Intent(),
+                                "currentDestinationString",
+                                new Intent(),
+                                "dataManagementLabel"));
+    }
+
+    private RefactoredBackupManagerService createBackupManagerService() {
+        return new RefactoredBackupManagerService(
+                mContext,
+                new Trampoline(mContext),
+                mBackupThread,
+                mBaseStateDir,
+                mDataDir,
+                mTransportManager);
+    }
+
+    private void grantBackupPermission() {
+        doNothing().when(mContext).enforceCallingOrSelfPermission(any(), anyString());
+    }
+
+    private void denyBackupPermission() {
+        doThrow(new SecurityException())
+                .when(mContext)
+                .enforceCallingOrSelfPermission(
+                        eq(android.Manifest.permission.BACKUP), anyString());
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
index 27ef9d7..ca93663 100644
--- a/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
+++ b/services/tests/servicestests/src/com/android/server/backup/TrampolineTest.java
@@ -559,6 +559,24 @@
     }
 
     @Test
+    public void describeTransport_calledBeforeInitialize_ignored() throws RemoteException {
+        mTrampoline.updateTransportAttributes(TRANSPORT_COMPONENT_NAME, TRANSPORT_NAME, null,
+                "Transport Destination", null, "Data Management");
+        verifyNoMoreInteractions(mBackupManagerServiceMock);
+    }
+
+    @Test
+    public void describeTransport_forwarded() throws RemoteException {
+        when(mBackupManagerServiceMock.getTransportWhitelist()).thenReturn(TRANSPORTS);
+
+        mTrampoline.initialize(UserHandle.USER_SYSTEM);
+        mTrampoline.updateTransportAttributes(TRANSPORT_COMPONENT_NAME, TRANSPORT_NAME, null,
+                "Transport Destination", null, "Data Management");
+        verify(mBackupManagerServiceMock).updateTransportAttributes(TRANSPORT_COMPONENT_NAME,
+                TRANSPORT_NAME, null, "Transport Destination", null, "Data Management");
+    }
+
+    @Test
     public void selectBackupTransport_calledBeforeInitialize_ignored() throws RemoteException {
         mTrampoline.selectBackupTransport(TRANSPORT_NAME);
         verifyNoMoreInteractions(mBackupManagerServiceMock);