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