1 /* 2 * Copyright (C) 2021 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 android.car.telemetry; 18 19 import android.annotation.CallbackExecutor; 20 import android.annotation.IntDef; 21 import android.annotation.NonNull; 22 import android.annotation.RequiresPermission; 23 import android.car.Car; 24 import android.car.CarManagerBase; 25 import android.car.annotation.RequiredFeature; 26 import android.os.Binder; 27 import android.os.IBinder; 28 import android.os.RemoteException; 29 import android.util.Slog; 30 31 import com.android.internal.annotations.GuardedBy; 32 33 import java.lang.annotation.Retention; 34 import java.lang.annotation.RetentionPolicy; 35 import java.lang.ref.WeakReference; 36 import java.util.concurrent.Executor; 37 38 /** 39 * Provides an application interface for interacting with the Car Telemetry Service. 40 * 41 * @hide 42 */ 43 @RequiredFeature(Car.CAR_TELEMETRY_SERVICE) 44 public final class CarTelemetryManager extends CarManagerBase { 45 46 private static final boolean DEBUG = false; 47 private static final String TAG = CarTelemetryManager.class.getSimpleName(); 48 private static final int METRICS_CONFIG_MAX_SIZE_BYTES = 10 * 1024; // 10 kb 49 50 private final CarTelemetryServiceListener mCarTelemetryServiceListener = 51 new CarTelemetryServiceListener(this); 52 private final ICarTelemetryService mService; 53 private final Object mLock = new Object(); 54 55 @GuardedBy("mLock") 56 private CarTelemetryResultsListener mResultsListener; 57 @GuardedBy("mLock") 58 private Executor mExecutor; 59 60 /** 61 * Status to indicate that MetricsConfig was added successfully. 62 */ 63 public static final int ERROR_METRICS_CONFIG_NONE = 0; 64 65 /** 66 * Status to indicate that add MetricsConfig failed because the same MetricsConfig based on the 67 * ManifestKey already exists. 68 */ 69 public static final int ERROR_METRICS_CONFIG_ALREADY_EXISTS = 1; 70 71 /** 72 * Status to indicate that add MetricsConfig failed because a newer version of the MetricsConfig 73 * exists. 74 */ 75 public static final int ERROR_METRICS_CONFIG_VERSION_TOO_OLD = 2; 76 77 /** 78 * Status to indicate that add MetricsConfig failed because CarTelemetryService is unable to 79 * parse the given byte array into a MetricsConfig. 80 */ 81 public static final int ERROR_METRICS_CONFIG_PARSE_FAILED = 3; 82 83 /** 84 * Status to indicate that add MetricsConfig failed because of failure to verify the signature 85 * of the MetricsConfig. 86 */ 87 public static final int ERROR_METRICS_CONFIG_SIGNATURE_VERIFICATION_FAILED = 4; 88 89 /** 90 * Status to indicate that add MetricsConfig failed because of a general error in cars. 91 */ 92 public static final int ERROR_METRICS_CONFIG_UNKNOWN = 5; 93 94 /** @hide */ 95 @IntDef(prefix = {"ERROR_METRICS_CONFIG_"}, value = { 96 ERROR_METRICS_CONFIG_NONE, 97 ERROR_METRICS_CONFIG_ALREADY_EXISTS, 98 ERROR_METRICS_CONFIG_VERSION_TOO_OLD, 99 ERROR_METRICS_CONFIG_PARSE_FAILED, 100 ERROR_METRICS_CONFIG_SIGNATURE_VERIFICATION_FAILED, 101 ERROR_METRICS_CONFIG_UNKNOWN 102 }) 103 @Retention(RetentionPolicy.SOURCE) 104 public @interface MetricsConfigError { 105 } 106 107 /** 108 * Application registers {@link CarTelemetryResultsListener} object to receive data from 109 * {@link com.android.car.telemetry.CarTelemetryService}. 110 * 111 * @hide 112 */ 113 public interface CarTelemetryResultsListener { 114 /** 115 * Sends script results to the client. Called by {@link CarTelemetryServiceListener}. 116 * 117 * TODO(b/184964661): Publish the documentation for the format of the results. 118 * 119 * @param key the {@link MetricsConfigKey} that the result is associated with. 120 * @param result the car telemetry result as serialized bytes. 121 */ onResult(@onNull MetricsConfigKey key, @NonNull byte[] result)122 void onResult(@NonNull MetricsConfigKey key, @NonNull byte[] result); 123 124 /** 125 * Sends script execution errors to the client. 126 * 127 * @param key the {@link MetricsConfigKey} that the error is associated with 128 * @param error the serialized car telemetry error. 129 */ onError(@onNull MetricsConfigKey key, @NonNull byte[] error)130 void onError(@NonNull MetricsConfigKey key, @NonNull byte[] error); 131 132 /** 133 * Sends the {@link #addMetricsConfig(MetricsConfigKey, byte[])} status to the client. 134 * 135 * @param key the {@link MetricsConfigKey} that the status is associated with 136 * @param statusCode See {@link MetricsConfigError}. 137 */ onAddMetricsConfigStatus(@onNull MetricsConfigKey key, @MetricsConfigError int statusCode)138 void onAddMetricsConfigStatus(@NonNull MetricsConfigKey key, 139 @MetricsConfigError int statusCode); 140 } 141 142 /** 143 * Class implementing the listener interface 144 * {@link com.android.car.ICarTelemetryServiceListener} to receive telemetry results. 145 */ 146 private static final class CarTelemetryServiceListener 147 extends ICarTelemetryServiceListener.Stub { 148 private WeakReference<CarTelemetryManager> mManager; 149 CarTelemetryServiceListener(CarTelemetryManager manager)150 private CarTelemetryServiceListener(CarTelemetryManager manager) { 151 mManager = new WeakReference<>(manager); 152 } 153 154 @Override onResult(@onNull MetricsConfigKey key, @NonNull byte[] result)155 public void onResult(@NonNull MetricsConfigKey key, @NonNull byte[] result) { 156 CarTelemetryManager manager = mManager.get(); 157 if (manager == null) { 158 return; 159 } 160 manager.onResult(key, result); 161 } 162 163 @Override onError(@onNull MetricsConfigKey key, @NonNull byte[] error)164 public void onError(@NonNull MetricsConfigKey key, @NonNull byte[] error) { 165 CarTelemetryManager manager = mManager.get(); 166 if (manager == null) { 167 return; 168 } 169 manager.onError(key, error); 170 } 171 172 @Override onAddMetricsConfigStatus(@onNull MetricsConfigKey key, @MetricsConfigError int statusCode)173 public void onAddMetricsConfigStatus(@NonNull MetricsConfigKey key, 174 @MetricsConfigError int statusCode) { 175 CarTelemetryManager manager = mManager.get(); 176 if (manager == null) { 177 return; 178 } 179 manager.onAddMetricsConfigStatus(key, statusCode); 180 } 181 } 182 onResult(MetricsConfigKey key, byte[] result)183 private void onResult(MetricsConfigKey key, byte[] result) { 184 long token = Binder.clearCallingIdentity(); 185 Executor executor = getExecutor(); 186 if (executor == null) { 187 return; 188 } 189 executor.execute(() -> { 190 CarTelemetryResultsListener listener = getResultsListener(); 191 if (listener != null) { 192 listener.onResult(key, result); 193 } 194 }); 195 Binder.restoreCallingIdentity(token); 196 } 197 onError(MetricsConfigKey key, byte[] error)198 private void onError(MetricsConfigKey key, byte[] error) { 199 long token = Binder.clearCallingIdentity(); 200 Executor executor = getExecutor(); 201 if (executor == null) { 202 return; 203 } 204 executor.execute(() -> { 205 CarTelemetryResultsListener listener = getResultsListener(); 206 if (listener != null) { 207 listener.onError(key, error); 208 } 209 }); 210 Binder.restoreCallingIdentity(token); 211 } 212 onAddMetricsConfigStatus(MetricsConfigKey key, int statusCode)213 private void onAddMetricsConfigStatus(MetricsConfigKey key, int statusCode) { 214 long token = Binder.clearCallingIdentity(); 215 Executor executor = getExecutor(); 216 if (executor == null) { 217 return; 218 } 219 executor.execute(() -> { 220 CarTelemetryResultsListener listener = getResultsListener(); 221 if (listener != null) { 222 listener.onAddMetricsConfigStatus(key, statusCode); 223 } 224 }); 225 Binder.restoreCallingIdentity(token); 226 } 227 228 /** 229 * Gets an instance of CarTelemetryManager. 230 * 231 * CarTelemetryManager manages {@link com.android.car.telemetry.CarTelemetryService} and 232 * provides APIs so the client can use the car telemetry service. 233 * 234 * There is only one client to this manager, which is OEM's cloud application. It uses the 235 * APIs to send config to and receive data from CarTelemetryService. 236 * 237 * @hide 238 */ CarTelemetryManager(Car car, IBinder service)239 public CarTelemetryManager(Car car, IBinder service) { 240 super(car); 241 mService = ICarTelemetryService.Stub.asInterface(service); 242 if (DEBUG) { 243 Slog.d(TAG, "starting car telemetry manager"); 244 } 245 } 246 247 /** @hide */ 248 @Override onCarDisconnected()249 public void onCarDisconnected() { 250 synchronized (mLock) { 251 mResultsListener = null; 252 mExecutor = null; 253 } 254 } 255 256 /** 257 * Registers a listener with {@link com.android.car.telemetry.CarTelemetryService} for client 258 * to receive script execution results. The listener must be set before invoking other APIs in 259 * this class. 260 * 261 * @param listener to received data from {@link com.android.car.telemetry.CarTelemetryService}. 262 * @throws IllegalStateException if the listener is already set. 263 * @hide 264 */ 265 @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE) setListener(@onNull @allbackExecutor Executor executor, @NonNull CarTelemetryResultsListener listener)266 public void setListener(@NonNull @CallbackExecutor Executor executor, 267 @NonNull CarTelemetryResultsListener listener) { 268 synchronized (mLock) { 269 if (mResultsListener != null) { 270 throw new IllegalStateException( 271 "Attempting to set a listener that is already set."); 272 } 273 mExecutor = executor; 274 mResultsListener = listener; 275 } 276 try { 277 mService.setListener(mCarTelemetryServiceListener); 278 } catch (RemoteException e) { 279 handleRemoteExceptionFromCarService(e); 280 } 281 } 282 283 /** 284 * Unregisters the listener from {@link com.android.car.telemetry.CarTelemetryService}. 285 * 286 * @hide 287 */ 288 @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE) clearListener()289 public void clearListener() { 290 synchronized (mLock) { 291 mResultsListener = null; 292 mExecutor = null; 293 } 294 try { 295 mService.clearListener(); 296 } catch (RemoteException e) { 297 handleRemoteExceptionFromCarService(e); 298 } 299 } 300 301 /** 302 * Sends a telemetry MetricsConfig to CarTelemetryService. The size of the MetricsConfig cannot 303 * exceed a predefined size, otherwise an exception is thrown. 304 * The {@link MetricsConfigKey} is used to uniquely identify a MetricsConfig. If a MetricsConfig 305 * of the same name already exists in {@link com.android.car.telemetry.CarTelemetryService}, 306 * the config version will be compared. If the version is strictly higher, the existing 307 * MetricsConfig will be replaced by the new one. All legacy data will be cleared if replaced. 308 * Client should use {@link #sendFinishedReports(MetricsConfigKey)} to get the result before 309 * replacing a MetricsConfig. 310 * The status of this API is sent back asynchronously via {@link CarTelemetryResultsListener}. 311 * 312 * @param key the unique key to identify the MetricsConfig. 313 * @param metricsConfig the serialized bytes of a MetricsConfig object. 314 * @throws IllegalArgumentException if the MetricsConfig size exceeds limit. 315 * @throws IllegalStateException if the listener is not set. 316 * @hide 317 */ 318 @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE) addMetricsConfig(@onNull MetricsConfigKey key, @NonNull byte[] metricsConfig)319 public void addMetricsConfig(@NonNull MetricsConfigKey key, @NonNull byte[] metricsConfig) { 320 if (getResultsListener() == null) { 321 throw new IllegalStateException("Listener must be set."); 322 } 323 if (metricsConfig.length > METRICS_CONFIG_MAX_SIZE_BYTES) { 324 throw new IllegalArgumentException("MetricsConfig size exceeds limit."); 325 } 326 try { 327 mService.addMetricsConfig(key, metricsConfig); 328 } catch (RemoteException e) { 329 handleRemoteExceptionFromCarService(e); 330 } 331 } 332 333 /** 334 * Removes a MetricsConfig from {@link com.android.car.telemetry.CarTelemetryService}. This 335 * will also remove outputs produced by the MetricsConfig. If the MetricsConfig does not exist, 336 * nothing will be removed. 337 * 338 * @param key the unique key to identify the MetricsConfig. Name and version must be exact. 339 * @throws IllegalStateException if the listener is not set. 340 * @hide 341 */ 342 @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE) removeMetricsConfig(@onNull MetricsConfigKey key)343 public void removeMetricsConfig(@NonNull MetricsConfigKey key) { 344 if (getResultsListener() == null) { 345 throw new IllegalStateException("Listener must be set."); 346 } 347 try { 348 mService.removeMetricsConfig(key); 349 } catch (RemoteException e) { 350 handleRemoteExceptionFromCarService(e); 351 } 352 } 353 354 /** 355 * Removes all MetricsConfigs from {@link com.android.car.telemetry.CarTelemetryService}. This 356 * will also remove all MetricsConfig outputs. 357 * 358 * @throws IllegalStateException if the listener is not set. 359 * @hide 360 */ 361 @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE) removeAllMetricsConfigs()362 public void removeAllMetricsConfigs() { 363 if (getResultsListener() == null) { 364 throw new IllegalStateException("Listener must be set."); 365 } 366 try { 367 mService.removeAllMetricsConfigs(); 368 } catch (RemoteException e) { 369 handleRemoteExceptionFromCarService(e); 370 } 371 } 372 373 /** 374 * Gets script execution results of a MetricsConfig as from the 375 * {@link com.android.car.telemetry.CarTelemetryService}. This API is asynchronous and the 376 * result is sent back asynchronously via the {@link CarTelemetryResultsListener}. 377 * This call is destructive. The returned results will be deleted from CarTelemetryService. 378 * 379 * @param key the unique key to identify the MetricsConfig. 380 * @throws IllegalStateException if the listener is not set. 381 * @hide 382 */ 383 @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE) sendFinishedReports(@onNull MetricsConfigKey key)384 public void sendFinishedReports(@NonNull MetricsConfigKey key) { 385 if (getResultsListener() == null) { 386 throw new IllegalStateException("Listener must be set."); 387 } 388 try { 389 mService.sendFinishedReports(key); 390 } catch (RemoteException e) { 391 handleRemoteExceptionFromCarService(e); 392 } 393 } 394 395 /** 396 * Gets all script execution results from {@link com.android.car.telemetry.CarTelemetryService} 397 * asynchronously via the {@link CarTelemetryResultsListener}. 398 * This call is destructive. The returned results will be deleted from CarTelemetryService. 399 * 400 * @throws IllegalStateException if the listener is not set. 401 * @hide 402 */ 403 @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE) sendAllFinishedReports()404 public void sendAllFinishedReports() { 405 if (getResultsListener() == null) { 406 throw new IllegalStateException("Listener must be set."); 407 } 408 try { 409 mService.sendAllFinishedReports(); 410 } catch (RemoteException e) { 411 handleRemoteExceptionFromCarService(e); 412 } 413 } 414 getResultsListener()415 private CarTelemetryResultsListener getResultsListener() { 416 synchronized (mLock) { 417 return mResultsListener; 418 } 419 } 420 getExecutor()421 private Executor getExecutor() { 422 synchronized (mLock) { 423 return mExecutor; 424 } 425 } 426 } 427