1/*
2 * Copyright (C) 2017 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License
15 */
16
17package com.android.server.backup.transport;
18
19import static com.android.server.backup.transport.TransportUtils.formatMessage;
20
21import android.annotation.IntDef;
22import android.annotation.Nullable;
23import android.annotation.WorkerThread;
24import android.content.ComponentName;
25import android.content.Context;
26import android.content.Intent;
27import android.content.ServiceConnection;
28import android.os.DeadObjectException;
29import android.os.Handler;
30import android.os.IBinder;
31import android.os.Looper;
32import android.os.SystemClock;
33import android.os.UserHandle;
34import android.text.format.DateFormat;
35import android.util.ArrayMap;
36import android.util.EventLog;
37import android.util.Slog;
38
39import com.android.internal.annotations.GuardedBy;
40import com.android.internal.annotations.VisibleForTesting;
41import com.android.internal.backup.IBackupTransport;
42import com.android.internal.util.Preconditions;
43import com.android.server.EventLogTags;
44import com.android.server.backup.TransportManager;
45import com.android.server.backup.transport.TransportUtils.Priority;
46
47import dalvik.system.CloseGuard;
48
49import java.lang.annotation.Retention;
50import java.lang.annotation.RetentionPolicy;
51import java.lang.ref.WeakReference;
52import java.util.Collections;
53import java.util.LinkedList;
54import java.util.List;
55import java.util.Locale;
56import java.util.Map;
57import java.util.concurrent.CompletableFuture;
58import java.util.concurrent.ExecutionException;
59
60/**
61 * A {@link TransportClient} manages the connection to an {@link IBackupTransport} service, obtained
62 * via the {@param bindIntent} parameter provided in the constructor. A {@link TransportClient} is
63 * responsible for only one connection to the transport service, not more.
64 *
65 * <p>After retrieved using {@link TransportManager#getTransportClient(String, String)}, you can
66 * call either {@link #connect(String)}, if you can block your thread, or {@link
67 * #connectAsync(TransportConnectionListener, String)}, otherwise, to obtain a {@link
68 * IBackupTransport} instance. It's meant to be passed around as a token to a connected transport.
69 * When the connection is not needed anymore you should call {@link #unbind(String)} or indirectly
70 * via {@link TransportManager#disposeOfTransportClient(TransportClient, String)}.
71 *
72 * <p>DO NOT forget to unbind otherwise there will be dangling connections floating around.
73 *
74 * <p>This class is thread-safe.
75 *
76 * @see TransportManager
77 */
78public class TransportClient {
79    @VisibleForTesting static final String TAG = "TransportClient";
80    private static final int LOG_BUFFER_SIZE = 5;
81
82    private final Context mContext;
83    private final TransportStats mTransportStats;
84    private final Intent mBindIntent;
85    private final ServiceConnection mConnection;
86    private final String mIdentifier;
87    private final String mCreatorLogString;
88    private final ComponentName mTransportComponent;
89    private final Handler mListenerHandler;
90    private final String mPrefixForLog;
91    private final Object mStateLock = new Object();
92    private final Object mLogBufferLock = new Object();
93    private final CloseGuard mCloseGuard = CloseGuard.get();
94
95    @GuardedBy("mLogBufferLock")
96    private final List<String> mLogBuffer = new LinkedList<>();
97
98    @GuardedBy("mStateLock")
99    private final Map<TransportConnectionListener, String> mListeners = new ArrayMap<>();
100
101    @GuardedBy("mStateLock")
102    @State
103    private int mState = State.IDLE;
104
105    @GuardedBy("mStateLock")
106    private volatile IBackupTransport mTransport;
107
108    TransportClient(
109            Context context,
110            TransportStats transportStats,
111            Intent bindIntent,
112            ComponentName transportComponent,
113            String identifier,
114            String caller) {
115        this(
116                context,
117                transportStats,
118                bindIntent,
119                transportComponent,
120                identifier,
121                caller,
122                new Handler(Looper.getMainLooper()));
123    }
124
125    @VisibleForTesting
126    TransportClient(
127            Context context,
128            TransportStats transportStats,
129            Intent bindIntent,
130            ComponentName transportComponent,
131            String identifier,
132            String caller,
133            Handler listenerHandler) {
134        mContext = context;
135        mTransportStats = transportStats;
136        mTransportComponent = transportComponent;
137        mBindIntent = bindIntent;
138        mIdentifier = identifier;
139        mCreatorLogString = caller;
140        mListenerHandler = listenerHandler;
141        mConnection = new TransportConnection(context, this);
142
143        // For logging
144        String classNameForLog = mTransportComponent.getShortClassName().replaceFirst(".*\\.", "");
145        mPrefixForLog = classNameForLog + "#" + mIdentifier + ":";
146
147        mCloseGuard.open("markAsDisposed");
148    }
149
150    public ComponentName getTransportComponent() {
151        return mTransportComponent;
152    }
153
154    /**
155     * Attempts to connect to the transport (if needed).
156     *
157     * <p>Note that being bound is not the same as connected. To be connected you also need to be
158     * bound. You go from nothing to bound, then to bound and connected. To have a usable transport
159     * binder instance you need to be connected. This method will attempt to connect and return an
160     * usable transport binder regardless of the state of the object, it may already be connected,
161     * or bound but not connected, not bound at all or even unusable.
162     *
163     * <p>So, a {@link Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)} (or
164     * one of its variants) can be called or not depending on the inner state. However, it won't be
165     * called again if we're already bound. For example, if one was already requested but the
166     * framework has not yet returned (meaning we're bound but still trying to connect) it won't
167     * trigger another one, just piggyback on the original request.
168     *
169     * <p>It's guaranteed that you are going to get a call back to {@param listener} after this
170     * call. However, the {@param IBackupTransport} parameter, the transport binder, is not
171     * guaranteed to be non-null, or if it's non-null it's not guaranteed to be usable - i.e. it can
172     * throw {@link DeadObjectException}s on method calls. You should check for both in your code.
173     * The reasons for a null transport binder are:
174     *
175     * <ul>
176     *   <li>Some code called {@link #unbind(String)} before you got a callback.
177     *   <li>The framework had already called {@link
178     *       ServiceConnection#onServiceDisconnected(ComponentName)} or {@link
179     *       ServiceConnection#onBindingDied(ComponentName)} on this object's connection before.
180     *       Check the documentation of those methods for when that happens.
181     *   <li>The framework returns false for {@link Context#bindServiceAsUser(Intent,
182     *       ServiceConnection, int, UserHandle)} (or one of its variants). Check documentation for
183     *       when this happens.
184     * </ul>
185     *
186     * For unusable transport binders check {@link DeadObjectException}.
187     *
188     * @param listener The listener that will be called with the (possibly null or unusable) {@link
189     *     IBackupTransport} instance and this {@link TransportClient} object.
190     * @param caller A {@link String} identifying the caller for logging/debugging purposes. This
191     *     should be a human-readable short string that is easily identifiable in the logs. Ideally
192     *     TAG.methodName(), where TAG is the one used in logcat. In cases where this is is not very
193     *     descriptive like MyHandler.handleMessage() you should put something that someone reading
194     *     the code would understand, like MyHandler/MSG_FOO.
195     * @see #connect(String)
196     * @see DeadObjectException
197     * @see ServiceConnection#onServiceConnected(ComponentName, IBinder)
198     * @see ServiceConnection#onServiceDisconnected(ComponentName)
199     * @see Context#bindServiceAsUser(Intent, ServiceConnection, int, UserHandle)
200     */
201    public void connectAsync(TransportConnectionListener listener, String caller) {
202        synchronized (mStateLock) {
203            checkStateIntegrityLocked();
204
205            switch (mState) {
206                case State.UNUSABLE:
207                    log(Priority.WARN, caller, "Async connect: UNUSABLE client");
208                    notifyListener(listener, null, caller);
209                    break;
210                case State.IDLE:
211                    boolean hasBound =
212                            mContext.bindServiceAsUser(
213                                    mBindIntent,
214                                    mConnection,
215                                    Context.BIND_AUTO_CREATE,
216                                    UserHandle.SYSTEM);
217                    if (hasBound) {
218                        // We don't need to set a time-out because we are guaranteed to get a call
219                        // back in ServiceConnection, either an onServiceConnected() or
220                        // onBindingDied().
221                        log(Priority.DEBUG, caller, "Async connect: service bound, connecting");
222                        setStateLocked(State.BOUND_AND_CONNECTING, null);
223                        mListeners.put(listener, caller);
224                    } else {
225                        log(Priority.ERROR, "Async connect: bindService returned false");
226                        // mState remains State.IDLE
227                        mContext.unbindService(mConnection);
228                        notifyListener(listener, null, caller);
229                    }
230                    break;
231                case State.BOUND_AND_CONNECTING:
232                    log(
233                            Priority.DEBUG,
234                            caller,
235                            "Async connect: already connecting, adding listener");
236                    mListeners.put(listener, caller);
237                    break;
238                case State.CONNECTED:
239                    log(Priority.DEBUG, caller, "Async connect: reusing transport");
240                    notifyListener(listener, mTransport, caller);
241                    break;
242            }
243        }
244    }
245
246    /**
247     * Removes the transport binding.
248     *
249     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
250     *     {@link #connectAsync(TransportConnectionListener, String)} for more details.
251     */
252    public void unbind(String caller) {
253        synchronized (mStateLock) {
254            checkStateIntegrityLocked();
255
256            log(Priority.DEBUG, caller, "Unbind requested (was " + stateToString(mState) + ")");
257            switch (mState) {
258                case State.UNUSABLE:
259                case State.IDLE:
260                    break;
261                case State.BOUND_AND_CONNECTING:
262                    setStateLocked(State.IDLE, null);
263                    // After unbindService() no calls back to mConnection
264                    mContext.unbindService(mConnection);
265                    notifyListenersAndClearLocked(null);
266                    break;
267                case State.CONNECTED:
268                    setStateLocked(State.IDLE, null);
269                    mContext.unbindService(mConnection);
270                    break;
271            }
272        }
273    }
274
275    /** Marks this TransportClient as disposed, allowing it to be GC'ed without warnings. */
276    public void markAsDisposed() {
277        synchronized (mStateLock) {
278            Preconditions.checkState(
279                    mState < State.BOUND_AND_CONNECTING, "Can't mark as disposed if still bound");
280            mCloseGuard.close();
281        }
282    }
283
284    /**
285     * Attempts to connect to the transport (if needed) and returns it.
286     *
287     * <p>Synchronous version of {@link #connectAsync(TransportConnectionListener, String)}. The
288     * same observations about state are valid here. Also, what was said about the {@link
289     * IBackupTransport} parameter of {@link TransportConnectionListener} now apply to the return
290     * value of this method.
291     *
292     * <p>This is a potentially blocking operation, so be sure to call this carefully on the correct
293     * threads. You can't call this from the process main-thread (it throws an exception if you do
294     * so).
295     *
296     * <p>In most cases only the first call to this method will block, the following calls should
297     * return instantly. However, this is not guaranteed.
298     *
299     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
300     *     {@link #connectAsync(TransportConnectionListener, String)} for more details.
301     * @return A {@link IBackupTransport} transport binder instance or null. If it's non-null it can
302     *     still be unusable - throws {@link DeadObjectException} on method calls
303     */
304    @WorkerThread
305    @Nullable
306    public IBackupTransport connect(String caller) {
307        // If called on the main-thread this could deadlock waiting because calls to
308        // ServiceConnection are on the main-thread as well
309        Preconditions.checkState(
310                !Looper.getMainLooper().isCurrentThread(), "Can't call connect() on main thread");
311
312        IBackupTransport transport = mTransport;
313        if (transport != null) {
314            log(Priority.DEBUG, caller, "Sync connect: reusing transport");
315            return transport;
316        }
317
318        // If it's already UNUSABLE we return straight away, no need to go to main-thread
319        synchronized (mStateLock) {
320            if (mState == State.UNUSABLE) {
321                log(Priority.WARN, caller, "Sync connect: UNUSABLE client");
322                return null;
323            }
324        }
325
326        CompletableFuture<IBackupTransport> transportFuture = new CompletableFuture<>();
327        TransportConnectionListener requestListener =
328                (requestedTransport, transportClient) ->
329                        transportFuture.complete(requestedTransport);
330
331        long requestTime = SystemClock.elapsedRealtime();
332        log(Priority.DEBUG, caller, "Sync connect: calling async");
333        connectAsync(requestListener, caller);
334
335        try {
336            transport = transportFuture.get();
337            long time = SystemClock.elapsedRealtime() - requestTime;
338            mTransportStats.registerConnectionTime(mTransportComponent, time);
339            log(Priority.DEBUG, caller, String.format(Locale.US, "Connect took %d ms", time));
340            return transport;
341        } catch (InterruptedException | ExecutionException e) {
342            String error = e.getClass().getSimpleName();
343            log(Priority.ERROR, caller, error + " while waiting for transport: " + e.getMessage());
344            return null;
345        }
346    }
347
348    /**
349     * Tries to connect to the transport, if it fails throws {@link TransportNotAvailableException}.
350     *
351     * <p>Same as {@link #connect(String)} except it throws instead of returning null.
352     *
353     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
354     *     {@link #connectAsync(TransportConnectionListener, String)} for more details.
355     * @return A {@link IBackupTransport} transport binder instance.
356     * @see #connect(String)
357     * @throws TransportNotAvailableException if connection attempt fails.
358     */
359    @WorkerThread
360    public IBackupTransport connectOrThrow(String caller) throws TransportNotAvailableException {
361        IBackupTransport transport = connect(caller);
362        if (transport == null) {
363            log(Priority.ERROR, caller, "Transport connection failed");
364            throw new TransportNotAvailableException();
365        }
366        return transport;
367    }
368
369    /**
370     * If the {@link TransportClient} is already connected to the transport, returns the transport,
371     * otherwise throws {@link TransportNotAvailableException}.
372     *
373     * @param caller A {@link String} identifying the caller for logging/debugging purposes. Check
374     *     {@link #connectAsync(TransportConnectionListener, String)} for more details.
375     * @return A {@link IBackupTransport} transport binder instance.
376     * @throws TransportNotAvailableException if not connected.
377     */
378    public IBackupTransport getConnectedTransport(String caller)
379            throws TransportNotAvailableException {
380        IBackupTransport transport = mTransport;
381        if (transport == null) {
382            log(Priority.ERROR, caller, "Transport not connected");
383            throw new TransportNotAvailableException();
384        }
385        return transport;
386    }
387
388    @Override
389    public String toString() {
390        return "TransportClient{"
391                + mTransportComponent.flattenToShortString()
392                + "#"
393                + mIdentifier
394                + "}";
395    }
396
397    @Override
398    protected void finalize() throws Throwable {
399        synchronized (mStateLock) {
400            mCloseGuard.warnIfOpen();
401            if (mState >= State.BOUND_AND_CONNECTING) {
402                String callerLogString = "TransportClient.finalize()";
403                log(
404                        Priority.ERROR,
405                        callerLogString,
406                        "Dangling TransportClient created in [" + mCreatorLogString + "] being "
407                                + "GC'ed. Left bound, unbinding...");
408                try {
409                    unbind(callerLogString);
410                } catch (IllegalStateException e) {
411                    // May throw because there may be a race between this method being called and
412                    // the framework calling any method on the connection with the weak reference
413                    // there already cleared. In this case the connection will unbind before this
414                    // is called. This is fine.
415                }
416            }
417        }
418    }
419
420    private void onServiceConnected(IBinder binder) {
421        IBackupTransport transport = IBackupTransport.Stub.asInterface(binder);
422        synchronized (mStateLock) {
423            checkStateIntegrityLocked();
424
425            if (mState != State.UNUSABLE) {
426                log(Priority.DEBUG, "Transport connected");
427                setStateLocked(State.CONNECTED, transport);
428                notifyListenersAndClearLocked(transport);
429            }
430        }
431    }
432
433    /**
434     * If we are called here the TransportClient becomes UNUSABLE. After one of these calls, if a
435     * binding happen again the new service can be a different instance. Since transports are
436     * stateful, we don't want a new instance responding for an old instance's state.
437     */
438    private void onServiceDisconnected() {
439        synchronized (mStateLock) {
440            log(Priority.ERROR, "Service disconnected: client UNUSABLE");
441            setStateLocked(State.UNUSABLE, null);
442            try {
443                // After unbindService() no calls back to mConnection
444                mContext.unbindService(mConnection);
445            } catch (IllegalArgumentException e) {
446                // TODO: Investigate why this is happening
447                // We're UNUSABLE, so any calls to mConnection will be no-op, so it's safe to
448                // swallow this one
449                log(
450                        Priority.WARN,
451                        "Exception trying to unbind onServiceDisconnected(): " + e.getMessage());
452            }
453        }
454    }
455
456    /**
457     * If we are called here the TransportClient becomes UNUSABLE for the same reason as in {@link
458     * #onServiceDisconnected()}.
459     */
460    private void onBindingDied() {
461        synchronized (mStateLock) {
462            checkStateIntegrityLocked();
463
464            log(Priority.ERROR, "Binding died: client UNUSABLE");
465            // After unbindService() no calls back to mConnection
466            switch (mState) {
467                case State.UNUSABLE:
468                    break;
469                case State.IDLE:
470                    log(Priority.ERROR, "Unexpected state transition IDLE => UNUSABLE");
471                    setStateLocked(State.UNUSABLE, null);
472                    break;
473                case State.BOUND_AND_CONNECTING:
474                    setStateLocked(State.UNUSABLE, null);
475                    mContext.unbindService(mConnection);
476                    notifyListenersAndClearLocked(null);
477                    break;
478                case State.CONNECTED:
479                    setStateLocked(State.UNUSABLE, null);
480                    mContext.unbindService(mConnection);
481                    break;
482            }
483        }
484    }
485
486    private void notifyListener(
487            TransportConnectionListener listener,
488            @Nullable IBackupTransport transport,
489            String caller) {
490        String transportString = (transport != null) ? "IBackupTransport" : "null";
491        log(Priority.INFO, "Notifying [" + caller + "] transport = " + transportString);
492        mListenerHandler.post(() -> listener.onTransportConnectionResult(transport, this));
493    }
494
495    @GuardedBy("mStateLock")
496    private void notifyListenersAndClearLocked(@Nullable IBackupTransport transport) {
497        for (Map.Entry<TransportConnectionListener, String> entry : mListeners.entrySet()) {
498            TransportConnectionListener listener = entry.getKey();
499            String caller = entry.getValue();
500            notifyListener(listener, transport, caller);
501        }
502        mListeners.clear();
503    }
504
505    @GuardedBy("mStateLock")
506    private void setStateLocked(@State int state, @Nullable IBackupTransport transport) {
507        log(Priority.VERBOSE, "State: " + stateToString(mState) + " => " + stateToString(state));
508        onStateTransition(mState, state);
509        mState = state;
510        mTransport = transport;
511    }
512
513    private void onStateTransition(int oldState, int newState) {
514        String transport = mTransportComponent.flattenToShortString();
515        int bound = transitionThroughState(oldState, newState, State.BOUND_AND_CONNECTING);
516        int connected = transitionThroughState(oldState, newState, State.CONNECTED);
517        if (bound != Transition.NO_TRANSITION) {
518            int value = (bound == Transition.UP) ? 1 : 0; // 1 is bound, 0 is not bound
519            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_LIFECYCLE, transport, value);
520        }
521        if (connected != Transition.NO_TRANSITION) {
522            int value = (connected == Transition.UP) ? 1 : 0; // 1 is connected, 0 is not connected
523            EventLog.writeEvent(EventLogTags.BACKUP_TRANSPORT_CONNECTION, transport, value);
524        }
525    }
526
527    /**
528     * Returns:
529     *
530     * <ul>
531     *   <li>{@link Transition#UP}, if oldState < stateReference <= newState
532     *   <li>{@link Transition#DOWN}, if oldState >= stateReference > newState
533     *   <li>{@link Transition#NO_TRANSITION}, otherwise
534     */
535    @Transition
536    private int transitionThroughState(
537            @State int oldState, @State int newState, @State int stateReference) {
538        if (oldState < stateReference && stateReference <= newState) {
539            return Transition.UP;
540        }
541        if (oldState >= stateReference && stateReference > newState) {
542            return Transition.DOWN;
543        }
544        return Transition.NO_TRANSITION;
545    }
546
547    @GuardedBy("mStateLock")
548    private void checkStateIntegrityLocked() {
549        switch (mState) {
550            case State.UNUSABLE:
551                checkState(mListeners.isEmpty(), "Unexpected listeners when state = UNUSABLE");
552                checkState(
553                        mTransport == null, "Transport expected to be null when state = UNUSABLE");
554            case State.IDLE:
555                checkState(mListeners.isEmpty(), "Unexpected listeners when state = IDLE");
556                checkState(mTransport == null, "Transport expected to be null when state = IDLE");
557                break;
558            case State.BOUND_AND_CONNECTING:
559                checkState(
560                        mTransport == null,
561                        "Transport expected to be null when state = BOUND_AND_CONNECTING");
562                break;
563            case State.CONNECTED:
564                checkState(mListeners.isEmpty(), "Unexpected listeners when state = CONNECTED");
565                checkState(
566                        mTransport != null,
567                        "Transport expected to be non-null when state = CONNECTED");
568                break;
569            default:
570                checkState(false, "Unexpected state = " + stateToString(mState));
571        }
572    }
573
574    private void checkState(boolean assertion, String message) {
575        if (!assertion) {
576            log(Priority.ERROR, message);
577        }
578    }
579
580    private String stateToString(@State int state) {
581        switch (state) {
582            case State.UNUSABLE:
583                return "UNUSABLE";
584            case State.IDLE:
585                return "IDLE";
586            case State.BOUND_AND_CONNECTING:
587                return "BOUND_AND_CONNECTING";
588            case State.CONNECTED:
589                return "CONNECTED";
590            default:
591                return "<UNKNOWN = " + state + ">";
592        }
593    }
594
595    private void log(int priority, String message) {
596        TransportUtils.log(priority, TAG, formatMessage(mPrefixForLog, null, message));
597        saveLogEntry(formatMessage(null, null, message));
598    }
599
600    private void log(int priority, String caller, String message) {
601        TransportUtils.log(priority, TAG, formatMessage(mPrefixForLog, caller, message));
602        saveLogEntry(formatMessage(null, caller, message));
603    }
604
605    private void saveLogEntry(String message) {
606        CharSequence time = DateFormat.format("yyyy-MM-dd HH:mm:ss", System.currentTimeMillis());
607        message = time + " " + message;
608        synchronized (mLogBufferLock) {
609            if (mLogBuffer.size() == LOG_BUFFER_SIZE) {
610                mLogBuffer.remove(mLogBuffer.size() - 1);
611            }
612            mLogBuffer.add(0, message);
613        }
614    }
615
616    List<String> getLogBuffer() {
617        synchronized (mLogBufferLock) {
618            return Collections.unmodifiableList(mLogBuffer);
619        }
620    }
621
622    @IntDef({Transition.DOWN, Transition.NO_TRANSITION, Transition.UP})
623    @Retention(RetentionPolicy.SOURCE)
624    private @interface Transition {
625        int DOWN = -1;
626        int NO_TRANSITION = 0;
627        int UP = 1;
628    }
629
630    @IntDef({State.UNUSABLE, State.IDLE, State.BOUND_AND_CONNECTING, State.CONNECTED})
631    @Retention(RetentionPolicy.SOURCE)
632    private @interface State {
633        // Constant values MUST be in order
634        int UNUSABLE = 0;
635        int IDLE = 1;
636        int BOUND_AND_CONNECTING = 2;
637        int CONNECTED = 3;
638    }
639
640    /**
641     * This class is a proxy to TransportClient methods that doesn't hold a strong reference to the
642     * TransportClient, allowing it to be GC'ed. If the reference was lost it logs a message.
643     */
644    private static class TransportConnection implements ServiceConnection {
645        private final Context mContext;
646        private final WeakReference<TransportClient> mTransportClientRef;
647
648        private TransportConnection(Context context, TransportClient transportClient) {
649            mContext = context;
650            mTransportClientRef = new WeakReference<>(transportClient);
651        }
652
653        @Override
654        public void onServiceConnected(ComponentName transportComponent, IBinder binder) {
655            TransportClient transportClient = mTransportClientRef.get();
656            if (transportClient == null) {
657                referenceLost("TransportConnection.onServiceConnected()");
658                return;
659            }
660            transportClient.onServiceConnected(binder);
661        }
662
663        @Override
664        public void onServiceDisconnected(ComponentName transportComponent) {
665            TransportClient transportClient = mTransportClientRef.get();
666            if (transportClient == null) {
667                referenceLost("TransportConnection.onServiceDisconnected()");
668                return;
669            }
670            transportClient.onServiceDisconnected();
671        }
672
673        @Override
674        public void onBindingDied(ComponentName transportComponent) {
675            TransportClient transportClient = mTransportClientRef.get();
676            if (transportClient == null) {
677                referenceLost("TransportConnection.onBindingDied()");
678                return;
679            }
680            transportClient.onBindingDied();
681        }
682
683        /** @see TransportClient#finalize() */
684        private void referenceLost(String caller) {
685            mContext.unbindService(this);
686            TransportUtils.log(
687                    Priority.INFO,
688                    TAG,
689                    caller + " called but TransportClient reference has been GC'ed");
690        }
691    }
692}
693