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), 0)); 308 } 309 310 mConnection = new ServiceConnection() { 311 @Override 312 public void onBindingDied(ComponentName componentName) { 313 final long timestamp = System.currentTimeMillis(); 314 Slog.w(TAG, "Service binding died: " + componentName); 315 synchronized (mLock) { 316 if (mConnection != this) { 317 return; 318 } 319 mHandler.post(() -> { 320 mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent, 321 LogEvent.EVENT_BINDING_DIED)); 322 }); 323 324 mBoundInterface = null; 325 startRetriesLocked(); 326 } 327 } 328 329 @Override 330 public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 331 final long timestamp = System.currentTimeMillis(); 332 Slog.i(TAG, "Service connected: " + componentName); 333 IInterface iface = null; 334 PendingEvent pendingEvent = null; 335 synchronized (mLock) { 336 if (mConnection != this) { 337 // Must've been unbound. 338 return; 339 } 340 mHandler.post(() -> { 341 mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent, 342 LogEvent.EVENT_CONNECTED)); 343 }); 344 345 stopRetriesLocked(); 346 347 mBoundInterface = null; 348 if (mChecker != null) { 349 mBoundInterface = mChecker.asInterface(iBinder); 350 if (!mChecker.checkType(mBoundInterface)) { 351 // Received an invalid binder, disconnect. 352 mBoundInterface = null; 353 Slog.w(TAG, "Invalid binder from " + componentName); 354 startRetriesLocked(); 355 return; 356 } 357 iface = mBoundInterface; 358 pendingEvent = mPendingEvent; 359 mPendingEvent = null; 360 } 361 } 362 if (iface != null && pendingEvent != null) { 363 try { 364 pendingEvent.runEvent(iface); 365 } catch (RuntimeException | RemoteException ex) { 366 Slog.e(TAG, "Received exception from user service: ", ex); 367 startRetriesLocked(); 368 } 369 } 370 } 371 372 @Override 373 public void onServiceDisconnected(ComponentName componentName) { 374 final long timestamp = System.currentTimeMillis(); 375 Slog.w(TAG, "Service disconnected: " + componentName); 376 synchronized (mLock) { 377 if (mConnection != this) { 378 return; 379 } 380 381 mHandler.post(() -> { 382 mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent, 383 LogEvent.EVENT_DISCONNECTED)); 384 }); 385 386 mBoundInterface = null; 387 startRetriesLocked(); 388 } 389 } 390 }; 391 392 int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE; 393 if (mIsImportant) { 394 flags |= Context.BIND_IMPORTANT; 395 } 396 try { 397 if (!mContext.bindServiceAsUser(intent, mConnection, flags, 398 new UserHandle(mUserId))) { 399 Slog.w(TAG, "Unable to bind service: " + intent); 400 startRetriesLocked(); 401 } 402 } catch (SecurityException e) { 403 Slog.w(TAG, "Unable to bind service: " + intent, e); 404 startRetriesLocked(); 405 } 406 } 407 } 408 matches(final ComponentName component, final int userId)409 private boolean matches(final ComponentName component, final int userId) { 410 return Objects.equals(mComponent, component) && mUserId == userId; 411 } 412 startRetriesLocked()413 private void startRetriesLocked() { 414 if (checkAndDeliverServiceDiedCbLocked()) { 415 // If we delivered the service callback, disconnect and stop retrying. 416 disconnect(); 417 return; 418 } 419 420 if (mRetrying) { 421 // Retry already queued, don't queue a new one. 422 return; 423 } 424 mRetrying = true; 425 queueRetryLocked(); 426 } 427 stopRetriesLocked()428 private void stopRetriesLocked() { 429 mRetrying = false; 430 mHandler.removeCallbacks(mRetryRunnable); 431 } 432 queueRetryLocked()433 private void queueRetryLocked() { 434 long now = SystemClock.uptimeMillis(); 435 if ((now - mLastRetryTimeMs) > RETRY_RESET_TIME_MS) { 436 // It's been longer than the reset time since we last had to retry. Re-initialize. 437 mNextRetryDurationMs = MIN_RETRY_DURATION_MS; 438 mRetryCount = 0; 439 } 440 mLastRetryTimeMs = now; 441 mHandler.postDelayed(mRetryRunnable, mNextRetryDurationMs); 442 mNextRetryDurationMs = Math.min(2 * mNextRetryDurationMs, MAX_RETRY_DURATION_MS); 443 mRetryCount++; 444 } 445 checkAndDeliverServiceDiedCbLocked()446 private boolean checkAndDeliverServiceDiedCbLocked() { 447 448 if (mRetryType == RETRY_NEVER || (mRetryType == RETRY_BEST_EFFORT 449 && mRetryCount >= MAX_RETRY_COUNT)) { 450 // If we never retry, or we've exhausted our retries, post the onServiceDied callback. 451 Slog.e(TAG, "Service " + mComponent + " has died too much, not retrying."); 452 if (mEventCb != null) { 453 final long timestamp = System.currentTimeMillis(); 454 mHandler.post(() -> { 455 mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent, 456 LogEvent.EVENT_STOPPED_PERMANENTLY)); 457 }); 458 } 459 return true; 460 } 461 return false; 462 } 463 doRetry()464 private void doRetry() { 465 synchronized (mLock) { 466 if (mConnection == null) { 467 // We disconnected for good. Don't attempt to retry. 468 return; 469 } 470 if (!mRetrying) { 471 // We successfully connected. Don't attempt to retry. 472 return; 473 } 474 Slog.i(TAG, "Attempting to reconnect " + mComponent + "..."); 475 // While frameworks may restart the remote Service if we stay bound, we have little 476 // control of the backoff timing for reconnecting the service. In the event of a 477 // process crash, the backoff time can be very large (1-30 min), which is not 478 // acceptable for the types of services this is used for. Instead force an unbind/bind 479 // sequence to cause a more immediate retry. 480 disconnect(); 481 if (checkAndDeliverServiceDiedCbLocked()) { 482 // No more retries. 483 return; 484 } 485 queueRetryLocked(); 486 connect(); 487 } 488 } 489 } 490