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