summaryrefslogtreecommitdiff
path: root/com/android/server/backup/TransportManager.java
blob: 7e179e5d24f867147c5d2f8b04647e151df2ebc6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
/*
 * 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 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.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.RemoteException;
import android.os.UserHandle;
import android.util.ArrayMap;
import android.util.ArraySet;
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.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.io.PrintWriter;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Predicate;

/** 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 final Intent mTransportServiceIntent = new Intent(SERVICE_ACTION_TRANSPORT_HOST);
    private final Context mContext;
    private final PackageManager mPackageManager;
    private final Set<ComponentName> mTransportWhitelist;
    private final TransportClientManager mTransportClientManager;
    private OnTransportRegisteredListener mOnTransportRegisteredListener = (c, n) -> {};

    /**
     * Lock for registered transports and currently selected transport.
     *
     * <p><b>Warning:</b> No calls to {@link IBackupTransport} or calls that result in transport
     * code being executed such as {@link TransportClient#connect(String)}} and its variants should
     * be made with this lock held, risk of deadlock.
     */
    private final Object mTransportLock = new Object();

    /** @see #getRegisteredTransportNames() */
    @GuardedBy("mTransportLock")
    private final Map<ComponentName, TransportDescription> mRegisteredTransportsDescriptionMap =
            new ArrayMap<>();

    @GuardedBy("mTransportLock")
    @Nullable
    private volatile String mCurrentTransportName;

    TransportManager(Context context, Set<ComponentName> whitelist, String selectedTransport) {
        this(context, whitelist, selectedTransport, new TransportClientManager(context));
    }

    @VisibleForTesting
    TransportManager(
            Context context,
            Set<ComponentName> whitelist,
            String selectedTransport,
            TransportClientManager transportClientManager) {
        mContext = context;
        mPackageManager = context.getPackageManager();
        mTransportWhitelist = Preconditions.checkNotNull(whitelist);
        mCurrentTransportName = selectedTransport;
        mTransportClientManager = transportClientManager;
    }

    /* Sets a listener to be called whenever a transport is registered. */
    public void setOnTransportRegisteredListener(OnTransportRegisteredListener listener) {
        mOnTransportRegisteredListener = listener;
    }

    @WorkerThread
    void onPackageAdded(String packageName) {
        registerTransportsFromPackage(packageName, transportComponent -> true);
    }

    void onPackageRemoved(String packageName) {
        synchronized (mTransportLock) {
            mRegisteredTransportsDescriptionMap.keySet().removeIf(fromPackageFilter(packageName));
        }
    }

    @WorkerThread
    void onPackageChanged(String packageName, String... components) {
        // Unfortunately this can't be atomic because we risk a deadlock if
        // registerTransportsFromPackage() is put inside the synchronized block
        Set<ComponentName> transportComponents = new ArraySet<>(components.length);
        for (String componentName : components) {
            transportComponents.add(new ComponentName(packageName, componentName));
        }
        synchronized (mTransportLock) {
            mRegisteredTransportsDescriptionMap.keySet().removeIf(transportComponents::contains);
        }
        registerTransportsFromPackage(packageName, transportComponents::contains);
    }

    /**
     * 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) {
            return mRegisteredTransportsDescriptionMap
                    .keySet()
                    .toArray(new ComponentName[mRegisteredTransportsDescriptionMap.size()]);
        }
    }

    /**
     * Returns the names of the registered transports.
     *
     * @see #getRegisteredTransportComponents()
     */
    String[] getRegisteredTransportNames() {
        synchronized (mTransportLock) {
            String[] transportNames = new String[mRegisteredTransportsDescriptionMap.size()];
            int i = 0;
            for (TransportDescription description : mRegisteredTransportsDescriptionMap.values()) {
                transportNames[i] = description.name;
                i++;
            }
            return transportNames;
        }
    }

    /** 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)
            throws TransportNotRegisteredException {
        synchronized (mTransportLock) {
            return getRegisteredTransportDescriptionOrThrowLocked(transportComponent).name;
        }
    }

    /**
     * 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
    public Intent getTransportConfigurationIntent(String transportName)
            throws TransportNotRegisteredException {
        synchronized (mTransportLock) {
            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
                    .configurationIntent;
        }
    }

    /**
     * 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
    public Intent getTransportDataManagementIntent(String transportName)
            throws TransportNotRegisteredException {
        synchronized (mTransportLock) {
            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
                    .dataManagementIntent;
        }
    }

    /**
     * Retrieves the data management label of {@code transportName}.
     *
     * @throws TransportNotRegisteredException if the transport is not registered.
     */
    @Nullable
    public String getTransportDataManagementLabel(String transportName)
            throws TransportNotRegisteredException {
        synchronized (mTransportLock) {
            return getRegisteredTransportDescriptionOrThrowLocked(transportName)
                    .dataManagementLabel;
        }
    }

    /* Returns true if the transport identified by {@code transportName} is registered. */
    public boolean isTransportRegistered(String transportName) {
        synchronized (mTransportLock) {
            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}.
     *
     * <p><b>Warning:</b> Do NOT make any calls to {@link IBackupTransport} or call any variants of
     * {@link TransportClient#connect(String)} here, otherwise you risk deadlock.
     */
    public void forEachRegisteredTransport(Consumer<String> transportConsumer) {
        synchronized (mTransportLock) {
            for (TransportDescription transportDescription :
                    mRegisteredTransportsDescriptionMap.values()) {
                transportConsumer.accept(transportDescription.name);
            }
        }
    }

    /**
     * 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");
        }
    }

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

    /**
     * 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 {
            return getTransportClientOrThrow(transportName, caller);
        } catch (TransportNotRegisteredException e) {
            Slog.w(TAG, "Transport " + transportName + " not registered");
            return null;
        }
    }

    /**
     * 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) {
            ComponentName component = getRegisteredTransportComponentLocked(transportName);
            if (component == null) {
                throw new TransportNotRegisteredException(transportName);
            }
            return mTransportClientManager.getTransportClient(component, caller);
        }
    }

    /**
     * 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 registered.
     */
    @Nullable
    public TransportClient getCurrentTransportClient(String caller) {
        synchronized (mTransportLock) {
            return getTransportClient(mCurrentTransportName, caller);
        }
    }

    /**
     * Returns a {@link TransportClient} for the current transport or throws 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}.
     * @throws TransportNotRegisteredException if the transport is not registered.
     */
    public TransportClient getCurrentTransportClientOrThrow(String caller)
            throws TransportNotRegisteredException {
        synchronized (mTransportLock) {
            return getTransportClientOrThrow(mCurrentTransportName, caller);
        }
    }

    /**
     * Disposes of the {@link TransportClient}.
     *
     * @param transportClient The {@link TransportClient} to be disposed of.
     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
     *     {@link TransportClient#connectAsync(TransportConnectionListener, String)} for more
     *     details.
     */
    public void disposeOfTransportClient(TransportClient transportClient, String caller) {
        mTransportClientManager.disposeOfTransportClient(transportClient, caller);
    }

    /**
     * Sets {@code transportName} as selected transport and returns previously selected transport
     * name. If there was no previous transport it returns null.
     *
     * <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.
     */
    @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) {
        // If it's already registered we select and return
        synchronized (mTransportLock) {
            try {
                selectTransport(getTransportName(transportComponent));
                return BackupManager.SUCCESS;
            } catch (TransportNotRegisteredException e) {
                // Fall through and release lock
            }
        }

        // We can't call registerTransport() with the transport lock held
        int result = registerTransport(transportComponent);
        if (result != BackupManager.SUCCESS) {
            return result;
        }
        synchronized (mTransportLock) {
            try {
                selectTransport(getTransportName(transportComponent));
                return BackupManager.SUCCESS;
            } catch (TransportNotRegisteredException e) {
                Slog.wtf(TAG, "Transport got unregistered");
                return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
            }
        }
    }

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

        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;
        }
        for (ResolveInfo host : hosts) {
            ComponentName transportComponent = host.serviceInfo.getComponentName();
            if (transportComponentFilter.test(transportComponent)
                    && isTransportTrusted(transportComponent)) {
                registerTransport(transportComponent);
            }
        }
    }

    /** Transport has to be whitelisted and privileged. */
    private boolean isTransportTrusted(ComponentName transport) {
        if (!mTransportWhitelist.contains(transport)) {
            Slog.w(TAG, "BackupTransport " + transport.flattenToShortString() +
                    " not whitelisted.");
            return false;
        }
        try {
            PackageInfo packInfo = mPackageManager.getPackageInfo(transport.getPackageName(), 0);
            if ((packInfo.applicationInfo.privateFlags & ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
                    == 0) {
                Slog.w(TAG, "Transport package " + transport.getPackageName() + " not privileged");
                return false;
            }
        } catch (PackageManager.NameNotFoundException e) {
            Slog.w(TAG, "Package not found.", e);
            return false;
        }
        return true;
    }

    /**
     * Tries to register transport represented by {@code transportComponent}.
     *
     * <p><b>Warning:</b> Don't call this with the transport lock held.
     *
     * @param transportComponent Host of the transport that we want to register.
     * @return One of {@link BackupManager#SUCCESS}, {@link BackupManager#ERROR_TRANSPORT_INVALID}
     *     or {@link BackupManager#ERROR_TRANSPORT_UNAVAILABLE}.
     */
    @WorkerThread
    private int registerTransport(ComponentName transportComponent) {
        checkCanUseTransport();

        if (!isTransportTrusted(transportComponent)) {
            return BackupManager.ERROR_TRANSPORT_INVALID;
        }

        String transportString = transportComponent.flattenToShortString();
        String callerLogString = "TransportManager.registerTransport()";
        TransportClient transportClient =
                mTransportClientManager.getTransportClient(transportComponent, callerLogString);
        final IBackupTransport transport;
        try {
            transport = transportClient.connectOrThrow(callerLogString);
        } catch (TransportNotAvailableException e) {
            Slog.e(TAG, "Couldn't connect to transport " + transportString + " for registration");
            mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
            return BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
        }

        int result;
        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");
            result = BackupManager.ERROR_TRANSPORT_UNAVAILABLE;
        }

        mTransportClientManager.disposeOfTransportClient(transportClient, callerLogString);
        return result;
    }

    /** If {@link RemoteException} is thrown the transport is guaranteed to not be registered. */
    private void registerTransport(ComponentName transportComponent, IBackupTransport transport)
            throws RemoteException {
        checkCanUseTransport();

        TransportDescription description =
                new TransportDescription(
                        transport.name(),
                        transport.transportDirName(),
                        transport.configurationIntent(),
                        transport.currentDestinationString(),
                        transport.dataManagementIntent(),
                        transport.dataManagementLabel());
        synchronized (mTransportLock) {
            mRegisteredTransportsDescriptionMap.put(transportComponent, description);
        }
    }

    private void checkCanUseTransport() {
        Preconditions.checkState(
                !Thread.holdsLock(mTransportLock), "Can't call transport with transport lock held");
    }

    public void dump(PrintWriter pw) {
        mTransportClientManager.dump(pw);
    }

    private static Predicate<ComponentName> fromPackageFilter(String packageName) {
        return transportComponent -> packageName.equals(transportComponent.getPackageName());
    }

    private static class TransportDescription {
        private String name;
        private final String transportDirName;
        @Nullable private Intent configurationIntent;
        private String currentDestinationString;
        @Nullable private Intent dataManagementIntent;
        @Nullable private String dataManagementLabel;

        private TransportDescription(
                String name,
                String transportDirName,
                @Nullable Intent configurationIntent,
                String currentDestinationString,
                @Nullable Intent dataManagementIntent,
                @Nullable String dataManagementLabel) {
            this.name = name;
            this.transportDirName = transportDirName;
            this.configurationIntent = configurationIntent;
            this.currentDestinationString = currentDestinationString;
            this.dataManagementIntent = dataManagementIntent;
            this.dataManagementLabel = dataManagementLabel;
        }
    }
}