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