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.vms;
18 
19 import android.car.Car;
20 import android.car.vms.IVmsPublisherClient;
21 import android.car.vms.IVmsSubscriberClient;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.ServiceConnection;
26 import android.content.pm.PackageManager;
27 import android.content.pm.ServiceInfo;
28 import android.os.Binder;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.Process;
33 import android.os.RemoteException;
34 import android.os.UserHandle;
35 import android.os.UserManager;
36 import android.util.ArrayMap;
37 import android.util.Log;
38 
39 import com.android.car.CarServiceBase;
40 import com.android.car.R;
41 import com.android.car.VmsPublisherService;
42 import com.android.car.hal.VmsHalService;
43 import com.android.car.stats.CarStatsService;
44 import com.android.car.stats.VmsClientLogger;
45 import com.android.car.stats.VmsClientLogger.ConnectionState;
46 import com.android.car.user.CarUserService;
47 import com.android.internal.annotations.GuardedBy;
48 import com.android.internal.annotations.VisibleForTesting;
49 
50 import java.io.PrintWriter;
51 import java.util.Collection;
52 import java.util.Map;
53 import java.util.NoSuchElementException;
54 import java.util.function.IntSupplier;
55 import java.util.stream.Collectors;
56 import java.util.stream.Stream;
57 
58 /**
59  * Manages service connections lifecycle for VMS publisher clients.
60  *
61  * Binds to system-level clients at boot and creates/destroys bindings for userspace clients
62  * according to the Android user lifecycle.
63  */
64 public class VmsClientManager implements CarServiceBase {
65     private static final boolean DBG = false;
66     private static final String TAG = "VmsClientManager";
67     private static final String HAL_CLIENT_NAME = "HalClient";
68     private static final String UNKNOWN_PACKAGE = "UnknownPackage";
69 
70     private final Context mContext;
71     private final PackageManager mPackageManager;
72     private final UserManager mUserManager;
73     private final CarUserService mUserService;
74     private final CarStatsService mStatsService;
75     private final Handler mHandler;
76     private final IntSupplier mGetCallingUid;
77     private final int mMillisBeforeRebind;
78 
79     private final Object mLock = new Object();
80 
81     @GuardedBy("mLock")
82     private final VmsBrokerService mBrokerService;
83     @GuardedBy("mLock")
84     private VmsPublisherService mPublisherService;
85 
86     @GuardedBy("mLock")
87     private final Map<String, PublisherConnection> mSystemClients = new ArrayMap<>();
88     @GuardedBy("mLock")
89     private IVmsPublisherClient mHalClient;
90     @GuardedBy("mLock")
91     private boolean mSystemUserUnlocked;
92 
93     @GuardedBy("mLock")
94     private final Map<String, PublisherConnection> mCurrentUserClients = new ArrayMap<>();
95     @GuardedBy("mLock")
96     private int mCurrentUser;
97 
98     @GuardedBy("mLock")
99     private final Map<IBinder, SubscriberConnection> mSubscribers = new ArrayMap<>();
100 
101     @VisibleForTesting
102     final Runnable mSystemUserUnlockedListener = () -> {
103         synchronized (mLock) {
104             mSystemUserUnlocked = true;
105         }
106         bindToSystemClients();
107     };
108 
109     @VisibleForTesting
110     public final CarUserService.UserCallback mUserCallback = new CarUserService.UserCallback() {
111         @Override
112         public void onSwitchUser(int userId) {
113             synchronized (mLock) {
114                 if (mCurrentUser != userId) {
115                     mCurrentUser = userId;
116                     terminate(mCurrentUserClients);
117                     terminate(mSubscribers.values().stream()
118                             .filter(subscriber -> subscriber.mUserId != mCurrentUser)
119                             .filter(subscriber -> subscriber.mUserId != UserHandle.USER_SYSTEM));
120                 }
121             }
122             bindToUserClients();
123         }
124 
125         @Override
126         public void onUserLockChanged(int userId, boolean unlocked) {
127             synchronized (mLock) {
128                 if (mCurrentUser == userId && unlocked) {
129                     bindToUserClients();
130                 }
131             }
132         }
133     };
134 
135     /**
136      * Constructor for client manager.
137      *
138      * @param context           Context to use for registering receivers and binding services.
139      * @param statsService      Service for logging client metrics.
140      * @param userService       User service for registering system unlock listener.
141      * @param brokerService     Service managing the VMS publisher/subscriber state.
142      * @param halService        Service providing the HAL client interface
143      */
VmsClientManager(Context context, CarStatsService statsService, CarUserService userService, VmsBrokerService brokerService, VmsHalService halService)144     public VmsClientManager(Context context, CarStatsService statsService,
145             CarUserService userService, VmsBrokerService brokerService,
146             VmsHalService halService) {
147         this(context, statsService, userService, brokerService, halService,
148                 new Handler(Looper.getMainLooper()), Binder::getCallingUid);
149     }
150 
151     @VisibleForTesting
VmsClientManager(Context context, CarStatsService statsService, CarUserService userService, VmsBrokerService brokerService, VmsHalService halService, Handler handler, IntSupplier getCallingUid)152     VmsClientManager(Context context, CarStatsService statsService,
153             CarUserService userService, VmsBrokerService brokerService,
154             VmsHalService halService, Handler handler, IntSupplier getCallingUid) {
155         mContext = context;
156         mPackageManager = context.getPackageManager();
157         mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE);
158         mStatsService = statsService;
159         mUserService = userService;
160         mCurrentUser = UserHandle.USER_NULL;
161         mBrokerService = brokerService;
162         mHandler = handler;
163         mGetCallingUid = getCallingUid;
164         mMillisBeforeRebind = context.getResources().getInteger(
165                 com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher);
166 
167         halService.setClientManager(this);
168     }
169 
170     /**
171      * Registers the publisher service for connection callbacks.
172      *
173      * @param publisherService Publisher service to register.
174      */
setPublisherService(VmsPublisherService publisherService)175     public void setPublisherService(VmsPublisherService publisherService) {
176         synchronized (mLock) {
177             mPublisherService = publisherService;
178         }
179     }
180 
181     @Override
init()182     public void init() {
183         mUserService.runOnUser0Unlock(mSystemUserUnlockedListener);
184         mUserService.addUserCallback(mUserCallback);
185     }
186 
187     @Override
release()188     public void release() {
189         mUserService.removeUserCallback(mUserCallback);
190         synchronized (mLock) {
191             if (mHalClient != null) {
192                 mPublisherService.onClientDisconnected(HAL_CLIENT_NAME);
193             }
194             terminate(mSystemClients);
195             terminate(mCurrentUserClients);
196             terminate(mSubscribers.values().stream());
197         }
198     }
199 
200     @Override
dump(PrintWriter writer)201     public void dump(PrintWriter writer) {
202         writer.println("*" + getClass().getSimpleName() + "*");
203         synchronized (mLock) {
204             writer.println("mCurrentUser:" + mCurrentUser);
205             writer.println("mHalClient: " + (mHalClient != null ? "connected" : "disconnected"));
206             writer.println("mSystemClients:");
207             dumpConnections(writer, mSystemClients);
208 
209             writer.println("mCurrentUserClients:");
210             dumpConnections(writer, mCurrentUserClients);
211 
212             writer.println("mSubscribers:");
213             for (SubscriberConnection subscriber : mSubscribers.values()) {
214                 writer.printf("\t%s\n", subscriber);
215             }
216         }
217     }
218 
219 
220     /**
221      * Adds a subscriber for connection tracking.
222      *
223      * @param subscriberClient Subscriber client to track.
224      */
addSubscriber(IVmsSubscriberClient subscriberClient)225     public void addSubscriber(IVmsSubscriberClient subscriberClient) {
226         if (subscriberClient == null) {
227             Log.e(TAG, "Trying to add a null subscriber: "
228                     + getCallingPackage(mGetCallingUid.getAsInt()));
229             throw new IllegalArgumentException("subscriber cannot be null.");
230         }
231 
232         synchronized (mLock) {
233             IBinder subscriberBinder = subscriberClient.asBinder();
234             if (mSubscribers.containsKey(subscriberBinder)) {
235                 // Already registered
236                 return;
237             }
238 
239             int callingUid = mGetCallingUid.getAsInt();
240             int subscriberUserId = UserHandle.getUserId(callingUid);
241             if (subscriberUserId != mCurrentUser && subscriberUserId != UserHandle.USER_SYSTEM) {
242                 throw new SecurityException("Caller must be foreground user or system");
243             }
244 
245             SubscriberConnection subscriber = new SubscriberConnection(
246                     subscriberClient, callingUid, getCallingPackage(callingUid), subscriberUserId);
247             if (DBG) Log.d(TAG, "Registering subscriber: " + subscriber);
248             try {
249                 subscriberBinder.linkToDeath(subscriber, 0);
250             } catch (RemoteException e) {
251                 throw new IllegalStateException("Subscriber already dead: " + subscriber, e);
252             }
253             mSubscribers.put(subscriberBinder, subscriber);
254         }
255     }
256 
257     /**
258      * Removes a subscriber for connection tracking and expires its subscriptions.
259      *
260      * @param subscriberClient Subscriber client to remove.
261      */
removeSubscriber(IVmsSubscriberClient subscriberClient)262     public void removeSubscriber(IVmsSubscriberClient subscriberClient) {
263         synchronized (mLock) {
264             SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder());
265             if (subscriber != null) {
266                 subscriber.terminate();
267             }
268         }
269     }
270 
271     /**
272      * Returns all active subscriber clients.
273      */
getAllSubscribers()274     public Collection<IVmsSubscriberClient> getAllSubscribers() {
275         synchronized (mLock) {
276             return mSubscribers.values().stream()
277                     .map(subscriber -> subscriber.mClient)
278                     .collect(Collectors.toList());
279         }
280     }
281 
282     /**
283      * Gets the application UID associated with a subscriber client.
284      */
getSubscriberUid(IVmsSubscriberClient subscriberClient)285     public int getSubscriberUid(IVmsSubscriberClient subscriberClient) {
286         synchronized (mLock) {
287             SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder());
288             return subscriber != null ? subscriber.mUid : Process.INVALID_UID;
289         }
290     }
291 
292     /**
293      * Gets the package name for a given subscriber client.
294      */
getPackageName(IVmsSubscriberClient subscriberClient)295     public String getPackageName(IVmsSubscriberClient subscriberClient) {
296         synchronized (mLock) {
297             SubscriberConnection subscriber = mSubscribers.get(subscriberClient.asBinder());
298             return subscriber != null ? subscriber.mPackageName : UNKNOWN_PACKAGE;
299         }
300     }
301 
302     /**
303      * Registers the HAL client connections.
304      */
onHalConnected(IVmsPublisherClient publisherClient, IVmsSubscriberClient subscriberClient)305     public void onHalConnected(IVmsPublisherClient publisherClient,
306             IVmsSubscriberClient subscriberClient) {
307         synchronized (mLock) {
308             mHalClient = publisherClient;
309             mPublisherService.onClientConnected(HAL_CLIENT_NAME, mHalClient);
310             mSubscribers.put(subscriberClient.asBinder(),
311                     new SubscriberConnection(subscriberClient, Process.myUid(), HAL_CLIENT_NAME,
312                             UserHandle.USER_SYSTEM));
313         }
314         mStatsService.getVmsClientLogger(Process.myUid())
315                 .logConnectionState(ConnectionState.CONNECTED);
316     }
317 
318     /**
319      *
320      */
onHalDisconnected()321     public void onHalDisconnected() {
322         synchronized (mLock) {
323             if (mHalClient != null) {
324                 mPublisherService.onClientDisconnected(HAL_CLIENT_NAME);
325                 mStatsService.getVmsClientLogger(Process.myUid())
326                         .logConnectionState(ConnectionState.DISCONNECTED);
327             }
328             mHalClient = null;
329             terminate(mSubscribers.values().stream()
330                     .filter(subscriber -> HAL_CLIENT_NAME.equals(subscriber.mPackageName)));
331         }
332     }
333 
dumpConnections(PrintWriter writer, Map<String, PublisherConnection> connectionMap)334     private void dumpConnections(PrintWriter writer,
335             Map<String, PublisherConnection> connectionMap) {
336         for (PublisherConnection connection : connectionMap.values()) {
337             writer.printf("\t%s: %s\n",
338                     connection.mName.getPackageName(),
339                     connection.mIsBound ? "connected" : "disconnected");
340         }
341     }
342 
bindToSystemClients()343     private void bindToSystemClients() {
344         String[] clientNames = mContext.getResources().getStringArray(
345                 R.array.vmsPublisherSystemClients);
346         synchronized (mLock) {
347             if (!mSystemUserUnlocked) {
348                 return;
349             }
350             Log.i(TAG, "Attempting to bind " + clientNames.length + " system client(s)");
351             for (String clientName : clientNames) {
352                 bind(mSystemClients, clientName, UserHandle.SYSTEM);
353             }
354         }
355     }
356 
bindToUserClients()357     private void bindToUserClients() {
358         bindToSystemClients(); // Bind system clients on user switch, if they are not already bound.
359         synchronized (mLock) {
360             if (mCurrentUser == UserHandle.USER_NULL) {
361                 Log.e(TAG, "Unknown user in foreground.");
362                 return;
363             }
364             // To avoid the risk of double-binding, clients running as the system user must only
365             // ever be bound in bindToSystemClients().
366             if (mCurrentUser == UserHandle.USER_SYSTEM) {
367                 Log.e(TAG, "System user in foreground. Userspace clients will not be bound.");
368                 return;
369             }
370 
371             if (!mUserManager.isUserUnlockingOrUnlocked(mCurrentUser)) {
372                 Log.i(TAG, "Waiting for foreground user " + mCurrentUser + " to be unlocked.");
373                 return;
374             }
375 
376             String[] clientNames = mContext.getResources().getStringArray(
377                     R.array.vmsPublisherUserClients);
378             Log.i(TAG, "Attempting to bind " + clientNames.length + " user client(s)");
379             UserHandle currentUserHandle = UserHandle.of(mCurrentUser);
380             for (String clientName : clientNames) {
381                 bind(mCurrentUserClients, clientName, currentUserHandle);
382             }
383         }
384     }
385 
bind(Map<String, PublisherConnection> connectionMap, String clientName, UserHandle userHandle)386     private void bind(Map<String, PublisherConnection> connectionMap, String clientName,
387             UserHandle userHandle) {
388         if (connectionMap.containsKey(clientName)) {
389             Log.i(TAG, "Already bound: " + clientName);
390             return;
391         }
392 
393         ComponentName name = ComponentName.unflattenFromString(clientName);
394         if (name == null) {
395             Log.e(TAG, "Invalid client name: " + clientName);
396             return;
397         }
398 
399         ServiceInfo serviceInfo;
400         try {
401             serviceInfo = mContext.getPackageManager().getServiceInfo(name,
402                     PackageManager.MATCH_DIRECT_BOOT_AUTO);
403         } catch (PackageManager.NameNotFoundException e) {
404             Log.w(TAG, "Client not installed: " + clientName);
405             return;
406         }
407 
408         VmsClientLogger statsLog = mStatsService.getVmsClientLogger(
409                 UserHandle.getUid(userHandle.getIdentifier(), serviceInfo.applicationInfo.uid));
410 
411         if (!Car.PERMISSION_BIND_VMS_CLIENT.equals(serviceInfo.permission)) {
412             Log.e(TAG, "Client service: " + clientName
413                     + " does not require " + Car.PERMISSION_BIND_VMS_CLIENT + " permission");
414             statsLog.logConnectionState(ConnectionState.CONNECTION_ERROR);
415             return;
416         }
417 
418         PublisherConnection connection = new PublisherConnection(name, userHandle, statsLog);
419         if (connection.bind()) {
420             Log.i(TAG, "Client bound: " + connection);
421             connectionMap.put(clientName, connection);
422         } else {
423             Log.e(TAG, "Binding failed: " + connection);
424         }
425     }
426 
terminate(Map<String, PublisherConnection> connectionMap)427     private void terminate(Map<String, PublisherConnection> connectionMap) {
428         connectionMap.values().forEach(PublisherConnection::terminate);
429         connectionMap.clear();
430     }
431 
432     class PublisherConnection implements ServiceConnection {
433         private final ComponentName mName;
434         private final UserHandle mUser;
435         private final String mFullName;
436         private final VmsClientLogger mStatsLog;
437         private boolean mIsBound = false;
438         private boolean mIsTerminated = false;
439         private boolean mRebindScheduled = false;
440         private IVmsPublisherClient mClientService;
441 
PublisherConnection(ComponentName name, UserHandle user, VmsClientLogger statsLog)442         PublisherConnection(ComponentName name, UserHandle user, VmsClientLogger statsLog) {
443             mName = name;
444             mUser = user;
445             mFullName = mName.flattenToString() + " U=" + mUser.getIdentifier();
446             mStatsLog = statsLog;
447         }
448 
bind()449         synchronized boolean bind() {
450             if (mIsBound) {
451                 return true;
452             }
453             if (mIsTerminated) {
454                 return false;
455             }
456             mStatsLog.logConnectionState(ConnectionState.CONNECTING);
457 
458             if (DBG) Log.d(TAG, "binding: " + mFullName);
459             Intent intent = new Intent();
460             intent.setComponent(mName);
461             try {
462                 mIsBound = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE,
463                         mHandler, mUser);
464             } catch (SecurityException e) {
465                 Log.e(TAG, "While binding " + mFullName, e);
466             }
467 
468             if (!mIsBound) {
469                 mStatsLog.logConnectionState(ConnectionState.CONNECTION_ERROR);
470             }
471 
472             return mIsBound;
473         }
474 
unbind()475         synchronized void unbind() {
476             if (!mIsBound) {
477                 return;
478             }
479 
480             if (DBG) Log.d(TAG, "unbinding: " + mFullName);
481             try {
482                 mContext.unbindService(this);
483             } catch (Throwable t) {
484                 Log.e(TAG, "While unbinding " + mFullName, t);
485             }
486             mIsBound = false;
487         }
488 
scheduleRebind()489         synchronized void scheduleRebind() {
490             if (mRebindScheduled) {
491                 return;
492             }
493 
494             if (DBG) {
495                 Log.d(TAG,
496                         String.format("rebinding %s after %dms", mFullName, mMillisBeforeRebind));
497             }
498             mHandler.postDelayed(this::doRebind, mMillisBeforeRebind);
499             mRebindScheduled = true;
500         }
501 
doRebind()502         synchronized void doRebind() {
503             mRebindScheduled = false;
504             // Do not rebind if the connection has been terminated, or the client service has
505             // reconnected on its own.
506             if (mIsTerminated || mClientService != null) {
507                 return;
508             }
509 
510             Log.i(TAG, "Rebinding: " + mFullName);
511             // Ensure that the client is not bound before attempting to rebind.
512             // If the client is not currently bound, unbind() will have no effect.
513             unbind();
514             bind();
515         }
516 
terminate()517         synchronized void terminate() {
518             if (DBG) Log.d(TAG, "terminating: " + mFullName);
519             mIsTerminated = true;
520             notifyOnDisconnect(ConnectionState.TERMINATED);
521             unbind();
522         }
523 
notifyOnDisconnect(int connectionState)524         synchronized void notifyOnDisconnect(int connectionState) {
525             if (mClientService != null) {
526                 mPublisherService.onClientDisconnected(mFullName);
527                 mClientService = null;
528                 mStatsLog.logConnectionState(connectionState);
529             }
530         }
531 
532         @Override
onServiceConnected(ComponentName name, IBinder service)533         public void onServiceConnected(ComponentName name, IBinder service) {
534             if (DBG) Log.d(TAG, "onServiceConnected: " + mFullName);
535             mClientService = IVmsPublisherClient.Stub.asInterface(service);
536             mPublisherService.onClientConnected(mFullName, mClientService);
537             mStatsLog.logConnectionState(ConnectionState.CONNECTED);
538         }
539 
540         @Override
onServiceDisconnected(ComponentName name)541         public void onServiceDisconnected(ComponentName name) {
542             if (DBG) Log.d(TAG, "onServiceDisconnected: " + mFullName);
543             notifyOnDisconnect(ConnectionState.DISCONNECTED);
544             scheduleRebind();
545         }
546 
547         @Override
onBindingDied(ComponentName name)548         public void onBindingDied(ComponentName name) {
549             if (DBG) Log.d(TAG, "onBindingDied: " + mFullName);
550             notifyOnDisconnect(ConnectionState.DISCONNECTED);
551             scheduleRebind();
552         }
553 
554         @Override
toString()555         public String toString() {
556             return mFullName;
557         }
558     }
559 
terminate(Stream<SubscriberConnection> subscribers)560     private void terminate(Stream<SubscriberConnection> subscribers) {
561         // Make a copy of the stream, so that terminate() doesn't cause a concurrent modification
562         subscribers.collect(Collectors.toList()).forEach(SubscriberConnection::terminate);
563     }
564 
565     // If we're in a binder call, returns back the package name of the caller of the binder call.
getCallingPackage(int uid)566     private String getCallingPackage(int uid) {
567         String packageName = mPackageManager.getNameForUid(uid);
568         if (packageName == null) {
569             return UNKNOWN_PACKAGE;
570         } else {
571             return packageName;
572         }
573     }
574 
575     private class SubscriberConnection implements IBinder.DeathRecipient {
576         private final IVmsSubscriberClient mClient;
577         private final int mUid;
578         private final String mPackageName;
579         private final int mUserId;
580 
SubscriberConnection(IVmsSubscriberClient subscriberClient, int uid, String packageName, int userId)581         SubscriberConnection(IVmsSubscriberClient subscriberClient, int uid, String packageName,
582                 int userId) {
583             mClient = subscriberClient;
584             mUid = uid;
585             mPackageName = packageName;
586             mUserId = userId;
587         }
588 
589         @Override
binderDied()590         public void binderDied() {
591             if (DBG) Log.d(TAG, "Subscriber died: " + this);
592             terminate();
593         }
594 
595         @Override
toString()596         public String toString() {
597             return mPackageName + " U=" + mUserId;
598         }
599 
terminate()600         void terminate() {
601             if (DBG) Log.d(TAG, "Terminating subscriber: " + this);
602             synchronized (mLock) {
603                 mBrokerService.removeDeadSubscriber(mClient);
604                 IBinder subscriberBinder = mClient.asBinder();
605                 try {
606                     subscriberBinder.unlinkToDeath(this, 0);
607                 } catch (NoSuchElementException e) {
608                     if (DBG) Log.d(TAG, "While unlinking subscriber binder for " + this, e);
609                 }
610                 mSubscribers.remove(subscriberBinder);
611             }
612         }
613     }
614 }
615