1 /* 2 * Copyright (C) 2016 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 android.car.cluster.renderer; 17 18 import android.annotation.CallSuper; 19 import android.annotation.MainThread; 20 import android.annotation.SystemApi; 21 import android.app.ActivityOptions; 22 import android.app.Service; 23 import android.car.CarLibLog; 24 import android.car.CarNotConnectedException; 25 import android.car.navigation.CarNavigationInstrumentCluster; 26 import android.content.Intent; 27 import android.graphics.Bitmap; 28 import android.os.Bundle; 29 import android.os.Handler; 30 import android.os.IBinder; 31 import android.os.Looper; 32 import android.os.Message; 33 import android.os.RemoteException; 34 import android.util.Log; 35 import android.util.Pair; 36 import android.view.KeyEvent; 37 38 import com.android.internal.annotations.GuardedBy; 39 40 import java.io.FileDescriptor; 41 import java.io.PrintWriter; 42 import java.lang.ref.WeakReference; 43 44 /** 45 * A service that used for interaction between Car Service and Instrument Cluster. Car Service may 46 * provide internal navigation binder interface to Navigation App and all notifications will be 47 * eventually land in the {@link NavigationRenderer} returned by {@link #getNavigationRenderer()}. 48 * 49 * <p>To extend this class, you must declare the service in your manifest file with 50 * the {@code android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE} permission 51 * <pre> 52 * <service android:name=".MyInstrumentClusterService" 53 * android:permission="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"> 54 * </service></pre> 55 * <p>Also, you will need to register this service in the following configuration file: 56 * {@code packages/services/Car/service/res/values/config.xml} 57 * 58 * @hide 59 */ 60 @SystemApi 61 public abstract class InstrumentClusterRenderingService extends Service { 62 63 private static final String TAG = CarLibLog.TAG_CLUSTER; 64 65 private RendererBinder mRendererBinder; 66 67 /** @hide */ 68 public static final String EXTRA_KEY_CALLBACK_SERVICE = 69 "android.car.cluster.IInstrumentClusterCallback"; 70 71 private final Object mLock = new Object(); 72 @GuardedBy("mLock") 73 private IInstrumentClusterCallback mCallback; 74 75 @Override 76 @CallSuper 77 public IBinder onBind(Intent intent) { 78 if (Log.isLoggable(TAG, Log.DEBUG)) { 79 Log.d(TAG, "onBind, intent: " + intent); 80 } 81 82 if (intent.getExtras().containsKey(EXTRA_KEY_CALLBACK_SERVICE)) { 83 IBinder callbackBinder = intent.getExtras().getBinder(EXTRA_KEY_CALLBACK_SERVICE); 84 synchronized (mLock) { 85 mCallback = IInstrumentClusterCallback.Stub.asInterface(callbackBinder); 86 } 87 } else { 88 Log.w(TAG, "onBind, no callback in extra!"); 89 } 90 91 if (mRendererBinder == null) { 92 mRendererBinder = new RendererBinder(getNavigationRenderer()); 93 } 94 95 return mRendererBinder; 96 } 97 98 /** Returns {@link NavigationRenderer} or null if it's not supported. */ 99 @MainThread 100 protected abstract NavigationRenderer getNavigationRenderer(); 101 102 /** Called when key event that was addressed to instrument cluster display has been received. */ 103 @MainThread 104 protected void onKeyEvent(KeyEvent keyEvent) { 105 } 106 107 /** 108 * 109 * Sets configuration for activities that should be launched directly in the instrument 110 * cluster. 111 * 112 * @param category category of cluster activity 113 * @param activityOptions contains information of how to start cluster activity (on what display 114 * or activity stack. 115 * 116 * @hide 117 */ 118 public void setClusterActivityLaunchOptions(String category, 119 ActivityOptions activityOptions) throws CarNotConnectedException { 120 IInstrumentClusterCallback cb; 121 synchronized (mLock) { 122 cb = mCallback; 123 } 124 if (cb == null) throw new CarNotConnectedException(); 125 try { 126 cb.setClusterActivityLaunchOptions(category, activityOptions.toBundle()); 127 } catch (RemoteException e) { 128 throw new CarNotConnectedException(e); 129 } 130 } 131 132 /** 133 * 134 * @param category cluster activity category, 135 * see {@link android.car.cluster.CarInstrumentClusterManager} 136 * @param state pass information about activity state, 137 * see {@link android.car.cluster.ClusterActivityState} 138 * @return true if information was sent to Car Service 139 * @throws CarNotConnectedException 140 * 141 * @hide 142 */ 143 public void setClusterActivityState(String category, Bundle state) 144 throws CarNotConnectedException { 145 IInstrumentClusterCallback cb; 146 synchronized (mLock) { 147 cb = mCallback; 148 } 149 if (cb == null) throw new CarNotConnectedException(); 150 try { 151 cb.setClusterActivityState(category, state); 152 } catch (RemoteException e) { 153 throw new CarNotConnectedException(e); 154 } 155 } 156 157 158 @Override 159 protected void dump(FileDescriptor fd, PrintWriter writer, String[] args) { 160 writer.println("**" + getClass().getSimpleName() + "**"); 161 writer.println("renderer binder: " + mRendererBinder); 162 if (mRendererBinder != null) { 163 writer.println("navigation renderer: " + mRendererBinder.mNavigationRenderer); 164 String owner = "none"; 165 synchronized (mLock) { 166 if (mRendererBinder.mNavContextOwner != null) { 167 owner = "[uid: " + mRendererBinder.mNavContextOwner.first 168 + ", pid: " + mRendererBinder.mNavContextOwner.second + "]"; 169 } 170 } 171 writer.println("navigation focus owner: " + owner); 172 } 173 IInstrumentClusterCallback cb; 174 synchronized (mLock) { 175 cb = mCallback; 176 } 177 writer.println("callback: " + cb); 178 } 179 180 private class RendererBinder extends IInstrumentCluster.Stub { 181 182 private final NavigationRenderer mNavigationRenderer; 183 private final UiHandler mUiHandler; 184 185 @GuardedBy("mLock") 186 private NavigationBinder mNavigationBinder; 187 @GuardedBy("mLock") 188 private Pair<Integer, Integer> mNavContextOwner; 189 190 RendererBinder(NavigationRenderer navigationRenderer) { 191 mNavigationRenderer = navigationRenderer; 192 mUiHandler = new UiHandler(InstrumentClusterRenderingService.this); 193 } 194 195 @Override 196 public IInstrumentClusterNavigation getNavigationService() throws RemoteException { 197 synchronized (mLock) { 198 if (mNavigationBinder == null) { 199 mNavigationBinder = new NavigationBinder(mNavigationRenderer); 200 if (mNavContextOwner != null) { 201 mNavigationBinder.setNavigationContextOwner( 202 mNavContextOwner.first, mNavContextOwner.second); 203 } 204 } 205 return mNavigationBinder; 206 } 207 } 208 209 @Override 210 public void setNavigationContextOwner(int uid, int pid) throws RemoteException { 211 synchronized (mLock) { 212 mNavContextOwner = new Pair<>(uid, pid); 213 if (mNavigationBinder != null) { 214 mNavigationBinder.setNavigationContextOwner(uid, pid); 215 } 216 } 217 } 218 219 @Override 220 public void onKeyEvent(KeyEvent keyEvent) throws RemoteException { 221 mUiHandler.doKeyEvent(keyEvent); 222 } 223 } 224 225 private class NavigationBinder extends IInstrumentClusterNavigation.Stub { 226 227 private final NavigationRenderer mNavigationRenderer; // Thread-safe navigation renderer. 228 229 private volatile Pair<Integer, Integer> mNavContextOwner; 230 231 NavigationBinder(NavigationRenderer navigationRenderer) { 232 mNavigationRenderer = ThreadSafeNavigationRenderer.createFor( 233 Looper.getMainLooper(), 234 navigationRenderer); 235 } 236 237 void setNavigationContextOwner(int uid, int pid) { 238 mNavContextOwner = new Pair<>(uid, pid); 239 } 240 241 @Override 242 public void onStartNavigation() throws RemoteException { 243 assertContextOwnership(); 244 mNavigationRenderer.onStartNavigation(); 245 } 246 247 @Override 248 public void onStopNavigation() throws RemoteException { 249 assertContextOwnership(); 250 mNavigationRenderer.onStopNavigation(); 251 } 252 253 @Override 254 public void onNextManeuverChanged(int event, CharSequence eventName, int turnAngle, 255 int turnNumber, Bitmap image, int turnSide) throws RemoteException { 256 assertContextOwnership(); 257 mNavigationRenderer.onNextTurnChanged(event, eventName, turnAngle, turnNumber, 258 image, turnSide); 259 } 260 261 @Override 262 public void onNextManeuverDistanceChanged(int distanceMeters, int timeSeconds, 263 int displayDistanceMillis, int displayDistanceUnit) throws RemoteException { 264 assertContextOwnership(); 265 mNavigationRenderer.onNextTurnDistanceChanged(distanceMeters, timeSeconds, 266 displayDistanceMillis, displayDistanceUnit); 267 } 268 269 @Override 270 public void onEvent(int eventType, Bundle bundle) throws RemoteException { 271 assertContextOwnership(); 272 mNavigationRenderer.onEvent(eventType, bundle); 273 } 274 275 @Override 276 public CarNavigationInstrumentCluster getInstrumentClusterInfo() throws RemoteException { 277 return mNavigationRenderer.getNavigationProperties(); 278 } 279 280 private void assertContextOwnership() { 281 int uid = getCallingUid(); 282 int pid = getCallingPid(); 283 284 Pair<Integer, Integer> owner = mNavContextOwner; 285 if (owner == null || owner.first != uid || owner.second != pid) { 286 throw new IllegalStateException("Client (uid:" + uid + ", pid: " + pid + ") is" 287 + "not an owner of APP_CONTEXT_NAVIGATION"); 288 } 289 } 290 } 291 292 private static class UiHandler extends Handler { 293 private static int KEY_EVENT = 0; 294 private final WeakReference<InstrumentClusterRenderingService> mRefService; 295 296 UiHandler(InstrumentClusterRenderingService service) { 297 mRefService = new WeakReference<>(service); 298 } 299 300 @Override 301 public void handleMessage(Message msg) { 302 InstrumentClusterRenderingService service = mRefService.get(); 303 if (service == null) { 304 return; 305 } 306 307 if (msg.what == KEY_EVENT) { 308 service.onKeyEvent((KeyEvent) msg.obj); 309 } else { 310 throw new IllegalArgumentException("Unexpected message: " + msg); 311 } 312 } 313 314 void doKeyEvent(KeyEvent event) { 315 sendMessage(obtainMessage(KEY_EVENT, event)); 316 } 317 } 318 } 319