blob: 0f251fd8382205b9aefb66d2f7646187b8550aaa [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 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 /**
Ruben Brunkc7354fe2016-03-07 23:37:12 -080085 * Implement to call IInterface methods after service is connected.
86 */
87 public interface PendingEvent {
88 void runEvent(IInterface service) throws RemoteException;
89 }
90
91 /**
Ruben Brunke24b9a62016-02-16 21:38:24 -080092 * 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
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800141
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
Ruben Brunke24b9a62016-02-16 21:38:24 -0800165 /**
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) {
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800202 IInterface iface = null;
203 PendingEvent pendingEvent = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800204 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);
Ruben Brunk4beb6be2016-03-22 19:12:25 -0700212 return;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800213 }
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 }
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800223 iface = mBoundInterface;
224 pendingEvent = mPendingEvent;
225 mPendingEvent = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800226 } catch (RemoteException e) {
227 // DOA
228 Slog.w(TAG, "Unable to bind service: " + intent, e);
229 mBoundInterface = null;
230 }
231 }
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800232 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 }
Ruben Brunke24b9a62016-02-16 21:38:24 -0800239 }
240
241 @Override
242 public void onServiceDisconnected(ComponentName componentName) {
243 Slog.w(TAG, "Service disconnected: " + intent);
Ruben Brunk4beb6be2016-03-22 19:12:25 -0700244 mConnection = null;
245 mBoundInterface = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800246 }
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}