Home | History | Annotate | Download | only in cluster
      1 /*
      2  * Copyright (C) 2015 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 package com.android.car.cluster;
     17 
     18 import android.car.CarAppContextManager;
     19 import android.car.cluster.renderer.NavigationRenderer;
     20 import android.car.navigation.CarNavigationInstrumentCluster;
     21 import android.car.navigation.CarNavigationManager;
     22 import android.car.navigation.ICarNavigation;
     23 import android.car.navigation.ICarNavigationEventListener;
     24 import android.content.Context;
     25 import android.graphics.Bitmap;
     26 import android.os.Binder;
     27 import android.os.IBinder;
     28 import android.os.Looper;
     29 import android.os.RemoteException;
     30 import android.util.Log;
     31 
     32 import com.android.car.AppContextService;
     33 import com.android.car.CarLog;
     34 import com.android.car.CarServiceBase;
     35 import com.android.car.cluster.InstrumentClusterService.RendererInitializationListener;
     36 import com.android.car.cluster.renderer.ThreadSafeNavigationRenderer;
     37 
     38 import java.io.PrintWriter;
     39 import java.util.ArrayList;
     40 import java.util.List;
     41 
     42 /**
     43  * Service that will push navigation event to navigation renderer in instrument cluster.
     44  */
     45 public class CarNavigationService extends ICarNavigation.Stub
     46         implements CarServiceBase, RendererInitializationListener {
     47     private static final String TAG = CarLog.TAG_NAV;
     48 
     49     private final List<CarNavigationEventListener> mListeners = new ArrayList<>();
     50     private final AppContextService mAppContextService;
     51     private final Context mContext;
     52     private final InstrumentClusterService mInstrumentClusterService;
     53 
     54     private volatile CarNavigationInstrumentCluster mInstrumentClusterInfo = null;
     55     private volatile NavigationRenderer mNavigationRenderer;
     56 
     57     public CarNavigationService(Context context, AppContextService appContextService,
     58             InstrumentClusterService instrumentClusterService) {
     59         mContext = context;
     60         mAppContextService = appContextService;
     61         mInstrumentClusterService = instrumentClusterService;
     62     }
     63 
     64     @Override
     65     public void init() {
     66         Log.d(TAG, "init");
     67         mInstrumentClusterService.registerListener(this);
     68     }
     69 
     70     @Override
     71     public void release() {
     72         synchronized(mListeners) {
     73             mListeners.clear();
     74         }
     75         mInstrumentClusterService.unregisterListener(this);
     76     }
     77 
     78     @Override
     79     public void sendNavigationStatus(int status) {
     80         Log.d(TAG, "sendNavigationStatus, status: " + status);
     81         if (!isRendererAvailable()) {
     82             return;
     83         }
     84         verifyNavigationContextOwner();
     85 
     86         if (status == CarNavigationManager.STATUS_ACTIVE) {
     87             mNavigationRenderer.onStartNavigation();
     88         } else if (status == CarNavigationManager.STATUS_INACTIVE
     89                 || status == CarNavigationManager.STATUS_UNAVAILABLE) {
     90             mNavigationRenderer.onStopNavigation();
     91         } else {
     92             throw new IllegalArgumentException("Unknown navigation status: " + status);
     93         }
     94     }
     95 
     96     @Override
     97     public void sendNavigationTurnEvent(
     98             int event, String road, int turnAngle, int turnNumber, Bitmap image, int turnSide) {
     99         Log.d(TAG, "sendNavigationTurnEvent, event:" + event + ", turnAngle: " + turnAngle + ", "
    100                 + "turnNumber: " + turnNumber + ", " + "turnSide: " + turnSide);
    101         if (!isRendererAvailable()) {
    102             return;
    103         }
    104         verifyNavigationContextOwner();
    105 
    106         mNavigationRenderer.onNextTurnChanged(event, road, turnAngle, turnNumber, image, turnSide);
    107     }
    108 
    109     @Override
    110     public void sendNavigationTurnDistanceEvent(int distanceMeters, int timeSeconds) {
    111         Log.d(TAG, "sendNavigationTurnDistanceEvent, distanceMeters:" + distanceMeters + ", "
    112                 + "timeSeconds: " + timeSeconds);
    113         if (!isRendererAvailable()) {
    114             return;
    115         }
    116         verifyNavigationContextOwner();
    117 
    118         mNavigationRenderer.onNextTurnDistanceChanged(distanceMeters, timeSeconds);
    119     }
    120 
    121     @Override
    122     public boolean registerEventListener(ICarNavigationEventListener listener) {
    123         CarNavigationEventListener eventListener;
    124         synchronized(mListeners) {
    125             if (findClientLocked(listener) != null) {
    126                 return true;
    127             }
    128 
    129             eventListener = new CarNavigationEventListener(listener);
    130             try {
    131                 listener.asBinder().linkToDeath(eventListener, 0);
    132             } catch (RemoteException e) {
    133                 Log.w(TAG, "Adding listener failed.", e);
    134                 return false;
    135             }
    136             mListeners.add(eventListener);
    137         }
    138 
    139         // The new listener needs to be told the instrument cluster parameters.
    140         if (isRendererAvailable()) {
    141             return eventListener.onInstrumentClusterStart(mInstrumentClusterInfo);
    142         }
    143         return true;
    144     }
    145 
    146     @Override
    147     public boolean unregisterEventListener(ICarNavigationEventListener listener) {
    148         CarNavigationEventListener client;
    149         synchronized (mListeners) {
    150             client = findClientLocked(listener);
    151         }
    152         return client != null && removeClient(client);
    153     }
    154 
    155     @Override
    156     public CarNavigationInstrumentCluster getInstrumentClusterInfo() {
    157         return mInstrumentClusterInfo;
    158     }
    159 
    160     @Override
    161     public boolean isInstrumentClusterSupported() {
    162         return mInstrumentClusterInfo != null;
    163     }
    164 
    165     private void verifyNavigationContextOwner() {
    166         if (!mAppContextService.isContextOwner(
    167                 Binder.getCallingUid(),
    168                 Binder.getCallingPid(),
    169                 CarAppContextManager.APP_CONTEXT_NAVIGATION)) {
    170             throw new IllegalStateException(
    171                     "Client is not an owner of APP_CONTEXT_NAVIGATION.");
    172         }
    173     }
    174 
    175     private boolean removeClient(CarNavigationEventListener listener) {
    176         synchronized(mListeners) {
    177             for (CarNavigationEventListener currentListener : mListeners) {
    178                 // Use asBinder() for comparison.
    179                 if (currentListener == listener) {
    180                     currentListener.listener.asBinder().unlinkToDeath(currentListener, 0);
    181                     return mListeners.remove(currentListener);
    182                 }
    183             }
    184         }
    185         return false;
    186     }
    187 
    188     private CarNavigationEventListener findClientLocked(
    189             ICarNavigationEventListener listener) {
    190         for (CarNavigationEventListener existingListener : mListeners) {
    191             if (existingListener.listener.asBinder() == listener.asBinder()) {
    192                 return existingListener;
    193             }
    194         }
    195         return null;
    196     }
    197 
    198     @Override
    199     public void onRendererInitSucceeded() {
    200         Log.d(TAG, "onRendererInitSucceeded");
    201         mNavigationRenderer = ThreadSafeNavigationRenderer.createFor(
    202                 Looper.getMainLooper(),
    203                 mInstrumentClusterService.getNavigationRenderer());
    204 
    205         // TODO: we need to obtain this information from InstrumentClusterRenderer.
    206         mInstrumentClusterInfo = CarNavigationInstrumentCluster.createCluster(1000);
    207 
    208         if (isRendererAvailable()) {
    209             for (CarNavigationEventListener listener : mListeners) {
    210                 listener.onInstrumentClusterStart(mInstrumentClusterInfo);
    211             }
    212         }
    213     }
    214 
    215     private class CarNavigationEventListener implements IBinder.DeathRecipient {
    216         final ICarNavigationEventListener listener;
    217 
    218         public CarNavigationEventListener(ICarNavigationEventListener listener) {
    219             this.listener = listener;
    220         }
    221 
    222         @Override
    223         public void binderDied() {
    224             listener.asBinder().unlinkToDeath(this, 0);
    225             removeClient(this);
    226         }
    227 
    228         /** Returns true if event sent successfully */
    229         public boolean onInstrumentClusterStart(CarNavigationInstrumentCluster clusterInfo) {
    230             try {
    231                 listener.onInstrumentClusterStart(clusterInfo);
    232             } catch (RemoteException e) {
    233                 Log.e(TAG, "Unable to call onInstrumentClusterStart for listener: " + listener, e);
    234                 return false;
    235             }
    236             return true;
    237         }
    238     }
    239 
    240     @Override
    241     public void dump(PrintWriter writer) {
    242         // TODO Auto-generated method stub
    243     }
    244 
    245     private boolean isRendererAvailable() {
    246         boolean available = mNavigationRenderer != null;
    247         if (!available) {
    248             Log.w(TAG, "Instrument cluster renderer is not available.");
    249         }
    250         return available;
    251     }
    252 }
    253