blob: c5553881cf6cd2d4d5491f019416311b60c99d01 [file] [log] [blame]
Ruben Brunke24b9a62016-02-16 21:38:24 -08001/**
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 */
16package com.android.server.utils;
17
18import android.annotation.NonNull;
Ruben Brunk52ea6622017-10-02 23:51:25 -070019import android.annotation.Nullable;
Ruben Brunke24b9a62016-02-16 21:38:24 -080020import android.app.PendingIntent;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
Ruben Brunkd675f512017-10-03 15:39:41 -070025import android.os.Handler;
Ruben Brunke24b9a62016-02-16 21:38:24 -080026import android.os.IBinder;
27import android.os.IBinder.DeathRecipient;
28import android.os.IInterface;
29import android.os.RemoteException;
Ruben Brunkd675f512017-10-03 15:39:41 -070030import android.os.SystemClock;
Ruben Brunke24b9a62016-02-16 21:38:24 -080031import android.os.UserHandle;
32import android.util.Slog;
33
Ruben Brunkd675f512017-10-03 15:39:41 -070034import java.text.SimpleDateFormat;
Ruben Brunke24b9a62016-02-16 21:38:24 -080035import java.util.Objects;
Ruben Brunkd675f512017-10-03 15:39:41 -070036import java.util.Date;
Ruben Brunke24b9a62016-02-16 21:38:24 -080037
38/**
39 * Manages the lifecycle of an application-provided service bound from system server.
40 *
41 * @hide
42 */
43public class ManagedApplicationService {
44 private final String TAG = getClass().getSimpleName();
45
Ruben Brunkd675f512017-10-03 15:39:41 -070046 /**
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
Ruben Brunke24b9a62016-02-16 21:38:24 -080080 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;
Ruben Brunk589c73d2017-09-18 18:26:05 -070086 private final boolean mIsImportant;
Ruben Brunkd675f512017-10-03 15:39:41 -070087 private final int mRetryType;
88 private final Handler mHandler;
89 private final Runnable mRetryRunnable = this::doRetry;
90 private final EventCallback mEventCb;
Ruben Brunke24b9a62016-02-16 21:38:24 -080091
92 private final Object mLock = new Object();
93
94 // State protected by mLock
Ruben Brunke24b9a62016-02-16 21:38:24 -080095 private ServiceConnection mConnection;
96 private IInterface mBoundInterface;
Ruben Brunkc7354fe2016-03-07 23:37:12 -080097 private PendingEvent mPendingEvent;
Ruben Brunkd675f512017-10-03 15:39:41 -070098 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 }
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800151
Ruben Brunke24b9a62016-02-16 21:38:24 -0800152 private ManagedApplicationService(final Context context, final ComponentName component,
153 final int userId, int clientLabel, String settingsAction,
Ruben Brunkd675f512017-10-03 15:39:41 -0700154 BinderChecker binderChecker, boolean isImportant, int retryType, Handler handler,
155 EventCallback eventCallback) {
Ruben Brunke24b9a62016-02-16 21:38:24 -0800156 mContext = context;
157 mComponent = component;
158 mUserId = userId;
159 mClientLabel = clientLabel;
160 mSettingsAction = settingsAction;
161 mChecker = binderChecker;
Ruben Brunk589c73d2017-09-18 18:26:05 -0700162 mIsImportant = isImportant;
Ruben Brunkd675f512017-10-03 15:39:41 -0700163 mRetryType = retryType;
164 mHandler = handler;
165 mEventCb = eventCallback;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800166 }
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 /**
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800177 * Implement to call IInterface methods after service is connected.
178 */
179 public interface PendingEvent {
Ruben Brunkd675f512017-10-03 15:39:41 -0700180 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);
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800191 }
192
193 /**
Ruben Brunke24b9a62016-02-16 21:38:24 -0800194 * 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
Ruben Brunk52ea6622017-10-02 23:51:25 -0700200 * binding service, or 0 if none is desired.
Ruben Brunke24b9a62016-02-16 21:38:24 -0800201 * @param settingsAction an action that can be used to open the Settings UI to enable/disable
Ruben Brunk52ea6622017-10-02 23:51:25 -0700202 * 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.
Ruben Brunk589c73d2017-09-18 18:26:05 -0700205 * @param isImportant bind the user service with BIND_IMPORTANT.
Ruben Brunkd675f512017-10-03 15:39:41 -0700206 * @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.
Ruben Brunke24b9a62016-02-16 21:38:24 -0800210 * @return a ManagedApplicationService instance.
211 */
212 public static ManagedApplicationService build(@NonNull final Context context,
Ruben Brunk52ea6622017-10-02 23:51:25 -0700213 @NonNull final ComponentName component, final int userId, int clientLabel,
214 @Nullable String settingsAction, @Nullable BinderChecker binderChecker,
Ruben Brunkd675f512017-10-03 15:39:41 -0700215 boolean isImportant, int retryType, @NonNull Handler handler,
216 @Nullable EventCallback eventCallback) {
Ruben Brunke24b9a62016-02-16 21:38:24 -0800217 return new ManagedApplicationService(context, component, userId, clientLabel,
Ruben Brunkd675f512017-10-03 15:39:41 -0700218 settingsAction, binderChecker, isImportant, retryType, handler, eventCallback);
Ruben Brunke24b9a62016-02-16 21:38:24 -0800219 }
220
Ruben Brunk52ea6622017-10-02 23:51:25 -0700221
Ruben Brunke24b9a62016-02-16 21:38:24 -0800222 /**
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
Ruben Brunkd675f512017-10-03 15:39:41 -0700252 /**
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) {
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800258 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
Ruben Brunke24b9a62016-02-16 21:38:24 -0800275 /**
276 * Asynchronously unbind from the application service if bound.
277 */
278 public void disconnect() {
279 synchronized (mLock) {
Ruben Brunke24b9a62016-02-16 21:38:24 -0800280 // Unbind existing connection, if it exists
Ruben Brunkd675f512017-10-03 15:39:41 -0700281 if (mConnection == null) {
282 return;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800283 }
284
Ruben Brunkd675f512017-10-03 15:39:41 -0700285 mContext.unbindService(mConnection);
286 mConnection = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800287 mBoundInterface = null;
288 }
289 }
290
291 /**
292 * Asynchronously bind to the application service if not bound.
293 */
294 public void connect() {
295 synchronized (mLock) {
Ruben Brunkd675f512017-10-03 15:39:41 -0700296 if (mConnection != null) {
Ruben Brunke24b9a62016-02-16 21:38:24 -0800297 // We're already connected or are trying to connect
298 return;
299 }
300
Ruben Brunk52ea6622017-10-02 23:51:25 -0700301 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 }
Ruben Brunke24b9a62016-02-16 21:38:24 -0800309
Ruben Brunkd675f512017-10-03 15:39:41 -0700310 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
Ruben Brunke24b9a62016-02-16 21:38:24 -0800329 @Override
330 public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Ruben Brunkd675f512017-10-03 15:39:41 -0700331 final long timestamp = System.currentTimeMillis();
332 Slog.i(TAG, "Service connected: " + componentName);
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800333 IInterface iface = null;
334 PendingEvent pendingEvent = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800335 synchronized (mLock) {
Ruben Brunkd675f512017-10-03 15:39:41 -0700336 if (mConnection != this) {
337 // Must've been unbound.
Ruben Brunk4beb6be2016-03-22 19:12:25 -0700338 return;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800339 }
Ruben Brunkd675f512017-10-03 15:39:41 -0700340 mHandler.post(() -> {
341 mEventCb.onServiceEvent(new LogEvent(timestamp, mComponent,
342 LogEvent.EVENT_CONNECTED));
343 });
Ruben Brunke24b9a62016-02-16 21:38:24 -0800344
Ruben Brunkd675f512017-10-03 15:39:41 -0700345 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;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800356 }
Ruben Brunkd675f512017-10-03 15:39:41 -0700357 iface = mBoundInterface;
358 pendingEvent = mPendingEvent;
359 mPendingEvent = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800360 }
361 }
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800362 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);
Ruben Brunkd675f512017-10-03 15:39:41 -0700367 startRetriesLocked();
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800368 }
369 }
Ruben Brunke24b9a62016-02-16 21:38:24 -0800370 }
371
372 @Override
373 public void onServiceDisconnected(ComponentName componentName) {
Ruben Brunkd675f512017-10-03 15:39:41 -0700374 final long timestamp = System.currentTimeMillis();
Ruben Brunk52ea6622017-10-02 23:51:25 -0700375 Slog.w(TAG, "Service disconnected: " + componentName);
Ruben Brunkd675f512017-10-03 15:39:41 -0700376 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 }
Ruben Brunke24b9a62016-02-16 21:38:24 -0800389 }
390 };
391
Ruben Brunk589c73d2017-09-18 18:26:05 -0700392 int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
393 if (mIsImportant) {
394 flags |= Context.BIND_IMPORTANT;
395 }
Ruben Brunke24b9a62016-02-16 21:38:24 -0800396 try {
Ruben Brunkd675f512017-10-03 15:39:41 -0700397 if (!mContext.bindServiceAsUser(intent, mConnection, flags,
Ruben Brunke24b9a62016-02-16 21:38:24 -0800398 new UserHandle(mUserId))) {
399 Slog.w(TAG, "Unable to bind service: " + intent);
Ruben Brunkd675f512017-10-03 15:39:41 -0700400 startRetriesLocked();
Ruben Brunke24b9a62016-02-16 21:38:24 -0800401 }
402 } catch (SecurityException e) {
403 Slog.w(TAG, "Unable to bind service: " + intent, e);
Ruben Brunkd675f512017-10-03 15:39:41 -0700404 startRetriesLocked();
Ruben Brunke24b9a62016-02-16 21:38:24 -0800405 }
406 }
407 }
408
409 private boolean matches(final ComponentName component, final int userId) {
410 return Objects.equals(mComponent, component) && mUserId == userId;
411 }
Ruben Brunkd675f512017-10-03 15:39:41 -0700412
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 }
Ruben Brunke24b9a62016-02-16 21:38:24 -0800489}