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