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