Add car navigation status API

Bug: 25265715
Change-Id: I76755119dd20e86df69074372f3237f80d546544
diff --git a/service/src/com/android/car/AppContextService.java b/service/src/com/android/car/AppContextService.java
index e19a962..af05fc9 100644
--- a/service/src/com/android/car/AppContextService.java
+++ b/service/src/com/android/car/AppContextService.java
@@ -32,7 +32,6 @@
 import java.io.PrintWriter;
 import java.util.HashMap;
 
-
 public class AppContextService extends IAppContext.Stub implements CarServiceBase,
         BinderInterfaceContainer.BinderEventHandler<IAppContextListener> {
     private static final int VERSION = 1;
@@ -67,7 +66,7 @@
             ClientInfo info = (ClientInfo) mAllClients.getBinderInterface(listener);
             if (info == null) {
                 info = new ClientInfo(mAllClients, clientVersion, listener, Binder.getCallingUid(),
-                        Binder.getCallingUid(), filter);
+                        Binder.getCallingPid(), filter);
                 mAllClients.addBinderInterface(info);
             } else {
                 info.setFilter(filter);
@@ -82,7 +81,7 @@
             if (info == null) {
                 return;
             }
-            resetActiveContexts(listener, info.getOwnedCotexts());
+            resetActiveContexts(listener, info.getOwnedContexts());
             mAllClients.removeBinder(listener);
         }
     }
@@ -101,7 +100,7 @@
             if (info == null) {
                 return false;
             }
-            int ownedContexts = info.getOwnedCotexts();
+            int ownedContexts = info.getOwnedContexts();
             return (ownedContexts & context) == context;
         }
     }
@@ -113,14 +112,14 @@
             if (info == null) {
                 throw new IllegalStateException("listener not registered");
             }
-            int alreadyOwnedContexts = info.getOwnedCotexts();
+            int alreadyOwnedContexts = info.getOwnedContexts();
             int addedContexts = 0;
             for (int c = CarAppContextManager.APP_CONTEXT_START_FLAG;
                     c <= CarAppContextManager.APP_CONTEXT_END_FLAG; c = (c << 1)) {
                 if ((c & contexts) != 0 && (c & alreadyOwnedContexts) == 0) {
                     ClientInfo ownerInfo = mContextOwners.get(c);
                     if (ownerInfo != null && ownerInfo != info) {
-                        ownerInfo.setOwnedContexts(ownerInfo.getOwnedCotexts() & ~c);
+                        ownerInfo.setOwnedContexts(ownerInfo.getOwnedContexts() & ~c);
                         mDispatchHandler.requestAppContextOwnershipLossDispatch(
                                 ownerInfo.binderInterface, c);
                         if (DBG) {
@@ -169,7 +168,7 @@
                 return;
             }
             int removedContexts = 0;
-            int currentlyOwnedContexts = info.getOwnedCotexts();
+            int currentlyOwnedContexts = info.getOwnedContexts();
             for (int c = CarAppContextManager.APP_CONTEXT_START_FLAG;
                     c <= CarAppContextManager.APP_CONTEXT_END_FLAG; c = (c << 1)) {
                 if ((c & contexts) != 0 && (c & currentlyOwnedContexts) != 0) {
@@ -227,7 +226,7 @@
     public void onBinderDeath(
             BinderInterfaceContainer.BinderInterface<IAppContextListener> bInterface) {
         ClientInfo info = (ClientInfo) bInterface;
-        int ownedContexts = info.getOwnedCotexts();
+        int ownedContexts = info.getOwnedContexts();
         if (ownedContexts != 0) {
             resetActiveContexts(bInterface.binderInterface, ownedContexts);
         }
@@ -247,6 +246,19 @@
         }
     }
 
+    /**
+     * Returns true if process with given uid and pid owns provided context.
+     */
+    public boolean isContextOwner(int uid, int pid, int context) {
+        synchronized (this) {
+            if (mContextOwners.containsKey(context)) {
+                ClientInfo clientInfo = mContextOwners.get(context);
+                return clientInfo.uid == uid && clientInfo.pid == pid;
+            }
+        }
+        return false;
+    }
+
     private void dispatchAppContextOwnershipLoss(IAppContextListener listener, int contexts) {
         try {
             listener.onAppContextOwnershipLoss(contexts);
@@ -261,7 +273,7 @@
         }
     }
 
-    private void notifiyAppContextChangeToHal(int contexts, boolean callState) {
+    private void notifyAppContextChangeToHal(int contexts, boolean callState) {
         VehicleHal.getInstance().updateAppContext(
                 (contexts & CarAppContextManager.APP_CONTEXT_NAVIGATION) != 0/*navigation*/,
                 (contexts & CarAppContextManager.APP_CONTEXT_VOICE_COMMAND) != 0/*voice*/,
@@ -298,9 +310,9 @@
             mFilter = filter;
         }
 
-        private synchronized int getOwnedCotexts() {
+        private synchronized int getOwnedContexts() {
             if (DBG_EVENT) {
-                Log.i(CarLog.TAG_APP_CONTEXT, "getOwnedCotexts " +
+                Log.i(CarLog.TAG_APP_CONTEXT, "getOwnedContexts " +
                         Integer.toHexString(mOwnedContexts));
             }
             return mOwnedContexts;
@@ -358,7 +370,7 @@
                     dispatchAppContextChange((IAppContextListener) msg.obj, msg.arg1);
                     break;
                 case MSG_NOTIFY_HAL:
-                    notifiyAppContextChangeToHal(msg.arg1, msg.arg2 == 1);
+                    notifyAppContextChangeToHal(msg.arg1, msg.arg2 == 1);
                     break;
             }
         }
diff --git a/service/src/com/android/car/CarLog.java b/service/src/com/android/car/CarLog.java
index 143a77f..551b9a7 100644
--- a/service/src/com/android/car/CarLog.java
+++ b/service/src/com/android/car/CarLog.java
@@ -27,5 +27,6 @@
     public static final String TAG_RADIO = "CAR.RADIO";
     public static final String TAG_SENSOR = "CAR.SENSOR";
     public static final String TAG_SERVICE = "CAR.SERVICE";
+    public static final String TAG_NAV = "CAR.NAV";
     public static final String TAG_TEST = "CAR.TEST";
 }
diff --git a/service/src/com/android/car/CarNavigationStatusService.java b/service/src/com/android/car/CarNavigationStatusService.java
new file mode 100644
index 0000000..4b8c9ec
--- /dev/null
+++ b/service/src/com/android/car/CarNavigationStatusService.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2015 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.content.Context;
+import android.graphics.Bitmap;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.support.car.Car;
+import android.support.car.CarAppContextManager;
+import android.support.car.navigation.CarNavigationInstrumentCluster;
+import android.support.car.navigation.ICarNavigationStatus;
+import android.support.car.navigation.ICarNavigationStatusEventListener;
+import android.util.Log;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Service for talking to the instrument cluster.
+ * TODO: implement HAL integration.
+ */
+public class CarNavigationStatusService extends ICarNavigationStatus.Stub
+        implements CarServiceBase {
+    private static final String TAG = CarLog.TAG_NAV;
+
+    private final List<CarNavigationStatusEventListener> mListeners = new ArrayList<>();
+
+    private CarNavigationInstrumentCluster mInstrumentClusterInfo = null;
+    private AppContextService mAppContextService;
+    private Context mContext;
+
+    public CarNavigationStatusService(Context context) {
+        mContext = context;
+    }
+
+    @Override
+    public void init() {
+        // TODO: retrieve cluster info from vehicle HAL.
+        mInstrumentClusterInfo = CarNavigationInstrumentCluster.createCluster(1000);
+        mAppContextService = (AppContextService) ICarImpl.getInstance(mContext)
+                .getCarService(Car.APP_CONTEXT_SERVICE);
+    }
+
+    @Override
+    public void release() {
+        synchronized(mListeners) {
+            mListeners.clear();
+        }
+    }
+
+    @Override
+    public void sendNavigationStatus(int status) {
+        // TODO: propagate this event to vehicle HAL
+        Log.d(TAG, "sendNavigationStatus, status: " + status);
+        verifyNavigationContextOwner();
+    }
+
+    @Override
+    public void sendNavigationTurnEvent(
+            int event, String road, int turnAngle, int turnNumber, Bitmap image, int turnSide) {
+        // TODO: propagate this event to vehicle HAL
+        Log.d(TAG, "sendNavigationTurnEvent, event:" + event + ", turnAngle: " + turnAngle + ", "
+                + "turnNumber: " + turnNumber + ", " + "turnSide: " + turnSide);
+        verifyNavigationContextOwner();
+    }
+
+    @Override
+    public void sendNavigationTurnDistanceEvent(int distanceMeters, int timeSeconds) {
+        // TODO: propagate this event to vehicle HAL
+        Log.d(TAG, "sendNavigationTurnDistanceEvent, distanceMeters:" + distanceMeters + ", "
+                + "timeSeconds: " + timeSeconds);
+        verifyNavigationContextOwner();
+    }
+
+    @Override
+    public boolean registerEventListener(ICarNavigationStatusEventListener listener) {
+        synchronized(mListeners) {
+            if (findClientLocked(listener) == null) {
+                CarNavigationStatusEventListener carInstrumentClusterEventListener =
+                        new CarNavigationStatusEventListener(listener);
+                try {
+                    listener.asBinder().linkToDeath(carInstrumentClusterEventListener, 0);
+                } catch (RemoteException e) {
+                    Log.w(TAG, "Adding listener failed.");
+                    return false;
+                }
+                mListeners.add(carInstrumentClusterEventListener);
+
+                // The new listener needs to be told the instrument cluster parameters.
+                try {
+                    // TODO: onStart and onStop methods might be triggered from vehicle HAL as well.
+                    carInstrumentClusterEventListener.listener.onStart(mInstrumentClusterInfo);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "listener.onStart failed.");
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean unregisterEventListener(ICarNavigationStatusEventListener listener) {
+        CarNavigationStatusEventListener client;
+        synchronized (mListeners) {
+            client = findClientLocked(listener);
+        }
+        return client != null && removeClient(client);
+    }
+
+    @Override
+    public CarNavigationInstrumentCluster getInfo() {
+        return mInstrumentClusterInfo;
+    }
+
+    @Override
+    public boolean isSupported() {
+        return mInstrumentClusterInfo != null;
+    }
+
+    private void verifyNavigationContextOwner() {
+        if (!mAppContextService.isContextOwner(
+                Binder.getCallingUid(),
+                Binder.getCallingPid(),
+                CarAppContextManager.APP_CONTEXT_NAVIGATION)) {
+            throw new IllegalStateException(
+                    "Client is not an owner of APP_CONTEXT_NAVIGATION.");
+        }
+    }
+
+    private boolean removeClient(CarNavigationStatusEventListener listener) {
+        synchronized(mListeners) {
+            for (CarNavigationStatusEventListener currentListener : mListeners) {
+                // Use asBinder() for comparison.
+                if (currentListener == listener) {
+                    currentListener.listener.asBinder().unlinkToDeath(currentListener, 0);
+                    return mListeners.remove(currentListener);
+                }
+            }
+        }
+        return false;
+    }
+
+    private CarNavigationStatusEventListener findClientLocked(
+            ICarNavigationStatusEventListener listener) {
+        for (CarNavigationStatusEventListener existingListener : mListeners) {
+            if (existingListener.listener.asBinder() == listener.asBinder()) {
+                return existingListener;
+            }
+        }
+        return null;
+    }
+
+
+    private class CarNavigationStatusEventListener implements IBinder.DeathRecipient {
+        final ICarNavigationStatusEventListener listener;
+
+        public CarNavigationStatusEventListener(ICarNavigationStatusEventListener listener) {
+            this.listener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            listener.asBinder().unlinkToDeath(this, 0);
+            removeClient(this);
+        }
+    }
+
+    @Override
+    public void dump(PrintWriter writer) {
+        // TODO Auto-generated method stub
+    }
+}
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 5cbdf8d..8c19e6c 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -53,6 +53,7 @@
     private final AppContextService mAppContextService;
     private final CarPackageManagerService mCarPackageManagerService;
     private final GarageModeService mGarageModeService;
+    private final CarNavigationStatusService mCarNavigationStatusService;
 
     /** Test only service. Populate it only when necessary. */
     @GuardedBy("this")
@@ -92,6 +93,7 @@
         mCarRadioService = new CarRadioService(serviceContext);
         mCarNightService = new CarNightService(serviceContext);
         mCarPackageManagerService = new CarPackageManagerService(serviceContext);
+        mCarNavigationStatusService = new CarNavigationStatusService(serviceContext);
 
         // Be careful with order. Service depending on other service should be inited later.
         mAllServices = new CarServiceBase[] {
@@ -105,6 +107,7 @@
                 mCarHvacService,
                 mCarRadioService,
                 mCarNightService,
+                mCarNavigationStatusService,
                 };
     }
 
@@ -188,6 +191,10 @@
             case CarSystem.RADIO_SERVICE:
                 assertRadioPermission(mContext);
                 return mCarRadioService;
+
+            case Car.CAR_NAVIGATION_SERVICE:
+                return mCarNavigationStatusService;
+
             case CarSystemTest.TEST_SERVICE: {
                 assertVehicleHalMockPermission(mContext);
                 synchronized (this) {