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