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