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 TransportClient( Context context, TransportStats transportStats, Intent bindIntent, ComponentName transportComponent, String identifier, String caller)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 TransportClient( Context context, TransportStats transportStats, Intent bindIntent, ComponentName transportComponent, String identifier, String caller, Handler listenerHandler)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 getTransportComponent()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 */ connectAsync(TransportConnectionListener listener, String caller)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 */ unbind(String caller)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. */ markAsDisposed()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 connectOrThrow(String caller)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 */ getConnectedTransport(String caller)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 toString()389 public String toString() { 390 return "TransportClient{" 391 + mTransportComponent.flattenToShortString() 392 + "#" 393 + mIdentifier 394 + "}"; 395 } 396 397 @Override finalize()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 onServiceConnected(IBinder binder)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 */ onServiceDisconnected()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 */ onBindingDied()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 notifyListener( TransportConnectionListener listener, @Nullable IBackupTransport transport, String caller)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") notifyListenersAndClearLocked(@ullable IBackupTransport transport)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") setStateLocked(@tate int state, @Nullable IBackupTransport transport)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 onStateTransition(int oldState, int newState)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 transitionThroughState( @tate int oldState, @State int newState, @State int stateReference)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") checkStateIntegrityLocked()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 checkState(boolean assertion, String message)574 private void checkState(boolean assertion, String message) { 575 if (!assertion) { 576 log(Priority.ERROR, message); 577 } 578 } 579 stateToString(@tate int state)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 log(int priority, String message)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 log(int priority, String caller, String message)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 saveLogEntry(String message)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 getLogBuffer()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 TransportConnection(Context context, TransportClient transportClient)648 private TransportConnection(Context context, TransportClient transportClient) { 649 mContext = context; 650 mTransportClientRef = new WeakReference<>(transportClient); 651 } 652 653 @Override onServiceConnected(ComponentName transportComponent, IBinder binder)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 onServiceDisconnected(ComponentName transportComponent)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 onBindingDied(ComponentName transportComponent)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() */ referenceLost(String caller)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