1 /* 2 * Copyright (C) 2022 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 17 package com.android.car.oem; 18 19 import android.annotation.Nullable; 20 import android.car.builtin.content.pm.PackageManagerHelper; 21 import android.car.builtin.os.BuildHelper; 22 import android.car.builtin.util.Slogf; 23 import android.car.oem.IOemCarAudioDuckingService; 24 import android.car.oem.IOemCarAudioFocusService; 25 import android.car.oem.IOemCarAudioVolumeService; 26 import android.car.oem.IOemCarService; 27 import android.car.oem.IOemCarServiceCallback; 28 import android.content.ComponentName; 29 import android.content.Context; 30 import android.content.Intent; 31 import android.content.ServiceConnection; 32 import android.content.pm.PackageInfo; 33 import android.content.pm.PackageManager.NameNotFoundException; 34 import android.content.res.Resources; 35 import android.os.Binder; 36 import android.os.Handler; 37 import android.os.HandlerThread; 38 import android.os.IBinder; 39 import android.os.RemoteException; 40 import android.os.SystemClock; 41 import android.os.SystemProperties; 42 import android.os.UserHandle; 43 import android.text.TextUtils; 44 import android.util.Log; 45 46 import com.android.car.CarServiceBase; 47 import com.android.car.CarServiceUtils; 48 import com.android.car.R; 49 import com.android.car.internal.util.IndentingPrintWriter; 50 import com.android.internal.annotations.GuardedBy; 51 import com.android.internal.annotations.VisibleForTesting; 52 53 import java.util.ArrayList; 54 import java.util.concurrent.CountDownLatch; 55 import java.util.concurrent.TimeUnit; 56 import java.util.concurrent.TimeoutException; 57 58 /** 59 * Manages access to OemCarService. 60 * 61 * <p>All calls in this class are blocking on OEM service initialization, so should be called as 62 * late as possible. 63 * 64 * <b>NOTE</b>: All {@link CarOemProxyService} call should be after init of ICarImpl. If any 65 * component calls {@link CarOemProxyService} before init of ICarImpl complete, it would throw 66 * {@link IllegalStateException}. 67 */ 68 public final class CarOemProxyService implements CarServiceBase { 69 70 private static final String TAG = CarOemProxyService.class.getSimpleName(); 71 private static final String CALL_TAG = CarOemProxyService.class.getSimpleName(); 72 private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG); 73 // mock component name for testing if system property is set. 74 private static final String PROPERTY_EMULATED_OEM_CAR_SERVICE = 75 "persist.com.android.car.internal.debug.oem_car_service"; 76 77 private final int mOemServiceConnectionTimeoutMs; 78 private final int mOemServiceReadyTimeoutMs; 79 private final Object mLock = new Object(); 80 private final boolean mIsFeatureEnabled; 81 private final Context mContext; 82 private final boolean mIsOemServiceBound; 83 private final CarOemProxyServiceHelper mHelper; 84 private final HandlerThread mHandlerThread; 85 private final Handler mHandler; 86 @GuardedBy("mLock") 87 private final ArrayList<CarOemProxyServiceCallback> mCallbacks = new ArrayList<>(); 88 89 90 private String mComponentName; 91 92 // True once OemService return true for {@code isOemServiceReady} call. It means that OEM 93 // service has completed all the initialization and ready to serve requests. 94 @GuardedBy("mLock") 95 private boolean mIsOemServiceReady; 96 // True once OEM service is connected. It means that OEM service has return binder for 97 // communication. OEM service may still not be ready. 98 @GuardedBy("mLock") 99 private boolean mIsOemServiceConnected; 100 101 @GuardedBy("mLock") 102 private boolean mInitComplete; 103 @GuardedBy("mLock") 104 private IOemCarService mOemCarService; 105 @GuardedBy("mLock") 106 private CarOemAudioFocusProxyService mCarOemAudioFocusProxyService; 107 @GuardedBy("mLock") 108 private CarOemAudioVolumeProxyService mCarOemAudioVolumeProxyService; 109 @GuardedBy("mLock") 110 private CarOemAudioDuckingProxyService mCarOemAudioDuckingProxyService; 111 112 113 private final ServiceConnection mCarOemServiceConnection = new ServiceConnection() { 114 115 @Override 116 public void onServiceConnected(ComponentName componentName, IBinder iBinder) { 117 Slogf.i(TAG, "onServiceConnected: %s, %s", componentName, iBinder); 118 synchronized (mLock) { 119 if (mOemCarService == IOemCarService.Stub.asInterface(iBinder)) { 120 return; // already connected. 121 } 122 Slogf.i(TAG, "car oem service binder changed, was %s now: %s", 123 mOemCarService, iBinder); 124 mOemCarService = IOemCarService.Stub.asInterface(iBinder); 125 Slogf.i(TAG, "**CarOemService connected**"); 126 mIsOemServiceConnected = true; 127 mLock.notifyAll(); 128 } 129 } 130 131 @Override 132 public void onServiceDisconnected(ComponentName componentName) { 133 Slogf.e(TAG, "OEM service crashed. Crashing the CarService. ComponentName:%s", 134 componentName); 135 mHelper.crashCarService("Service Disconnected"); 136 } 137 }; 138 139 private final CountDownLatch mOemServiceReadyLatch = new CountDownLatch(1); 140 141 private final IOemCarServiceCallback mOemCarServiceCallback = new IOemCarServiceCallbackImpl(); 142 143 @VisibleForTesting CarOemProxyService(Context context)144 public CarOemProxyService(Context context) { 145 this(context, null); 146 } 147 148 @VisibleForTesting CarOemProxyService(Context context, CarOemProxyServiceHelper helper)149 public CarOemProxyService(Context context, CarOemProxyServiceHelper helper) { 150 this(context, helper, null); 151 } 152 CarOemProxyService(Context context, CarOemProxyServiceHelper helper, Handler handler)153 public CarOemProxyService(Context context, CarOemProxyServiceHelper helper, Handler handler) { 154 // Bind to the OemCarService 155 mContext = context; 156 Resources res = mContext.getResources(); 157 mOemServiceConnectionTimeoutMs = res 158 .getInteger(R.integer.config_oemCarService_connection_timeout_ms); 159 mOemServiceReadyTimeoutMs = res 160 .getInteger(R.integer.config_oemCarService_serviceReady_timeout_ms); 161 162 String componentName = res.getString(R.string.config_oemCarService); 163 164 if (TextUtils.isEmpty(componentName)) { 165 // mock component name for testing if system property is set. 166 String emulatedOemCarService = SystemProperties.get(PROPERTY_EMULATED_OEM_CAR_SERVICE, 167 ""); 168 if (!BuildHelper.isUserBuild() && emulatedOemCarService != null 169 && !emulatedOemCarService.isEmpty()) { 170 componentName = emulatedOemCarService; 171 Slogf.i(TAG, "Using emulated componentname for testing. ComponentName: %s", 172 mComponentName); 173 } 174 } 175 176 mComponentName = componentName; 177 178 Slogf.i(TAG, "Oem Car Service Config. Connection timeout:%s, Service Ready timeout:%d, " 179 + "component Name:%s", mOemServiceConnectionTimeoutMs, mOemServiceReadyTimeoutMs, 180 mComponentName); 181 182 if (isInvalidComponentName(context, mComponentName)) { 183 // feature disabled 184 mIsFeatureEnabled = false; 185 mIsOemServiceBound = false; 186 mHelper = null; 187 mHandlerThread = null; 188 mHandler = null; 189 Slogf.i(TAG, "**CarOemService is disabled.**"); 190 return; 191 } 192 193 Intent intent = (new Intent()) 194 .setComponent(ComponentName.unflattenFromString(mComponentName)); 195 196 Slogf.i(TAG, "Binding to Oem Service with intent: %s", intent); 197 mHandlerThread = CarServiceUtils.getHandlerThread("car_oem_service"); 198 mHandler = handler == null ? new Handler(mHandlerThread.getLooper()) : handler; 199 200 mIsOemServiceBound = mContext.bindServiceAsUser(intent, mCarOemServiceConnection, 201 Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM); 202 203 if (mIsOemServiceBound) { 204 mIsFeatureEnabled = true; 205 Slogf.i(TAG, "OemCarService bounded."); 206 } else { 207 mIsFeatureEnabled = false; 208 Slogf.e(TAG, 209 "Couldn't bound to OemCarService. Oem service feature is marked disabled."); 210 } 211 mHelper = helper == null ? new CarOemProxyServiceHelper(mContext) : helper; 212 } 213 isInvalidComponentName(Context context, String componentName)214 private boolean isInvalidComponentName(Context context, String componentName) { 215 if (componentName == null || componentName.isEmpty()) { 216 if (DBG) { 217 Slogf.d(TAG, "ComponentName is null or empty."); 218 } 219 return true; 220 } 221 222 // Only pre-installed package can be used for OEM Service. 223 String packageName = ComponentName.unflattenFromString(componentName).getPackageName(); 224 PackageInfo info; 225 try { 226 info = context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0); 227 } catch (NameNotFoundException e) { 228 Slogf.e(TAG, "componentName %s not found.", componentName); 229 return true; 230 } 231 232 if (info == null || info.applicationInfo == null 233 || !(PackageManagerHelper.isSystemApp(info.applicationInfo) 234 || PackageManagerHelper.isUpdatedSystemApp(info.applicationInfo) 235 || PackageManagerHelper.isOemApp(info.applicationInfo) 236 || PackageManagerHelper.isOdmApp(info.applicationInfo) 237 || PackageManagerHelper.isVendorApp(info.applicationInfo) 238 || PackageManagerHelper.isProductApp(info.applicationInfo) 239 || PackageManagerHelper.isSystemExtApp(info.applicationInfo))) { 240 if (DBG) { 241 Slogf.d(TAG, "Invalid component name. Info: %s", info); 242 } 243 return true; 244 } 245 246 if (DBG) { 247 Slogf.d(TAG, "Valid component name %s, ", componentName); 248 } 249 250 return false; 251 } 252 253 /** 254 * Registers callback to be called once OEM service is ready. 255 * 256 * <p>Other CarService components cannot call OEM service. But they can register a callback 257 * which would be called as soon as OEM Service is ready./ 258 */ registerCallback(CarOemProxyServiceCallback callback)259 public void registerCallback(CarOemProxyServiceCallback callback) { 260 synchronized (mLock) { 261 mCallbacks.add(callback); 262 } 263 } 264 265 /** 266 * Informs if OEM service is enabled. 267 */ isOemServiceEnabled()268 public boolean isOemServiceEnabled() { 269 synchronized (mLock) { 270 return mIsFeatureEnabled; 271 } 272 } 273 274 /** 275 * Informs if OEM service is ready. 276 */ isOemServiceReady()277 public boolean isOemServiceReady() { 278 synchronized (mLock) { 279 return mIsOemServiceReady; 280 } 281 } 282 283 @Override init()284 public void init() { 285 // Nothing to be done as OemCarService was initialized in the constructor. 286 } 287 288 @Override release()289 public void release() { 290 // Stop OEM Service; 291 if (mIsOemServiceBound) { 292 Slogf.i(TAG, "Unbinding Oem Service"); 293 mContext.unbindService(mCarOemServiceConnection); 294 } 295 } 296 297 @Override dump(IndentingPrintWriter writer)298 public void dump(IndentingPrintWriter writer) { 299 writer.println("***CarOemProxyService dump***"); 300 writer.increaseIndent(); 301 synchronized (mLock) { 302 writer.printf("mIsFeatureEnabled: %s\n", mIsFeatureEnabled); 303 writer.printf("mIsOemServiceBound: %s\n", mIsOemServiceBound); 304 writer.printf("mIsOemServiceReady: %s\n", mIsOemServiceReady); 305 writer.printf("mIsOemServiceConnected: %s\n", mIsOemServiceConnected); 306 writer.printf("mInitComplete: %s\n", mInitComplete); 307 writer.printf("OEM_CAR_SERVICE_CONNECTED_TIMEOUT_MS: %s\n", 308 mOemServiceConnectionTimeoutMs); 309 writer.printf("OEM_CAR_SERVICE_READY_TIMEOUT_MS: %s\n", mOemServiceReadyTimeoutMs); 310 writer.printf("mComponentName: %s\n", mComponentName); 311 writer.printf("mCallbacks size: %d\n", mCallbacks.size()); 312 // Dump OEM service stack 313 if (mIsOemServiceReady) { 314 writer.printf("OEM callstack\n"); 315 int timeoutMs = 2000; 316 try { 317 IOemCarService oemCarService = getOemService(); 318 writer.printf(mHelper.doBinderTimedCallWithTimeout(CALL_TAG, 319 () -> oemCarService.getAllStackTraces(), timeoutMs)); 320 } catch (TimeoutException e) { 321 writer.printf("Didn't received OEM stack within %d milliseconds.\n", timeoutMs); 322 } 323 } 324 // Dump helper 325 if (mHelper != null) { 326 mHelper.dump(writer); 327 } 328 } 329 writer.decreaseIndent(); 330 } 331 getOemServiceName()332 public String getOemServiceName() { 333 return mComponentName; 334 } 335 336 /** 337 * Gets OEM audio focus service. 338 */ 339 @Nullable getCarOemAudioFocusService()340 public CarOemAudioFocusProxyService getCarOemAudioFocusService() { 341 if (!mIsFeatureEnabled) { 342 if (DBG) { 343 Slogf.d(TAG, "Oem Car Service is disabled, returning null for" 344 + " getCarOemAudioFocusService"); 345 } 346 return null; 347 } 348 349 synchronized (mLock) { 350 if (mCarOemAudioFocusProxyService != null) { 351 return mCarOemAudioFocusProxyService; 352 } 353 } 354 355 waitForOemService(); 356 357 // Defaults to returning null service and try again next time the service is requested. 358 IOemCarService oemCarService = getOemService(); 359 IOemCarAudioFocusService oemAudioFocusService = mHelper.doBinderTimedCallWithDefaultValue( 360 CALL_TAG, () -> oemCarService.getOemAudioFocusService(), 361 /* defaultValue= */ null); 362 363 if (oemAudioFocusService == null) { 364 if (DBG) { 365 Slogf.d(TAG, "Oem Car Service doesn't implement AudioFocusService, returning null" 366 + " for getCarOemAudioFocusService"); 367 } 368 return null; 369 } 370 371 CarOemAudioFocusProxyService carOemAudioFocusProxyService = 372 new CarOemAudioFocusProxyService(mHelper, oemAudioFocusService); 373 synchronized (mLock) { 374 if (mCarOemAudioFocusProxyService != null) { 375 return mCarOemAudioFocusProxyService; 376 } 377 mCarOemAudioFocusProxyService = carOemAudioFocusProxyService; 378 Slogf.i(TAG, "CarOemAudioFocusProxyService is ready."); 379 return mCarOemAudioFocusProxyService; 380 } 381 } 382 383 /** 384 * Gets OEM audio volume service. 385 */ 386 @Nullable getCarOemAudioVolumeService()387 public CarOemAudioVolumeProxyService getCarOemAudioVolumeService() { 388 if (!mIsFeatureEnabled) { 389 if (DBG) { 390 Slogf.d(TAG, "Oem Car Service is disabled, returning null for" 391 + " getCarOemAudioVolumeService"); 392 } 393 return null; 394 } 395 396 synchronized (mLock) { 397 if (mCarOemAudioVolumeProxyService != null) { 398 return mCarOemAudioVolumeProxyService; 399 } 400 } 401 402 waitForOemService(); 403 IOemCarService oemCarService = getOemService(); 404 IOemCarAudioVolumeService oemAudioVolumeService = mHelper.doBinderTimedCallWithDefaultValue( 405 CALL_TAG, () -> oemCarService.getOemAudioVolumeService(), 406 /* defaultValue= */ null); 407 408 if (oemAudioVolumeService == null) { 409 if (DBG) { 410 Slogf.d(TAG, "Oem Car Service doesn't implement AudioVolumeService," 411 + "returning null for getCarOemAudioDuckingService"); 412 } 413 return null; 414 } 415 416 CarOemAudioVolumeProxyService carOemAudioVolumeProxyService = 417 new CarOemAudioVolumeProxyService(mHelper, oemAudioVolumeService); 418 synchronized (mLock) { 419 if (mCarOemAudioVolumeProxyService != null) { 420 return mCarOemAudioVolumeProxyService; 421 } 422 mCarOemAudioVolumeProxyService = carOemAudioVolumeProxyService; 423 Slogf.i(TAG, "CarOemAudioVolumeProxyService is ready."); 424 } 425 return carOemAudioVolumeProxyService; 426 } 427 428 /** 429 * Gets OEM audio ducking service. 430 */ 431 @Nullable getCarOemAudioDuckingService()432 public CarOemAudioDuckingProxyService getCarOemAudioDuckingService() { 433 if (!mIsFeatureEnabled) { 434 if (DBG) { 435 Slogf.d(TAG, "Oem Car Service is disabled, returning null for" 436 + " getCarOemAudioDuckingService"); 437 } 438 return null; 439 } 440 441 synchronized (mLock) { 442 if (mCarOemAudioDuckingProxyService != null) { 443 return mCarOemAudioDuckingProxyService; 444 } 445 } 446 447 waitForOemService(); 448 449 IOemCarService oemCarService = getOemService(); 450 IOemCarAudioDuckingService oemAudioDuckingService = 451 mHelper.doBinderTimedCallWithDefaultValue( 452 CALL_TAG, () -> oemCarService.getOemAudioDuckingService(), 453 /* defaultValue= */ null); 454 455 if (oemAudioDuckingService == null) { 456 if (DBG) { 457 Slogf.d(TAG, "Oem Car Service doesn't implement AudioDuckingService," 458 + "returning null for getCarOemAudioDuckingService"); 459 } 460 return null; 461 } 462 463 CarOemAudioDuckingProxyService carOemAudioDuckingProxyService = 464 new CarOemAudioDuckingProxyService(mHelper, oemAudioDuckingService); 465 synchronized (mLock) { 466 if (mCarOemAudioDuckingProxyService != null) { 467 return mCarOemAudioDuckingProxyService; 468 } 469 mCarOemAudioDuckingProxyService = carOemAudioDuckingProxyService; 470 Slogf.i(TAG, "CarOemAudioDuckingProxyService is ready."); 471 } 472 return carOemAudioDuckingProxyService; 473 } 474 475 /** 476 * Should be called when CarService is ready for communication. It updates the OEM service that 477 * CarService is ready. 478 */ onCarServiceReady()479 public void onCarServiceReady() { 480 waitForOemServiceConnected(); 481 IOemCarService oemCarService = getOemService(); 482 mHelper.doBinderOneWayCall(CALL_TAG, () -> { 483 try { 484 oemCarService.onCarServiceReady(mOemCarServiceCallback); 485 } catch (RemoteException ex) { 486 Slogf.e(TAG, "Binder call received RemoteException, calling to crash CarService", 487 ex); 488 } 489 }); 490 waitForOemServiceReady(); 491 } 492 waitForOemServiceConnected()493 private void waitForOemServiceConnected() { 494 synchronized (mLock) { 495 if (!mInitComplete) { 496 // No CarOemService call should be made before or during init of ICarImpl. 497 throw new IllegalStateException( 498 "CarOemService should not be call before CarService initialization"); 499 } 500 501 if (mIsOemServiceConnected) { 502 return; 503 } 504 waitForOemServiceConnectedLocked(); 505 } 506 } 507 508 @GuardedBy("mLock") waitForOemServiceConnectedLocked()509 private void waitForOemServiceConnectedLocked() { 510 long startTime = SystemClock.elapsedRealtime(); 511 long remainingTime = mOemServiceConnectionTimeoutMs; 512 513 while (!mIsOemServiceConnected && remainingTime > 0) { 514 try { 515 Slogf.i(TAG, "waiting to connect to OemService. wait time: %s", remainingTime); 516 mLock.wait(mOemServiceConnectionTimeoutMs); 517 remainingTime = mOemServiceConnectionTimeoutMs 518 - (SystemClock.elapsedRealtime() - startTime); 519 } catch (InterruptedException e) { 520 Thread.currentThread().interrupt(); 521 Slogf.w(TAG, "InterruptedException received. Reset interrupted status.", e); 522 } 523 } 524 525 if (!mIsOemServiceConnected) { 526 Slogf.e(TAG, "OEM Service is not connected within: %dms, calling to crash CarService", 527 mOemServiceConnectionTimeoutMs); 528 mHelper.crashCarService("OEM Service not connected"); 529 } 530 } 531 waitForOemService()532 private void waitForOemService() { 533 waitForOemServiceConnected(); 534 waitForOemServiceReady(); 535 } 536 waitForOemServiceReady()537 private void waitForOemServiceReady() { 538 synchronized (mLock) { 539 if (mIsOemServiceReady) { 540 return; 541 } 542 } 543 544 try { 545 mOemServiceReadyLatch.await(mOemServiceReadyTimeoutMs, TimeUnit.MILLISECONDS); 546 } catch (InterruptedException e) { 547 Thread.currentThread().interrupt(); 548 Slogf.i(TAG, "Exception while waiting for OEM Service to be ready.", e); 549 } 550 551 synchronized (mLock) { 552 if (!mIsOemServiceReady) { 553 Slogf.e(TAG, "OEM Service is not ready within: " + mOemServiceReadyTimeoutMs 554 + "ms, calling to crash CarService"); 555 mHelper.crashCarService("OEM Service not ready"); 556 } 557 } 558 Slogf.i(TAG, "OEM Service is ready."); 559 } 560 561 // Initialize all OEM related components. initOemServiceComponents()562 private void initOemServiceComponents() { 563 // Initialize all Oem Service components 564 getCarOemAudioFocusService(); 565 566 // Callback registered Car Service components for OEM service. 567 callCarServiceComponents(); 568 } 569 callCarServiceComponents()570 private void callCarServiceComponents() { 571 synchronized (mLock) { 572 for (int i = 0; i < mCallbacks.size(); i++) { 573 mCallbacks.get(i).onOemServiceReady(); 574 } 575 } 576 } 577 578 /** 579 * Informs CarOemService that ICarImpl's init is complete. 580 */ 581 // This would set mInitComplete, which is an additional check so that no car service component 582 // calls CarOemService during or before ICarImpl's init. onInitComplete()583 public void onInitComplete() { 584 if (!mIsFeatureEnabled) { 585 if (DBG) { 586 Slogf.d(TAG, "Oem Car Service is disabled, No-op for onInitComplete"); 587 } 588 return; 589 } 590 591 synchronized (mLock) { 592 mInitComplete = true; 593 } 594 // inform OEM Service that CarService is ready for communication. 595 // It has to be posted on the different thread as this call is part of init process. 596 mHandler.post(() -> onCarServiceReady()); 597 } 598 599 /** 600 * Gets OEM service latest binder. Don't pass the method to helper as it can cause deadlock. 601 */ getOemService()602 private IOemCarService getOemService() { 603 synchronized (mLock) { 604 return mOemCarService; 605 } 606 } 607 608 private class IOemCarServiceCallbackImpl extends IOemCarServiceCallback.Stub { 609 @Override sendOemCarServiceReady()610 public void sendOemCarServiceReady() { 611 synchronized (mLock) { 612 mIsOemServiceReady = true; 613 } 614 mOemServiceReadyLatch.countDown(); 615 int pid = Binder.getCallingPid(); 616 Slogf.i(TAG, "OEM Car service is ready and running. Process ID of OEM Car Service is:" 617 + " %d", pid); 618 mHelper.updateOemPid(pid); 619 IOemCarService oemCarService = getOemService(); 620 mHelper.updateOemStackCall(() -> oemCarService.getAllStackTraces()); 621 // Initialize other components on handler thread so that main thread is not 622 // blocked 623 mHandler.post(() -> initOemServiceComponents()); 624 } 625 } 626 } 627