Home | History | Annotate | Download | only in utils
      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