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