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