• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Nullable;
23 import android.annotation.RequiresPermission;
24 import android.annotation.SystemApi;
25 import android.annotation.TestApi;
26 import android.car.Car;
27 import android.car.CarManagerBase;
28 import android.car.annotation.AddedInOrBefore;
29 import android.car.annotation.RequiredFeature;
30 import android.car.builtin.util.Slogf;
31 import android.os.Bundle;
32 import android.os.IBinder;
33 import android.os.ParcelFileDescriptor;
34 import android.os.PersistableBundle;
35 import android.os.RemoteException;
36 import android.os.ResultReceiver;
37 
38 import libcore.io.IoUtils;
39 
40 import java.io.ByteArrayInputStream;
41 import java.io.DataInputStream;
42 import java.io.EOFException;
43 import java.io.IOException;
44 import java.lang.annotation.Retention;
45 import java.lang.annotation.RetentionPolicy;
46 import java.lang.ref.WeakReference;
47 import java.util.ArrayList;
48 import java.util.List;
49 import java.util.Objects;
50 import java.util.concurrent.Executor;
51 import java.util.concurrent.atomic.AtomicReference;
52 
53 /**
54  * Provides an application interface for interacting with the Car Telemetry Service.
55  *
56  * @hide
57  */
58 @RequiredFeature(Car.CAR_TELEMETRY_SERVICE)
59 @SystemApi
60 @TestApi
61 public final class CarTelemetryManager extends CarManagerBase {
62 
63     private static final boolean DEBUG = false;
64     private static final String TAG = CarTelemetryManager.class.getSimpleName();
65     private static final int METRICS_CONFIG_MAX_SIZE_BYTES = 10 * 1024; // 10 kb
66 
67     private final ICarTelemetryService mService;
68     private final AtomicReference<Executor> mReportReadyListenerExecutor;
69     private final AtomicReference<ReportReadyListener> mReportReadyListener;
70 
71     /** Status to indicate that MetricsConfig was added successfully. */
72     @AddedInOrBefore(majorVersion = 33)
73     public static final int STATUS_ADD_METRICS_CONFIG_SUCCEEDED = 0;
74 
75     /**
76      * Status to indicate that add MetricsConfig failed because the same MetricsConfig of the same
77      * name and version already exists.
78      */
79     @AddedInOrBefore(majorVersion = 33)
80     public static final int STATUS_ADD_METRICS_CONFIG_ALREADY_EXISTS = 1;
81 
82     /**
83      * Status to indicate that add MetricsConfig failed because a newer version of the MetricsConfig
84      * exists.
85      */
86     @AddedInOrBefore(majorVersion = 33)
87     public static final int STATUS_ADD_METRICS_CONFIG_VERSION_TOO_OLD = 2;
88 
89     /**
90      * Status to indicate that add MetricsConfig failed because CarTelemetryService is unable to
91      * parse the given byte array into a MetricsConfig.
92      */
93     @AddedInOrBefore(majorVersion = 33)
94     public static final int STATUS_ADD_METRICS_CONFIG_PARSE_FAILED = 3;
95 
96     /**
97      * Status to indicate that add MetricsConfig failed because of failure to verify the signature
98      * of the MetricsConfig.
99      */
100     @AddedInOrBefore(majorVersion = 33)
101     public static final int STATUS_ADD_METRICS_CONFIG_SIGNATURE_VERIFICATION_FAILED = 4;
102 
103     /** Status to indicate that add MetricsConfig failed because of a general error in cars. */
104     @AddedInOrBefore(majorVersion = 33)
105     public static final int STATUS_ADD_METRICS_CONFIG_UNKNOWN = 5;
106 
107     /** @hide */
108     @IntDef(
109             prefix = {"STATUS_ADD_METRICS_CONFIG_"},
110             value = {
111                 STATUS_ADD_METRICS_CONFIG_SUCCEEDED,
112                 STATUS_ADD_METRICS_CONFIG_ALREADY_EXISTS,
113                 STATUS_ADD_METRICS_CONFIG_VERSION_TOO_OLD,
114                 STATUS_ADD_METRICS_CONFIG_PARSE_FAILED,
115                 STATUS_ADD_METRICS_CONFIG_SIGNATURE_VERIFICATION_FAILED,
116                 STATUS_ADD_METRICS_CONFIG_UNKNOWN
117             })
118     @Retention(RetentionPolicy.SOURCE)
119     public @interface MetricsConfigStatus {}
120 
121     /** Status to indicate that MetricsConfig produced a report. */
122     @AddedInOrBefore(majorVersion = 33)
123     public static final int STATUS_GET_METRICS_CONFIG_FINISHED = 0;
124 
125     /**
126      * Status to indicate a MetricsConfig exists but has produced neither interim/final report nor
127      * runtime execution errors.
128      */
129     @AddedInOrBefore(majorVersion = 33)
130     public static final int STATUS_GET_METRICS_CONFIG_PENDING = 1;
131 
132     /** Status to indicate a MetricsConfig exists and produced interim results. */
133     @AddedInOrBefore(majorVersion = 33)
134     public static final int STATUS_GET_METRICS_CONFIG_INTERIM_RESULTS = 2;
135 
136     /** Status to indicate the MetricsConfig produced a runtime execution error. */
137     @AddedInOrBefore(majorVersion = 33)
138     public static final int STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR = 3;
139 
140     /** Status to indicate a MetricsConfig does not exist and hence no report can be found. */
141     @AddedInOrBefore(majorVersion = 33)
142     public static final int STATUS_GET_METRICS_CONFIG_DOES_NOT_EXIST = 4;
143 
144     /** @hide */
145     @IntDef(
146             prefix = {"STATUS_GET_METRICS_CONFIG_"},
147             value = {
148                 STATUS_GET_METRICS_CONFIG_FINISHED,
149                 STATUS_GET_METRICS_CONFIG_PENDING,
150                 STATUS_GET_METRICS_CONFIG_INTERIM_RESULTS,
151                 STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR,
152                 STATUS_GET_METRICS_CONFIG_DOES_NOT_EXIST
153             })
154     @Retention(RetentionPolicy.SOURCE)
155     public @interface MetricsReportStatus {}
156 
157     /**
158      * Application must pass a {@link AddMetricsConfigCallback} to use {@link
159      * #addMetricsConfig(String, byte[], Executor, AddMetricsConfigCallback)}
160      *
161      * @hide
162      */
163     @SystemApi
164     @TestApi
165     public interface AddMetricsConfigCallback {
166         /**
167          * Sends the {@link #addMetricsConfig(String, byte[], Executor, AddMetricsConfigCallback)}
168          * status to the client.
169          *
170          * @param metricsConfigName name of the MetricsConfig that the status is associated with.
171          * @param statusCode See {@link MetricsConfigStatus}.
172          */
173         @AddedInOrBefore(majorVersion = 33)
onAddMetricsConfigStatus( @onNull String metricsConfigName, @MetricsConfigStatus int statusCode)174         void onAddMetricsConfigStatus(
175                 @NonNull String metricsConfigName, @MetricsConfigStatus int statusCode);
176     }
177 
178     /**
179      * Application must pass a {@link MetricsReportCallback} object to receive finished reports from
180      * {@link #getFinishedReport(String, Executor, MetricsReportCallback)} and {@link
181      * #getAllFinishedReports(Executor, MetricsReportCallback)}.
182      *
183      * @hide
184      */
185     @SystemApi
186     @TestApi
187     public interface MetricsReportCallback {
188         /**
189          * Provides the metrics report associated with metricsConfigName. If there is a metrics
190          * report, it provides the metrics report. If the metrics report calculation failed due to a
191          * runtime error during the execution of reporting script, it provides the runtime error in
192          * the error parameter. The status parameter provides more information on the state of the
193          * metrics report.
194          *
195          * TODO(b/184964661): Publish the documentation for the format of the finished reports.
196          *
197          * @param metricsConfigName name of the MetricsConfig that the report is associated with.
198          * @param report the car telemetry report. Null if there is no report.
199          * @param telemetryError the serialized telemetry metrics configuration runtime execution
200          *     error.
201          * @param status of the metrics report. See {@link MetricsReportStatus}.
202          */
203         @AddedInOrBefore(majorVersion = 33)
onResult( @onNull String metricsConfigName, @Nullable PersistableBundle report, @Nullable byte[] telemetryError, @MetricsReportStatus int status)204         void onResult(
205                 @NonNull String metricsConfigName,
206                 @Nullable PersistableBundle report,
207                 @Nullable byte[] telemetryError,
208                 @MetricsReportStatus int status);
209     }
210 
211     /**
212      * Application can optionally use {@link #setReportReadyListener(Executor, ReportReadyListener)}
213      * to receive report ready notifications. Upon receiving the notification, client can use
214      * {@link #getFinishedReport(String, Executor, MetricsReportCallback)} on the received
215      * metricsConfigName.
216      *
217      * @hide
218      */
219     @SystemApi
220     @TestApi
221     public interface ReportReadyListener {
222         /**
223          * Sends the report ready notification to the client.
224          *
225          * @param metricsConfigName name of the MetricsConfig whose report is ready.
226          */
227         @AddedInOrBefore(majorVersion = 33)
onReady(@onNull String metricsConfigName)228         void onReady(@NonNull String metricsConfigName);
229     }
230 
231     /**
232      * Gets an instance of CarTelemetryManager.
233      *
234      * <p>CarTelemetryManager manages {@link com.android.car.telemetry.CarTelemetryService} and
235      * provides APIs so the client can use the car telemetry service.
236      *
237      * <p>There is only one client to this manager, which is OEM's cloud application. It uses the
238      * APIs to send config to and receive data from CarTelemetryService.
239      *
240      * @hide
241      */
CarTelemetryManager(Car car, IBinder service)242     public CarTelemetryManager(Car car, IBinder service) {
243         super(car);
244         mService = ICarTelemetryService.Stub.asInterface(service);
245         mReportReadyListenerExecutor = new AtomicReference<>(null);
246         mReportReadyListener = new AtomicReference<>(null);
247         if (DEBUG) {
248             Slogf.d(TAG, "starting car telemetry manager");
249         }
250     }
251 
252     /** @hide */
253     @Override
254     @AddedInOrBefore(majorVersion = 33)
onCarDisconnected()255     public void onCarDisconnected() {}
256 
257     /**
258      * Adds a MetricsConfig to CarTelemetryService. The size of the MetricsConfig cannot exceed a
259      * {@link #METRICS_CONFIG_MAX_SIZE_BYTES}, otherwise an exception is thrown.
260      *
261      * <p>The MetricsConfig will be uniquely identified by its name and version. If a MetricsConfig
262      * of the same name already exists in {@link com.android.car.telemetry.CarTelemetryService}, the
263      * config version will be compared. If the version is strictly higher, the existing
264      * MetricsConfig will be replaced by the new one. All legacy data will be cleared if replaced.
265      *
266      * <p>Client should use {@link #getFinishedReport(String, Executor, MetricsReportCallback)} to
267      * get the report before replacing a MetricsConfig.
268      *
269      * <p>The status of this API is sent back asynchronously via {@link AddMetricsConfigCallback}.
270      *
271      * @param metricsConfigName name of the MetricsConfig, must match {@link
272      *     TelemetryProto.MetricsConfig#getName()}.
273      * @param metricsConfig the serialized bytes of a MetricsConfig object.
274      * @param executor The {@link Executor} on which the callback will be invoked.
275      * @param callback A callback for receiving addMetricsConfig status codes.
276      * @throws IllegalArgumentException if the MetricsConfig size exceeds limit.
277      * @hide
278      */
279     @SystemApi
280     @TestApi
281     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
282     @AddedInOrBefore(majorVersion = 33)
addMetricsConfig( @onNull String metricsConfigName, @NonNull byte[] metricsConfig, @CallbackExecutor @NonNull Executor executor, @NonNull AddMetricsConfigCallback callback)283     public void addMetricsConfig(
284             @NonNull String metricsConfigName,
285             @NonNull byte[] metricsConfig,
286             @CallbackExecutor @NonNull Executor executor,
287             @NonNull AddMetricsConfigCallback callback) {
288         if (metricsConfig.length > METRICS_CONFIG_MAX_SIZE_BYTES) {
289             throw new IllegalArgumentException("MetricsConfig size exceeds limit.");
290         }
291         try {
292             mService.addMetricsConfig(metricsConfigName, metricsConfig, new ResultReceiver(null) {
293                 @Override
294                 protected void onReceiveResult(int resultCode, Bundle resultData) {
295                     executor.execute(() ->
296                             callback.onAddMetricsConfigStatus(metricsConfigName, resultCode));
297                 }
298             });
299         } catch (RemoteException e) {
300             handleRemoteExceptionFromCarService(e);
301         }
302     }
303 
304     /**
305      * Removes a MetricsConfig from {@link com.android.car.telemetry.CarTelemetryService}. This will
306      * also remove outputs produced by the MetricsConfig. If the MetricsConfig does not exist,
307      * nothing will be removed.
308      *
309      * @param metricsConfigName that identify the MetricsConfig.
310      * @hide
311      */
312     @SystemApi
313     @TestApi
314     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
315     @AddedInOrBefore(majorVersion = 33)
removeMetricsConfig(@onNull String metricsConfigName)316     public void removeMetricsConfig(@NonNull String metricsConfigName) {
317         try {
318             mService.removeMetricsConfig(metricsConfigName);
319         } catch (RemoteException e) {
320             handleRemoteExceptionFromCarService(e);
321         }
322     }
323 
324     /**
325      * Removes all MetricsConfigs from {@link com.android.car.telemetry.CarTelemetryService}. This
326      * will also remove all MetricsConfig outputs.
327      *
328      * @hide
329      */
330     @SystemApi
331     @TestApi
332     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
333     @AddedInOrBefore(majorVersion = 33)
removeAllMetricsConfigs()334     public void removeAllMetricsConfigs() {
335         try {
336             mService.removeAllMetricsConfigs();
337         } catch (RemoteException e) {
338             handleRemoteExceptionFromCarService(e);
339         }
340     }
341 
342     /**
343      * Gets script execution reports of a MetricsConfig as from the {@link
344      * com.android.car.telemetry.CarTelemetryService}. This API is asynchronous and the report is
345      * sent back asynchronously via the {@link MetricsReportCallback}. This call is destructive. The
346      * returned report will be deleted from CarTelemetryService.
347      *
348      * @param metricsConfigName to identify the MetricsConfig.
349      * @param executor The {@link Executor} on which the callback will be invoked.
350      * @param callback A callback for receiving finished reports.
351      * @hide
352      */
353     @SystemApi
354     @TestApi
355     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
356     @AddedInOrBefore(majorVersion = 33)
getFinishedReport( @onNull String metricsConfigName, @CallbackExecutor @NonNull Executor executor, @NonNull MetricsReportCallback callback)357     public void getFinishedReport(
358             @NonNull String metricsConfigName,
359             @CallbackExecutor @NonNull Executor executor,
360             @NonNull MetricsReportCallback callback) {
361         try {
362             mService.getFinishedReport(
363                     metricsConfigName, new CarTelemetryReportListenerImpl(executor, callback));
364         } catch (RemoteException e) {
365             handleRemoteExceptionFromCarService(e);
366         }
367     }
368 
369     /**
370      * Gets all script execution reports from {@link com.android.car.telemetry.CarTelemetryService}
371      * asynchronously via the {@link MetricsReportCallback}. The callback will be invoked multiple
372      * times if there are multiple reports. This call is destructive. The returned reports will be
373      * deleted from CarTelemetryService.
374      *
375      * @param executor The {@link Executor} on which the callback will be invoked.
376      * @param callback A callback for receiving finished reports.
377      * @hide
378      */
379     @SystemApi
380     @TestApi
381     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
382     @AddedInOrBefore(majorVersion = 33)
getAllFinishedReports( @allbackExecutor @onNull Executor executor, @NonNull MetricsReportCallback callback)383     public void getAllFinishedReports(
384             @CallbackExecutor @NonNull Executor executor, @NonNull MetricsReportCallback callback) {
385         try {
386             mService.getAllFinishedReports(new CarTelemetryReportListenerImpl(executor, callback));
387         } catch (RemoteException e) {
388             handleRemoteExceptionFromCarService(e);
389         }
390     }
391 
392     /**
393      * Registers a listener to receive report ready notifications. This is an optional feature that
394      * helps clients decide when is a good time to call {@link
395      * #getFinishedReport(String, Executor, MetricsReportCallback)}.
396      *
397      * <p>When a listener is set, it will receive notifications for reports or errors that are
398      * already produced before the listener is registered.
399      *
400      * <p>Clients who do not register a listener should use {@link
401      * #getFinishedReport(String, Executor, MetricsReportCallback)} periodically to check for
402      * report.
403      *
404      * @param executor The {@link Executor} on which the callback will be invoked.
405      * @param listener The listener to receive report ready notifications.
406      * @throws IllegalStateException if the listener is already set.
407      * @hide
408      */
409     @SystemApi
410     @TestApi
411     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
412     @AddedInOrBefore(majorVersion = 33)
setReportReadyListener( @allbackExecutor @onNull Executor executor, @NonNull ReportReadyListener listener)413     public void setReportReadyListener(
414             @CallbackExecutor @NonNull Executor executor, @NonNull ReportReadyListener listener) {
415         if (mReportReadyListener.get() != null) {
416             throw new IllegalStateException("ReportReadyListener is already set.");
417         }
418         mReportReadyListenerExecutor.set(executor);
419         mReportReadyListener.set(listener);
420         try {
421             mService.setReportReadyListener(new CarTelemetryReportReadyListenerImpl(this));
422         } catch (RemoteException e) {
423             handleRemoteExceptionFromCarService(e);
424         }
425     }
426 
427     /**
428      * Clears the listener for receiving telemetry report ready notifications.
429      *
430      * @hide
431      */
432     @SystemApi
433     @TestApi
434     @RequiresPermission(Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE)
435     @AddedInOrBefore(majorVersion = 33)
clearReportReadyListener()436     public void clearReportReadyListener() {
437         mReportReadyListenerExecutor.set(null);
438         mReportReadyListener.set(null);
439         try {
440             mService.clearReportReadyListener();
441         } catch (RemoteException e) {
442             handleRemoteExceptionFromCarService(e);
443         }
444     }
445 
446     /** Listens for report ready notifications.
447      * Atomic variables (mReportReadyListenerExecutor and mReportReadyListener)
448      * can be accessed from different threads simultaneously.
449      * Both of these variables can be set to null by {@link #clearReportReadyListener()}
450      * and simultaneously {@link #onReady(String)} may try to access the null value.
451      * So, to avoid possible NullPointerException due to this race condition,
452      * these atomic variables are needed to be retrieved in local variables
453      * and verified those are not null before accessing. */
454     private static final class CarTelemetryReportReadyListenerImpl
455             extends ICarTelemetryReportReadyListener.Stub {
456         private final WeakReference<CarTelemetryManager> mManager;
457 
CarTelemetryReportReadyListenerImpl(CarTelemetryManager manager)458         private CarTelemetryReportReadyListenerImpl(CarTelemetryManager manager) {
459             mManager = new WeakReference<>(manager);
460         }
461 
462         @Override
onReady(@onNull String metricsConfigName)463         public void onReady(@NonNull String metricsConfigName) {
464             CarTelemetryManager manager = mManager.get();
465             if (manager == null) {
466                 return;
467             }
468             Executor executor = manager.mReportReadyListenerExecutor.get();
469             if (executor == null) {
470                 return;
471             }
472             ReportReadyListener reportReadyListener = manager.mReportReadyListener.get();
473             if (reportReadyListener == null) {
474                 return;
475             }
476             executor.execute(
477                     () -> reportReadyListener.onReady(metricsConfigName));
478         }
479     }
480 
481     /**
482      * Receives responses to {@link #getFinishedReport(String, Executor, MetricsReportCallback)}
483      * requests.
484      */
485     private static final class CarTelemetryReportListenerImpl
486             extends ICarTelemetryReportListener.Stub {
487 
488         private final Executor mExecutor;
489         private final MetricsReportCallback mMetricsReportCallback;
490 
CarTelemetryReportListenerImpl(Executor executor, MetricsReportCallback callback)491         private CarTelemetryReportListenerImpl(Executor executor, MetricsReportCallback callback) {
492             Objects.requireNonNull(executor);
493             Objects.requireNonNull(callback);
494             mExecutor = executor;
495             mMetricsReportCallback = callback;
496         }
497 
498         @Override
onResult( @onNull String metricsConfigName, @Nullable ParcelFileDescriptor reportFileDescriptor, @Nullable byte[] telemetryError, @MetricsReportStatus int status)499         public void onResult(
500                 @NonNull String metricsConfigName,
501                 @Nullable ParcelFileDescriptor reportFileDescriptor,
502                 @Nullable byte[] telemetryError,
503                 @MetricsReportStatus int status) {
504             // return early if no need to stream reports
505             if (reportFileDescriptor == null) {
506                 mExecutor.execute(() -> mMetricsReportCallback.onResult(
507                         metricsConfigName, null, telemetryError, status));
508                 return;
509             }
510             // getting to this line means the reportFileDescriptor is non-null
511             ParcelFileDescriptor dup = null;
512             try {
513                 dup = reportFileDescriptor.dup();
514             } catch (IOException e) {
515                 Slogf.w(TAG, "Could not dup ParcelFileDescriptor", e);
516                 return;
517             } finally {
518                 IoUtils.closeQuietly(reportFileDescriptor);
519             }
520             final ParcelFileDescriptor readFd = dup;
521             mExecutor.execute(() -> {
522                 // read PersistableBundles from the pipe, this method will also close the fd
523                 List<PersistableBundle> reports = parseReports(readFd);
524                 // if a readFd is non-null, CarTelemetryService will write at least 1 report
525                 // to the pipe, so something must have gone wrong to get 0 report
526                 if (reports.size() == 0) {
527                     mMetricsReportCallback.onResult(metricsConfigName, null, null,
528                             STATUS_GET_METRICS_CONFIG_RUNTIME_ERROR);
529                     return;
530                 }
531                 for (PersistableBundle report : reports) {
532                     mMetricsReportCallback
533                             .onResult(metricsConfigName, report, telemetryError, status);
534                 }
535             });
536         }
537 
538         /** Helper method to parse reports (PersistableBundles) from the file descriptor. */
parseReports(ParcelFileDescriptor reportFileDescriptor)539         private List<PersistableBundle> parseReports(ParcelFileDescriptor reportFileDescriptor) {
540             List<PersistableBundle> reports = new ArrayList<>();
541             try (DataInputStream dataInputStream = new DataInputStream(
542                     new ParcelFileDescriptor.AutoCloseInputStream(reportFileDescriptor))) {
543                 while (true) {
544                     // read integer which tells us how many bytes to read for the PersistableBundle
545                     int size = dataInputStream.readInt();
546                     byte[] bundleBytes = dataInputStream.readNBytes(size);
547                     if (bundleBytes.length != size) {
548                         Slogf.e(TAG, "Expected to read " + size
549                                 + " bytes from the pipe, but only read "
550                                 + bundleBytes.length + " bytes");
551                         break;
552                     }
553                     PersistableBundle report = PersistableBundle.readFromStream(
554                             new ByteArrayInputStream(bundleBytes));
555                     reports.add(report);
556                 }
557             } catch (EOFException e) {
558                 // a graceful exit from the while true loop, thrown by DataInputStream#readInt(),
559                 // every successful parse should naturally reach this line
560                 if (DEBUG) {
561                     Slogf.d(TAG, "parseReports reached end of file");
562                 }
563             } catch (IOException e) {
564                 Slogf.e(TAG, "Failed to read metrics reports from pipe", e);
565             }
566             return reports;
567         }
568     }
569 }
570