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