/* * 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 static com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport.DUMP_INFO; import android.annotation.Nullable; import android.car.builtin.content.pm.PackageManagerHelper; import android.car.builtin.os.BuildHelper; import android.car.builtin.os.TraceHelper; import android.car.builtin.util.Slogf; import android.car.builtin.util.TimingsTraceLog; 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 android.util.proto.ProtoOutputStream; import com.android.car.CarServiceBase; import com.android.car.CarServiceUtils; import com.android.car.R; import com.android.car.internal.ExcludeFromCodeCoverageGeneratedReport; 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. * *
All calls in this class are blocking on OEM service initialization, so should be called as
* late as possible.
*
* NOTE: 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 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);
writer.printf("waitForOemServiceConnected completed in : %d ms\n",
mWaitForOemServiceConnectedDuration);
writer.printf("waitForOemServiceReady completed in : %d ms\n",
mWaitForOemServiceReadyDuration);
// Dump other service components.
getCarOemAudioFocusService().dump(writer);
getCarOemAudioVolumeService().dump(writer);
// 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();
}
@Override
@ExcludeFromCodeCoverageGeneratedReport(reason = DUMP_INFO)
public void dumpProto(ProtoOutputStream proto) {}
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() {
TimingsTraceLog t = new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE);
long startTime = SystemClock.uptimeMillis();
t.traceBegin("waitForOemServiceConnected");
waitForOemServiceConnected();
mWaitForOemServiceConnectedDuration = SystemClock.uptimeMillis() - startTime;
t.traceEnd();
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);
}
});
t.traceBegin("waitForOemServiceReady");
startTime = SystemClock.uptimeMillis();
waitForOemServiceReady();
mWaitForOemServiceReadyDuration = SystemClock.uptimeMillis() - startTime;
t.traceEnd();
}
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.
@Override
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());
}
}
}