blob: d965122ee3f374ad962c8ee5c8d6da9b9fabd936 [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;
Ruben Brunk52ea6622017-10-02 23:51:25 -070019import android.annotation.Nullable;
Ruben Brunke24b9a62016-02-16 21:38:24 -080020import android.app.PendingIntent;
21import android.content.ComponentName;
22import android.content.Context;
23import android.content.Intent;
24import android.content.ServiceConnection;
25import android.os.IBinder;
26import android.os.IBinder.DeathRecipient;
27import android.os.IInterface;
28import android.os.RemoteException;
29import android.os.UserHandle;
30import android.util.Slog;
31
32import java.util.Objects;
33
34/**
35 * Manages the lifecycle of an application-provided service bound from system server.
36 *
37 * @hide
38 */
39public class ManagedApplicationService {
40 private final String TAG = getClass().getSimpleName();
41
42 private final Context mContext;
43 private final int mUserId;
44 private final ComponentName mComponent;
45 private final int mClientLabel;
46 private final String mSettingsAction;
47 private final BinderChecker mChecker;
Ruben Brunk589c73d2017-09-18 18:26:05 -070048 private final boolean mIsImportant;
Ruben Brunke24b9a62016-02-16 21:38:24 -080049
50 private final DeathRecipient mDeathRecipient = new DeathRecipient() {
51 @Override
52 public void binderDied() {
53 synchronized (mLock) {
54 mBoundInterface = null;
55 }
56 }
57 };
58
59 private final Object mLock = new Object();
60
61 // State protected by mLock
62 private ServiceConnection mPendingConnection;
63 private ServiceConnection mConnection;
64 private IInterface mBoundInterface;
Ruben Brunkc7354fe2016-03-07 23:37:12 -080065 private PendingEvent mPendingEvent;
66
Ruben Brunke24b9a62016-02-16 21:38:24 -080067 private ManagedApplicationService(final Context context, final ComponentName component,
68 final int userId, int clientLabel, String settingsAction,
Ruben Brunk589c73d2017-09-18 18:26:05 -070069 BinderChecker binderChecker, boolean isImportant) {
Ruben Brunke24b9a62016-02-16 21:38:24 -080070 mContext = context;
71 mComponent = component;
72 mUserId = userId;
73 mClientLabel = clientLabel;
74 mSettingsAction = settingsAction;
75 mChecker = binderChecker;
Ruben Brunk589c73d2017-09-18 18:26:05 -070076 mIsImportant = isImportant;
Ruben Brunke24b9a62016-02-16 21:38:24 -080077 }
78
79 /**
80 * Implement to validate returned IBinder instance.
81 */
82 public interface BinderChecker {
83 IInterface asInterface(IBinder binder);
84 boolean checkType(IInterface service);
85 }
86
87 /**
Ruben Brunkc7354fe2016-03-07 23:37:12 -080088 * Implement to call IInterface methods after service is connected.
89 */
90 public interface PendingEvent {
91 void runEvent(IInterface service) throws RemoteException;
92 }
93
94 /**
Ruben Brunke24b9a62016-02-16 21:38:24 -080095 * Create a new ManagedApplicationService object but do not yet bind to the user service.
96 *
97 * @param context a Context to use for binding the application service.
98 * @param component the {@link ComponentName} of the application service to bind.
99 * @param userId the user ID of user to bind the application service as.
100 * @param clientLabel the resource ID of a label displayed to the user indicating the
Ruben Brunk52ea6622017-10-02 23:51:25 -0700101 * binding service, or 0 if none is desired.
Ruben Brunke24b9a62016-02-16 21:38:24 -0800102 * @param settingsAction an action that can be used to open the Settings UI to enable/disable
Ruben Brunk52ea6622017-10-02 23:51:25 -0700103 * binding to these services, or null if none is desired.
104 * @param binderChecker an interface used to validate the returned binder object, or null if
105 * this interface is unchecked.
Ruben Brunk589c73d2017-09-18 18:26:05 -0700106 * @param isImportant bind the user service with BIND_IMPORTANT.
Ruben Brunke24b9a62016-02-16 21:38:24 -0800107 * @return a ManagedApplicationService instance.
108 */
109 public static ManagedApplicationService build(@NonNull final Context context,
Ruben Brunk52ea6622017-10-02 23:51:25 -0700110 @NonNull final ComponentName component, final int userId, int clientLabel,
111 @Nullable String settingsAction, @Nullable BinderChecker binderChecker,
112 boolean isImportant) {
Ruben Brunke24b9a62016-02-16 21:38:24 -0800113 return new ManagedApplicationService(context, component, userId, clientLabel,
Ruben Brunk589c73d2017-09-18 18:26:05 -0700114 settingsAction, binderChecker, isImportant);
Ruben Brunke24b9a62016-02-16 21:38:24 -0800115 }
116
Ruben Brunk52ea6622017-10-02 23:51:25 -0700117
Ruben Brunke24b9a62016-02-16 21:38:24 -0800118 /**
119 * @return the user ID of the user that owns the bound service.
120 */
121 public int getUserId() {
122 return mUserId;
123 }
124
125 /**
126 * @return the component of the bound service.
127 */
128 public ComponentName getComponent() {
129 return mComponent;
130 }
131
132 /**
133 * Asynchronously unbind from the application service if the bound service component and user
134 * does not match the given signature.
135 *
136 * @param componentName the component that must match.
137 * @param userId the user ID that must match.
138 * @return {@code true} if not matching.
139 */
140 public boolean disconnectIfNotMatching(final ComponentName componentName, final int userId) {
141 if (matches(componentName, userId)) {
142 return false;
143 }
144 disconnect();
145 return true;
146 }
147
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800148
149 /**
150 * Send an event to run as soon as the binder interface is available.
151 *
152 * @param event a {@link PendingEvent} to send.
153 */
154 public void sendEvent(@NonNull PendingEvent event) {
155 IInterface iface;
156 synchronized (mLock) {
157 iface = mBoundInterface;
158 if (iface == null) {
159 mPendingEvent = event;
160 }
161 }
162
163 if (iface != null) {
164 try {
165 event.runEvent(iface);
166 } catch (RuntimeException | RemoteException ex) {
167 Slog.e(TAG, "Received exception from user service: ", ex);
168 }
169 }
170 }
171
Ruben Brunke24b9a62016-02-16 21:38:24 -0800172 /**
173 * Asynchronously unbind from the application service if bound.
174 */
175 public void disconnect() {
176 synchronized (mLock) {
177 // Wipe out pending connections
178 mPendingConnection = null;
179
180 // Unbind existing connection, if it exists
181 if (mConnection != null) {
182 mContext.unbindService(mConnection);
183 mConnection = null;
184 }
185
186 mBoundInterface = null;
187 }
188 }
189
190 /**
191 * Asynchronously bind to the application service if not bound.
192 */
193 public void connect() {
194 synchronized (mLock) {
195 if (mConnection != null || mPendingConnection != null) {
196 // We're already connected or are trying to connect
197 return;
198 }
199
Ruben Brunk52ea6622017-10-02 23:51:25 -0700200 Intent intent = new Intent().setComponent(mComponent);
201 if (mClientLabel != 0) {
202 intent.putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel);
203 }
204 if (mSettingsAction != null) {
205 intent.putExtra(Intent.EXTRA_CLIENT_INTENT,
206 PendingIntent.getActivity(mContext, 0, new Intent(mSettingsAction), 0));
207 }
Ruben Brunke24b9a62016-02-16 21:38:24 -0800208
209 final ServiceConnection serviceConnection = new ServiceConnection() {
210 @Override
211 public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800212 IInterface iface = null;
213 PendingEvent pendingEvent = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800214 synchronized (mLock) {
215 if (mPendingConnection == this) {
216 // No longer pending, remove from pending connection
217 mPendingConnection = null;
218 mConnection = this;
219 } else {
220 // Service connection wasn't pending, must have been disconnected
221 mContext.unbindService(this);
Ruben Brunk4beb6be2016-03-22 19:12:25 -0700222 return;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800223 }
224
225 try {
226 iBinder.linkToDeath(mDeathRecipient, 0);
Ruben Brunk52ea6622017-10-02 23:51:25 -0700227 mBoundInterface = null;
228 if (mChecker != null) {
229 mBoundInterface = mChecker.asInterface(iBinder);
230 if (!mChecker.checkType(mBoundInterface)) {
231 // Received an invalid binder, disconnect
232 mContext.unbindService(this);
233 mBoundInterface = null;
234 }
235 iface = mBoundInterface;
236 pendingEvent = mPendingEvent;
237 mPendingEvent = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800238 }
239 } catch (RemoteException e) {
240 // DOA
Ruben Brunk52ea6622017-10-02 23:51:25 -0700241 Slog.w(TAG, "Unable to bind service: " + componentName, e);
Ruben Brunke24b9a62016-02-16 21:38:24 -0800242 mBoundInterface = null;
243 }
244 }
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800245 if (iface != null && pendingEvent != null) {
246 try {
247 pendingEvent.runEvent(iface);
248 } catch (RuntimeException | RemoteException ex) {
249 Slog.e(TAG, "Received exception from user service: ", ex);
250 }
251 }
Ruben Brunke24b9a62016-02-16 21:38:24 -0800252 }
253
254 @Override
255 public void onServiceDisconnected(ComponentName componentName) {
Ruben Brunk52ea6622017-10-02 23:51:25 -0700256 Slog.w(TAG, "Service disconnected: " + componentName);
Ruben Brunk4beb6be2016-03-22 19:12:25 -0700257 mConnection = null;
258 mBoundInterface = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800259 }
260 };
261
262 mPendingConnection = serviceConnection;
263
Ruben Brunk589c73d2017-09-18 18:26:05 -0700264 int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
265 if (mIsImportant) {
266 flags |= Context.BIND_IMPORTANT;
267 }
Ruben Brunke24b9a62016-02-16 21:38:24 -0800268 try {
Ruben Brunk589c73d2017-09-18 18:26:05 -0700269 if (!mContext.bindServiceAsUser(intent, serviceConnection, flags,
Ruben Brunke24b9a62016-02-16 21:38:24 -0800270 new UserHandle(mUserId))) {
271 Slog.w(TAG, "Unable to bind service: " + intent);
272 }
273 } catch (SecurityException e) {
274 Slog.w(TAG, "Unable to bind service: " + intent, e);
275 }
276 }
277 }
278
279 private boolean matches(final ComponentName component, final int userId) {
280 return Objects.equals(mComponent, component) && mUserId == userId;
281 }
282}