blob: 7fd9d4d1f0f5a61653d5022258c2787d289ae15a [file] [log] [blame]
/*
* Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.oem;
import android.annotation.Nullable;
import android.car.builtin.content.pm.PackageManagerHelper;
import android.car.builtin.os.BuildHelper;
import android.car.builtin.util.Slogf;
import android.car.oem.IOemCarAudioDuckingService;
import android.car.oem.IOemCarAudioFocusService;
import android.car.oem.IOemCarAudioVolumeService;
import android.car.oem.IOemCarService;
import android.car.oem.IOemCarServiceCallback;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.os.Binder;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.Log;
import com.android.car.CarServiceBase;
import com.android.car.CarServiceUtils;
import com.android.car.R;
import com.android.car.internal.util.IndentingPrintWriter;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Manages access to OemCarService.
*
* <p>All calls in this class are blocking on OEM service initialization, so should be called as
* late as possible.
*
* <b>NOTE</b>: All {@link CarOemProxyService} call should be after init of ICarImpl. If any
* component calls {@link CarOemProxyService} before init of ICarImpl complete, it would throw
* {@link IllegalStateException}.
*/
public final class CarOemProxyService implements CarServiceBase {
private static final String TAG = CarOemProxyService.class.getSimpleName();
private static final String CALL_TAG = CarOemProxyService.class.getSimpleName();
private static final boolean DBG = Slogf.isLoggable(TAG, Log.DEBUG);
// mock component name for testing if system property is set.
private static final String PROPERTY_EMULATED_OEM_CAR_SERVICE =
"persist.com.android.car.internal.debug.oem_car_service";
private final int mOemServiceConnectionTimeoutMs;
private final int mOemServiceReadyTimeoutMs;
private final Object mLock = new Object();
private final boolean mIsFeatureEnabled;
private final Context mContext;
private final boolean mIsOemServiceBound;
private final CarOemProxyServiceHelper mHelper;
private final HandlerThread mHandlerThread;
private final Handler mHandler;
@GuardedBy("mLock")
private final ArrayList<CarOemProxyServiceCallback> mCallbacks = new ArrayList<>();
private String mComponentName;
// True once OemService return true for {@code isOemServiceReady} call. It means that OEM
// service has completed all the initialization and ready to serve requests.
@GuardedBy("mLock")
private boolean mIsOemServiceReady;
// True once OEM service is connected. It means that OEM service has return binder for
// communication. OEM service may still not be ready.
@GuardedBy("mLock")
private boolean mIsOemServiceConnected;
@GuardedBy("mLock")
private boolean mInitComplete;
@GuardedBy("mLock")
private IOemCarService mOemCarService;
@GuardedBy("mLock")
private CarOemAudioFocusProxyService mCarOemAudioFocusProxyService;
@GuardedBy("mLock")
private CarOemAudioVolumeProxyService mCarOemAudioVolumeProxyService;
@GuardedBy("mLock")
private CarOemAudioDuckingProxyService mCarOemAudioDuckingProxyService;
private final ServiceConnection mCarOemServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Slogf.i(TAG, "onServiceConnected: %s, %s", componentName, iBinder);
synchronized (mLock) {
if (mOemCarService == IOemCarService.Stub.asInterface(iBinder)) {
return; // already connected.
}
Slogf.i(TAG, "car oem service binder changed, was %s now: %s",
mOemCarService, iBinder);
mOemCarService = IOemCarService.Stub.asInterface(iBinder);
Slogf.i(TAG, "**CarOemService connected**");
mIsOemServiceConnected = true;
mLock.notifyAll();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Slogf.e(TAG, "OEM service crashed. Crashing the CarService. ComponentName:%s",
componentName);
mHelper.crashCarService("Service Disconnected");
}
};
private final CountDownLatch mOemServiceReadyLatch = new CountDownLatch(1);
private final IOemCarServiceCallback mOemCarServiceCallback = new IOemCarServiceCallbackImpl();
@VisibleForTesting
public CarOemProxyService(Context context) {
this(context, null);
}
@VisibleForTesting
public CarOemProxyService(Context context, CarOemProxyServiceHelper helper) {
this(context, helper, null);
}
public CarOemProxyService(Context context, CarOemProxyServiceHelper helper, Handler handler) {
// Bind to the OemCarService
mContext = context;
Resources res = mContext.getResources();
mOemServiceConnectionTimeoutMs = res
.getInteger(R.integer.config_oemCarService_connection_timeout_ms);
mOemServiceReadyTimeoutMs = res
.getInteger(R.integer.config_oemCarService_serviceReady_timeout_ms);
String componentName = res.getString(R.string.config_oemCarService);
if (TextUtils.isEmpty(componentName)) {
// mock component name for testing if system property is set.
String emulatedOemCarService = SystemProperties.get(PROPERTY_EMULATED_OEM_CAR_SERVICE,
"");
if (!BuildHelper.isUserBuild() && emulatedOemCarService != null
&& !emulatedOemCarService.isEmpty()) {
componentName = emulatedOemCarService;
Slogf.i(TAG, "Using emulated componentname for testing. ComponentName: %s",
mComponentName);
}
}
mComponentName = componentName;
Slogf.i(TAG, "Oem Car Service Config. Connection timeout:%s, Service Ready timeout:%d, "
+ "component Name:%s", mOemServiceConnectionTimeoutMs, mOemServiceReadyTimeoutMs,
mComponentName);
if (isInvalidComponentName(context, mComponentName)) {
// feature disabled
mIsFeatureEnabled = false;
mIsOemServiceBound = false;
mHelper = null;
mHandlerThread = null;
mHandler = null;
Slogf.i(TAG, "**CarOemService is disabled.**");
return;
}
Intent intent = (new Intent())
.setComponent(ComponentName.unflattenFromString(mComponentName));
Slogf.i(TAG, "Binding to Oem Service with intent: %s", intent);
mHandlerThread = CarServiceUtils.getHandlerThread("car_oem_service");
mHandler = handler == null ? new Handler(mHandlerThread.getLooper()) : handler;
mIsOemServiceBound = mContext.bindServiceAsUser(intent, mCarOemServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_IMPORTANT, UserHandle.SYSTEM);
if (mIsOemServiceBound) {
mIsFeatureEnabled = true;
Slogf.i(TAG, "OemCarService bounded.");
} else {
mIsFeatureEnabled = false;
Slogf.e(TAG,
"Couldn't bound to OemCarService. Oem service feature is marked disabled.");
}
mHelper = helper == null ? new CarOemProxyServiceHelper(mContext) : helper;
}
private boolean isInvalidComponentName(Context context, String componentName) {
if (componentName == null || componentName.isEmpty()) {
if (DBG) {
Slogf.d(TAG, "ComponentName is null or empty.");
}
return true;
}
// Only pre-installed package can be used for OEM Service.
String packageName = ComponentName.unflattenFromString(componentName).getPackageName();
PackageInfo info;
try {
info = context.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
} catch (NameNotFoundException e) {
Slogf.e(TAG, "componentName %s not found.", componentName);
return true;
}
if (info == null || info.applicationInfo == null
|| !(PackageManagerHelper.isSystemApp(info.applicationInfo)
|| PackageManagerHelper.isUpdatedSystemApp(info.applicationInfo)
|| PackageManagerHelper.isOemApp(info.applicationInfo)
|| PackageManagerHelper.isOdmApp(info.applicationInfo)
|| PackageManagerHelper.isVendorApp(info.applicationInfo)
|| PackageManagerHelper.isProductApp(info.applicationInfo)
|| PackageManagerHelper.isSystemExtApp(info.applicationInfo))) {
if (DBG) {
Slogf.d(TAG, "Invalid component name. Info: %s", info);
}
return true;
}
if (DBG) {
Slogf.d(TAG, "Valid component name %s, ", componentName);
}
return false;
}
/**
* Registers callback to be called once OEM service is ready.
*
* <p>Other CarService components cannot call OEM service. But they can register a callback
* which would be called as soon as OEM Service is ready./
*/
public void registerCallback(CarOemProxyServiceCallback callback) {
synchronized (mLock) {
mCallbacks.add(callback);
}
}
/**
* Informs if OEM service is enabled.
*/
public boolean isOemServiceEnabled() {
synchronized (mLock) {
return mIsFeatureEnabled;
}
}
/**
* Informs if OEM service is ready.
*/
public boolean isOemServiceReady() {
synchronized (mLock) {
return mIsOemServiceReady;
}
}
@Override
public void init() {
// Nothing to be done as OemCarService was initialized in the constructor.
}
@Override
public void release() {
// Stop OEM Service;
if (mIsOemServiceBound) {
Slogf.i(TAG, "Unbinding Oem Service");
mContext.unbindService(mCarOemServiceConnection);
}
}
@Override
public void dump(IndentingPrintWriter writer) {
writer.println("***CarOemProxyService dump***");
writer.increaseIndent();
synchronized (mLock) {
writer.printf("mIsFeatureEnabled: %s\n", mIsFeatureEnabled);
writer.printf("mIsOemServiceBound: %s\n", mIsOemServiceBound);
writer.printf("mIsOemServiceReady: %s\n", mIsOemServiceReady);
writer.printf("mIsOemServiceConnected: %s\n", mIsOemServiceConnected);
writer.printf("mInitComplete: %s\n", mInitComplete);
writer.printf("OEM_CAR_SERVICE_CONNECTED_TIMEOUT_MS: %s\n",
mOemServiceConnectionTimeoutMs);
writer.printf("OEM_CAR_SERVICE_READY_TIMEOUT_MS: %s\n", mOemServiceReadyTimeoutMs);
writer.printf("mComponentName: %s\n", mComponentName);
// Dump OEM service stack
if (mIsOemServiceReady) {
writer.printf("OEM callstack\n");
int timeoutMs = 2000;
try {
IOemCarService oemCarService = getOemService();
writer.printf(mHelper.doBinderTimedCallWithTimeout(CALL_TAG,
() -> oemCarService.getAllStackTraces(), timeoutMs));
} catch (TimeoutException e) {
writer.printf("Didn't received OEM stack within %d milliseconds.\n", timeoutMs);
}
}
// Dump helper
if (mHelper != null) {
mHelper.dump(writer);
}
}
writer.decreaseIndent();
}
public String getOemServiceName() {
return mComponentName;
}
/**
* Gets OEM audio focus service.
*/
@Nullable
public CarOemAudioFocusProxyService getCarOemAudioFocusService() {
if (!mIsFeatureEnabled) {
if (DBG) {
Slogf.d(TAG, "Oem Car Service is disabled, returning null for"
+ " getCarOemAudioFocusService");
}
return null;
}
synchronized (mLock) {
if (mCarOemAudioFocusProxyService != null) {
return mCarOemAudioFocusProxyService;
}
}
waitForOemService();
// Defaults to returning null service and try again next time the service is requested.
IOemCarService oemCarService = getOemService();
IOemCarAudioFocusService oemAudioFocusService = mHelper.doBinderTimedCallWithDefaultValue(
CALL_TAG, () -> oemCarService.getOemAudioFocusService(),
/* defaultValue= */ null);
if (oemAudioFocusService == null) {
if (DBG) {
Slogf.d(TAG, "Oem Car Service doesn't implement AudioFocusService, returning null"
+ " for getCarOemAudioFocusService");
}
return null;
}
CarOemAudioFocusProxyService carOemAudioFocusProxyService =
new CarOemAudioFocusProxyService(mHelper, oemAudioFocusService);
synchronized (mLock) {
if (mCarOemAudioFocusProxyService != null) {
return mCarOemAudioFocusProxyService;
}
mCarOemAudioFocusProxyService = carOemAudioFocusProxyService;
Slogf.i(TAG, "CarOemAudioFocusProxyService is ready.");
return mCarOemAudioFocusProxyService;
}
}
/**
* Gets OEM audio volume service.
*/
@Nullable
public CarOemAudioVolumeProxyService getCarOemAudioVolumeService() {
if (!mIsFeatureEnabled) {
if (DBG) {
Slogf.d(TAG, "Oem Car Service is disabled, returning null for"
+ " getCarOemAudioVolumeService");
}
return null;
}
synchronized (mLock) {
if (mCarOemAudioVolumeProxyService != null) {
return mCarOemAudioVolumeProxyService;
}
}
waitForOemService();
IOemCarService oemCarService = getOemService();
IOemCarAudioVolumeService oemAudioVolumeService = mHelper.doBinderTimedCallWithDefaultValue(
CALL_TAG, () -> oemCarService.getOemAudioVolumeService(),
/* defaultValue= */ null);
if (oemAudioVolumeService == null) {
if (DBG) {
Slogf.d(TAG, "Oem Car Service doesn't implement AudioVolumeService,"
+ "returning null for getCarOemAudioDuckingService");
}
return null;
}
CarOemAudioVolumeProxyService carOemAudioVolumeProxyService =
new CarOemAudioVolumeProxyService(mHelper, oemAudioVolumeService);
synchronized (mLock) {
if (mCarOemAudioVolumeProxyService != null) {
return mCarOemAudioVolumeProxyService;
}
mCarOemAudioVolumeProxyService = carOemAudioVolumeProxyService;
Slogf.i(TAG, "CarOemAudioVolumeProxyService is ready.");
}
return carOemAudioVolumeProxyService;
}
/**
* Gets OEM audio ducking service.
*/
@Nullable
public CarOemAudioDuckingProxyService getCarOemAudioDuckingService() {
if (!mIsFeatureEnabled) {
if (DBG) {
Slogf.d(TAG, "Oem Car Service is disabled, returning null for"
+ " getCarOemAudioDuckingService");
}
return null;
}
synchronized (mLock) {
if (mCarOemAudioDuckingProxyService != null) {
return mCarOemAudioDuckingProxyService;
}
}
waitForOemService();
IOemCarService oemCarService = getOemService();
IOemCarAudioDuckingService oemAudioDuckingService =
mHelper.doBinderTimedCallWithDefaultValue(
CALL_TAG, () -> oemCarService.getOemAudioDuckingService(),
/* defaultValue= */ null);
if (oemAudioDuckingService == null) {
if (DBG) {
Slogf.d(TAG, "Oem Car Service doesn't implement AudioDuckingService,"
+ "returning null for getCarOemAudioDuckingService");
}
return null;
}
CarOemAudioDuckingProxyService carOemAudioDuckingProxyService =
new CarOemAudioDuckingProxyService(mHelper, oemAudioDuckingService);
synchronized (mLock) {
if (mCarOemAudioDuckingProxyService != null) {
return mCarOemAudioDuckingProxyService;
}
mCarOemAudioDuckingProxyService = carOemAudioDuckingProxyService;
Slogf.i(TAG, "CarOemAudioDuckingProxyService is ready.");
}
return carOemAudioDuckingProxyService;
}
/**
* Should be called when CarService is ready for communication. It updates the OEM service that
* CarService is ready.
*/
public void onCarServiceReady() {
waitForOemServiceConnected();
IOemCarService oemCarService = getOemService();
mHelper.doBinderOneWayCall(CALL_TAG, () -> {
try {
oemCarService.onCarServiceReady(mOemCarServiceCallback);
} catch (RemoteException ex) {
Slogf.e(TAG, "Binder call received RemoteException, calling to crash CarService",
ex);
}
});
waitForOemServiceReady();
}
private void waitForOemServiceConnected() {
synchronized (mLock) {
if (!mInitComplete) {
// No CarOemService call should be made before or during init of ICarImpl.
throw new IllegalStateException(
"CarOemService should not be call before CarService initialization");
}
if (mIsOemServiceConnected) {
return;
}
waitForOemServiceConnectedLocked();
}
}
@GuardedBy("mLock")
private void waitForOemServiceConnectedLocked() {
long startTime = SystemClock.elapsedRealtime();
long remainingTime = mOemServiceConnectionTimeoutMs;
while (!mIsOemServiceConnected && remainingTime > 0) {
try {
Slogf.i(TAG, "waiting to connect to OemService. wait time: %s", remainingTime);
mLock.wait(mOemServiceConnectionTimeoutMs);
remainingTime = mOemServiceConnectionTimeoutMs
- (SystemClock.elapsedRealtime() - startTime);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Slogf.w(TAG, "InterruptedException received. Reset interrupted status.", e);
}
}
if (!mIsOemServiceConnected) {
Slogf.e(TAG, "OEM Service is not connected within: %dms, calling to crash CarService",
mOemServiceConnectionTimeoutMs);
mHelper.crashCarService("OEM Service not connected");
}
}
private void waitForOemService() {
waitForOemServiceConnected();
waitForOemServiceReady();
}
private void waitForOemServiceReady() {
synchronized (mLock) {
if (mIsOemServiceReady) {
return;
}
}
try {
mOemServiceReadyLatch.await(mOemServiceReadyTimeoutMs, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
Slogf.i(TAG, "Exception while waiting for OEM Service to be ready.", e);
}
synchronized (mLock) {
if (!mIsOemServiceReady) {
Slogf.e(TAG, "OEM Service is not ready within: " + mOemServiceReadyTimeoutMs
+ "ms, calling to crash CarService");
mHelper.crashCarService("OEM Service not ready");
}
}
Slogf.i(TAG, "OEM Service is ready.");
}
// Initialize all OEM related components.
private void initOemServiceComponents() {
// Initialize all Oem Service components
getCarOemAudioFocusService();
// Callback registered Car Service components for OEM service.
callCarServiceComponents();
}
private void callCarServiceComponents() {
synchronized (mLock) {
for (int i = 0; i < mCallbacks.size(); i++) {
mCallbacks.get(i).onOemServiceReady();
}
}
}
/**
* Informs CarOemService that ICarImpl's init is complete.
*/
// This would set mInitComplete, which is an additional check so that no car service component
// calls CarOemService during or before ICarImpl's init.
public void onInitComplete() {
if (!mIsFeatureEnabled) {
if (DBG) {
Slogf.d(TAG, "Oem Car Service is disabled, No-op for onInitComplete");
}
return;
}
synchronized (mLock) {
mInitComplete = true;
}
// inform OEM Service that CarService is ready for communication.
// It has to be posted on the different thread as this call is part of init process.
mHandler.post(() -> onCarServiceReady());
}
/**
* Gets OEM service latest binder. Don't pass the method to helper as it can cause deadlock.
*/
private IOemCarService getOemService() {
synchronized (mLock) {
return mOemCarService;
}
}
private class IOemCarServiceCallbackImpl extends IOemCarServiceCallback.Stub {
@Override
public void sendOemCarServiceReady() {
synchronized (mLock) {
mIsOemServiceReady = true;
}
mOemServiceReadyLatch.countDown();
int pid = Binder.getCallingPid();
Slogf.i(TAG, "OEM Car service is ready and running. Process ID of OEM Car Service is:"
+ " %d", pid);
mHelper.updateOemPid(pid);
IOemCarService oemCarService = getOemService();
mHelper.updateOemStackCall(() -> oemCarService.getAllStackTraces());
// Initialize other components on handler thread so that main thread is not
// blocked
mHandler.post(() -> initOemServiceComponents());
}
}
}