Home | History | Annotate | Download | only in transport
      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 
    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