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