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 
CarNavigationService(Context context, AppContextService appContextService, InstrumentClusterService instrumentClusterService)57     public CarNavigationService(Context context, AppContextService appContextService,
58             InstrumentClusterService instrumentClusterService) {
59         mContext = context;
60         mAppContextService = appContextService;
61         mInstrumentClusterService = instrumentClusterService;
62     }
63 
64     @Override
init()65     public void init() {
66         Log.d(TAG, "init");
67         mInstrumentClusterService.registerListener(this);
68     }
69 
70     @Override
release()71     public void release() {
72         synchronized(mListeners) {
73             mListeners.clear();
74         }
75         mInstrumentClusterService.unregisterListener(this);
76     }
77 
78     @Override
sendNavigationStatus(int status)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
sendNavigationTurnEvent( int event, String road, int turnAngle, int turnNumber, Bitmap image, int turnSide)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
sendNavigationTurnDistanceEvent(int distanceMeters, int timeSeconds)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
registerEventListener(ICarNavigationEventListener listener)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
unregisterEventListener(ICarNavigationEventListener listener)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
getInstrumentClusterInfo()156     public CarNavigationInstrumentCluster getInstrumentClusterInfo() {
157         return mInstrumentClusterInfo;
158     }
159 
160     @Override
isInstrumentClusterSupported()161     public boolean isInstrumentClusterSupported() {
162         return mInstrumentClusterInfo != null;
163     }
164 
verifyNavigationContextOwner()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 
removeClient(CarNavigationEventListener listener)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 
findClientLocked( ICarNavigationEventListener listener)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
onRendererInitSucceeded()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 
CarNavigationEventListener(ICarNavigationEventListener listener)218         public CarNavigationEventListener(ICarNavigationEventListener listener) {
219             this.listener = listener;
220         }
221 
222         @Override
binderDied()223         public void binderDied() {
224             listener.asBinder().unlinkToDeath(this, 0);
225             removeClient(this);
226         }
227 
228         /** Returns true if event sent successfully */
onInstrumentClusterStart(CarNavigationInstrumentCluster clusterInfo)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
dump(PrintWriter writer)241     public void dump(PrintWriter writer) {
242         // TODO Auto-generated method stub
243     }
244 
isRendererAvailable()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