blob: 189370e8301be06d5787b4452bfe9638b418a1eb [file] [log] [blame]
Pavel Maltsev38da4312019-04-08 10:38:38 -07001/*
2 * Copyright (C) 2019 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 */
16
17package com.android.car.pm;
18
19import static android.content.Context.BIND_AUTO_CREATE;
20
21import android.car.userlib.CarUserManagerHelper;
22import android.content.ComponentName;
23import android.content.Context;
24import android.content.Intent;
25import android.content.ServiceConnection;
26import android.content.res.Resources;
27import android.os.Debug;
28import android.os.Handler;
29import android.os.IBinder;
30import android.os.Looper;
31import android.os.Message;
32import android.os.UserHandle;
33import android.os.UserManager;
34import android.text.TextUtils;
35import android.util.Log;
36
37import com.android.car.CarLocalServices;
38import com.android.car.CarLog;
39import com.android.car.R;
40import com.android.car.user.CarUserService;
41
42import java.util.ArrayList;
43import java.util.HashMap;
44import java.util.List;
45import java.util.Objects;
46
47/**
48 * Class that responsible for controlling vendor services that was opted in to be bound/started
49 * by the Car Service.
50 *
51 * <p>Thread-safety note: all code runs in the {@code Handler} provided in the constructor, whenever
52 * possible pass {@link #mHandler} when subscribe for callbacks otherwise redirect code to the
53 * handler.
54 */
55class VendorServiceController implements CarUserService.UserCallback {
56 private static final boolean DBG = true;
57
58 private static final int MSG_SWITCH_USER = 1;
59 private static final int MSG_USER_LOCK_CHANGED = 2;
60
61 private final List<VendorServiceInfo> mVendorServiceInfos = new ArrayList<>();
62 private final HashMap<ConnectionKey, VendorServiceConnection> mConnections =
63 new HashMap<>();
64 private final Context mContext;
65 private final UserManager mUserManager;
66 private final Handler mHandler;
67 private final CarUserManagerHelper mUserManagerHelper;
68 private CarUserService mCarUserService;
69
70
71 VendorServiceController(Context context, Looper looper,
72 CarUserManagerHelper userManagerHelper) {
73 mContext = context;
74 mUserManager = context.getSystemService(UserManager.class);
75 mUserManagerHelper = userManagerHelper;
76 mHandler = new Handler(looper) {
77 @Override
78 public void handleMessage(Message msg) {
79 VendorServiceController.this.handleMessage(msg);
80 }
81 };
82 }
83
84 private void handleMessage(Message msg) {
85 switch (msg.what) {
86 case MSG_SWITCH_USER: {
87 int userId = msg.arg1;
88 doSwitchUser(userId);
89 break;
90 }
91 case MSG_USER_LOCK_CHANGED: {
92 int userId = msg.arg1;
93 boolean locked = msg.arg2 == 1;
94 doUserLockChanged(userId, locked);
95 break;
96 }
97 default:
98 Log.e(CarLog.TAG_PACKAGE, "Unexpected message " + msg);
99 }
100 }
101
102 void init() {
103 if (!loadXmlConfiguration()) {
104 return; // Nothing to do
105 }
106
107 mCarUserService = CarLocalServices.getService(CarUserService.class);
108 mCarUserService.addUserCallback(this);
109
110 startOrBindServicesIfNeeded();
111 }
112
113 void release() {
114 if (mCarUserService != null) {
115 mCarUserService.removeUserCallback(this);
116 }
117
118 for (ConnectionKey key : mConnections.keySet()) {
119 stopOrUnbindService(key.mVendorServiceInfo, key.mUserHandle);
120 }
121 mVendorServiceInfos.clear();
122 mConnections.clear();
123 }
124
125 private void doSwitchUser(int userId) {
126 // Stop all services which which do not run under foreground or system user.
127 final int fgUser = mUserManagerHelper.getCurrentForegroundUserId();
128 if (fgUser != userId) {
129 Log.w(CarLog.TAG_PACKAGE, "Received userSwitch event for user " + userId
130 + " while current foreground user is " + fgUser + "."
131 + " Ignore the switch user event.");
132 return;
133 }
134
135 for (VendorServiceConnection connection : mConnections.values()) {
136 final int connectedUserId = connection.mUser.getIdentifier();
137 if (connectedUserId != UserHandle.USER_SYSTEM && connectedUserId != userId) {
138 connection.stopOrUnbindService();
139 }
140 }
141
142 if (userId != UserHandle.USER_SYSTEM) {
143 startOrBindServicesForUser(UserHandle.of(userId));
144 } else {
145 Log.e(CarLog.TAG_PACKAGE, "Unexpected to receive switch user event for system user");
146 }
147 }
148
149 private void doUserLockChanged(int userId, boolean unlocked) {
150 final int currentUserId = mUserManagerHelper.getCurrentForegroundUserId();
151
152 if (DBG) {
153 Log.i(CarLog.TAG_PACKAGE, "onUserLockedChanged, user: " + userId
154 + ", unlocked: " + unlocked + ", currentUser: " + currentUserId);
155 }
156 if (unlocked && (userId == currentUserId || userId == UserHandle.USER_SYSTEM)) {
157 startOrBindServicesForUser(UserHandle.of(userId));
158 } else if (!unlocked && userId != UserHandle.USER_SYSTEM) {
159 for (ConnectionKey key : mConnections.keySet()) {
160 if (key.mUserHandle.getIdentifier() == userId) {
161 stopOrUnbindService(key.mVendorServiceInfo, key.mUserHandle);
162 }
163 }
164 }
165 }
166
167 private void startOrBindServicesForUser(UserHandle user) {
168 boolean unlocked = mUserManager.isUserUnlockingOrUnlocked(user);
169 boolean systemUser = UserHandle.SYSTEM.equals(user);
170 for (VendorServiceInfo service: mVendorServiceInfos) {
171 boolean userScopeChecked = (!systemUser && service.isForegroundUserService())
172 || (systemUser && service.isSystemUserService());
173 boolean triggerChecked = service.shouldStartAsap()
174 || (unlocked && service.shouldStartOnUnlock());
175
176 if (userScopeChecked && triggerChecked) {
177 startOrBindService(service, user);
178 }
179 }
180 }
181
182 private void startOrBindServicesIfNeeded() {
183 int userId = mUserManagerHelper.getCurrentForegroundUserId();
184 startOrBindServicesForUser(UserHandle.SYSTEM);
185 if (userId > 0) {
186 startOrBindServicesForUser(UserHandle.of(userId));
187 }
188 }
189
190 @Override
191 public void onUserLockChanged(int userId, boolean unlocked) {
192 Message msg = mHandler.obtainMessage(MSG_USER_LOCK_CHANGED, userId, unlocked ? 1 : 0);
193 mHandler.executeOrSendMessage(msg);
194 }
195
196 @Override
197 public void onSwitchUser(int userId) {
198 mHandler.removeMessages(MSG_SWITCH_USER);
199 Message msg = mHandler.obtainMessage(MSG_SWITCH_USER, userId, 0);
200 mHandler.executeOrSendMessage(msg);
201 }
202
203 private void startOrBindService(VendorServiceInfo service, UserHandle user) {
204 ConnectionKey key = ConnectionKey.of(service, user);
205 VendorServiceConnection connection = getOrCreateConnection(key);
206 if (!connection.startOrBindService()) {
207 Log.e(CarLog.TAG_PACKAGE, "Failed to start or bind service " + service);
208 mConnections.remove(key);
209 }
210 }
211
212 private void stopOrUnbindService(VendorServiceInfo service, UserHandle user) {
213 ConnectionKey key = ConnectionKey.of(service, user);
214 VendorServiceConnection connection = mConnections.get(key);
215 if (connection != null) {
216 connection.stopOrUnbindService();
217 }
218 }
219
220 private VendorServiceConnection getOrCreateConnection(ConnectionKey key) {
221 VendorServiceConnection connection = mConnections.get(key);
222 if (connection == null) {
223 connection = new VendorServiceConnection(mContext, mHandler, mUserManagerHelper,
224 key.mVendorServiceInfo, key.mUserHandle);
225 mConnections.put(key, connection);
226 }
227
228 return connection;
229 }
230
231 /** Loads data from XML resources and returns true if any services needs to be started/bound. */
232 private boolean loadXmlConfiguration() {
233 final Resources res = mContext.getResources();
234 for (String rawServiceInfo: res.getStringArray(R.array.config_earlyStartupServices)) {
235 if (TextUtils.isEmpty(rawServiceInfo)) {
236 continue;
237 }
238 VendorServiceInfo service = VendorServiceInfo.parse(rawServiceInfo);
239 mVendorServiceInfos.add(service);
240 if (DBG) {
241 Log.i(CarLog.TAG_PACKAGE, "Registered vendor service: " + service);
242 }
243 }
244 Log.i(CarLog.TAG_PACKAGE, "Found " + mVendorServiceInfos.size()
245 + " services to be started/bound");
246
247 return !mVendorServiceInfos.isEmpty();
248 }
249
250 /**
251 * Represents connection to the vendor service.
252 */
253 private static class VendorServiceConnection implements ServiceConnection {
254 private static final int REBIND_DELAY_MS = 1000;
255 private static final int MAX_RECENT_FAILURES = 5;
256 private static final int FAILURE_COUNTER_RESET_TIMEOUT = 5 * 60 * 1000; // 5 min.
257 private static final int MSG_REBIND = 0;
258 private static final int MSG_FAILURE_COUNTER_RESET = 1;
259
260 private int mRecentFailures = 0;
261 private boolean mBound = false;
262 private boolean mStarted = false;
263 private boolean mStopRequested = false;
264 private final VendorServiceInfo mVendorServiceInfo;
265 private final Context mContext;
266 private final UserHandle mUser;
267 private final Handler mHandler;
268 private final Handler mFailureHandler;
269 private final CarUserManagerHelper mUserManagerHelper;
270
271 VendorServiceConnection(Context context, Handler handler,
272 CarUserManagerHelper userManagerHelper, VendorServiceInfo vendorServiceInfo,
273 UserHandle user) {
274 mContext = context;
275 mHandler = handler;
276 mUserManagerHelper = userManagerHelper;
277 mVendorServiceInfo = vendorServiceInfo;
278 mUser = user;
279
280 mFailureHandler = new Handler(handler.getLooper()) {
281 @Override
282 public void handleMessage(Message msg) {
283 handleFailureMessage(msg);
284 }
285 };
286 }
287
288 boolean startOrBindService() {
289 if (mStarted || mBound) {
290 return true; // Already started or bound
291 }
292
293 if (DBG) {
294 Log.d(CarLog.TAG_PACKAGE, "startOrBindService " + mVendorServiceInfo.toShortString()
295 + ", as user: " + mUser + ", bind: " + mVendorServiceInfo.shouldBeBound()
296 + ", stack: " + Debug.getCallers(5));
297 }
298 mStopRequested = false;
299
300 Intent intent = mVendorServiceInfo.getIntent();
301 if (mVendorServiceInfo.shouldBeBound()) {
302 return mContext.bindServiceAsUser(intent, this, BIND_AUTO_CREATE, mHandler, mUser);
Ritwika Mitra8e45f862019-06-10 14:50:38 -0700303 } else if (mVendorServiceInfo.shouldBeStartedInForeground()) {
304 mStarted = mContext.startForegroundServiceAsUser(intent, mUser) != null;
305 return mStarted;
Pavel Maltsev38da4312019-04-08 10:38:38 -0700306 } else {
307 mStarted = mContext.startServiceAsUser(intent, mUser) != null;
308 return mStarted;
309 }
310 }
311
312 void stopOrUnbindService() {
313 mStopRequested = true;
314 if (mStarted) {
315 mContext.stopServiceAsUser(mVendorServiceInfo.getIntent(), mUser);
316 mStarted = false;
317 } else if (mBound) {
318 mContext.unbindService(this);
319 mBound = false;
320 }
321 }
322
323 @Override
324 public void onServiceConnected(ComponentName name, IBinder service) {
325 mBound = true;
326 if (DBG) {
327 Log.d(CarLog.TAG_PACKAGE, "onServiceConnected, name: " + name);
328 }
329 if (mStopRequested) {
330 stopOrUnbindService();
331 }
332 }
333
334 @Override
335 public void onServiceDisconnected(ComponentName name) {
336 mBound = false;
337 if (DBG) {
338 Log.d(CarLog.TAG_PACKAGE, "onServiceDisconnected, name: " + name);
339 }
340 tryToRebind();
341 }
342
343 @Override
344 public void onBindingDied(ComponentName name) {
345 mBound = false;
346 tryToRebind();
347 }
348
349 private void tryToRebind() {
350 if (mStopRequested) {
351 return;
352 }
353
354 if (UserHandle.of(mUserManagerHelper.getCurrentForegroundUserId()).equals(mUser)
355 || UserHandle.SYSTEM.equals(mUser)) {
356 mFailureHandler.sendMessageDelayed(
357 mFailureHandler.obtainMessage(MSG_REBIND), REBIND_DELAY_MS);
358 scheduleResetFailureCounter();
359 } else {
360 Log.w(CarLog.TAG_PACKAGE, "No need to rebind anymore as the user " + mUser
361 + " is no longer in foreground.");
362 }
363 }
364
365 private void scheduleResetFailureCounter() {
366 mFailureHandler.removeMessages(MSG_FAILURE_COUNTER_RESET);
367 mFailureHandler.sendMessageDelayed(
368 mFailureHandler.obtainMessage(MSG_FAILURE_COUNTER_RESET),
369 FAILURE_COUNTER_RESET_TIMEOUT);
370 }
371
372 private void handleFailureMessage(Message msg) {
373 switch (msg.what) {
374 case MSG_REBIND: {
375 if (mRecentFailures < MAX_RECENT_FAILURES && !mBound) {
376 Log.i(CarLog.TAG_PACKAGE, "Attempting to rebind to the service "
377 + mVendorServiceInfo.toShortString());
378 ++mRecentFailures;
379 startOrBindService();
380 } else {
381 Log.w(CarLog.TAG_PACKAGE, "Exceeded maximum number of attempts to rebind"
382 + "to the service " + mVendorServiceInfo.toShortString());
383 }
384 break;
385 }
386 case MSG_FAILURE_COUNTER_RESET:
387 mRecentFailures = 0;
388 break;
389 default:
390 Log.e(CarLog.TAG_PACKAGE,
391 "Unexpected message received in failure handler: " + msg.what);
392 }
393 }
394 }
395
396 /** Defines a key in the HashMap to store connection on per user and vendor service basis */
397 private static class ConnectionKey {
398 private final UserHandle mUserHandle;
399 private final VendorServiceInfo mVendorServiceInfo;
400
401 private ConnectionKey(VendorServiceInfo service, UserHandle user) {
402 mVendorServiceInfo = service;
403 mUserHandle = user;
404 }
405
406 static ConnectionKey of(VendorServiceInfo service, UserHandle user) {
407 return new ConnectionKey(service, user);
408 }
409
410 @Override
411 public boolean equals(Object o) {
412 if (this == o) {
413 return true;
414 }
415 if (!(o instanceof ConnectionKey)) {
416 return false;
417 }
418 ConnectionKey that = (ConnectionKey) o;
419 return Objects.equals(mUserHandle, that.mUserHandle)
420 && Objects.equals(mVendorServiceInfo, that.mVendorServiceInfo);
421 }
422
423 @Override
424 public int hashCode() {
425 return Objects.hash(mUserHandle, mVendorServiceInfo);
426 }
427 }
428}