1 /** 2 * Copyright (c) 2016, 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.utils; 17 18 import android.annotation.NonNull; 19 import android.annotation.Nullable; 20 import android.app.PendingIntent; 21 import android.content.ComponentName; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.ServiceConnection; 25 import android.os.Handler; 26 import android.os.IBinder; 27 import android.os.IBinder.DeathRecipient; 28 import android.os.IInterface; 29 import android.os.RemoteException; 30 import android.os.SystemClock; 31 import android.os.UserHandle; 32 import android.util.Slog; 33 34 import java.text.SimpleDateFormat; 35 import java.util.Objects; 36 import java.util.Date; 37 38 /** 39 * Manages the lifecycle of an application-provided service bound from system server. 40 * 41 * @hide 42 */ 43 public class ManagedApplicationService { 44 private final String TAG = getClass().getSimpleName(); 45 46 /** 47 * Attempt to reconnect service forever if an onBindingDied or onServiceDisconnected event 48 * is received. 49 */ 50 public static final int RETRY_FOREVER = 1; 51 52 /** 53 * Never attempt to reconnect the service - a single onBindingDied or onServiceDisconnected 54 * event will cause this to fully unbind the service and never attempt to reconnect. 55 */ 56 public static final int RETRY_NEVER = 2; 57 58 /** 59 * Attempt to reconnect the service until the maximum number of retries is reached, then stop. 60 * 61 * The first retry will occur MIN_RETRY_DURATION_MS after the disconnection, and each 62 * subsequent retry will occur after 2x the duration used for the previous retry up to the 63 * MAX_RETRY_DURATION_MS duration. 64 * 65 * In this case, retries mean a full unbindService/bindService pair to handle cases when the 66 * usual service re-connection logic in ActiveServices has very high backoff times or when the 67 * serviceconnection has fully died due to a package update or similar. 68 */ 69 public static final int RETRY_BEST_EFFORT = 3; 70 71 // Maximum number of retries before giving up (for RETRY_BEST_EFFORT). 72 private static final int MAX_RETRY_COUNT = 4; 73 // Max time between retry attempts. 74 private static final long MAX_RETRY_DURATION_MS = 16000; 75 // Min time between retry attempts. 76 private static final long MIN_RETRY_DURATION_MS = 2000; 77 // Time since the last retry attempt after which to clear the retry attempt counter. 78 private static final long RETRY_RESET_TIME_MS = MAX_RETRY_DURATION_MS * 4; 79 80 private final Context mContext; 81 private final int mUserId; 82 private final ComponentName mComponent; 83 private final int mClientLabel; 84 private final String mSettingsAction; 85 private final BinderChecker mChecker; 86 private final boolean mIsImportant; 87 private final int mRetryType; 88 private final Handler mHandler; 89 private final Runnable mRetryRunnable = this::doRetry; 90 private final EventCallback mEventCb; 91 92 private final Object mLock = new Object(); 93 94 // State protected by mLock 95 private ServiceConnection mConnection; 96 private IInterface mBoundInterface; 97 private PendingEvent mPendingEvent; 98 private int mRetryCount; 99 private long mLastRetryTimeMs; 100 private long mNextRetryDurationMs = MIN_RETRY_DURATION_MS; 101 private boolean mRetrying; 102 103 public static interface LogFormattable { toLogString(SimpleDateFormat dateFormat)104 String toLogString(SimpleDateFormat dateFormat); 105 } 106 107 /** 108 * Lifecycle event of this managed service. 109 */ 110 public static class LogEvent implements LogFormattable { 111 public static final int EVENT_CONNECTED = 1; 112 public static final int EVENT_DISCONNECTED = 2; 113 public static final int EVENT_BINDING_DIED = 3; 114 public static final int EVENT_STOPPED_PERMANENTLY = 4; 115 116 // Time of the events in "current time ms" timebase. 117 public final long timestamp; 118 // Name of the component for this system service. 119 public final ComponentName component; 120 // ID of the event that occurred. 121 public final int event; 122 LogEvent(long timestamp, ComponentName component, int event)123 public LogEvent(long timestamp, ComponentName component, int event) { 124 this.timestamp = timestamp; 125 this.component = component; 126 this.event = event; 127 } 128 129 @Override toLogString(SimpleDateFormat dateFormat)130 public String toLogString(SimpleDateFormat dateFormat) { 131 return dateFormat.format(new Date(timestamp)) + " " + eventToString(event) 132 + " Managed Service: " 133 + ((component == null) ? "None" : component.flattenToString()); 134 } 135 eventToString(int event)136 public static String eventToString(int event) { 137 switch (event) { 138 case EVENT_CONNECTED: 139 return "Connected"; 140 case EVENT_DISCONNECTED: 141 return "Disconnected"; 142 case EVENT_BINDING_DIED: 143 return "Binding Died For"; 144 case EVENT_STOPPED_PERMANENTLY: 145 return "Permanently Stopped"; 146 default: 147 return "Unknown Event Occurred"; 148 } 149 } 150 } 151 ManagedApplicationService(final Context context, final ComponentName component, final int userId, int clientLabel, String settingsAction, BinderChecker binderChecker, boolean isImportant, int retryType, Handler handler, EventCallback eventCallback)152 private ManagedApplicationService(final Context context, final ComponentName component, 153 final int userId, int clientLabel, String settingsAction, 154 BinderChecker binderChecker, boolean isImportant, int retryType, Handler handler, 155 EventCallback eventCallback) { 156 mContext = context; 157 mComponent = component; 158 mUserId = userId; 159 mClientLabel = clientLabel; 160 mSettingsAction = settingsAction; 161 mChecker = binderChecker; 162 mIsImportant = isImportant; 163 mRetryType = retryType; 164 mHandler = handler; 165 mEventCb = eventCallback; 166 } 167 168 /** 169 * Implement to validate returned IBinder instance. 170 */ 171 public interface BinderChecker { asInterface(IBinder binder)172 IInterface asInterface(IBinder binder); checkType(IInterface service)173 boolean checkType(IInterface service); 174 } 175 176 /** 177 * Implement to call IInterface methods after service is connected. 178 */ 179 public interface PendingEvent { runEvent(IInterface service)180 void runEvent(IInterface service) throws RemoteException; 181 } 182 183 /** 184 * Implement to be notified about any problems with remote service. 185 */ 186 public interface EventCallback { 187 /** 188 * Called when an sevice lifecycle event occurs. 189 */ onServiceEvent(LogEvent event)190 void onServiceEvent(LogEvent event); 191 } 192 193 /** 194 * Create a new ManagedApplicationService object but do not yet bind to the user service. 195 * 196 * @param context a Context to use for binding the application service. 197 * @param component the {@link ComponentName} of the application service to bind. 198 * @param userId the user ID of user to bind the application service as. 199 * @param clientLabel the resource ID of a label displayed to the user indicating the 200 * binding service, or 0 if none is desired. 201 * @param settingsAction an action that can be used to open the Settings UI to enable/disable 202 * binding to these services, or null if none is desired. 203 * @param binderChecker an interface used to validate the returned binder object, or null if 204 * this interface is unchecked. 205 * @param isImportant bind the user service with BIND_IMPORTANT. 206 * @param retryType reconnect behavior to have when bound service is disconnected. 207 * @param handler the Handler to use for retries and delivering EventCallbacks. 208 * @param eventCallback a callback used to deliver disconnection events, or null if you 209 * don't care. 210 * @return a ManagedApplicationService instance. 211 */ build(@onNull final Context context, @NonNull final ComponentName component, final int userId, int clientLabel, @Nullable String settingsAction, @Nullable BinderChecker binderChecker, boolean isImportant, int retryType, @NonNull Handler handler, @Nullable EventCallback eventCallback)212 public static ManagedApplicationService build(@NonNull final Context context, 213 @NonNull final ComponentName component, final int userId, int clientLabel, 214 @Nullable String settingsAction, @Nullable BinderChecker binderChecker, 215 boolean isImportant, int retryType, @NonNull Handler handler, 216 @Nullable EventCallback eventCallback) { 217 return new ManagedApplicationService(context, component, userId, clientLabel, 218 settingsAction, binderChecker, isImportant, retryType, handler, eventCallback); 219 } 220 221 222 /** 223 * @return the user ID of the user that owns the bound service. 224 */ getUserId()225 public int getUserId() { 226 return mUserId; 227 } 228 229 /** 230 * @return the component of the bound service. 231 */ getComponent()232 public ComponentName getComponent() { 233 return mComponent; 234 } 235 236 /** 237 * Asynchronously unbind from the application service if the bound service component and user 238 * does not match the given signature. 239 * 240 * @param componentName the component that must match. 241 * @param userId the user ID that must match. 242 * @return {@code true} if not matching. 243 */ disconnectIfNotMatching(final ComponentName componentName, final int userId)244 public boolean disconnectIfNotMatching(final ComponentName componentName, final int userId) { 245 if (matches(componentName, userId)) { 246 return false; 247 } 248 disconnect(); 249 return true; 250 } 251 252 /** 253 * Send an event to run as soon as the binder interface is available. 254 * 255 * @param event a {@link PendingEvent} to send. 256 */ sendEvent(@onNull PendingEvent event)257 public void sendEvent(@NonNull PendingEvent event) { 258 IInterface iface; 259 synchronized (mLock) { 260 iface = mBoundInterface; 261 if (iface == null) { 262 mPendingEvent = event; 263 } 264 } 265 266 if (iface != null) { 267 try { 268 event.runEvent(iface); 269 } catch (RuntimeException | RemoteException ex) { 270 Slog.e(TAG, "Received exception from user service: ", ex); 271 } 272 } 273 } 274 275 /** 276 * Asynchronously unbind from the application service if bound. 277 */ disconnect()278 public void disconnect() { 279 synchronized (mLock) { 280 // Unbind existing connection, if it exists 281 if (mConnection == null) { 282 return; 283 } 284 285 mContext.unbindService(mConnection); 286 mConnection = null; 287 mBoundInterface = null; 288 } 289 } 290 291 /** 292 * Asynchronously bind to the application service if not bound. 293 */ connect()294 public void connect() { 295 synchronized (mLock) { 296 if (mConnection != null) { 297 // We're already connected or are trying to connect 298 return; 299 } 300 301 Intent intent = new Intent().setComponent(mComponent); 302 if (mClientLabel != 0) { 303 intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel); 304 } 305 if (mSettingsAction != null) { 306 intent.putExtra(Intent.EXTRA_CLIENT_INTENT, 307 PendingIntent.getActivity(mContext, 0, new Intent(mSettingsAction), 308 PendingIntent.FLAG_IMMUTABLE)); 309 } 310 311 mConnection = new ServiceConnection() { 312 @Override 313 public void onBindingDied(ComponentName componentName) { 314 final long timestamp = System.currentTimeMillis(); 315 Slog.w(TAG, "Service binding died: " + componentName); 316 synchronized (mLock) { 317 if (mConnection != this) { 318 return; 319 } 320 mHandler.post(() -> { 321 mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent, 322 LogEvent.EVENT_BINDING_DIED)); 323 }); 324 325 mBoundInterface = null; 326 startRetriesLocked(); 327 } 328 } 329 330 @Override 331 public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 332 final long timestamp = System.currentTimeMillis(); 333 Slog.i(TAG, "Service connected: " + componentName); 334 IInterface iface = null; 335 PendingEvent pendingEvent = null; 336 synchronized (mLock) { 337 if (mConnection != this) { 338 // Must've been unbound. 339 return; 340 } 341 mHandler.post(() -> { 342 mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent, 343 LogEvent.EVENT_CONNECTED)); 344 }); 345 346 stopRetriesLocked(); 347 348 mBoundInterface = null; 349 if (mChecker != null) { 350 mBoundInterface = mChecker.asInterface(iBinder); 351 if (!mChecker.checkType(mBoundInterface)) { 352 // Received an invalid binder, disconnect. 353 mBoundInterface = null; 354 Slog.w(TAG, "Invalid binder from " + componentName); 355 startRetriesLocked(); 356 return; 357 } 358 iface = mBoundInterface; 359 pendingEvent = mPendingEvent; 360 mPendingEvent = null; 361 } 362 } 363 if (iface != null && pendingEvent != null) { 364 try { 365 pendingEvent.runEvent(iface); 366 } catch (RuntimeException | RemoteException ex) { 367 Slog.e(TAG, "Received exception from user service: ", ex); 368 startRetriesLocked(); 369 } 370 } 371 } 372 373 @Override 374 public void onServiceDisconnected(ComponentName componentName) { 375 final long timestamp = System.currentTimeMillis(); 376 Slog.w(TAG, "Service disconnected: " + componentName); 377 synchronized (mLock) { 378 if (mConnection != this) { 379 return; 380 } 381 382 mHandler.post(() -> { 383 mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent, 384 LogEvent.EVENT_DISCONNECTED)); 385 }); 386 387 mBoundInterface = null; 388 startRetriesLocked(); 389 } 390 } 391 }; 392 393 int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; 394 if (mIsImportant) { 395 flags |= Context.BIND_IMPORTANT; 396 } 397 try { 398 if (!mContext.bindServiceAsUser(intent, mConnection, flags, 399 new UserHandle(mUserId))) { 400 Slog.w(TAG, "Unable to bind service: " + intent); 401 startRetriesLocked(); 402 } 403 } catch (SecurityException e) { 404 Slog.w(TAG, "Unable to bind service: " + intent, e); 405 startRetriesLocked(); 406 } 407 } 408 } 409 matches(final ComponentName component, final int userId)410 private boolean matches(final ComponentName component, final int userId) { 411 return Objects.equals(mComponent, component) && mUserId == userId; 412 } 413 startRetriesLocked()414 private void startRetriesLocked() { 415 if (checkAndDeliverServiceDiedCbLocked()) { 416 // If we delivered the service callback, disconnect and stop retrying. 417 disconnect(); 418 return; 419 } 420 421 if (mRetrying) { 422 // Retry already queued, don't queue a new one. 423 return; 424 } 425 mRetrying = true; 426 queueRetryLocked(); 427 } 428 stopRetriesLocked()429 private void stopRetriesLocked() { 430 mRetrying = false; 431 mHandler.removeCallbacks(mRetryRunnable); 432 } 433 queueRetryLocked()434 private void queueRetryLocked() { 435 long now = SystemClock.uptimeMillis(); 436 if ((now - mLastRetryTimeMs) > RETRY_RESET_TIME_MS) { 437 // It's been longer than the reset time since we last had to retry. Re-initialize. 438 mNextRetryDurationMs = MIN_RETRY_DURATION_MS; 439 mRetryCount = 0; 440 } 441 mLastRetryTimeMs = now; 442 mHandler.postDelayed(mRetryRunnable, mNextRetryDurationMs); 443 mNextRetryDurationMs = Math.min(2 * mNextRetryDurationMs, MAX_RETRY_DURATION_MS); 444 mRetryCount++; 445 } 446 checkAndDeliverServiceDiedCbLocked()447 private boolean checkAndDeliverServiceDiedCbLocked() { 448 449 if (mRetryType == RETRY_NEVER || (mRetryType == RETRY_BEST_EFFORT 450 && mRetryCount >= MAX_RETRY_COUNT)) { 451 // If we never retry, or we've exhausted our retries, post the onServiceDied callback. 452 Slog.e(TAG, "Service " + mComponent + " has died too much, not retrying."); 453 if (mEventCb != null) { 454 final long timestamp = System.currentTimeMillis(); 455 mHandler.post(() -> { 456 mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent, 457 LogEvent.EVENT_STOPPED_PERMANENTLY)); 458 }); 459 } 460 return true; 461 } 462 return false; 463 } 464 doRetry()465 private void doRetry() { 466 synchronized (mLock) { 467 if (mConnection == null) { 468 // We disconnected for good. Don't attempt to retry. 469 return; 470 } 471 if (!mRetrying) { 472 // We successfully connected. Don't attempt to retry. 473 return; 474 } 475 Slog.i(TAG, "Attempting to reconnect " + mComponent + "..."); 476 // While frameworks may restart the remote Service if we stay bound, we have little 477 // control of the backoff timing for reconnecting the service. In the event of a 478 // process crash, the backoff time can be very large (1-30 min), which is not 479 // acceptable for the types of services this is used for. Instead force an unbind/bind 480 // sequence to cause a more immediate retry. 481 disconnect(); 482 if (checkAndDeliverServiceDiedCbLocked()) { 483 // No more retries. 484 return; 485 } 486 queueRetryLocked(); 487 connect(); 488 } 489 } 490 } 491