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 */
16package com.android.server.utils;
17
18import android.annotation.NonNull;
19import android.annotation.Nullable;
20import android.app.PendingIntent;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.os.Handler;
26import android.os.IBinder;
27import android.os.IBinder.DeathRecipient;
28import android.os.IInterface;
29import android.os.RemoteException;
30import android.os.SystemClock;
31import android.os.UserHandle;
32import android.util.Slog;
33
34import java.text.SimpleDateFormat;
35import java.util.Objects;
36import java.util.Date;
37
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
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