blob: a64570130e45c16cdffb4fd55d67da44e49d037b [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;
63
64
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 {
80 IInterface asInterface(IBinder binder);
81 boolean checkType(IInterface service);
82 }
83
84 /**
85 * Create a new ManagedApplicationService object but do not yet bind to the user service.
86 *
87 * @param context a Context to use for binding the application service.
88 * @param component the {@link ComponentName} of the application service to bind.
89 * @param userId the user ID of user to bind the application service as.
90 * @param clientLabel the resource ID of a label displayed to the user indicating the
91 * binding service.
92 * @param settingsAction an action that can be used to open the Settings UI to enable/disable
93 * binding to these services.
94 * @param binderChecker an interface used to validate the returned binder object.
95 * @return a ManagedApplicationService instance.
96 */
97 public static ManagedApplicationService build(@NonNull final Context context,
98 @NonNull final ComponentName component, final int userId, @NonNull int clientLabel,
99 @NonNull String settingsAction, @NonNull BinderChecker binderChecker) {
100 return new ManagedApplicationService(context, component, userId, clientLabel,
101 settingsAction, binderChecker);
102 }
103
104 /**
105 * @return the user ID of the user that owns the bound service.
106 */
107 public int getUserId() {
108 return mUserId;
109 }
110
111 /**
112 * @return the component of the bound service.
113 */
114 public ComponentName getComponent() {
115 return mComponent;
116 }
117
118 /**
119 * Asynchronously unbind from the application service if the bound service component and user
120 * does not match the given signature.
121 *
122 * @param componentName the component that must match.
123 * @param userId the user ID that must match.
124 * @return {@code true} if not matching.
125 */
126 public boolean disconnectIfNotMatching(final ComponentName componentName, final int userId) {
127 if (matches(componentName, userId)) {
128 return false;
129 }
130 disconnect();
131 return true;
132 }
133
134 /**
135 * Asynchronously unbind from the application service if bound.
136 */
137 public void disconnect() {
138 synchronized (mLock) {
139 // Wipe out pending connections
140 mPendingConnection = null;
141
142 // Unbind existing connection, if it exists
143 if (mConnection != null) {
144 mContext.unbindService(mConnection);
145 mConnection = null;
146 }
147
148 mBoundInterface = null;
149 }
150 }
151
152 /**
153 * Asynchronously bind to the application service if not bound.
154 */
155 public void connect() {
156 synchronized (mLock) {
157 if (mConnection != null || mPendingConnection != null) {
158 // We're already connected or are trying to connect
159 return;
160 }
161
162 final PendingIntent pendingIntent = PendingIntent.getActivity(
163 mContext, 0, new Intent(mSettingsAction), 0);
164 final Intent intent = new Intent().setComponent(mComponent).
165 putExtra(Intent.EXTRA_CLIENT_LABEL, mClientLabel).
166 putExtra(Intent.EXTRA_CLIENT_INTENT, pendingIntent);
167
168 final ServiceConnection serviceConnection = new ServiceConnection() {
169 @Override
170 public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
171 synchronized (mLock) {
172 if (mPendingConnection == this) {
173 // No longer pending, remove from pending connection
174 mPendingConnection = null;
175 mConnection = this;
176 } else {
177 // Service connection wasn't pending, must have been disconnected
178 mContext.unbindService(this);
179 }
180
181 try {
182 iBinder.linkToDeath(mDeathRecipient, 0);
183 mBoundInterface = mChecker.asInterface(iBinder);
184 if (!mChecker.checkType(mBoundInterface)) {
185 // Received an invalid binder, disconnect
186 mContext.unbindService(this);
187 mBoundInterface = null;
188 }
189 } catch (RemoteException e) {
190 // DOA
191 Slog.w(TAG, "Unable to bind service: " + intent, e);
192 mBoundInterface = null;
193 }
194 }
195 }
196
197 @Override
198 public void onServiceDisconnected(ComponentName componentName) {
199 Slog.w(TAG, "Service disconnected: " + intent);
200 }
201 };
202
203 mPendingConnection = serviceConnection;
204
205 try {
206 if (!mContext.bindServiceAsUser(intent, serviceConnection,
207 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE,
208 new UserHandle(mUserId))) {
209 Slog.w(TAG, "Unable to bind service: " + intent);
210 }
211 } catch (SecurityException e) {
212 Slog.w(TAG, "Unable to bind service: " + intent, e);
213 }
214 }
215 }
216
217 private boolean matches(final ComponentName component, final int userId) {
218 return Objects.equals(mComponent, component) && mUserId == userId;
219 }
220}