Merge "VMS skeleton code: VmsPublisherService"
diff --git a/car-lib/api/system-current.txt b/car-lib/api/system-current.txt
index f4e0a95..63bb8c7 100644
--- a/car-lib/api/system-current.txt
+++ b/car-lib/api/system-current.txt
@@ -31,6 +31,7 @@
     field public static final deprecated java.lang.String PERMISSION_MOCK_VEHICLE_HAL = "android.car.permission.CAR_MOCK_VEHICLE_HAL";
     field public static final java.lang.String PERMISSION_SPEED = "android.car.permission.CAR_SPEED";
     field public static final java.lang.String PERMISSION_VENDOR_EXTENSION = "android.car.permission.CAR_VENDOR_EXTENSION";
+    field public static final java.lang.String PERMISSION_VMS_PUBLISHER = "android.car.permission.VMS_PUBLISHER";
     field public static final java.lang.String PERMISSION_VMS_SUBSCRIBER = "android.car.permission.VMS_SUBSCRIBER";
     field public static final java.lang.String PROJECTION_SERVICE = "projection";
     field public static final java.lang.String RADIO_SERVICE = "radio";
diff --git a/car-lib/src/android/car/Car.java b/car-lib/src/android/car/Car.java
index 4c458bf..0a8ae41 100644
--- a/car-lib/src/android/car/Car.java
+++ b/car-lib/src/android/car/Car.java
@@ -239,7 +239,17 @@
             "android.car.permission.CAR_TEST_SERVICE";
 
     /**
-     * Permission necessary to access VMS APIs.
+     * Permissions necessary to access VMS publisher APIs.
+     *
+     * @hide
+     */
+    @FutureFeature
+    @SystemApi
+    public static final String PERMISSION_VMS_PUBLISHER = "android.car.permission.VMS_PUBLISHER";
+
+    /**
+     * Permissions necessary to access VMS subscriber APIs.
+     *
      * @hide
      */
     @FutureFeature
diff --git a/car-lib/src/android/car/vms/IVmsPublisherClient.aidl b/car-lib/src/android/car/vms/IVmsPublisherClient.aidl
new file mode 100644
index 0000000..d9652f0
--- /dev/null
+++ b/car-lib/src/android/car/vms/IVmsPublisherClient.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+import android.car.vms.IVmsPublisherService;
+
+/**
+ * @hide
+ */
+interface IVmsPublisherClient {
+    /**
+    * Once the VmsPublisherService is bound to the client, this callback is used to set the
+    * binder that the client can use to invoke publisher services.
+    */
+    oneway void setVmsPublisherService(IVmsPublisherService service) = 0;
+
+    /**
+     * The VmsPublisherService uses this callback to notify about subscription changes.
+     */
+    oneway void onVmsSubscriptionChange(int layer, int version, boolean hasSubscribers) = 1;
+}
diff --git a/car-lib/src/android/car/vms/IVmsPublisherService.aidl b/car-lib/src/android/car/vms/IVmsPublisherService.aidl
new file mode 100644
index 0000000..91cef7e
--- /dev/null
+++ b/car-lib/src/android/car/vms/IVmsPublisherService.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.vms;
+
+import android.car.vms.VmsProperty;
+
+/**
+ * Exposes publisher services to VMS clients.
+ *
+ * @hide
+ */
+interface IVmsPublisherService {
+    /**
+     * Client call to publish a message.
+     *
+     * TODO(antoniocortes): change VmsProperty to String or byte[] once b/35313272 is submitted.
+     */
+    oneway void publish(int layer, int version, in VmsProperty message) = 0;
+
+    /**
+     * Returns whether the layer/version has any clients subscribed to it.
+     */
+    boolean hasSubscribers(int layer, int version) = 1;
+}
diff --git a/service/res/values/config.xml b/service/res/values/config.xml
index ae60acd..c24e58e 100644
--- a/service/res/values/config.xml
+++ b/service/res/values/config.xml
@@ -74,4 +74,7 @@
     <string name="defauiltActivityWhitelist">android,com.android.systemui</string>
     <!-- Default home activity -->
     <string name="defaultHomeActivity">com.android.car.overview/com.android.car.overview.StreamOverviewActivity</string>
+    <!--  The com.android.car.VmsPublisherService will bind to this list of clients -->
+    <string-array translatable="false" name="vmsPublisherClients">
+    </string-array>
 </resources>
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index d98ca25..b85a41f 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -81,6 +81,8 @@
     private CarDiagnosticService mCarDiagnosticService;
     @FutureFeature
     private VmsSubscriberService mVmsSubscriberService;
+    @FutureFeature
+    private VmsPublisherService mVmsPublisherService;
 
     private final CarServiceBase[] mAllServices;
 
@@ -119,6 +121,7 @@
         mCarBluetoothService = new CarBluetoothService(serviceContext, mCarCabinService);
         if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) {
             mVmsSubscriberService = new VmsSubscriberService(serviceContext, mHal.getVmsHal());
+            mVmsPublisherService = new VmsPublisherService(serviceContext, mHal.getVmsHal());
         }
         if (FeatureConfiguration.ENABLE_DIAGNOSTIC) {
             mCarDiagnosticService = new CarDiagnosticService(serviceContext,
@@ -149,6 +152,7 @@
         ));
         if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) {
             allServices.add(mVmsSubscriberService);
+            allServices.add(mVmsPublisherService);
         }
         if (FeatureConfiguration.ENABLE_DIAGNOSTIC) {
             allServices.add(mCarDiagnosticService);
@@ -289,6 +293,11 @@
     }
 
     @FutureFeature
+    public static void assertVmsPublisherPermission(Context context) {
+        assertPermission(context, Car.PERMISSION_VMS_PUBLISHER);
+    }
+
+    @FutureFeature
     public static void assertVmsSubscriberPermission(Context context) {
         assertPermission(context, Car.PERMISSION_VMS_SUBSCRIBER);
     }
diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java
new file mode 100644
index 0000000..9447676
--- /dev/null
+++ b/service/src/com/android/car/VmsPublisherService.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.car;
+
+import android.car.annotation.FutureFeature;
+import android.car.vms.IVmsPublisherClient;
+import android.car.vms.IVmsPublisherService;
+import android.car.vms.VmsProperty;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.car.hal.VmsHalService;
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * + Receives HAL updates by implementing VmsHalService.VmsHalListener.
+ * + Binds to publishers and configures them to use this service.
+ * + Notifies publishers of subscription changes.
+ */
+@FutureFeature
+public class VmsPublisherService extends IVmsPublisherService.Stub
+        implements CarServiceBase, VmsHalService.VmsHalListener {
+    private static final boolean DBG = true;
+    private static final String TAG = "VmsPublisherService";
+
+    private final Context mContext;
+    private final VmsHalService mHal;
+    private final VmsPublisherManager mPublisherManager;
+
+    public VmsPublisherService(Context context, VmsHalService hal) {
+        mContext = context;
+        mHal = hal;
+        mPublisherManager = new VmsPublisherManager(this);
+    }
+
+    // Implements CarServiceBase interface.
+    @Override
+    public void init() {
+        mHal.addListener(this);
+        // Launch publishers.
+        String[] publisherNames = mContext.getResources().getStringArray(
+                R.array.vmsPublisherClients);
+        for (String publisherName : publisherNames) {
+            if (TextUtils.isEmpty(publisherName)) {
+                Log.e(TAG, "empty publisher name");
+                continue;
+            }
+            ComponentName name = ComponentName.unflattenFromString(publisherName);
+            if (name == null) {
+                Log.e(TAG, "invalid publisher name: " + publisherName);
+            }
+            mPublisherManager.bind(name);
+        }
+    }
+
+    @Override
+    public void release() {
+        mPublisherManager.release();
+        mHal.removeListener(this);
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+    }
+
+    // Implements IVmsPublisherService interface.
+    @Override
+    public void publish(int layer, int version, VmsProperty value) {
+        ICarImpl.assertVmsPublisherPermission(mContext);
+        // TODO(antoniocortes): update to latest VmsProperty structure.
+        mHal.setProperty(value);
+    }
+
+    @Override
+    public boolean hasSubscribers(int layer, int version) {
+        ICarImpl.assertVmsPublisherPermission(mContext);
+        // TODO(antoniocortes): implement this logic.
+        return true;
+    }
+
+    // Implements VmsHalListener interface
+    @Override
+    public void onChange(VmsProperty message) {
+        // TODO(antoniocortes): notify publishers if this message causes a subscription change.
+    }
+
+    /**
+     * Keeps track of publishers that are using this service.
+     */
+    private static class VmsPublisherManager {
+        /**
+         * Allows to modify mPublisherMap and mPublisherConnectionMap as a single unit.
+         */
+        private final Object mLock = new Object();
+        @GuardedBy("mLock")
+        private final Map<String, PublisherConnection> mPublisherConnectionMap = new HashMap<>();
+        @GuardedBy("mLock")
+        private final Map<String, IVmsPublisherClient> mPublisherMap = new HashMap<>();
+        private final WeakReference<VmsPublisherService> mPublisherService;
+
+        public VmsPublisherManager(VmsPublisherService publisherService) {
+            mPublisherService = new WeakReference<>(publisherService);
+        }
+
+        /**
+         * Tries to bind to a publisher.
+         *
+         * @param name publisher component name (e.g. android.car.vms.logger/.LoggingService).
+         */
+        public void bind(ComponentName name) {
+            VmsPublisherService publisherService = mPublisherService.get();
+            if (publisherService == null) return;
+            String publisherName = name.flattenToString();
+            if (DBG) {
+                Log.d(TAG, "binding to: " + publisherName);
+            }
+            synchronized (mLock) {
+                if (mPublisherConnectionMap.containsKey(publisherName)) {
+                    // Already registered, nothing to do.
+                    return;
+                }
+                Intent intent = new Intent();
+                intent.setComponent(name);
+                // Explicitly start service as we do not use BIND_AUTO_CREATE flag to handle crash.
+                publisherService.mContext.startService(intent);
+                PublisherConnection connection = new PublisherConnection();
+                if (publisherService.mContext.bindService(intent, connection,
+                        Context.BIND_IMPORTANT)) {
+                    mPublisherConnectionMap.put(publisherName, connection);
+                } else {
+                    Log.e(TAG, "unable to bind to: " + publisherName);
+                }
+            }
+        }
+
+        /**
+         * Removes the publisher and associated connection.
+         *
+         * @param name publisher component name (e.g. android.car.vms.Logger).
+         */
+        public void unbind(ComponentName name) {
+            VmsPublisherService publisherService = mPublisherService.get();
+            if (publisherService == null) return;
+            String publisherName = name.flattenToString();
+            if (DBG) {
+                Log.d(TAG, "unbinding from: " + publisherName);
+            }
+            synchronized (mLock) {
+                boolean found = mPublisherMap.remove(publisherName) != null;
+                if (found) {
+                    PublisherConnection connection = mPublisherConnectionMap.get(publisherName);
+                    publisherService.mContext.unbindService(connection);
+                    mPublisherConnectionMap.remove(publisherName);
+                } else {
+                    Log.e(TAG, "unbind: unknown publisher." + publisherName);
+                }
+            }
+        }
+
+        /**
+         * Returns the list of publishers currently registered.
+         *
+         * @return list of publishers.
+         */
+        public List<IVmsPublisherClient> getClients() {
+            synchronized (mLock) {
+                return new ArrayList<>(mPublisherMap.values());
+            }
+        }
+
+        public void release() {
+            VmsPublisherService publisherService = mPublisherService.get();
+            if (publisherService == null) return;
+            for (PublisherConnection connection : mPublisherConnectionMap.values()) {
+                publisherService.mContext.unbindService(connection);
+            }
+            mPublisherConnectionMap.clear();
+            mPublisherMap.clear();
+        }
+
+        class PublisherConnection implements ServiceConnection {
+            /**
+             * Once the service binds to a publisher service, the publisher binder is added to
+             * mPublisherMap
+             * and the publisher is configured to use this service.
+             */
+            @Override
+            public void onServiceConnected(ComponentName name, IBinder binder) {
+                VmsPublisherService publisherService = mPublisherService.get();
+                if (publisherService == null) return;
+                if (DBG) {
+                    Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder);
+                }
+                IVmsPublisherClient service = IVmsPublisherClient.Stub.asInterface(binder);
+                synchronized (mLock) {
+                    mPublisherMap.put(name.flattenToString(), service);
+                }
+                try {
+                    service.setVmsPublisherService(publisherService);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "unable to configure publisher: " + name);
+                }
+            }
+
+            /**
+             * Tries to rebind to the publisher service.
+             */
+            @Override
+            public void onServiceDisconnected(ComponentName name) {
+                String publisherName = name.flattenToString();
+                Log.d(TAG, "onServiceDisconnected, name: " + publisherName);
+                VmsPublisherManager.this.unbind(name);
+                VmsPublisherManager.this.bind(name);
+            }
+        }
+    }
+}