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 com.android.car.cluster; 17 18 import static android.content.pm.PackageManager.PERMISSION_GRANTED; 19 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.car.Car; 23 import android.car.CarAppFocusManager; 24 import android.car.cluster.CarInstrumentClusterManager; 25 import android.car.cluster.IInstrumentClusterManagerCallback; 26 import android.car.cluster.IInstrumentClusterManagerService; 27 import android.car.cluster.renderer.IInstrumentCluster; 28 import android.car.cluster.renderer.IInstrumentClusterCallback; 29 import android.car.cluster.renderer.IInstrumentClusterNavigation; 30 import android.car.cluster.renderer.InstrumentClusterRenderingService; 31 import android.content.ComponentName; 32 import android.content.Context; 33 import android.content.Intent; 34 import android.content.ServiceConnection; 35 import android.content.pm.PackageManager; 36 import android.content.pm.ResolveInfo; 37 import android.os.Binder; 38 import android.os.Bundle; 39 import android.os.Handler; 40 import android.os.IBinder; 41 import android.os.IBinder.DeathRecipient; 42 import android.os.Message; 43 import android.os.Process; 44 import android.os.RemoteException; 45 import android.text.TextUtils; 46 import android.util.Log; 47 import android.util.Pair; 48 import android.view.KeyEvent; 49 50 import com.android.car.AppFocusService; 51 import com.android.car.AppFocusService.FocusOwnershipCallback; 52 import com.android.car.CarInputService; 53 import com.android.car.CarInputService.KeyEventListener; 54 import com.android.car.CarLog; 55 import com.android.car.CarServiceBase; 56 import com.android.car.R; 57 import com.android.internal.annotations.GuardedBy; 58 59 import java.io.PrintWriter; 60 import java.util.ArrayList; 61 import java.util.HashMap; 62 import java.util.List; 63 import java.util.Map; 64 import java.util.Set; 65 66 /** 67 * Service responsible for interaction with car's instrument cluster. 68 * 69 * @hide 70 */ 71 @SystemApi 72 public class InstrumentClusterService implements CarServiceBase, 73 FocusOwnershipCallback, KeyEventListener { 74 75 private static final String TAG = CarLog.TAG_CLUSTER; 76 private static final Boolean DBG = false; 77 78 private final Context mContext; 79 80 private final AppFocusService mAppFocusService; 81 private final CarInputService mCarInputService; 82 private final PackageManager mPackageManager; 83 private final Object mSync = new Object(); 84 85 private final ClusterServiceCallback mClusterCallback = new ClusterServiceCallback(); 86 private final ClusterManagerService mClusterManagerService = new ClusterManagerService(); 87 88 @GuardedBy("mSync") 89 private ContextOwner mNavContextOwner; 90 @GuardedBy("mSync") 91 private IInstrumentCluster mRendererService; 92 @GuardedBy("mSync") 93 private final HashMap<String, ClusterActivityInfo> mActivityInfoByCategory = new HashMap<>(); 94 @GuardedBy("mSync") 95 private final HashMap<IBinder, ManagerCallbackInfo> mManagerCallbacks = new HashMap<>(); 96 97 // If renderer service crashed / stopped and this class fails to rebind with it immediately, 98 // we should wait some time before next attempt. This may happen during APK update for example. 99 private DeferredRebinder mDeferredRebinder; 100 101 private boolean mRendererBound = false; 102 103 private final ServiceConnection mRendererServiceConnection = new ServiceConnection() { 104 @Override 105 public void onServiceConnected(ComponentName name, IBinder binder) { 106 if (DBG) { 107 Log.d(TAG, "onServiceConnected, name: " + name + ", binder: " + binder); 108 } 109 IInstrumentCluster service = IInstrumentCluster.Stub.asInterface(binder); 110 ContextOwner navContextOwner; 111 synchronized (mSync) { 112 mRendererService = service; 113 navContextOwner = mNavContextOwner; 114 } 115 if (navContextOwner != null && service != null) { 116 notifyNavContextOwnerChanged(service, navContextOwner.uid, navContextOwner.pid); 117 } 118 } 119 120 @Override 121 public void onServiceDisconnected(ComponentName name) { 122 Log.d(TAG, "onServiceDisconnected, name: " + name); 123 mRendererBound = false; 124 125 synchronized (mSync) { 126 mRendererService = null; 127 } 128 129 if (mDeferredRebinder == null) { 130 mDeferredRebinder = new DeferredRebinder(); 131 } 132 mDeferredRebinder.rebind(); 133 } 134 }; 135 InstrumentClusterService(Context context, AppFocusService appFocusService, CarInputService carInputService)136 public InstrumentClusterService(Context context, AppFocusService appFocusService, 137 CarInputService carInputService) { 138 mContext = context; 139 mAppFocusService = appFocusService; 140 mCarInputService = carInputService; 141 mPackageManager = mContext.getPackageManager(); 142 } 143 144 @Override init()145 public void init() { 146 if (DBG) { 147 Log.d(TAG, "init"); 148 } 149 150 mAppFocusService.registerContextOwnerChangedCallback(this /* FocusOwnershipCallback */); 151 mCarInputService.setInstrumentClusterKeyListener(this /* KeyEventListener */); 152 mRendererBound = bindInstrumentClusterRendererService(); 153 } 154 155 @Override release()156 public void release() { 157 if (DBG) { 158 Log.d(TAG, "release"); 159 } 160 161 mAppFocusService.unregisterContextOwnerChangedCallback(this); 162 if (mRendererBound) { 163 mContext.unbindService(mRendererServiceConnection); 164 mRendererBound = false; 165 } 166 } 167 168 @Override dump(PrintWriter writer)169 public void dump(PrintWriter writer) { 170 writer.println("**" + getClass().getSimpleName() + "**"); 171 writer.println("bound with renderer: " + mRendererBound); 172 writer.println("renderer service: " + mRendererService); 173 } 174 175 @Override onFocusAcquired(int appType, int uid, int pid)176 public void onFocusAcquired(int appType, int uid, int pid) { 177 if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) { 178 return; 179 } 180 181 IInstrumentCluster service; 182 synchronized (mSync) { 183 mNavContextOwner = new ContextOwner(uid, pid); 184 service = mRendererService; 185 } 186 187 if (service != null) { 188 notifyNavContextOwnerChanged(service, uid, pid); 189 } 190 } 191 192 @Override onFocusAbandoned(int appType, int uid, int pid)193 public void onFocusAbandoned(int appType, int uid, int pid) { 194 if (appType != CarAppFocusManager.APP_FOCUS_TYPE_NAVIGATION) { 195 return; 196 } 197 198 IInstrumentCluster service; 199 synchronized (mSync) { 200 if (mNavContextOwner == null 201 || mNavContextOwner.uid != uid 202 || mNavContextOwner.pid != pid) { 203 return; // Nothing to do here, no active focus or not owned by this client. 204 } 205 206 mNavContextOwner = null; 207 service = mRendererService; 208 } 209 210 if (service != null) { 211 notifyNavContextOwnerChanged(service, 0, 0); 212 } 213 } 214 notifyNavContextOwnerChanged(IInstrumentCluster service, int uid, int pid)215 private static void notifyNavContextOwnerChanged(IInstrumentCluster service, int uid, int pid) { 216 try { 217 service.setNavigationContextOwner(uid, pid); 218 } catch (RemoteException e) { 219 Log.e(TAG, "Failed to call setNavigationContextOwner", e); 220 } 221 } 222 bindInstrumentClusterRendererService()223 private boolean bindInstrumentClusterRendererService() { 224 String rendererService = mContext.getString(R.string.instrumentClusterRendererService); 225 if (TextUtils.isEmpty(rendererService)) { 226 Log.i(TAG, "Instrument cluster renderer was not configured"); 227 return false; 228 } 229 230 Log.d(TAG, "bindInstrumentClusterRendererService, component: " + rendererService); 231 232 Intent intent = new Intent(); 233 intent.setComponent(ComponentName.unflattenFromString(rendererService)); 234 Bundle extras = new Bundle(); 235 extras.putBinder( 236 InstrumentClusterRenderingService.EXTRA_KEY_CALLBACK_SERVICE, 237 mClusterCallback); 238 intent.putExtras(extras); 239 return mContext.bindService(intent, mRendererServiceConnection, 240 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT); 241 } 242 243 @Nullable getNavigationService()244 public IInstrumentClusterNavigation getNavigationService() { 245 IInstrumentCluster service; 246 synchronized (mSync) { 247 service = mRendererService; 248 } 249 250 try { 251 return service == null ? null : service.getNavigationService(); 252 } catch (RemoteException e) { 253 Log.e(TAG, "getNavigationServiceBinder" , e); 254 return null; 255 } 256 } 257 getManagerService()258 public IInstrumentClusterManagerService.Stub getManagerService() { 259 return mClusterManagerService; 260 } 261 262 @Override onKeyEvent(KeyEvent event)263 public boolean onKeyEvent(KeyEvent event) { 264 if (DBG) { 265 Log.d(TAG, "InstrumentClusterService#onKeyEvent: " + event); 266 } 267 268 IInstrumentCluster service; 269 synchronized (mSync) { 270 service = mRendererService; 271 } 272 273 if (service != null) { 274 try { 275 service.onKeyEvent(event); 276 } catch (RemoteException e) { 277 Log.e(TAG, "onKeyEvent", e); 278 } 279 } 280 return true; 281 } 282 283 private static class ContextOwner { 284 final int uid; 285 final int pid; 286 ContextOwner(int uid, int pid)287 ContextOwner(int uid, int pid) { 288 this.uid = uid; 289 this.pid = pid; 290 } 291 } 292 293 private static class ClusterActivityInfo { 294 Bundle launchOptions; // ActivityOptions 295 Bundle state; // ClusterActivityState 296 } 297 enforcePermission(String permission)298 private void enforcePermission(String permission) { 299 int callingUid = Binder.getCallingUid(); 300 int callingPid = Binder.getCallingPid(); 301 if (Binder.getCallingUid() == Process.myUid()) { 302 if (mContext.checkCallingOrSelfPermission(permission) != PERMISSION_GRANTED) { 303 throw new SecurityException("Permission " + permission + " is not granted to " 304 + "client {uid: " + callingUid + ", pid: " + callingPid + "}"); 305 } 306 } 307 } 308 enforceClusterControlPermission()309 private void enforceClusterControlPermission() { 310 enforcePermission(Car.PERMISSION_CAR_INSTRUMENT_CLUSTER_CONTROL); 311 } 312 doStartClusterActivity(Intent intent)313 private void doStartClusterActivity(Intent intent) { 314 enforceClusterControlPermission(); 315 316 // Category from given intent should match category from cluster vendor implementation. 317 List<ResolveInfo> resolveList = mPackageManager.queryIntentActivities(intent, 318 PackageManager.GET_RESOLVED_FILTER); 319 if (resolveList == null || resolveList.isEmpty()) { 320 Log.w(TAG, "Failed to resolve an intent: " + intent); 321 return; 322 } 323 324 resolveList = checkPermission(resolveList, Car.PERMISSION_CAR_DISPLAY_IN_CLUSTER); 325 if (resolveList.isEmpty()) { 326 return; 327 } 328 329 // TODO(b/63861009): we may have multiple navigation apps that eligible to be launched in 330 // the cluster. We need to resolve intent that may have multiple activity candidates, right 331 // now we pickup the first one that matches registered category (resolveList is sorted 332 // priority). 333 Pair<ResolveInfo, ClusterActivityInfo> attributedResolveInfo = 334 findClusterActivityOptions(resolveList); 335 if (attributedResolveInfo == null) { 336 Log.w(TAG, "Unable to start an activity with intent: " + intent + " in the cluster: " 337 + "category intent didn't match with any categories from vendor " 338 + "implementation"); 339 return; 340 } 341 ClusterActivityInfo opts = attributedResolveInfo.second; 342 343 // Intent was already checked for permission and resolved, make it explicit. 344 intent.setComponent(attributedResolveInfo.first.getComponentInfo().getComponentName()); 345 346 intent.putExtra(CarInstrumentClusterManager.KEY_EXTRA_ACTIVITY_STATE, opts.state); 347 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 348 // Virtual display could be private and not available to calling process. 349 final long token = Binder.clearCallingIdentity(); 350 try { 351 mContext.startActivity(intent, opts.launchOptions); 352 } finally { 353 Binder.restoreCallingIdentity(token); 354 } 355 } 356 checkPermission(List<ResolveInfo> resolveList, String permission)357 private List<ResolveInfo> checkPermission(List<ResolveInfo> resolveList, 358 String permission) { 359 List<ResolveInfo> permittedResolveList = new ArrayList<>(resolveList.size()); 360 for (ResolveInfo info : resolveList) { 361 String pkgName = info.getComponentInfo().packageName; 362 if (mPackageManager.checkPermission(permission, pkgName) == PERMISSION_GRANTED) { 363 permittedResolveList.add(info); 364 } else { 365 Log.w(TAG, "Permission " + permission + " not granted for " 366 + info.getComponentInfo()); 367 } 368 369 } 370 return permittedResolveList; 371 } 372 doRegisterManagerCallback(IInstrumentClusterManagerCallback callback)373 private void doRegisterManagerCallback(IInstrumentClusterManagerCallback callback) 374 throws RemoteException { 375 enforceClusterControlPermission(); 376 IBinder binder = callback.asBinder(); 377 378 List<Pair<String, Bundle>> knownActivityStates = null; 379 ManagerCallbackDeathRecipient deathRecipient = new ManagerCallbackDeathRecipient(binder); 380 synchronized (mSync) { 381 if (mManagerCallbacks.containsKey(binder)) { 382 Log.w(TAG, "Manager callback already registered for binder: " + binder); 383 return; 384 } 385 mManagerCallbacks.put(binder, new ManagerCallbackInfo(callback, deathRecipient)); 386 if (!mActivityInfoByCategory.isEmpty()) { 387 knownActivityStates = new ArrayList<>(mActivityInfoByCategory.size()); 388 for (Map.Entry<String, ClusterActivityInfo> it : mActivityInfoByCategory.entrySet()) { 389 knownActivityStates.add(new Pair<>(it.getKey(), it.getValue().state)); 390 } 391 } 392 } 393 binder.linkToDeath(deathRecipient, 0); 394 395 // Notify manager immediately with known states. 396 if (knownActivityStates != null) { 397 for (Pair<String, Bundle> it : knownActivityStates) { 398 callback.setClusterActivityState(it.first, it.second); 399 } 400 } 401 } 402 doUnregisterManagerCallback(IBinder binder)403 private void doUnregisterManagerCallback(IBinder binder) throws RemoteException { 404 enforceClusterControlPermission(); 405 ManagerCallbackInfo info; 406 synchronized (mSync) { 407 info = mManagerCallbacks.get(binder); 408 if (info == null) { 409 Log.w(TAG, "Unable to unregister manager callback binder: " + binder + " because " 410 + "it wasn't previously registered."); 411 return; 412 } 413 mManagerCallbacks.remove(binder); 414 } 415 binder.unlinkToDeath(info.deathRecipient, 0); 416 } 417 418 @Nullable findClusterActivityOptions( List<ResolveInfo> resolveList)419 private Pair<ResolveInfo, ClusterActivityInfo> findClusterActivityOptions( 420 List<ResolveInfo> resolveList) { 421 synchronized (mSync) { 422 Set<String> registeredCategories = mActivityInfoByCategory.keySet(); 423 424 for (ResolveInfo resolveInfo : resolveList) { 425 for (String category : registeredCategories) { 426 if (resolveInfo.filter != null && resolveInfo.filter.hasCategory(category)) { 427 ClusterActivityInfo categoryInfo = mActivityInfoByCategory.get(category); 428 return new Pair<>(resolveInfo, categoryInfo); 429 } 430 } 431 } 432 } 433 return null; 434 } 435 436 private class ManagerCallbackDeathRecipient implements DeathRecipient { 437 private final IBinder mBinder; 438 ManagerCallbackDeathRecipient(IBinder binder)439 ManagerCallbackDeathRecipient(IBinder binder) { 440 mBinder = binder; 441 } 442 443 @Override binderDied()444 public void binderDied() { 445 try { 446 doUnregisterManagerCallback(mBinder); 447 } catch (RemoteException e) { 448 // Ignore, shutdown route. 449 } 450 } 451 } 452 453 private class ClusterManagerService extends IInstrumentClusterManagerService.Stub { 454 455 @Override startClusterActivity(Intent intent)456 public void startClusterActivity(Intent intent) throws RemoteException { 457 doStartClusterActivity(intent); 458 } 459 460 @Override registerCallback(IInstrumentClusterManagerCallback callback)461 public void registerCallback(IInstrumentClusterManagerCallback callback) 462 throws RemoteException { 463 doRegisterManagerCallback(callback); 464 } 465 466 @Override unregisterCallback(IInstrumentClusterManagerCallback callback)467 public void unregisterCallback(IInstrumentClusterManagerCallback callback) 468 throws RemoteException { 469 doUnregisterManagerCallback(callback.asBinder()); 470 } 471 } 472 473 @GuardedBy("mSync") getOrCreateActivityInfoLocked(String category)474 private ClusterActivityInfo getOrCreateActivityInfoLocked(String category) { 475 return mActivityInfoByCategory.computeIfAbsent(category, k -> new ClusterActivityInfo()); 476 } 477 478 /** This is communication channel from vendor cluster implementation to Car Service. */ 479 private class ClusterServiceCallback extends IInstrumentClusterCallback.Stub { 480 481 @Override setClusterActivityLaunchOptions(String category, Bundle activityOptions)482 public void setClusterActivityLaunchOptions(String category, Bundle activityOptions) 483 throws RemoteException { 484 doSetActivityLaunchOptions(category, activityOptions); 485 } 486 487 @Override setClusterActivityState(String category, Bundle clusterActivityState)488 public void setClusterActivityState(String category, Bundle clusterActivityState) 489 throws RemoteException { 490 doSetClusterActivityState(category, clusterActivityState); 491 } 492 } 493 494 /** Called from cluster vendor implementation */ doSetActivityLaunchOptions(String category, Bundle activityOptions)495 private void doSetActivityLaunchOptions(String category, Bundle activityOptions) { 496 if (DBG) { 497 Log.d(TAG, "doSetActivityLaunchOptions, category: " + category 498 + ", options: " + activityOptions); 499 } 500 synchronized (mSync) { 501 ClusterActivityInfo info = getOrCreateActivityInfoLocked(category); 502 info.launchOptions = activityOptions; 503 } 504 } 505 506 /** Called from cluster vendor implementation */ doSetClusterActivityState(String category, Bundle clusterActivityState)507 private void doSetClusterActivityState(String category, Bundle clusterActivityState) 508 throws RemoteException { 509 if (DBG) { 510 Log.d(TAG, "doSetClusterActivityState, category: " + category 511 + ", state: " + clusterActivityState); 512 } 513 514 List<ManagerCallbackInfo> managerCallbacks; 515 synchronized (mSync) { 516 ClusterActivityInfo info = getOrCreateActivityInfoLocked(category); 517 info.state = clusterActivityState; 518 managerCallbacks = new ArrayList<>(mManagerCallbacks.values()); 519 } 520 521 for (ManagerCallbackInfo cbInfo : managerCallbacks) { 522 cbInfo.callback.setClusterActivityState(category, clusterActivityState); 523 } 524 } 525 526 private static class ManagerCallbackInfo { 527 final IInstrumentClusterManagerCallback callback; 528 final ManagerCallbackDeathRecipient deathRecipient; 529 ManagerCallbackInfo(IInstrumentClusterManagerCallback callback, ManagerCallbackDeathRecipient deathRecipient)530 ManagerCallbackInfo(IInstrumentClusterManagerCallback callback, 531 ManagerCallbackDeathRecipient deathRecipient) { 532 this.callback = callback; 533 this.deathRecipient = deathRecipient; 534 } 535 } 536 537 private class DeferredRebinder extends Handler { 538 private static final long NEXT_REBIND_ATTEMPT_DELAY_MS = 1000L; 539 private static final int NUMBER_OF_ATTEMPTS = 10; 540 rebind()541 public void rebind() { 542 mRendererBound = bindInstrumentClusterRendererService(); 543 544 if (!mRendererBound) { 545 removeMessages(0); 546 sendMessageDelayed(obtainMessage(0, NUMBER_OF_ATTEMPTS, 0), 547 NEXT_REBIND_ATTEMPT_DELAY_MS); 548 } 549 } 550 551 @Override handleMessage(Message msg)552 public void handleMessage(Message msg) { 553 mRendererBound = bindInstrumentClusterRendererService(); 554 555 if (mRendererBound) { 556 Log.w(TAG, "Failed to bound to render service, next attempt in " 557 + NEXT_REBIND_ATTEMPT_DELAY_MS + "ms."); 558 559 int attempts = msg.arg1; 560 if (--attempts >= 0) { 561 sendMessageDelayed(obtainMessage(0, attempts, 0), NEXT_REBIND_ATTEMPT_DELAY_MS); 562 } else { 563 Log.wtf(TAG, "Failed to rebind with cluster rendering service"); 564 } 565 } 566 } 567 } 568 } 569