/* * 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.remoteaccess.hal; import android.annotation.Nullable; import android.car.builtin.os.ServiceManagerHelper; import android.car.builtin.os.TraceHelper; import android.car.builtin.util.Slogf; import android.car.builtin.util.TimingsTraceLog; import android.hardware.automotive.remoteaccess.ApState; import android.hardware.automotive.remoteaccess.IRemoteAccess; import android.hardware.automotive.remoteaccess.IRemoteTaskCallback; import android.hardware.automotive.remoteaccess.ScheduleInfo; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceSpecificException; import android.util.Log; import com.android.car.CarLog; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; import java.lang.ref.WeakReference; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; /** * Class to mediate communication between CarRemoteAccessSerevice and remote access HAL. */ public final class RemoteAccessHalWrapper implements IBinder.DeathRecipient { private static final String TAG = CarLog.tagFor(RemoteAccessHalWrapper.class); private static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG); private static final String NOT_SUPPORTED_V1_MESSAGE = "is not supported by remote access HAL v1"; private final Object mLock = new Object(); // Callback which is given by the instance creator. private final RemoteAccessHalCallback mRemoteAccessHalCallback; // Callback which is registered to remote access HAL. private final IRemoteTaskCallback mRemoteTaskCallback = new RemoteTaskCallbackImpl(this); private final AtomicBoolean mConnecting = new AtomicBoolean(); @GuardedBy("mLock") private IBinder mBinder; @GuardedBy("mLock") private IRemoteAccess mRemoteAccessHal; private IRemoteAccess mTestRemoteAccessHal; public RemoteAccessHalWrapper(RemoteAccessHalCallback callback) { this(callback, /* testRemoteAccessHal= */ null); } /* For car service test. */ @VisibleForTesting public RemoteAccessHalWrapper(RemoteAccessHalCallback callback, IRemoteAccess testRemoteAccessHal) { mRemoteAccessHalCallback = Objects.requireNonNull(callback, "Callback cannot be null"); mTestRemoteAccessHal = testRemoteAccessHal; } /** Initializes connection to remote task HAL service. */ public void init() { try { connectToHal(); } catch (Exception e) { Slogf.wtf(TAG, e, "Cannot connect to remote access HAL"); } } /** Releases the internal resources. */ public void release() { IRemoteAccess remoteAccessHal; synchronized (mLock) { remoteAccessHal = mRemoteAccessHal; mRemoteAccessHal = null; } try { if (remoteAccessHal != null) { remoteAccessHal.clearRemoteTaskCallback(); } } catch (RemoteException e) { Slogf.w(TAG, e, "Failed to clear remote task callback"); } clearBinder(); } @Override public void binderDied() { Slogf.w(TAG, "Remote access HAL service died"); synchronized (mLock) { mRemoteAccessHal = null; mBinder = null; } try { connectToHal(); } catch (Exception e) { Slogf.wtf(TAG, e, "Cannot connect to remote access HAL"); } } /** Check {@link IRemoteAccess#getVehicleId()}. */ public String getVehicleId() { IRemoteAccess remoteAccessHal = getRemoteAccessHal(); try { return remoteAccessHal.getVehicleId(); } catch (RemoteException | RuntimeException e) { throw new IllegalStateException("Failed to get vehicle ID", e); } } /** Check {@link IRemoteAccess#getWakeupServiceName()}. */ public String getWakeupServiceName() { IRemoteAccess remoteAccessHal = getRemoteAccessHal(); try { return remoteAccessHal.getWakeupServiceName(); } catch (RemoteException | RuntimeException e) { throw new IllegalStateException("Failed to get wakeup service name", e); } } /** Check {@link IRemoteAccess#getProcessorId()}. */ public String getProcessorId() { IRemoteAccess remoteAccessHal = getRemoteAccessHal(); try { return remoteAccessHal.getProcessorId(); } catch (RemoteException | RuntimeException e) { throw new IllegalStateException("Failed to get processor ID", e); } } /** Check {@link IRemoteAccess#notifyApStateChange(ApState)}. */ public boolean notifyApStateChange(boolean isReadyForRemoteTask, boolean isWakeupRequired) { try { IRemoteAccess remoteAccessHal = getRemoteAccessHal(); ApState state = new ApState(); state.isReadyForRemoteTask = isReadyForRemoteTask; state.isWakeupRequired = isWakeupRequired; remoteAccessHal.notifyApStateChange(state); } catch (RemoteException | RuntimeException e) { Slogf.w(TAG, e, "Failed to notify power state change: isReadyForRemoteTask=%b, " + "isWakeupRequired=%b", isReadyForRemoteTask, isWakeupRequired); return false; } return true; } /** * Check {@link IRemoteAccess#isTaskScheduleSupported}. * *
Returns {@code false} if error happens. */ public boolean isTaskScheduleSupported() { try { IRemoteAccess remoteAccessHal = getRemoteAccessHal(); if (remoteAccessHal.getInterfaceVersion() < 2) { Slogf.w(TAG, "isTaskScheduleSupported %s, default to false", NOT_SUPPORTED_V1_MESSAGE); return false; } return remoteAccessHal.isTaskScheduleSupported(); } catch (RemoteException | ServiceSpecificException e) { Slogf.w(TAG, e, "Failed to call isTaskScheduleSupported, default to false"); return false; } } /** * Check {@link IRemoteAccess#scheduleTask}. * *
Client should check {@link isTaskScheduleSupported} is {@code true} before calling this. * *
Client should handle the thrown exception. */ public void scheduleTask(ScheduleInfo scheduleInfo) throws RemoteException, ServiceSpecificException { try { IRemoteAccess remoteAccessHal = getRemoteAccessHal(); remoteAccessHal.scheduleTask(scheduleInfo); } catch (RemoteException | ServiceSpecificException e) { Slogf.w(TAG, e, "Failed to call scheduleTask with scheduleInfo: %s", scheduleInfo); throw e; } } /** * Check {@link IRemoteAccess#unscheduleTask}. * *
Client should check {@link isTaskScheduleSupported} is {@code true} before calling this. * *
Do nothing if error happens. */ public void unscheduleTask(String clientId, String scheduleId) throws RemoteException, ServiceSpecificException { try { IRemoteAccess remoteAccessHal = getRemoteAccessHal(); remoteAccessHal.unscheduleTask(clientId, scheduleId); } catch (RemoteException | ServiceSpecificException e) { Slogf.w(TAG, e, "Failed to call unscheduleTask with clientId: %s, scheduleId: %s", clientId, scheduleId); throw e; } } /** * Check {@link IRemoteAccess#unscheduleAllTasks}. * *
Client should check {@link isTaskScheduleSupported} is {@code true} before calling this. * *
Do nothing if error happens. */ public void unscheduleAllTasks(String clientId) throws RemoteException, ServiceSpecificException { try { IRemoteAccess remoteAccessHal = getRemoteAccessHal(); remoteAccessHal.unscheduleAllTasks(clientId); } catch (RemoteException | ServiceSpecificException e) { Slogf.w(TAG, e, "Failed to call unscheduleAllTasks with clientId: %s", clientId); throw e; } } /** * Check {@link IRemoteAccess#isTaskScheduled}. * *
Client should check {@link isTaskScheduleSupported} is {@code true} before calling this. * *
Returns {@code false} if error happens. */ public boolean isTaskScheduled(String clientId, String scheduleId) throws RemoteException, ServiceSpecificException { try { IRemoteAccess remoteAccessHal = getRemoteAccessHal(); return remoteAccessHal.isTaskScheduled(clientId, scheduleId); } catch (RemoteException | ServiceSpecificException e) { Slogf.w(TAG, e, "Failed to call isTaskScheduled with clientId: %s, scheduleId: %s," + " default to false", clientId, scheduleId); throw e; } } /** * Check {@link IRemoteAccess#getAllPendingScheduledTasks}. * *
Client should check {@link isTaskScheduleSupported} is {@code true} before calling this. * *
Client should handle the thrown exception.
*/
public List Client should check {@link isTaskScheduleSupported} is {@code true} before calling this.
*
* Client should handle the thrown exception.
*/
public int[] getSupportedTaskTypesForScheduling()
throws RemoteException, ServiceSpecificException {
try {
IRemoteAccess remoteAccessHal = getRemoteAccessHal();
return remoteAccessHal.getSupportedTaskTypesForScheduling();
} catch (RemoteException | ServiceSpecificException e) {
Slogf.w(TAG, e, "Failed to call getSupportedTaskTypesForScheduling");
throw e;
}
}
private void connectToHal() {
if (!mConnecting.compareAndSet(/* expect= */ false, /* update= */ true)) {
Slogf.w(TAG, "Connecting to remote access HAL is in progress");
return;
}
TimingsTraceLog t = new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE);
IRemoteAccess remoteAccessHal;
if (mTestRemoteAccessHal != null) {
remoteAccessHal = mTestRemoteAccessHal;
} else {
t.traceBegin("connect-to-remote-access-hal");
IBinder binder = getRemoteAccessHalService();
t.traceEnd();
if (binder == null) {
mConnecting.set(/* newValue= */ false);
throw new IllegalStateException("Remote access HAL not found");
}
try {
binder.linkToDeath(this, /* flags= */ 0);
} catch (RemoteException e) {
mConnecting.set(/* newValue= */ false);
throw new IllegalStateException(
"Failed to link a death recipient to remote access HAL", e);
}
synchronized (mLock) {
if (mBinder != null) {
Slogf.w(TAG, "Remote access HAL is already connected");
binder.unlinkToDeath(this, /* flags= */ 0);
mConnecting.set(/* newValue= */ false);
return;
}
mBinder = binder;
mRemoteAccessHal = IRemoteAccess.Stub.asInterface(mBinder);
remoteAccessHal = mRemoteAccessHal;
}
mConnecting.set(/* newValue= */ false);
}
try {
remoteAccessHal.setRemoteTaskCallback(mRemoteTaskCallback);
} catch (RemoteException e) {
throw new IllegalStateException("Failed to set remote task callback", e);
}
Slogf.i(TAG, "Connected to remote access HAL");
}
private void clearBinder() {
synchronized (mLock) {
if (mBinder == null) {
return;
}
mBinder.unlinkToDeath(this, /* flags= */ 0);
mBinder = null;
}
}
private IRemoteAccess getRemoteAccessHal() {
synchronized (mLock) {
if (mTestRemoteAccessHal != null) {
return mTestRemoteAccessHal;
}
if (mRemoteAccessHal == null) {
throw new IllegalStateException("Remote access HAL is not ready");
}
return mRemoteAccessHal;
}
}
private void onRemoteTaskRequested(String clientId, byte[] data) {
mRemoteAccessHalCallback.onRemoteTaskRequested(clientId, data);
}
/**
* Connects to remote access HAL.
*
* If there are multiple service implementations, connection is made in an order of service
* declaration.
*/
@VisibleForTesting
@Nullable
public static IBinder getRemoteAccessHalService() {
String[] instances = ServiceManagerHelper.getDeclaredInstances(IRemoteAccess.DESCRIPTOR);
if (instances == null || instances.length == 0) {
Slogf.e(TAG, "No remote access HAL service found");
return null;
}
// If there are many remote access HAL instances, they are traversed in a declaring order.
for (int i = 0; i < instances.length; i++) {
String fqName = IRemoteAccess.DESCRIPTOR + "/" + instances[i];
IBinder binder = ServiceManagerHelper.waitForDeclaredService(fqName);
if (binder != null) {
Slogf.i(TAG, "Connected to remote access HAL instance(%s)", fqName);
return binder;
}
}
return null;
}
private static final class RemoteTaskCallbackImpl extends IRemoteTaskCallback.Stub {
private final WeakReference