blob: 1ea70585aa5f5fb7a5ecef4ae002ed3e1179b17a [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;
Ruben Brunk589c73d2017-09-18 18:26:05 -070047 private final boolean mIsImportant;
Ruben Brunke24b9a62016-02-16 21:38:24 -080048
49 private final DeathRecipient mDeathRecipient = new DeathRecipient() {
50 @Override
51 public void binderDied() {
52 synchronized (mLock) {
53 mBoundInterface = null;
54 }
55 }
56 };
57
58 private final Object mLock = new Object();
59
60 // State protected by mLock
61 private ServiceConnection mPendingConnection;
62 private ServiceConnection mConnection;
63 private IInterface mBoundInterface;
Ruben Brunkc7354fe2016-03-07 23:37:12 -080064 private PendingEvent mPendingEvent;
65
Ruben Brunke24b9a62016-02-16 21:38:24 -080066 private ManagedApplicationService(final Context context, final ComponentName component,
67 final int userId, int clientLabel, String settingsAction,
Ruben Brunk589c73d2017-09-18 18:26:05 -070068 BinderChecker binderChecker, boolean isImportant) {
Ruben Brunke24b9a62016-02-16 21:38:24 -080069 mContext = context;
70 mComponent = component;
71 mUserId = userId;
72 mClientLabel = clientLabel;
73 mSettingsAction = settingsAction;
74 mChecker = binderChecker;
Ruben Brunk589c73d2017-09-18 18:26:05 -070075 mIsImportant = isImportant;
Ruben Brunke24b9a62016-02-16 21:38:24 -080076 }
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.
Ruben Brunk589c73d2017-09-18 18:26:05 -0700104 * @param isImportant bind the user service with BIND_IMPORTANT.
Ruben Brunke24b9a62016-02-16 21:38:24 -0800105 * @return a ManagedApplicationService instance.
106 */
107 public static ManagedApplicationService build(@NonNull final Context context,
108 @NonNull final ComponentName component, final int userId, @NonNull int clientLabel,
Ruben Brunk589c73d2017-09-18 18:26:05 -0700109 @NonNull String settingsAction, @NonNull BinderChecker binderChecker,
110 boolean isImportant) {
Ruben Brunke24b9a62016-02-16 21:38:24 -0800111 return new ManagedApplicationService(context, component, userId, clientLabel,
Ruben Brunk589c73d2017-09-18 18:26:05 -0700112 settingsAction, binderChecker, isImportant);
Ruben Brunke24b9a62016-02-16 21:38:24 -0800113 }
114
115 /**
116 * @return the user ID of the user that owns the bound service.
117 */
118 public int getUserId() {
119 return mUserId;
120 }
121
122 /**
123 * @return the component of the bound service.
124 */
125 public ComponentName getComponent() {
126 return mComponent;
127 }
128
129 /**
130 * Asynchronously unbind from the application service if the bound service component and user
131 * does not match the given signature.
132 *
133 * @param componentName the component that must match.
134 * @param userId the user ID that must match.
135 * @return {@code true} if not matching.
136 */
137 public boolean disconnectIfNotMatching(final ComponentName componentName, final int userId) {
138 if (matches(componentName, userId)) {
139 return false;
140 }
141 disconnect();
142 return true;
143 }
144
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800145
146 /**
147 * Send an event to run as soon as the binder interface is available.
148 *
149 * @param event a {@link PendingEvent} to send.
150 */
151 public void sendEvent(@NonNull PendingEvent event) {
152 IInterface iface;
153 synchronized (mLock) {
154 iface = mBoundInterface;
155 if (iface == null) {
156 mPendingEvent = event;
157 }
158 }
159
160 if (iface != null) {
161 try {
162 event.runEvent(iface);
163 } catch (RuntimeException | RemoteException ex) {
164 Slog.e(TAG, "Received exception from user service: ", ex);
165 }
166 }
167 }
168
Ruben Brunke24b9a62016-02-16 21:38:24 -0800169 /**
170 * Asynchronously unbind from the application service if bound.
171 */
172 public void disconnect() {
173 synchronized (mLock) {
174 // Wipe out pending connections
175 mPendingConnection = null;
176
177 // Unbind existing connection, if it exists
178 if (mConnection != null) {
179 mContext.unbindService(mConnection);
180 mConnection = null;
181 }
182
183 mBoundInterface = null;
184 }
185 }
186
187 /**
188 * Asynchronously bind to the application service if not bound.
189 */
190 public void connect() {
191 synchronized (mLock) {
192 if (mConnection != null || mPendingConnection != null) {
193 // We're already connected or are trying to connect
194 return;
195 }
196
197 final PendingIntent pendingIntent = PendingIntent.getActivity(
198 mContext, 0, new Intent(mSettingsAction), 0);
199 final Intent intent = new Intent().setComponent(mComponent).
200 putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel).
201 putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
202
203 final ServiceConnection serviceConnection = new ServiceConnection() {
204 @Override
205 public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800206 IInterface iface = null;
207 PendingEvent pendingEvent = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800208 synchronized (mLock) {
209 if (mPendingConnection == this) {
210 // No longer pending, remove from pending connection
211 mPendingConnection = null;
212 mConnection = this;
213 } else {
214 // Service connection wasn't pending, must have been disconnected
215 mContext.unbindService(this);
Ruben Brunk4beb6be2016-03-22 19:12:25 -0700216 return;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800217 }
218
219 try {
220 iBinder.linkToDeath(mDeathRecipient, 0);
221 mBoundInterface = mChecker.asInterface(iBinder);
222 if (!mChecker.checkType(mBoundInterface)) {
223 // Received an invalid binder, disconnect
224 mContext.unbindService(this);
225 mBoundInterface = null;
226 }
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800227 iface = mBoundInterface;
228 pendingEvent = mPendingEvent;
229 mPendingEvent = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800230 } catch (RemoteException e) {
231 // DOA
232 Slog.w(TAG, "Unable to bind service: " + intent, e);
233 mBoundInterface = null;
234 }
235 }
Ruben Brunkc7354fe2016-03-07 23:37:12 -0800236 if (iface != null && pendingEvent != null) {
237 try {
238 pendingEvent.runEvent(iface);
239 } catch (RuntimeException | RemoteException ex) {
240 Slog.e(TAG, "Received exception from user service: ", ex);
241 }
242 }
Ruben Brunke24b9a62016-02-16 21:38:24 -0800243 }
244
245 @Override
246 public void onServiceDisconnected(ComponentName componentName) {
247 Slog.w(TAG, "Service disconnected: " + intent);
Ruben Brunk4beb6be2016-03-22 19:12:25 -0700248 mConnection = null;
249 mBoundInterface = null;
Ruben Brunke24b9a62016-02-16 21:38:24 -0800250 }
251 };
252
253 mPendingConnection = serviceConnection;
254
Ruben Brunk589c73d2017-09-18 18:26:05 -0700255 int flags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE;
256 if (mIsImportant) {
257 flags |= Context.BIND_IMPORTANT;
258 }
Ruben Brunke24b9a62016-02-16 21:38:24 -0800259 try {
Ruben Brunk589c73d2017-09-18 18:26:05 -0700260 if (!mContext.bindServiceAsUser(intent, serviceConnection, flags,
Ruben Brunke24b9a62016-02-16 21:38:24 -0800261 new UserHandle(mUserId))) {
262 Slog.w(TAG, "Unable to bind service: " + intent);
263 }
264 } catch (SecurityException e) {
265 Slog.w(TAG, "Unable to bind service: " + intent, e);
266 }
267 }
268 }
269
270 private boolean matches(final ComponentName component, final int userId) {
271 return Objects.equals(mComponent, component) && mUserId == userId;
272 }
273}