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.WorkerThread;
24 import android.content.ComponentName;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.ServiceConnection;
28 import android.os.DeadObjectException;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.SystemClock;
33 import android.os.UserHandle;
34 import android.text.format.DateFormat;
35 import android.util.ArrayMap;
36 import android.util.EventLog;
37 import android.util.Slog;
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 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 
TransportClient( Context context, TransportStats transportStats, Intent bindIntent, ComponentName transportComponent, String identifier, String caller)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
TransportClient( Context context, TransportStats transportStats, Intent bindIntent, ComponentName transportComponent, String identifier, String caller, Handler listenerHandler)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 
getTransportComponent()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      */
connectAsync(TransportConnectionListener listener, String caller)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      */
unbind(String caller)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. */
markAsDisposed()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
connectOrThrow(String caller)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      */
getConnectedTransport(String caller)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
toString()389     public String toString() {
390         return "TransportClient{"
391                 + mTransportComponent.flattenToShortString()
392                 + "#"
393                 + mIdentifier
394                 + "}";
395     }
396 
397     @Override
finalize()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 
onServiceConnected(IBinder binder)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      */
onServiceDisconnected()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      */
onBindingDied()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 
notifyListener( TransportConnectionListener listener, @Nullable IBackupTransport transport, String caller)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")
notifyListenersAndClearLocked(@ullable IBackupTransport transport)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")
setStateLocked(@tate int state, @Nullable IBackupTransport transport)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 
onStateTransition(int oldState, int newState)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
transitionThroughState( @tate int oldState, @State int newState, @State int stateReference)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")
checkStateIntegrityLocked()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 
checkState(boolean assertion, String message)574     private void checkState(boolean assertion, String message) {
575         if (!assertion) {
576             log(Priority.ERROR, message);
577         }
578     }
579 
stateToString(@tate int state)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 
log(int priority, String message)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 
log(int priority, String caller, String message)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 
saveLogEntry(String message)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 
getLogBuffer()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 
TransportConnection(Context context, TransportClient transportClient)648         private TransportConnection(Context context, TransportClient transportClient) {
649             mContext = context;
650             mTransportClientRef = new WeakReference<>(transportClient);
651         }
652 
653         @Override
onServiceConnected(ComponentName transportComponent, IBinder binder)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
onServiceDisconnected(ComponentName transportComponent)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
onBindingDied(ComponentName transportComponent)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() */
referenceLost(String caller)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