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 
ManagedApplicationService(final Context context, final ComponentName component, final int userId, int clientLabel, String settingsAction, BinderChecker binderChecker)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 {
asInterface(IBinder binder)80         IInterface asInterface(IBinder binder);
checkType(IInterface service)81         boolean checkType(IInterface service);
82     }
83 
84     /**
85      * Implement to call IInterface methods after service is connected.
86      */
87     public interface PendingEvent {
runEvent(IInterface service)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      */
build(@onNull final Context context, @NonNull final ComponentName component, final int userId, @NonNull int clientLabel, @NonNull String settingsAction, @NonNull BinderChecker binderChecker)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      */
getUserId()114     public int getUserId() {
115         return mUserId;
116     }
117 
118     /**
119      * @return the component of the bound service.
120      */
getComponent()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      */
disconnectIfNotMatching(final ComponentName componentName, final int userId)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    */
sendEvent(@onNull PendingEvent event)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      */
disconnect()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      */
connect()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 
matches(final ComponentName component, final int userId)263     private boolean matches(final ComponentName component, final int userId) {
264         return Objects.equals(mComponent, component) && mUserId == userId;
265     }
266 }
267