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 { 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 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 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 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 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 { 172 IInterface asInterface(IBinder binder); 173 boolean checkType(IInterface service); 174 } 175 176 /** 177 * Implement to call IInterface methods after service is connected. 178 */ 179 public interface PendingEvent { 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 */ 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 */ 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 */ 225 public int getUserId() { 226 return mUserId; 227 } 228 229 /** 230 * @return the component of the bound service. 231 */ 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 */ 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 */ 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 */ 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 */ 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 409 private boolean matches(final ComponentName component, final int userId) { 410 return Objects.equals(mComponent, component) && mUserId == userId; 411 } 412 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 428 private void stopRetriesLocked() { 429 mRetrying = false; 430 mHandler.removeCallbacks(mRetryRunnable); 431 } 432 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 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 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