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