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 package com.android.server.am; 17 18 import android.annotation.NonNull; 19 import android.content.ComponentName; 20 import android.content.Context; 21 import android.content.Intent; 22 import android.content.ServiceConnection; 23 import android.os.Handler; 24 import android.os.IBinder; 25 import android.os.SystemClock; 26 import android.os.UserHandle; 27 import android.util.Log; 28 import android.util.TimeUtils; 29 30 import com.android.internal.annotations.GuardedBy; 31 import com.android.internal.annotations.VisibleForTesting; 32 33 import java.io.PrintWriter; 34 35 /** 36 * Connects to a given service component on a given user. 37 * 38 * - Call {@link #bind()} to create a connection. 39 * - Call {@link #unbind()} to disconnect. Make sure to disconnect when the user stops. 40 * 41 * Add onConnected/onDisconnected callbacks as needed. 42 * 43 * When the target process gets killed (by OOM-killer, etc), then the activity manager will 44 * re-connect the connection automatically, in which case onServiceDisconnected() gets called 45 * and then onServiceConnected(). 46 * 47 * However sometimes the activity manager just "kills" the connection -- like when the target 48 * package gets updated or the target process crashes multiple times in a row, in which case 49 * onBindingDied() gets called. This class handles this case by re-connecting in the time 50 * {@link #mRebindBackoffMs}. If this happens again, this class increases the back-off time 51 * by {@link #mRebindBackoffIncrease} and retry. The back-off time is capped at 52 * {@link #mRebindMaxBackoffMs}. 53 * 54 * The back-off time will never be reset until {@link #unbind()} and {@link #bind()} are called 55 * explicitly. 56 * 57 * NOTE: This class does *not* handle package-updates -- i.e. even if the binding dies due to 58 * the target package being updated, this class won't reconnect. This is because this class doesn't 59 * know what to do when the service component has gone missing, for example. If the user of this 60 * class wants to restore the connection, then it should call {@link #unbind()} and {@link #bind} 61 * explicitly. 62 * 63 * atest ${ANDROID_BUILD_TOP}/frameworks/base/services/tests/mockingservicestests/src/com/android/server/am/PersistentConnectionTest.java 64 */ 65 public abstract class PersistentConnection<T> { 66 private final Object mLock = new Object(); 67 68 private final static boolean DEBUG = false; 69 70 private final String mTag; 71 private final Context mContext; 72 private final Handler mHandler; 73 private final int mUserId; 74 private final ComponentName mComponentName; 75 76 private long mNextBackoffMs; 77 78 private final long mRebindBackoffMs; 79 private final double mRebindBackoffIncrease; 80 private final long mRebindMaxBackoffMs; 81 private final long mResetBackoffDelay; 82 83 private long mReconnectTime; 84 85 // TODO too many booleans... Should clean up. 86 87 @GuardedBy("mLock") 88 private boolean mBound; 89 90 /** 91 * Whether {@link #bind()} has been called and {@link #unbind()} hasn't been yet; meaning this 92 * is the expected bind state from the caller's point of view. 93 */ 94 @GuardedBy("mLock") 95 private boolean mShouldBeBound; 96 97 @GuardedBy("mLock") 98 private boolean mRebindScheduled; 99 100 @GuardedBy("mLock") 101 private boolean mIsConnected; 102 103 @GuardedBy("mLock") 104 private T mService; 105 106 @GuardedBy("mLock") 107 private int mNumConnected; 108 109 @GuardedBy("mLock") 110 private int mNumDisconnected; 111 112 @GuardedBy("mLock") 113 private int mNumBindingDied; 114 115 @GuardedBy("mLock") 116 private long mLastConnectedTime; 117 118 private final ServiceConnection mServiceConnection = new ServiceConnection() { 119 @Override 120 public void onServiceConnected(ComponentName name, IBinder service) { 121 synchronized (mLock) { 122 if (!mBound) { 123 // Callback came in after PersistentConnection.unbind() was called. 124 // We just ignore this. 125 // (We've already called unbindService() already in unbind) 126 Log.w(mTag, "Connected: " + mComponentName.flattenToShortString() 127 + " u" + mUserId + " but not bound, ignore."); 128 return; 129 } 130 Log.i(mTag, "Connected: " + mComponentName.flattenToShortString() 131 + " u" + mUserId); 132 133 mNumConnected++; 134 135 mIsConnected = true; 136 mLastConnectedTime = injectUptimeMillis(); 137 mService = asInterface(service); 138 139 scheduleStableCheckLocked(); 140 } 141 } 142 143 @Override 144 public void onServiceDisconnected(ComponentName name) { 145 synchronized (mLock) { 146 Log.i(mTag, "Disconnected: " + mComponentName.flattenToShortString() 147 + " u" + mUserId); 148 149 mNumDisconnected++; 150 151 cleanUpConnectionLocked(); 152 153 // Note we won't increase the rebind timeout here, because we don't explicitly 154 // rebind in this case. 155 } 156 } 157 158 @Override 159 public void onBindingDied(ComponentName name) { 160 // Activity manager gave up; we'll schedule a re-connect by ourselves. 161 synchronized (mLock) { 162 if (!mBound) { 163 // Callback came in late? 164 Log.w(mTag, "Binding died: " + mComponentName.flattenToShortString() 165 + " u" + mUserId + " but not bound, ignore."); 166 return; 167 } 168 169 Log.w(mTag, "Binding died: " + mComponentName.flattenToShortString() 170 + " u" + mUserId); 171 172 mNumBindingDied++; 173 174 scheduleRebindLocked(); 175 } 176 } 177 }; 178 179 private final Runnable mBindForBackoffRunnable = () -> bindForBackoff(); 180 PersistentConnection(@onNull String tag, @NonNull Context context, @NonNull Handler handler, int userId, @NonNull ComponentName componentName, long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds, long resetBackoffDelay)181 public PersistentConnection(@NonNull String tag, @NonNull Context context, 182 @NonNull Handler handler, int userId, @NonNull ComponentName componentName, 183 long rebindBackoffSeconds, double rebindBackoffIncrease, long rebindMaxBackoffSeconds, 184 long resetBackoffDelay) { 185 mTag = tag; 186 mContext = context; 187 mHandler = handler; 188 mUserId = userId; 189 mComponentName = componentName; 190 191 mRebindBackoffMs = rebindBackoffSeconds * 1000; 192 mRebindBackoffIncrease = rebindBackoffIncrease; 193 mRebindMaxBackoffMs = rebindMaxBackoffSeconds * 1000; 194 mResetBackoffDelay = resetBackoffDelay * 1000; 195 196 mNextBackoffMs = mRebindBackoffMs; 197 } 198 getComponentName()199 public final ComponentName getComponentName() { 200 return mComponentName; 201 } 202 getUserId()203 public final int getUserId() { 204 return mUserId; 205 } 206 getBindFlags()207 protected abstract int getBindFlags(); 208 209 /** 210 * @return whether {@link #bind()} has been called and {@link #unbind()} hasn't. 211 * 212 * Note when the AM gives up on connection, this class detects it and un-bind automatically, 213 * and schedule rebind, and {@link #isBound} returns false when it's waiting for a retry. 214 */ isBound()215 public final boolean isBound() { 216 synchronized (mLock) { 217 return mBound; 218 } 219 } 220 221 /** 222 * @return whether re-bind is scheduled after the AM gives up on a connection. 223 */ isRebindScheduled()224 public final boolean isRebindScheduled() { 225 synchronized (mLock) { 226 return mRebindScheduled; 227 } 228 } 229 230 /** 231 * @return whether connected. 232 */ isConnected()233 public final boolean isConnected() { 234 synchronized (mLock) { 235 return mIsConnected; 236 } 237 } 238 239 /** 240 * @return the service binder interface. 241 */ getServiceBinder()242 public final T getServiceBinder() { 243 synchronized (mLock) { 244 return mService; 245 } 246 } 247 248 /** 249 * Connects to the service. 250 */ bind()251 public final void bind() { 252 synchronized (mLock) { 253 mShouldBeBound = true; 254 255 bindInnerLocked(/* resetBackoff= */ true); 256 } 257 } 258 259 /** Return the next back-off time */ getNextBackoffMs()260 public long getNextBackoffMs() { 261 synchronized (mLock) { 262 return mNextBackoffMs; 263 } 264 } 265 266 /** Return the number of times the connected callback called. */ getNumConnected()267 public int getNumConnected() { 268 synchronized (mLock) { 269 return mNumConnected; 270 } 271 } 272 273 /** Return the number of times the disconnected callback called. */ getNumDisconnected()274 public int getNumDisconnected() { 275 synchronized (mLock) { 276 return mNumDisconnected; 277 } 278 } 279 280 /** Return the number of times the binding died callback called. */ getNumBindingDied()281 public int getNumBindingDied() { 282 synchronized (mLock) { 283 return mNumBindingDied; 284 } 285 } 286 287 @GuardedBy("mLock") resetBackoffLocked()288 private void resetBackoffLocked() { 289 if (mNextBackoffMs != mRebindBackoffMs) { 290 mNextBackoffMs = mRebindBackoffMs; 291 Log.i(mTag, "Backoff reset to " + mNextBackoffMs); 292 } 293 } 294 295 @GuardedBy("mLock") bindInnerLocked(boolean resetBackoff)296 public final void bindInnerLocked(boolean resetBackoff) { 297 unscheduleRebindLocked(); 298 299 if (mBound) { 300 return; 301 } 302 mBound = true; 303 304 unscheduleStableCheckLocked(); 305 306 if (resetBackoff) { 307 resetBackoffLocked(); 308 } 309 310 final Intent service = new Intent().setComponent(mComponentName); 311 312 if (DEBUG) { 313 Log.d(mTag, "Attempting to connect to " + mComponentName); 314 } 315 316 final boolean success = mContext.bindServiceAsUser(service, mServiceConnection, 317 Context.BIND_AUTO_CREATE | getBindFlags(), 318 mHandler, UserHandle.of(mUserId)); 319 320 if (!success) { 321 Log.e(mTag, "Binding: " + service.getComponent() + " u" + mUserId 322 + " failed."); 323 } 324 } 325 bindForBackoff()326 final void bindForBackoff() { 327 synchronized (mLock) { 328 if (!mShouldBeBound) { 329 // Race condition -- by the time we got here, unbind() has already been called. 330 return; 331 } 332 333 bindInnerLocked(/* resetBackoff= */ false); 334 } 335 } 336 337 @GuardedBy("mLock") cleanUpConnectionLocked()338 private void cleanUpConnectionLocked() { 339 mIsConnected = false; 340 mService = null; 341 342 unscheduleStableCheckLocked(); 343 } 344 345 /** 346 * Disconnect from the service. 347 */ unbind()348 public final void unbind() { 349 synchronized (mLock) { 350 mShouldBeBound = false; 351 352 unbindLocked(); 353 unscheduleStableCheckLocked(); 354 } 355 } 356 357 @GuardedBy("mLock") unbindLocked()358 private final void unbindLocked() { 359 unscheduleRebindLocked(); 360 361 if (!mBound) { 362 return; 363 } 364 Log.i(mTag, "Stopping: " + mComponentName.flattenToShortString() + " u" + mUserId); 365 mBound = false; 366 mContext.unbindService(mServiceConnection); 367 368 cleanUpConnectionLocked(); 369 } 370 371 @GuardedBy("mLock") unscheduleRebindLocked()372 void unscheduleRebindLocked() { 373 injectRemoveCallbacks(mBindForBackoffRunnable); 374 mRebindScheduled = false; 375 } 376 377 @GuardedBy("mLock") scheduleRebindLocked()378 void scheduleRebindLocked() { 379 unbindLocked(); 380 381 if (!mRebindScheduled) { 382 Log.i(mTag, "Scheduling to reconnect in " + mNextBackoffMs + " ms (uptime)"); 383 384 mReconnectTime = injectUptimeMillis() + mNextBackoffMs; 385 386 injectPostAtTime(mBindForBackoffRunnable, mReconnectTime); 387 388 mNextBackoffMs = Math.min(mRebindMaxBackoffMs, 389 (long) (mNextBackoffMs * mRebindBackoffIncrease)); 390 391 mRebindScheduled = true; 392 } 393 } 394 395 private final Runnable mStableCheck = this::stableConnectionCheck; 396 stableConnectionCheck()397 private void stableConnectionCheck() { 398 synchronized (mLock) { 399 final long now = injectUptimeMillis(); 400 final long timeRemaining = (mLastConnectedTime + mResetBackoffDelay) - now; 401 if (DEBUG) { 402 Log.d(mTag, "stableConnectionCheck: bound=" + mBound + " connected=" + mIsConnected 403 + " remaining=" + timeRemaining); 404 } 405 if (mBound && mIsConnected && timeRemaining <= 0) { 406 resetBackoffLocked(); 407 } 408 } 409 } 410 411 @GuardedBy("mLock") unscheduleStableCheckLocked()412 private void unscheduleStableCheckLocked() { 413 injectRemoveCallbacks(mStableCheck); 414 } 415 416 @GuardedBy("mLock") scheduleStableCheckLocked()417 private void scheduleStableCheckLocked() { 418 unscheduleStableCheckLocked(); 419 injectPostAtTime(mStableCheck, injectUptimeMillis() + mResetBackoffDelay); 420 } 421 422 /** Must be implemented by a subclass to convert an {@link IBinder} to a stub. */ asInterface(IBinder binder)423 protected abstract T asInterface(IBinder binder); 424 dump(String prefix, PrintWriter pw)425 public void dump(String prefix, PrintWriter pw) { 426 synchronized (mLock) { 427 pw.print(prefix); 428 pw.print(mComponentName.flattenToShortString()); 429 pw.print(" u"); 430 pw.print(mUserId); 431 pw.print(mBound ? " [bound]" : " [not bound]"); 432 pw.print(mIsConnected ? " [connected]" : " [not connected]"); 433 if (mRebindScheduled) { 434 pw.print(" reconnect in "); 435 TimeUtils.formatDuration((mReconnectTime - injectUptimeMillis()), pw); 436 } 437 pw.println(); 438 439 pw.print(prefix); 440 pw.print(" Next backoff(sec): "); 441 pw.print(mNextBackoffMs / 1000); 442 pw.println(); 443 444 pw.print(prefix); 445 pw.print(" Connected: "); 446 pw.print(mNumConnected); 447 pw.print(" Disconnected: "); 448 pw.print(mNumDisconnected); 449 pw.print(" Died: "); 450 pw.print(mNumBindingDied); 451 if (mIsConnected) { 452 pw.print(" Duration: "); 453 TimeUtils.formatDuration((injectUptimeMillis() - mLastConnectedTime), pw); 454 } 455 pw.println(); 456 } 457 } 458 459 @VisibleForTesting injectRemoveCallbacks(Runnable r)460 void injectRemoveCallbacks(Runnable r) { 461 mHandler.removeCallbacks(r); 462 } 463 464 @VisibleForTesting injectPostAtTime(Runnable r, long uptimeMillis)465 void injectPostAtTime(Runnable r, long uptimeMillis) { 466 mHandler.postAtTime(r, uptimeMillis); 467 } 468 469 @VisibleForTesting injectUptimeMillis()470 long injectUptimeMillis() { 471 return SystemClock.uptimeMillis(); 472 } 473 474 @VisibleForTesting getNextBackoffMsForTest()475 long getNextBackoffMsForTest() { 476 return mNextBackoffMs; 477 } 478 479 @VisibleForTesting getReconnectTimeForTest()480 long getReconnectTimeForTest() { 481 return mReconnectTime; 482 } 483 484 @VisibleForTesting getServiceConnectionForTest()485 ServiceConnection getServiceConnectionForTest() { 486 return mServiceConnection; 487 } 488 489 @VisibleForTesting getBindForBackoffRunnableForTest()490 Runnable getBindForBackoffRunnableForTest() { 491 return mBindForBackoffRunnable; 492 } 493 494 @VisibleForTesting getStableCheckRunnableForTest()495 Runnable getStableCheckRunnableForTest() { 496 return mStableCheck; 497 } 498 499 @VisibleForTesting shouldBeBoundForTest()500 boolean shouldBeBoundForTest() { 501 return mShouldBeBound; 502 } 503 } 504