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