• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.userlib.CarUserManagerHelper;
21 import android.content.BroadcastReceiver;
22 import android.content.ComponentName;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.ServiceConnection;
27 import android.content.pm.PackageManager;
28 import android.content.pm.ServiceInfo;
29 import android.os.Handler;
30 import android.os.IBinder;
31 import android.os.Looper;
32 import android.os.UserHandle;
33 import android.os.UserManager;
34 import android.util.ArrayMap;
35 import android.util.Log;
36 
37 import com.android.car.CarServiceBase;
38 import com.android.car.R;
39 import com.android.car.hal.VmsHalService;
40 import com.android.car.user.CarUserService;
41 import com.android.internal.annotations.GuardedBy;
42 import com.android.internal.annotations.VisibleForTesting;
43 
44 import java.io.PrintWriter;
45 import java.util.ArrayList;
46 import java.util.Map;
47 import java.util.concurrent.atomic.AtomicLong;
48 
49 /**
50  * Manages service connections lifecycle for VMS publisher clients.
51  *
52  * Binds to system-level clients at boot and creates/destroys bindings for userspace clients
53  * according to the Android user lifecycle.
54  */
55 public class VmsClientManager implements CarServiceBase {
56     private static final boolean DBG = true;
57     private static final String TAG = "VmsClientManager";
58     private static final String HAL_CLIENT_NAME = "VmsHalClient";
59 
60     /**
61      * Interface for receiving updates about client connections.
62      */
63     public interface ConnectionListener {
64         /**
65          * Called when a client connection is established or re-established.
66          *
67          * @param clientName    String that uniquely identifies the service and user.
68          * @param clientService The IBinder of the client's communication channel.
69          */
onClientConnected(String clientName, IBinder clientService)70         void onClientConnected(String clientName, IBinder clientService);
71 
72         /**
73          * Called when a client connection is terminated.
74          *
75          * @param clientName String that uniquely identifies the service and user.
76          */
onClientDisconnected(String clientName)77         void onClientDisconnected(String clientName);
78     }
79 
80     private final Context mContext;
81     private final Handler mHandler;
82     private final UserManager mUserManager;
83     private final CarUserService mUserService;
84     private final CarUserManagerHelper mUserManagerHelper;
85     private final int mMillisBeforeRebind;
86 
87     @GuardedBy("mListeners")
88     private final ArrayList<ConnectionListener> mListeners = new ArrayList<>();
89     @GuardedBy("mSystemClients")
90     private final Map<String, ClientConnection> mSystemClients = new ArrayMap<>();
91     @GuardedBy("mSystemClients")
92     private IBinder mHalClient;
93     @GuardedBy("mSystemClients")
94     private boolean mSystemUserUnlocked;
95 
96     @GuardedBy("mCurrentUserClients")
97     private final Map<String, ClientConnection> mCurrentUserClients = new ArrayMap<>();
98     @GuardedBy("mCurrentUserClients")
99     private int mCurrentUser;
100 
101     @GuardedBy("mRebindCounts")
102     private final Map<String, AtomicLong> mRebindCounts = new ArrayMap<>();
103 
104     @VisibleForTesting
105     final Runnable mSystemUserUnlockedListener = () -> {
106         synchronized (mSystemClients) {
107             mSystemUserUnlocked = true;
108         }
109         bindToSystemClients();
110     };
111 
112     @VisibleForTesting
113     final BroadcastReceiver mUserSwitchReceiver = new BroadcastReceiver() {
114         @Override
115         public void onReceive(Context context, Intent intent) {
116             if (DBG) Log.d(TAG, "Received " + intent);
117             synchronized (mCurrentUserClients) {
118                 int currentUserId = mUserManagerHelper.getCurrentForegroundUserId();
119                 if (mCurrentUser != currentUserId) {
120                     terminate(mCurrentUserClients);
121                 }
122                 mCurrentUser = currentUserId;
123 
124                 if (Intent.ACTION_USER_UNLOCKED.equals(intent.getAction())
125                         || mUserManager.isUserUnlocked(mCurrentUser)) {
126                     bindToSystemClients();
127                     bindToUserClients();
128                 }
129             }
130         }
131     };
132 
133     /**
134      * Constructor for client managers.
135      *
136      * @param context           Context to use for registering receivers and binding services.
137      * @param userService       User service for registering system unlock listener.
138      * @param userManagerHelper User manager for querying current user state.
139      * @param halService        Service providing the HAL client interface
140      */
VmsClientManager(Context context, CarUserService userService, CarUserManagerHelper userManagerHelper, VmsHalService halService)141     public VmsClientManager(Context context, CarUserService userService,
142             CarUserManagerHelper userManagerHelper, VmsHalService halService) {
143         mContext = context;
144         mHandler = new Handler(Looper.getMainLooper());
145         mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
146         mUserService = userService;
147         mUserManagerHelper = userManagerHelper;
148         mMillisBeforeRebind = mContext.getResources().getInteger(
149                 com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher);
150         halService.setPublisherConnectionCallbacks(this::onHalConnected, this::onHalDisconnected);
151     }
152 
153     @Override
init()154     public void init() {
155         mUserService.runOnUser0Unlock(mSystemUserUnlockedListener);
156 
157         IntentFilter userSwitchFilter = new IntentFilter();
158         userSwitchFilter.addAction(Intent.ACTION_USER_SWITCHED);
159         userSwitchFilter.addAction(Intent.ACTION_USER_UNLOCKED);
160         mContext.registerReceiverAsUser(mUserSwitchReceiver, UserHandle.ALL, userSwitchFilter, null,
161                 null);
162     }
163 
164     @Override
release()165     public void release() {
166         mContext.unregisterReceiver(mUserSwitchReceiver);
167         notifyListenersOnClientDisconnected(HAL_CLIENT_NAME);
168         synchronized (mSystemClients) {
169             terminate(mSystemClients);
170         }
171         synchronized (mCurrentUserClients) {
172             terminate(mCurrentUserClients);
173         }
174     }
175 
176     @Override
dump(PrintWriter writer)177     public void dump(PrintWriter writer) {
178         dumpMetrics(writer);
179     }
180 
181     @Override
dumpMetrics(PrintWriter writer)182     public void dumpMetrics(PrintWriter writer) {
183         writer.println("*" + getClass().getSimpleName() + "*");
184         synchronized (mSystemClients) {
185             writer.println("mHalClient: " + (mHalClient != null ? "connected" : "disconnected"));
186             writer.println("mSystemClients:");
187             dumpConnections(writer, mSystemClients);
188         }
189         synchronized (mCurrentUserClients) {
190             writer.println("mCurrentUserClients:");
191             dumpConnections(writer, mCurrentUserClients);
192             writer.println("mCurrentUser:" + mCurrentUser);
193         }
194         synchronized (mRebindCounts) {
195             writer.println("mRebindCounts:");
196             for (Map.Entry<String, AtomicLong> entry : mRebindCounts.entrySet()) {
197                 writer.printf("\t%s: %s\n", entry.getKey(), entry.getValue());
198             }
199         }
200     }
201 
dumpConnections(PrintWriter writer, Map<String, ClientConnection> connectionMap)202     private void dumpConnections(PrintWriter writer, Map<String, ClientConnection> connectionMap) {
203         for (ClientConnection connection : connectionMap.values()) {
204             writer.printf("\t%s: %s\n",
205                     connection.mName.getPackageName(),
206                     connection.mIsBound ? "connected" : "disconnected");
207         }
208     }
209 
210     /**
211      * Registers a new client connection state listener.
212      *
213      * @param listener Listener to register.
214      */
registerConnectionListener(ConnectionListener listener)215     public void registerConnectionListener(ConnectionListener listener) {
216         synchronized (mListeners) {
217             if (!mListeners.contains(listener)) {
218                 mListeners.add(listener);
219             }
220         }
221         notifyListenerOfConnectedClients(listener);
222     }
223 
224     /**
225      * Unregisters a client connection state listener.
226      *
227      * @param listener Listener to remove.
228      */
unregisterConnectionListener(ConnectionListener listener)229     public void unregisterConnectionListener(ConnectionListener listener) {
230         synchronized (mListeners) {
231             mListeners.remove(listener);
232         }
233     }
234 
bindToSystemClients()235     private void bindToSystemClients() {
236         String[] clientNames = mContext.getResources().getStringArray(
237                 R.array.vmsPublisherSystemClients);
238         Log.i(TAG, "Attempting to bind " + clientNames.length + " system client(s)");
239         synchronized (mSystemClients) {
240             if (!mSystemUserUnlocked) {
241                 return;
242             }
243             for (String clientName : clientNames) {
244                 bind(mSystemClients, clientName, UserHandle.SYSTEM);
245             }
246         }
247     }
248 
bindToUserClients()249     private void bindToUserClients() {
250         synchronized (mCurrentUserClients) {
251             // To avoid the risk of double-binding, clients running as the system user must only
252             // ever be bound in bindToSystemClients().
253             // In a headless multi-user system, the system user will never be in the foreground.
254             if (mCurrentUser == UserHandle.USER_SYSTEM) {
255                 Log.e(TAG, "System user in foreground. Userspace clients will not be bound.");
256                 return;
257             }
258 
259             String[] clientNames = mContext.getResources().getStringArray(
260                     R.array.vmsPublisherUserClients);
261             Log.i(TAG, "Attempting to bind " + clientNames.length + " user client(s)");
262             UserHandle currentUserHandle = UserHandle.of(mCurrentUser);
263             for (String clientName : clientNames) {
264                 bind(mCurrentUserClients, clientName, currentUserHandle);
265             }
266         }
267     }
268 
bind(Map<String, ClientConnection> connectionMap, String clientName, UserHandle userHandle)269     private void bind(Map<String, ClientConnection> connectionMap, String clientName,
270             UserHandle userHandle) {
271         if (connectionMap.containsKey(clientName)) {
272             Log.i(TAG, "Already bound: " + clientName);
273             return;
274         }
275 
276         ComponentName name = ComponentName.unflattenFromString(clientName);
277         if (name == null) {
278             Log.e(TAG, "Invalid client name: " + clientName);
279             return;
280         }
281 
282         ServiceInfo serviceInfo;
283         try {
284             serviceInfo = mContext.getPackageManager().getServiceInfo(name,
285                     PackageManager.MATCH_DIRECT_BOOT_AUTO);
286         } catch (PackageManager.NameNotFoundException e) {
287             Log.w(TAG, "Client not installed: " + clientName);
288             return;
289         }
290 
291         if (!Car.PERMISSION_BIND_VMS_CLIENT.equals(serviceInfo.permission)) {
292             Log.w(TAG, "Client service: " + clientName
293                     + " does not require " + Car.PERMISSION_BIND_VMS_CLIENT + " permission");
294             return;
295         }
296 
297         ClientConnection connection = new ClientConnection(name, userHandle);
298         if (connection.bind()) {
299             Log.i(TAG, "Client bound: " + connection);
300             connectionMap.put(clientName, connection);
301         } else {
302             Log.w(TAG, "Binding failed: " + connection);
303         }
304     }
305 
terminate(Map<String, ClientConnection> connectionMap)306     private void terminate(Map<String, ClientConnection> connectionMap) {
307         connectionMap.values().forEach(ClientConnection::terminate);
308         connectionMap.clear();
309     }
310 
notifyListenerOfConnectedClients(ConnectionListener listener)311     private void notifyListenerOfConnectedClients(ConnectionListener listener) {
312         synchronized (mSystemClients) {
313             if (mHalClient != null) {
314                 listener.onClientConnected(HAL_CLIENT_NAME, mHalClient);
315             }
316             mSystemClients.values().forEach(conn -> conn.notifyIfConnected(listener));
317         }
318         synchronized (mCurrentUserClients) {
319             mCurrentUserClients.values().forEach(conn -> conn.notifyIfConnected(listener));
320         }
321     }
322 
notifyListenersOnClientConnected(String clientName, IBinder clientService)323     private void notifyListenersOnClientConnected(String clientName, IBinder clientService) {
324         synchronized (mListeners) {
325             for (ConnectionListener listener : mListeners) {
326                 listener.onClientConnected(clientName, clientService);
327             }
328         }
329     }
330 
notifyListenersOnClientDisconnected(String clientName)331     private void notifyListenersOnClientDisconnected(String clientName) {
332         synchronized (mListeners) {
333             for (ConnectionListener listener : mListeners) {
334                 listener.onClientDisconnected(clientName);
335             }
336         }
337     }
338 
onHalConnected(IBinder halClient)339     private void onHalConnected(IBinder halClient) {
340         synchronized (mSystemClients) {
341             mHalClient = halClient;
342             notifyListenersOnClientConnected(HAL_CLIENT_NAME, mHalClient);
343         }
344     }
345 
onHalDisconnected()346     private void onHalDisconnected() {
347         synchronized (mSystemClients) {
348             mHalClient = null;
349             notifyListenersOnClientDisconnected(HAL_CLIENT_NAME);
350         }
351         synchronized (mRebindCounts) {
352             mRebindCounts.computeIfAbsent(HAL_CLIENT_NAME, k -> new AtomicLong()).incrementAndGet();
353         }
354     }
355 
356     class ClientConnection implements ServiceConnection {
357         private final ComponentName mName;
358         private final UserHandle mUser;
359         private final String mFullName;
360         private boolean mIsBound = false;
361         private boolean mIsTerminated = false;
362         private IBinder mClientService;
363 
ClientConnection(ComponentName name, UserHandle user)364         ClientConnection(ComponentName name, UserHandle user) {
365             mName = name;
366             mUser = user;
367             mFullName = mName.flattenToString() + " U=" + mUser.getIdentifier();
368         }
369 
bind()370         synchronized boolean bind() {
371             if (mIsBound) {
372                 return true;
373             }
374             if (mIsTerminated) {
375                 return false;
376             }
377 
378             if (DBG) Log.d(TAG, "binding: " + mFullName);
379             Intent intent = new Intent();
380             intent.setComponent(mName);
381             try {
382                 mIsBound = mContext.bindServiceAsUser(intent, this, Context.BIND_AUTO_CREATE,
383                         mHandler, mUser);
384             } catch (SecurityException e) {
385                 Log.e(TAG, "While binding " + mFullName, e);
386             }
387 
388             return mIsBound;
389         }
390 
unbind()391         synchronized void unbind() {
392             if (!mIsBound) {
393                 return;
394             }
395 
396             if (DBG) Log.d(TAG, "unbinding: " + mFullName);
397             try {
398                 mContext.unbindService(this);
399             } catch (Throwable t) {
400                 Log.e(TAG, "While unbinding " + mFullName, t);
401             }
402             mIsBound = false;
403             if (mClientService != null) {
404                 notifyListenersOnClientDisconnected(mFullName);
405             }
406             mClientService = null;
407         }
408 
rebind()409         synchronized void rebind() {
410             unbind();
411             if (DBG) {
412                 Log.d(TAG,
413                         String.format("rebinding %s after %dms", mFullName, mMillisBeforeRebind));
414             }
415             if (!mIsTerminated) {
416                 mHandler.postDelayed(this::bind, mMillisBeforeRebind);
417                 synchronized (mRebindCounts) {
418                     mRebindCounts.computeIfAbsent(mName.getPackageName(), k -> new AtomicLong())
419                             .incrementAndGet();
420                 }
421             }
422         }
423 
terminate()424         synchronized void terminate() {
425             if (DBG) Log.d(TAG, "terminating: " + mFullName);
426             mIsTerminated = true;
427             unbind();
428         }
429 
notifyIfConnected(ConnectionListener listener)430         synchronized void notifyIfConnected(ConnectionListener listener) {
431             if (mClientService != null) {
432                 listener.onClientConnected(mFullName, mClientService);
433             }
434         }
435 
436         @Override
onServiceConnected(ComponentName name, IBinder service)437         public void onServiceConnected(ComponentName name, IBinder service) {
438             if (DBG) Log.d(TAG, "onServiceConnected: " + mFullName);
439             mClientService = service;
440             notifyListenersOnClientConnected(mFullName, mClientService);
441         }
442 
443         @Override
onServiceDisconnected(ComponentName name)444         public void onServiceDisconnected(ComponentName name) {
445             if (DBG) Log.d(TAG, "onServiceDisconnected: " + mFullName);
446             rebind();
447         }
448 
449         @Override
toString()450         public String toString() {
451             return mFullName;
452         }
453     }
454 }
455