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.remoteaccess.hal; 18 19 import android.annotation.Nullable; 20 import android.car.builtin.os.ServiceManagerHelper; 21 import android.car.builtin.os.TraceHelper; 22 import android.car.builtin.util.Slogf; 23 import android.car.builtin.util.TimingsTraceLog; 24 import android.hardware.automotive.remoteaccess.ApState; 25 import android.hardware.automotive.remoteaccess.IRemoteAccess; 26 import android.hardware.automotive.remoteaccess.IRemoteTaskCallback; 27 import android.hardware.automotive.remoteaccess.ScheduleInfo; 28 import android.os.IBinder; 29 import android.os.RemoteException; 30 import android.os.ServiceSpecificException; 31 import android.util.Log; 32 33 import com.android.car.CarLog; 34 import com.android.internal.annotations.GuardedBy; 35 import com.android.internal.annotations.VisibleForTesting; 36 37 import java.lang.ref.WeakReference; 38 import java.util.List; 39 import java.util.Objects; 40 import java.util.concurrent.atomic.AtomicBoolean; 41 42 /** 43 * Class to mediate communication between CarRemoteAccessSerevice and remote access HAL. 44 */ 45 public final class RemoteAccessHalWrapper implements IBinder.DeathRecipient { 46 47 private static final String TAG = CarLog.tagFor(RemoteAccessHalWrapper.class); 48 private static final boolean DEBUG = Slogf.isLoggable(TAG, Log.DEBUG); 49 private static final String NOT_SUPPORTED_V1_MESSAGE = 50 "is not supported by remote access HAL v1"; 51 52 private final Object mLock = new Object(); 53 // Callback which is given by the instance creator. 54 private final RemoteAccessHalCallback mRemoteAccessHalCallback; 55 // Callback which is registered to remote access HAL. 56 private final IRemoteTaskCallback mRemoteTaskCallback = new RemoteTaskCallbackImpl(this); 57 58 private final AtomicBoolean mConnecting = new AtomicBoolean(); 59 @GuardedBy("mLock") 60 private IBinder mBinder; 61 @GuardedBy("mLock") 62 private IRemoteAccess mRemoteAccessHal; 63 64 private IRemoteAccess mTestRemoteAccessHal; 65 RemoteAccessHalWrapper(RemoteAccessHalCallback callback)66 public RemoteAccessHalWrapper(RemoteAccessHalCallback callback) { 67 this(callback, /* testRemoteAccessHal= */ null); 68 } 69 70 /* For car service test. */ 71 @VisibleForTesting RemoteAccessHalWrapper(RemoteAccessHalCallback callback, IRemoteAccess testRemoteAccessHal)72 public RemoteAccessHalWrapper(RemoteAccessHalCallback callback, 73 IRemoteAccess testRemoteAccessHal) { 74 mRemoteAccessHalCallback = Objects.requireNonNull(callback, "Callback cannot be null"); 75 mTestRemoteAccessHal = testRemoteAccessHal; 76 } 77 78 /** Initializes connection to remote task HAL service. */ init()79 public void init() { 80 try { 81 connectToHal(); 82 } catch (Exception e) { 83 Slogf.wtf(TAG, e, "Cannot connect to remote access HAL"); 84 } 85 } 86 87 /** Releases the internal resources. */ release()88 public void release() { 89 IRemoteAccess remoteAccessHal; 90 synchronized (mLock) { 91 remoteAccessHal = mRemoteAccessHal; 92 mRemoteAccessHal = null; 93 } 94 try { 95 if (remoteAccessHal != null) { 96 remoteAccessHal.clearRemoteTaskCallback(); 97 } 98 } catch (RemoteException e) { 99 Slogf.w(TAG, e, "Failed to clear remote task callback"); 100 } 101 clearBinder(); 102 } 103 104 @Override binderDied()105 public void binderDied() { 106 Slogf.w(TAG, "Remote access HAL service died"); 107 synchronized (mLock) { 108 mRemoteAccessHal = null; 109 mBinder = null; 110 } 111 try { 112 connectToHal(); 113 } catch (Exception e) { 114 Slogf.wtf(TAG, e, "Cannot connect to remote access HAL"); 115 } 116 } 117 118 /** Check {@link IRemoteAccess#getVehicleId()}. */ getVehicleId()119 public String getVehicleId() { 120 IRemoteAccess remoteAccessHal = getRemoteAccessHal(); 121 try { 122 return remoteAccessHal.getVehicleId(); 123 } catch (RemoteException | RuntimeException e) { 124 throw new IllegalStateException("Failed to get vehicle ID", e); 125 } 126 } 127 128 /** Check {@link IRemoteAccess#getWakeupServiceName()}. */ getWakeupServiceName()129 public String getWakeupServiceName() { 130 IRemoteAccess remoteAccessHal = getRemoteAccessHal(); 131 try { 132 return remoteAccessHal.getWakeupServiceName(); 133 } catch (RemoteException | RuntimeException e) { 134 throw new IllegalStateException("Failed to get wakeup service name", e); 135 } 136 } 137 138 /** Check {@link IRemoteAccess#getProcessorId()}. */ getProcessorId()139 public String getProcessorId() { 140 IRemoteAccess remoteAccessHal = getRemoteAccessHal(); 141 try { 142 return remoteAccessHal.getProcessorId(); 143 } catch (RemoteException | RuntimeException e) { 144 throw new IllegalStateException("Failed to get processor ID", e); 145 } 146 } 147 148 /** Check {@link IRemoteAccess#notifyApStateChange(ApState)}. */ notifyApStateChange(boolean isReadyForRemoteTask, boolean isWakeupRequired)149 public boolean notifyApStateChange(boolean isReadyForRemoteTask, boolean isWakeupRequired) { 150 try { 151 IRemoteAccess remoteAccessHal = getRemoteAccessHal(); 152 ApState state = new ApState(); 153 state.isReadyForRemoteTask = isReadyForRemoteTask; 154 state.isWakeupRequired = isWakeupRequired; 155 remoteAccessHal.notifyApStateChange(state); 156 } catch (RemoteException | RuntimeException e) { 157 Slogf.w(TAG, e, "Failed to notify power state change: isReadyForRemoteTask=%b, " 158 + "isWakeupRequired=%b", isReadyForRemoteTask, isWakeupRequired); 159 return false; 160 } 161 return true; 162 } 163 164 /** 165 * Check {@link IRemoteAccess#isTaskScheduleSupported}. 166 * 167 * <p>Returns {@code false} if error happens. 168 */ isTaskScheduleSupported()169 public boolean isTaskScheduleSupported() { 170 try { 171 IRemoteAccess remoteAccessHal = getRemoteAccessHal(); 172 if (remoteAccessHal.getInterfaceVersion() < 2) { 173 Slogf.w(TAG, "isTaskScheduleSupported %s, default to false", 174 NOT_SUPPORTED_V1_MESSAGE); 175 return false; 176 } 177 return remoteAccessHal.isTaskScheduleSupported(); 178 } catch (RemoteException | ServiceSpecificException e) { 179 Slogf.w(TAG, e, "Failed to call isTaskScheduleSupported, default to false"); 180 return false; 181 } 182 } 183 184 /** 185 * Check {@link IRemoteAccess#scheduleTask}. 186 * 187 * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this. 188 * 189 * <p>Client should handle the thrown exception. 190 */ scheduleTask(ScheduleInfo scheduleInfo)191 public void scheduleTask(ScheduleInfo scheduleInfo) 192 throws RemoteException, ServiceSpecificException { 193 try { 194 IRemoteAccess remoteAccessHal = getRemoteAccessHal(); 195 remoteAccessHal.scheduleTask(scheduleInfo); 196 } catch (RemoteException | ServiceSpecificException e) { 197 Slogf.w(TAG, e, "Failed to call scheduleTask with scheduleInfo: %s", scheduleInfo); 198 throw e; 199 } 200 } 201 202 /** 203 * Check {@link IRemoteAccess#unscheduleTask}. 204 * 205 * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this. 206 * 207 * <p>Do nothing if error happens. 208 */ unscheduleTask(String clientId, String scheduleId)209 public void unscheduleTask(String clientId, String scheduleId) 210 throws RemoteException, ServiceSpecificException { 211 try { 212 IRemoteAccess remoteAccessHal = getRemoteAccessHal(); 213 remoteAccessHal.unscheduleTask(clientId, scheduleId); 214 } catch (RemoteException | ServiceSpecificException e) { 215 Slogf.w(TAG, e, "Failed to call unscheduleTask with clientId: %s, scheduleId: %s", 216 clientId, scheduleId); 217 throw e; 218 } 219 } 220 221 /** 222 * Check {@link IRemoteAccess#unscheduleAllTasks}. 223 * 224 * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this. 225 * 226 * <p>Do nothing if error happens. 227 */ unscheduleAllTasks(String clientId)228 public void unscheduleAllTasks(String clientId) 229 throws RemoteException, ServiceSpecificException { 230 try { 231 IRemoteAccess remoteAccessHal = getRemoteAccessHal(); 232 remoteAccessHal.unscheduleAllTasks(clientId); 233 } catch (RemoteException | ServiceSpecificException e) { 234 Slogf.w(TAG, e, "Failed to call unscheduleAllTasks with clientId: %s", clientId); 235 throw e; 236 } 237 } 238 239 /** 240 * Check {@link IRemoteAccess#isTaskScheduled}. 241 * 242 * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this. 243 * 244 * <p>Returns {@code false} if error happens. 245 */ isTaskScheduled(String clientId, String scheduleId)246 public boolean isTaskScheduled(String clientId, String scheduleId) 247 throws RemoteException, ServiceSpecificException { 248 try { 249 IRemoteAccess remoteAccessHal = getRemoteAccessHal(); 250 return remoteAccessHal.isTaskScheduled(clientId, scheduleId); 251 } catch (RemoteException | ServiceSpecificException e) { 252 Slogf.w(TAG, e, "Failed to call isTaskScheduled with clientId: %s, scheduleId: %s," 253 + " default to false", clientId, scheduleId); 254 throw e; 255 } 256 } 257 258 /** 259 * Check {@link IRemoteAccess#getAllPendingScheduledTasks}. 260 * 261 * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this. 262 * 263 * <p>Client should handle the thrown exception. 264 */ getAllPendingScheduledTasks(String clientId)265 public List<ScheduleInfo> getAllPendingScheduledTasks(String clientId) 266 throws RemoteException, ServiceSpecificException { 267 try { 268 IRemoteAccess remoteAccessHal = getRemoteAccessHal(); 269 return remoteAccessHal.getAllPendingScheduledTasks(clientId); 270 } catch (RemoteException | ServiceSpecificException e) { 271 Slogf.w(TAG, e, "Failed to call getAllPendingScheduledTasks with clientId: %s", 272 clientId); 273 throw e; 274 } 275 } 276 277 /** 278 * Check {@link IRemoteAccess#getSupportedTaskTypesForScheduling}. 279 * 280 * <p>Client should check {@link isTaskScheduleSupported} is {@code true} before calling this. 281 * 282 * <p>Client should handle the thrown exception. 283 */ getSupportedTaskTypesForScheduling()284 public int[] getSupportedTaskTypesForScheduling() 285 throws RemoteException, ServiceSpecificException { 286 try { 287 IRemoteAccess remoteAccessHal = getRemoteAccessHal(); 288 return remoteAccessHal.getSupportedTaskTypesForScheduling(); 289 } catch (RemoteException | ServiceSpecificException e) { 290 Slogf.w(TAG, e, "Failed to call getSupportedTaskTypesForScheduling"); 291 throw e; 292 } 293 } 294 295 connectToHal()296 private void connectToHal() { 297 if (!mConnecting.compareAndSet(/* expect= */ false, /* update= */ true)) { 298 Slogf.w(TAG, "Connecting to remote access HAL is in progress"); 299 return; 300 } 301 TimingsTraceLog t = new TimingsTraceLog(TAG, TraceHelper.TRACE_TAG_CAR_SERVICE); 302 303 IRemoteAccess remoteAccessHal; 304 if (mTestRemoteAccessHal != null) { 305 remoteAccessHal = mTestRemoteAccessHal; 306 } else { 307 t.traceBegin("connect-to-remote-access-hal"); 308 IBinder binder = getRemoteAccessHalService(); 309 t.traceEnd(); 310 if (binder == null) { 311 mConnecting.set(/* newValue= */ false); 312 throw new IllegalStateException("Remote access HAL not found"); 313 } 314 315 try { 316 binder.linkToDeath(this, /* flags= */ 0); 317 } catch (RemoteException e) { 318 mConnecting.set(/* newValue= */ false); 319 throw new IllegalStateException( 320 "Failed to link a death recipient to remote access HAL", e); 321 } 322 323 synchronized (mLock) { 324 if (mBinder != null) { 325 Slogf.w(TAG, "Remote access HAL is already connected"); 326 binder.unlinkToDeath(this, /* flags= */ 0); 327 mConnecting.set(/* newValue= */ false); 328 return; 329 } 330 mBinder = binder; 331 mRemoteAccessHal = IRemoteAccess.Stub.asInterface(mBinder); 332 remoteAccessHal = mRemoteAccessHal; 333 } 334 mConnecting.set(/* newValue= */ false); 335 } 336 try { 337 remoteAccessHal.setRemoteTaskCallback(mRemoteTaskCallback); 338 } catch (RemoteException e) { 339 throw new IllegalStateException("Failed to set remote task callback", e); 340 } 341 Slogf.i(TAG, "Connected to remote access HAL"); 342 } 343 clearBinder()344 private void clearBinder() { 345 synchronized (mLock) { 346 if (mBinder == null) { 347 return; 348 } 349 mBinder.unlinkToDeath(this, /* flags= */ 0); 350 mBinder = null; 351 } 352 } 353 getRemoteAccessHal()354 private IRemoteAccess getRemoteAccessHal() { 355 synchronized (mLock) { 356 if (mTestRemoteAccessHal != null) { 357 return mTestRemoteAccessHal; 358 } 359 if (mRemoteAccessHal == null) { 360 throw new IllegalStateException("Remote access HAL is not ready"); 361 } 362 return mRemoteAccessHal; 363 } 364 } 365 onRemoteTaskRequested(String clientId, byte[] data)366 private void onRemoteTaskRequested(String clientId, byte[] data) { 367 mRemoteAccessHalCallback.onRemoteTaskRequested(clientId, data); 368 } 369 370 /** 371 * Connects to remote access HAL. 372 * 373 * <p>If there are multiple service implementations, connection is made in an order of service 374 * declaration. 375 */ 376 @VisibleForTesting 377 @Nullable getRemoteAccessHalService()378 public static IBinder getRemoteAccessHalService() { 379 String[] instances = ServiceManagerHelper.getDeclaredInstances(IRemoteAccess.DESCRIPTOR); 380 if (instances == null || instances.length == 0) { 381 Slogf.e(TAG, "No remote access HAL service found"); 382 return null; 383 } 384 // If there are many remote access HAL instances, they are traversed in a declaring order. 385 for (int i = 0; i < instances.length; i++) { 386 String fqName = IRemoteAccess.DESCRIPTOR + "/" + instances[i]; 387 IBinder binder = ServiceManagerHelper.waitForDeclaredService(fqName); 388 if (binder != null) { 389 Slogf.i(TAG, "Connected to remote access HAL instance(%s)", fqName); 390 return binder; 391 } 392 } 393 return null; 394 } 395 396 private static final class RemoteTaskCallbackImpl extends IRemoteTaskCallback.Stub { 397 private final WeakReference<RemoteAccessHalWrapper> mHalWrapper; 398 RemoteTaskCallbackImpl(RemoteAccessHalWrapper halWrapper)399 RemoteTaskCallbackImpl(RemoteAccessHalWrapper halWrapper) { 400 mHalWrapper = new WeakReference<>(halWrapper); 401 } 402 403 @Override onRemoteTaskRequested(String clientId, byte[] data)404 public void onRemoteTaskRequested(String clientId, byte[] data) { 405 if (DEBUG) { 406 Slogf.d(TAG, "onRemoteTaskRequested is called: clientId = %s, data size = %d", 407 clientId, data == null ? 0 : data.length); 408 } 409 RemoteAccessHalWrapper halWrapper = mHalWrapper.get(); 410 if (halWrapper == null) { 411 Slogf.w(TAG, "RemoteAccessHalWrapper is not available: clientId = %s", clientId); 412 return; 413 } 414 halWrapper.onRemoteTaskRequested(clientId, data); 415 } 416 417 @Override getInterfaceHash()418 public String getInterfaceHash() { 419 return IRemoteTaskCallback.HASH; 420 } 421 422 @Override getInterfaceVersion()423 public int getInterfaceVersion() { 424 return IRemoteTaskCallback.VERSION; 425 } 426 } 427 } 428