• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2017 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;
18 
19 import android.car.vms.IVmsPublisherClient;
20 import android.car.vms.IVmsPublisherService;
21 import android.car.vms.IVmsSubscriberClient;
22 import android.car.vms.VmsLayer;
23 import android.car.vms.VmsLayersOffering;
24 import android.car.vms.VmsSubscriptionState;
25 import android.content.BroadcastReceiver;
26 import android.content.ComponentName;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.IntentFilter;
30 import android.content.ServiceConnection;
31 import android.content.pm.PackageInfo;
32 import android.content.pm.PackageManager;
33 import android.os.Binder;
34 import android.os.Handler;
35 import android.os.IBinder;
36 import android.os.Message;
37 import android.os.RemoteException;
38 import android.os.UserHandle;
39 import android.text.TextUtils;
40 import android.util.ArrayMap;
41 import android.util.ArraySet;
42 import android.util.Log;
43 
44 import com.android.car.hal.VmsHalService;
45 import com.android.car.hal.VmsHalService.VmsHalPublisherListener;
46 
47 import java.io.PrintWriter;
48 import java.util.Arrays;
49 import java.util.Map;
50 import java.util.Set;
51 
52 /**
53  * Receives HAL updates by implementing VmsHalService.VmsHalListener.
54  * Binds to publishers and configures them to use this service.
55  * Notifies publishers of subscription changes.
56  */
57 public class VmsPublisherService extends IVmsPublisherService.Stub implements CarServiceBase {
58     private static final boolean DBG = true;
59     private static final String TAG = "VmsPublisherService";
60 
61     private static final int MSG_HAL_SUBSCRIPTION_CHANGED = 1;
62 
63     private final Context mContext;
64     private final VmsHalService mHal;
65     private final Map<String, PublisherConnection> mPublisherConnectionMap = new ArrayMap<>();
66     private final Map<String, IVmsPublisherClient> mPublisherMap = new ArrayMap<>();
67     private final Set<String> mSafePermissions;
68     private final Handler mHandler = new EventHandler();
69     private final VmsHalPublisherListener mHalPublisherListener;
70 
71     private BroadcastReceiver mBootCompleteReceiver;
72 
VmsPublisherService(Context context, VmsHalService hal)73     public VmsPublisherService(Context context, VmsHalService hal) {
74         mContext = context;
75         mHal = hal;
76 
77         mHalPublisherListener = subscriptionState -> mHandler.sendMessage(
78                 mHandler.obtainMessage(MSG_HAL_SUBSCRIPTION_CHANGED, subscriptionState));
79 
80         // Load permissions that can be granted to publishers.
81         mSafePermissions = new ArraySet<>(
82                 Arrays.asList(mContext.getResources().getStringArray(R.array.vmsSafePermissions)));
83     }
84 
85     // Implements CarServiceBase interface.
86     @Override
init()87     public void init() {
88         mHal.addPublisherListener(mHalPublisherListener);
89 
90         if (isTestEnvironment()) {
91             Log.d(TAG, "Running under test environment");
92             bindToAllPublishers();
93         } else {
94             mBootCompleteReceiver = new BroadcastReceiver() {
95                 @Override
96                 public void onReceive(Context context, Intent intent) {
97                     if (Intent.ACTION_LOCKED_BOOT_COMPLETED.equals(intent.getAction())) {
98                         onLockedBootCompleted();
99                     } else {
100                         Log.e(TAG, "Unexpected action received: " + intent);
101                     }
102                 }
103             };
104 
105             mContext.registerReceiver(mBootCompleteReceiver,
106                     new IntentFilter(Intent.ACTION_LOCKED_BOOT_COMPLETED));
107         }
108     }
109 
bindToAllPublishers()110     private void bindToAllPublishers() {
111         String[] publisherNames = mContext.getResources().getStringArray(
112                 R.array.vmsPublisherClients);
113         if (DBG) Log.d(TAG, "Publishers found: " + publisherNames.length);
114 
115         for (String publisherName : publisherNames) {
116             if (TextUtils.isEmpty(publisherName)) {
117                 Log.e(TAG, "empty publisher name");
118                 continue;
119             }
120             ComponentName name = ComponentName.unflattenFromString(publisherName);
121             if (name == null) {
122                 Log.e(TAG, "invalid publisher name: " + publisherName);
123                 continue;
124             }
125 
126             if (!mContext.getPackageManager().isPackageAvailable(name.getPackageName())) {
127                 Log.w(TAG, "VMS publisher not installed: " + publisherName);
128                 continue;
129             }
130 
131             bind(name);
132         }
133     }
134 
135     @Override
release()136     public void release() {
137         if (mBootCompleteReceiver != null) {
138             mContext.unregisterReceiver(mBootCompleteReceiver);
139             mBootCompleteReceiver = null;
140         }
141         mHal.removePublisherListener(mHalPublisherListener);
142 
143         for (PublisherConnection connection : mPublisherConnectionMap.values()) {
144             mContext.unbindService(connection);
145         }
146         mPublisherConnectionMap.clear();
147         mPublisherMap.clear();
148     }
149 
150     @Override
dump(PrintWriter writer)151     public void dump(PrintWriter writer) {
152         writer.println("*" + getClass().getSimpleName() + "*");
153         writer.println("mSafePermissions: " + mSafePermissions);
154         writer.println("mPublisherMap:" + mPublisherMap);
155         writer.println("mPublisherConnectionMap:" + mPublisherConnectionMap);
156     }
157 
158     /* Called in arbitrary binder thread */
159     @Override
setLayersOffering(IBinder token, VmsLayersOffering offering)160     public void setLayersOffering(IBinder token, VmsLayersOffering offering) {
161         mHal.setPublisherLayersOffering(token, offering);
162     }
163 
164     /* Called in arbitrary binder thread */
165     @Override
publish(IBinder token, VmsLayer layer, int publisherId, byte[] payload)166     public void publish(IBinder token, VmsLayer layer, int publisherId, byte[] payload) {
167         if (DBG) {
168             Log.d(TAG, "Publishing for layer: " + layer);
169         }
170         ICarImpl.assertVmsPublisherPermission(mContext);
171 
172         // Send the message to application listeners.
173         Set<IVmsSubscriberClient> listeners =
174                 mHal.getSubscribersForLayerFromPublisher(layer, publisherId);
175 
176         if (DBG) {
177             Log.d(TAG, "Number of subscribed apps: " + listeners.size());
178         }
179         for (IVmsSubscriberClient listener : listeners) {
180             try {
181                 listener.onVmsMessageReceived(layer, payload);
182             } catch (RemoteException ex) {
183                 Log.e(TAG, "unable to publish to listener: " + listener);
184             }
185         }
186 
187         // Send the message to HAL
188         if (mHal.isHalSubscribed(layer)) {
189             Log.d(TAG, "HAL is subscribed");
190             mHal.setDataMessage(layer, payload);
191         } else {
192             Log.d(TAG, "HAL is NOT subscribed");
193         }
194     }
195 
196     /* Called in arbitrary binder thread */
197     @Override
getSubscriptions()198     public VmsSubscriptionState getSubscriptions() {
199         ICarImpl.assertVmsPublisherPermission(mContext);
200         return mHal.getSubscriptionState();
201     }
202 
203     /* Called in arbitrary binder thread */
204     @Override
getPublisherId(byte[] publisherInfo)205     public int getPublisherId(byte[] publisherInfo) {
206         ICarImpl.assertVmsPublisherPermission(mContext);
207         return mHal.getPublisherId(publisherInfo);
208     }
209 
onLockedBootCompleted()210     private void onLockedBootCompleted() {
211         if (DBG) Log.i(TAG, "onLockedBootCompleted");
212 
213         bindToAllPublishers();
214     }
215 
216     /**
217      * This method is only invoked by VmsHalService.notifyPublishers which is synchronized.
218      * Therefore this method only sees a non-decreasing sequence.
219      */
handleHalSubscriptionChanged(VmsSubscriptionState subscriptionState)220     private void handleHalSubscriptionChanged(VmsSubscriptionState subscriptionState) {
221         // Send the message to application listeners.
222         for (IVmsPublisherClient client : mPublisherMap.values()) {
223             try {
224                 client.onVmsSubscriptionChange(subscriptionState);
225             } catch (RemoteException ex) {
226                 Log.e(TAG, "unable to send notification to: " + client, ex);
227             }
228         }
229     }
230 
231     /**
232      * Tries to bind to a publisher.
233      *
234      * @param name publisher component name (e.g. android.car.vms.logger/.LoggingService).
235      */
bind(ComponentName name)236     private void bind(ComponentName name) {
237         String publisherName = name.flattenToString();
238         if (DBG) {
239             Log.d(TAG, "binding to: " + publisherName);
240         }
241 
242         if (mPublisherConnectionMap.containsKey(publisherName)) {
243             // Already registered, nothing to do.
244             return;
245         }
246         grantPermissions(name);
247         Intent intent = new Intent();
248         intent.setComponent(name);
249         PublisherConnection connection = new PublisherConnection(name);
250         if (mContext.bindServiceAsUser(intent, connection,
251                 Context.BIND_AUTO_CREATE, UserHandle.SYSTEM)) {
252             mPublisherConnectionMap.put(publisherName, connection);
253         } else {
254             Log.e(TAG, "unable to bind to: " + publisherName);
255         }
256     }
257 
258     /**
259      * Removes the publisher and associated connection.
260      *
261      * @param name publisher component name (e.g. android.car.vms.Logger).
262      */
unbind(ComponentName name)263     private void unbind(ComponentName name) {
264         String publisherName = name.flattenToString();
265         if (DBG) {
266             Log.d(TAG, "unbinding from: " + publisherName);
267         }
268 
269         boolean found = mPublisherMap.remove(publisherName) != null;
270         if (found) {
271             PublisherConnection connection = mPublisherConnectionMap.get(publisherName);
272             mContext.unbindService(connection);
273             mPublisherConnectionMap.remove(publisherName);
274         } else {
275             Log.e(TAG, "unbind: unknown publisher." + publisherName);
276         }
277     }
278 
grantPermissions(ComponentName component)279     private void grantPermissions(ComponentName component) {
280         final PackageManager packageManager = mContext.getPackageManager();
281         final String packageName = component.getPackageName();
282         PackageInfo packageInfo;
283         try {
284             packageInfo = packageManager.getPackageInfo(packageName,
285                     PackageManager.GET_PERMISSIONS);
286         } catch (PackageManager.NameNotFoundException e) {
287             Log.e(TAG, "Error getting package info for " + packageName, e);
288             return;
289         }
290         if (packageInfo.requestedPermissions == null) return;
291         for (String permission : packageInfo.requestedPermissions) {
292             if (!mSafePermissions.contains(permission)) {
293                 continue;
294             }
295             if (packageManager.checkPermission(permission, packageName)
296                     == PackageManager.PERMISSION_GRANTED) {
297                 continue;
298             }
299             try {
300                 packageManager.grantRuntimePermission(packageName, permission,
301                         UserHandle.SYSTEM);
302                 Log.d(TAG, "Permission " + permission + " granted to " + packageName);
303             } catch (SecurityException | IllegalArgumentException e) {
304                 Log.e(TAG, "Error while trying to grant " + permission + " to " + packageName,
305                         e);
306             }
307         }
308     }
309 
isTestEnvironment()310     private boolean isTestEnvironment() {
311         // If the context is derived from other package it means we're running under
312         // environment.
313         return !TextUtils.equals(mContext.getBasePackageName(), mContext.getPackageName());
314     }
315 
316     class PublisherConnection implements ServiceConnection {
317         private final IBinder mToken = new Binder();
318         private final ComponentName mName;
319 
PublisherConnection(ComponentName name)320         PublisherConnection(ComponentName name) {
321             mName = name;
322         }
323 
324         private final Runnable mBindRunnable = new Runnable() {
325             @Override
326             public void run() {
327                 Log.d(TAG, "delayed binding for: " + mName);
328                 bind(mName);
329             }
330         };
331 
332         /**
333          * Once the service binds to a publisher service, the publisher binder is added to
334          * mPublisherMap
335          * and the publisher is configured to use this service.
336          */
337         @Override
onServiceConnected(ComponentName name, IBinder binder)338         public void onServiceConnected(ComponentName name, IBinder binder) {
339             if (DBG) {
340                 Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder);
341             }
342             IVmsPublisherClient service = IVmsPublisherClient.Stub.asInterface(binder);
343             mPublisherMap.put(name.flattenToString(), service);
344             try {
345                 service.setVmsPublisherService(mToken, VmsPublisherService.this);
346             } catch (RemoteException e) {
347                 Log.e(TAG, "unable to configure publisher: " + name, e);
348             }
349         }
350 
351         /**
352          * Tries to rebind to the publisher service.
353          */
354         @Override
onServiceDisconnected(ComponentName name)355         public void onServiceDisconnected(ComponentName name) {
356             String publisherName = name.flattenToString();
357             Log.d(TAG, "onServiceDisconnected, name: " + publisherName);
358 
359             int millisecondsToWait = mContext.getResources().getInteger(
360                     com.android.car.R.integer.millisecondsBeforeRebindToVmsPublisher);
361             if (!mName.flattenToString().equals(name.flattenToString())) {
362                 throw new IllegalArgumentException(
363                     "Mismatch on publisherConnection. Expected: " + mName + " Got: " + name);
364             }
365             mHandler.postDelayed(mBindRunnable, millisecondsToWait);
366 
367             unbind(name);
368         }
369     }
370 
371     private class EventHandler extends Handler {
372         @Override
handleMessage(Message msg)373         public void handleMessage(Message msg) {
374             switch (msg.what) {
375                 case MSG_HAL_SUBSCRIPTION_CHANGED:
376                     handleHalSubscriptionChanged((VmsSubscriptionState) msg.obj);
377                     return;
378             }
379             super.handleMessage(msg);
380         }
381     }
382 }
383