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