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.app.PendingIntent;
     20 import android.content.ComponentName;
     21 import android.content.Context;
     22 import android.content.Intent;
     23 import android.content.ServiceConnection;
     24 import android.os.IBinder;
     25 import android.os.IBinder.DeathRecipient;
     26 import android.os.IInterface;
     27 import android.os.RemoteException;
     28 import android.os.UserHandle;
     29 import android.util.Slog;
     30 
     31 import java.util.Objects;
     32 
     33 /**
     34  * Manages the lifecycle of an application-provided service bound from system server.
     35  *
     36  * @hide
     37  */
     38 public class ManagedApplicationService {
     39     private final String TAG = getClass().getSimpleName();
     40 
     41     private final Context mContext;
     42     private final int mUserId;
     43     private final ComponentName mComponent;
     44     private final int mClientLabel;
     45     private final String mSettingsAction;
     46     private final BinderChecker mChecker;
     47 
     48     private final DeathRecipient mDeathRecipient = new DeathRecipient() {
     49         @Override
     50         public void binderDied() {
     51             synchronized (mLock) {
     52                 mBoundInterface = null;
     53             }
     54         }
     55     };
     56 
     57     private final Object mLock = new Object();
     58 
     59     // State protected by mLock
     60     private ServiceConnection mPendingConnection;
     61     private ServiceConnection mConnection;
     62     private IInterface mBoundInterface;
     63     private PendingEvent mPendingEvent;
     64 
     65     private ManagedApplicationService(final Context context, final ComponentName component,
     66             final int userId, int clientLabel, String settingsAction,
     67             BinderChecker binderChecker) {
     68         mContext = context;
     69         mComponent = component;
     70         mUserId = userId;
     71         mClientLabel = clientLabel;
     72         mSettingsAction = settingsAction;
     73         mChecker = binderChecker;
     74     }
     75 
     76     /**
     77      * Implement to validate returned IBinder instance.
     78      */
     79     public interface BinderChecker {
     80         IInterface asInterface(IBinder binder);
     81         boolean checkType(IInterface service);
     82     }
     83 
     84     /**
     85      * Implement to call IInterface methods after service is connected.
     86      */
     87     public interface PendingEvent {
     88          void runEvent(IInterface service) throws RemoteException;
     89     }
     90 
     91     /**
     92      * Create a new ManagedApplicationService object but do not yet bind to the user service.
     93      *
     94      * @param context a Context to use for binding the application service.
     95      * @param component the {@link ComponentName} of the application service to bind.
     96      * @param userId the user ID of user to bind the application service as.
     97      * @param clientLabel the resource ID of a label displayed to the user indicating the
     98      *      binding service.
     99      * @param settingsAction an action that can be used to open the Settings UI to enable/disable
    100      *      binding to these services.
    101      * @param binderChecker an interface used to validate the returned binder object.
    102      * @return a ManagedApplicationService instance.
    103      */
    104     public static ManagedApplicationService build(@NonNull final Context context,
    105         @NonNull final ComponentName component, final int userId, @NonNull int clientLabel,
    106         @NonNull String settingsAction, @NonNull BinderChecker binderChecker) {
    107         return new ManagedApplicationService(context, component, userId, clientLabel,
    108             settingsAction, binderChecker);
    109     }
    110 
    111     /**
    112      * @return the user ID of the user that owns the bound service.
    113      */
    114     public int getUserId() {
    115         return mUserId;
    116     }
    117 
    118     /**
    119      * @return the component of the bound service.
    120      */
    121     public ComponentName getComponent() {
    122         return mComponent;
    123     }
    124 
    125     /**
    126      * Asynchronously unbind from the application service if the bound service component and user
    127      * does not match the given signature.
    128      *
    129      * @param componentName the component that must match.
    130      * @param userId the user ID that must match.
    131      * @return {@code true} if not matching.
    132      */
    133     public boolean disconnectIfNotMatching(final ComponentName componentName, final int userId) {
    134         if (matches(componentName, userId)) {
    135             return false;
    136         }
    137         disconnect();
    138         return true;
    139     }
    140 
    141 
    142   /**
    143    * Send an event to run as soon as the binder interface is available.
    144    *
    145    * @param event a {@link PendingEvent} to send.
    146    */
    147   public void sendEvent(@NonNull PendingEvent event) {
    148         IInterface iface;
    149         synchronized (mLock) {
    150             iface = mBoundInterface;
    151             if (iface == null) {
    152                 mPendingEvent = event;
    153             }
    154         }
    155 
    156         if (iface != null) {
    157             try {
    158                 event.runEvent(iface);
    159             } catch (RuntimeException | RemoteException ex) {
    160                 Slog.e(TAG, "Received exception from user service: ", ex);
    161             }
    162         }
    163     }
    164 
    165     /**
    166      * Asynchronously unbind from the application service if bound.
    167      */
    168     public void disconnect() {
    169         synchronized (mLock) {
    170             // Wipe out pending connections
    171             mPendingConnection = null;
    172 
    173             // Unbind existing connection, if it exists
    174             if (mConnection != null) {
    175                 mContext.unbindService(mConnection);
    176                 mConnection = null;
    177             }
    178 
    179             mBoundInterface = null;
    180         }
    181     }
    182 
    183     /**
    184      * Asynchronously bind to the application service if not bound.
    185      */
    186     public void connect() {
    187         synchronized (mLock) {
    188             if (mConnection != null || mPendingConnection != null) {
    189                 // We're already connected or are trying to connect
    190                 return;
    191             }
    192 
    193             final PendingIntent pendingIntent = PendingIntent.getActivity(
    194                     mContext, 0, new Intent(mSettingsAction), 0);
    195             final Intent intent = new Intent().setComponent(mComponent).
    196                     putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel).
    197                     putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
    198 
    199             final ServiceConnection serviceConnection = new ServiceConnection() {
    200                 @Override
    201                 public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
    202                     IInterface iface = null;
    203                     PendingEvent pendingEvent = null;
    204                     synchronized (mLock) {
    205                         if (mPendingConnection == this) {
    206                             // No longer pending, remove from pending connection
    207                             mPendingConnection = null;
    208                             mConnection = this;
    209                         } else {
    210                             // Service connection wasn't pending, must have been disconnected
    211                             mContext.unbindService(this);
    212                             return;
    213                         }
    214 
    215                         try {
    216                             iBinder.linkToDeath(mDeathRecipient, 0);
    217                             mBoundInterface = mChecker.asInterface(iBinder);
    218                             if (!mChecker.checkType(mBoundInterface)) {
    219                                 // Received an invalid binder, disconnect
    220                                 mContext.unbindService(this);
    221                                 mBoundInterface = null;
    222                             }
    223                             iface = mBoundInterface;
    224                             pendingEvent = mPendingEvent;
    225                             mPendingEvent = null;
    226                         } catch (RemoteException e) {
    227                             // DOA
    228                             Slog.w(TAG, "Unable to bind service: " + intent, e);
    229                             mBoundInterface = null;
    230                         }
    231                     }
    232                     if (iface != null && pendingEvent != null) {
    233                         try {
    234                             pendingEvent.runEvent(iface);
    235                         } catch (RuntimeException | RemoteException ex) {
    236                             Slog.e(TAG, "Received exception from user service: ", ex);
    237                         }
    238                     }
    239                 }
    240 
    241                 @Override
    242                 public void onServiceDisconnected(ComponentName componentName) {
    243                     Slog.w(TAG, "Service disconnected: " + intent);
    244                     mConnection = null;
    245                     mBoundInterface = null;
    246                 }
    247             };
    248 
    249             mPendingConnection = serviceConnection;
    250 
    251             try {
    252                 if (!mContext.bindServiceAsUser(intent, serviceConnection,
    253                         Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
    254                         new UserHandle(mUserId))) {
    255                     Slog.w(TAG, "Unable to bind service: " + intent);
    256                 }
    257             } catch (SecurityException e) {
    258                 Slog.w(TAG, "Unable to bind service: " + intent, e);
    259             }
    260         }
    261     }
    262 
    263     private boolean matches(final ComponentName component, final int userId) {
    264         return Objects.equals(mComponent, component) && mUserId == userId;
    265     }
    266 }
    267