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 package com.android.car.telemetry;
17 
18 import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_NONE;
19 import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_PARSE_FAILED;
20 
21 import android.annotation.NonNull;
22 import android.app.StatsManager;
23 import android.car.Car;
24 import android.car.telemetry.ICarTelemetryService;
25 import android.car.telemetry.ICarTelemetryServiceListener;
26 import android.car.telemetry.MetricsConfigKey;
27 import android.content.Context;
28 import android.os.Handler;
29 import android.os.HandlerThread;
30 import android.os.PersistableBundle;
31 import android.os.RemoteException;
32 import android.util.IndentingPrintWriter;
33 
34 import com.android.car.CarLocalServices;
35 import com.android.car.CarLog;
36 import com.android.car.CarPropertyService;
37 import com.android.car.CarServiceBase;
38 import com.android.car.CarServiceUtils;
39 import com.android.car.systeminterface.SystemInterface;
40 import com.android.car.telemetry.databroker.DataBroker;
41 import com.android.car.telemetry.databroker.DataBrokerController;
42 import com.android.car.telemetry.databroker.DataBrokerImpl;
43 import com.android.car.telemetry.publisher.PublisherFactory;
44 import com.android.car.telemetry.publisher.StatsManagerImpl;
45 import com.android.car.telemetry.publisher.StatsManagerProxy;
46 import com.android.car.telemetry.systemmonitor.SystemMonitor;
47 import com.android.internal.annotations.VisibleForTesting;
48 import com.android.server.utils.Slogf;
49 
50 import com.google.protobuf.InvalidProtocolBufferException;
51 
52 import java.io.ByteArrayOutputStream;
53 import java.io.File;
54 import java.io.IOException;
55 
56 /**
57  * CarTelemetryService manages OEM telemetry collection, processing and communication
58  * with a data upload service.
59  */
60 public class CarTelemetryService extends ICarTelemetryService.Stub implements CarServiceBase {
61 
62     private static final boolean DEBUG = false;
63     private static final String PUBLISHER_DIR = "publisher";
64     public static final String TELEMETRY_DIR = "telemetry";
65 
66     private final Context mContext;
67     private final CarPropertyService mCarPropertyService;
68     private final HandlerThread mTelemetryThread = CarServiceUtils.getHandlerThread(
69             CarTelemetryService.class.getSimpleName());
70     private final Handler mTelemetryHandler = new Handler(mTelemetryThread.getLooper());
71 
72     private ICarTelemetryServiceListener mListener;
73     private DataBroker mDataBroker;
74     private DataBrokerController mDataBrokerController;
75     private MetricsConfigStore mMetricsConfigStore;
76     private PublisherFactory mPublisherFactory;
77     private ResultStore mResultStore;
78     private StatsManagerProxy mStatsManagerProxy;
79     private SystemMonitor mSystemMonitor;
80 
CarTelemetryService(Context context, CarPropertyService carPropertyService)81     public CarTelemetryService(Context context, CarPropertyService carPropertyService) {
82         mContext = context;
83         mCarPropertyService = carPropertyService;
84     }
85 
86     @Override
init()87     public void init() {
88         mTelemetryHandler.post(() -> {
89             SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
90             // full root directory path is /data/system/car/telemetry
91             File rootDirectory = new File(systemInterface.getSystemCarDir(), TELEMETRY_DIR);
92             File publisherDirectory = new File(rootDirectory, PUBLISHER_DIR);
93             publisherDirectory.mkdirs();
94             // initialize all necessary components
95             mMetricsConfigStore = new MetricsConfigStore(rootDirectory);
96             mResultStore = new ResultStore(rootDirectory);
97             mStatsManagerProxy = new StatsManagerImpl(
98                     mContext.getSystemService(StatsManager.class));
99             mPublisherFactory = new PublisherFactory(mCarPropertyService, mTelemetryHandler,
100                     mStatsManagerProxy, publisherDirectory);
101             mDataBroker = new DataBrokerImpl(mContext, mPublisherFactory, mResultStore);
102             mSystemMonitor = SystemMonitor.create(mContext, mTelemetryHandler);
103             // controller starts metrics collection after boot complete
104             mDataBrokerController = new DataBrokerController(mDataBroker, mTelemetryHandler,
105                     mMetricsConfigStore, mSystemMonitor,
106                     systemInterface.getSystemStateInterface());
107         });
108     }
109 
110     @Override
release()111     public void release() {
112         // TODO(b/197969149): prevent threading issue, block main thread
113         mTelemetryHandler.post(() -> mResultStore.flushToDisk());
114     }
115 
116     @Override
dump(IndentingPrintWriter writer)117     public void dump(IndentingPrintWriter writer) {
118         writer.println("Car Telemetry service");
119     }
120 
121     /**
122      * Registers a listener with CarTelemetryService for the service to send data to cloud app.
123      */
124     @Override
setListener(@onNull ICarTelemetryServiceListener listener)125     public void setListener(@NonNull ICarTelemetryServiceListener listener) {
126         // TODO(b/184890506): verify that only a hardcoded app can set the listener
127         mContext.enforceCallingOrSelfPermission(
128                 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
129         mTelemetryHandler.post(() -> {
130             if (DEBUG) {
131                 Slogf.d(CarLog.TAG_TELEMETRY, "Setting the listener for car telemetry service");
132             }
133             mListener = listener;
134         });
135     }
136 
137     /**
138      * Clears the listener registered with CarTelemetryService.
139      */
140     @Override
clearListener()141     public void clearListener() {
142         mContext.enforceCallingOrSelfPermission(
143                 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "clearListener");
144         mTelemetryHandler.post(() -> {
145             if (DEBUG) {
146                 Slogf.d(CarLog.TAG_TELEMETRY, "Clearing the listener for car telemetry service");
147             }
148             mListener = null;
149         });
150     }
151 
152     /**
153      * Send a telemetry metrics config to the service. This method assumes
154      * {@link #setListener(ICarTelemetryServiceListener)} is called. Otherwise it does nothing.
155      *
156      * @param key    the unique key to identify the MetricsConfig.
157      * @param config the serialized bytes of a MetricsConfig object.
158      */
159     @Override
addMetricsConfig(@onNull MetricsConfigKey key, @NonNull byte[] config)160     public void addMetricsConfig(@NonNull MetricsConfigKey key, @NonNull byte[] config) {
161         mContext.enforceCallingOrSelfPermission(
162                 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "addMetricsConfig");
163         mTelemetryHandler.post(() -> {
164             if (mListener == null) {
165                 Slogf.w(CarLog.TAG_TELEMETRY, "ICarTelemetryServiceListener is not set");
166                 return;
167             }
168             Slogf.d(CarLog.TAG_TELEMETRY, "Adding metrics config: " + key.getName()
169                     + " to car telemetry service");
170             TelemetryProto.MetricsConfig metricsConfig = null;
171             int status;
172             try {
173                 metricsConfig = TelemetryProto.MetricsConfig.parseFrom(config);
174                 status = mMetricsConfigStore.addMetricsConfig(metricsConfig);
175             } catch (InvalidProtocolBufferException e) {
176                 Slogf.e(CarLog.TAG_TELEMETRY, "Failed to parse MetricsConfig.", e);
177                 status = ERROR_METRICS_CONFIG_PARSE_FAILED;
178             }
179             // If no error (config is added to MetricsConfigStore), remove legacy data and add
180             // config to data broker for metrics collection
181             if (status == ERROR_METRICS_CONFIG_NONE) {
182                 mResultStore.removeResult(key);
183                 mDataBroker.removeMetricsConfig(key);
184                 mDataBroker.addMetricsConfig(key, metricsConfig);
185                 // TODO(b/199410900): update logic once metrics configs have expiration dates
186             }
187             try {
188                 mListener.onAddMetricsConfigStatus(key, status);
189             } catch (RemoteException e) {
190                 Slogf.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
191             }
192         });
193     }
194 
195     /**
196      * Removes a metrics config based on the key. This will also remove outputs produced by the
197      * MetricsConfig.
198      *
199      * @param key the unique identifier of a MetricsConfig.
200      */
201     @Override
removeMetricsConfig(@onNull MetricsConfigKey key)202     public void removeMetricsConfig(@NonNull MetricsConfigKey key) {
203         mContext.enforceCallingOrSelfPermission(
204                 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "removeMetricsConfig");
205         mTelemetryHandler.post(() -> {
206             Slogf.d(CarLog.TAG_TELEMETRY, "Removing metrics config " + key.getName()
207                     + " from car telemetry service");
208             if (mMetricsConfigStore.removeMetricsConfig(key)) {
209                 mDataBroker.removeMetricsConfig(key);
210                 mResultStore.removeResult(key);
211             }
212         });
213     }
214 
215     /**
216      * Removes all MetricsConfigs. This will also remove all MetricsConfig outputs.
217      */
218     @Override
removeAllMetricsConfigs()219     public void removeAllMetricsConfigs() {
220         mContext.enforceCallingOrSelfPermission(
221                 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "removeAllMetricsConfigs");
222         mTelemetryHandler.post(() -> {
223             Slogf.d(CarLog.TAG_TELEMETRY,
224                     "Removing all metrics config from car telemetry service");
225             mDataBroker.removeAllMetricsConfigs();
226             mMetricsConfigStore.removeAllMetricsConfigs();
227             mResultStore.removeAllResults();
228         });
229     }
230 
231     /**
232      * Sends script results associated with the given key using the
233      * {@link ICarTelemetryServiceListener}. This method assumes listener is set. Otherwise it
234      * does nothing.
235      *
236      * @param key the unique identifier of a MetricsConfig.
237      */
238     @Override
sendFinishedReports(@onNull MetricsConfigKey key)239     public void sendFinishedReports(@NonNull MetricsConfigKey key) {
240         mContext.enforceCallingOrSelfPermission(
241                 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "sendFinishedReports");
242         mTelemetryHandler.post(() -> {
243             if (mListener == null) {
244                 Slogf.w(CarLog.TAG_TELEMETRY, "ICarTelemetryServiceListener is not set");
245                 return;
246             }
247             if (DEBUG) {
248                 Slogf.d(CarLog.TAG_TELEMETRY,
249                         "Flushing reports for metrics config " + key.getName());
250             }
251             PersistableBundle result = mResultStore.getFinalResult(key.getName(), true);
252             TelemetryProto.TelemetryError error = mResultStore.getError(key.getName(), true);
253             if (result != null) {
254                 sendFinalResult(key, result);
255             } else if (error != null) {
256                 sendError(key, error);
257             } else {
258                 Slogf.w(CarLog.TAG_TELEMETRY, "config " + key.getName()
259                         + " did not produce any results");
260             }
261         });
262     }
263 
264     /**
265      * Sends all script results or errors using the {@link ICarTelemetryServiceListener}.
266      */
267     @Override
sendAllFinishedReports()268     public void sendAllFinishedReports() {
269         // TODO(b/184087869): Implement
270         mContext.enforceCallingOrSelfPermission(
271                 Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "sendAllFinishedReports");
272         if (DEBUG) {
273             Slogf.d(CarLog.TAG_TELEMETRY, "Flushing all reports");
274         }
275     }
276 
sendFinalResult(MetricsConfigKey key, PersistableBundle result)277     private void sendFinalResult(MetricsConfigKey key, PersistableBundle result) {
278         try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
279             result.writeToStream(bos);
280             mListener.onResult(key, bos.toByteArray());
281         } catch (RemoteException e) {
282             Slogf.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
283         } catch (IOException e) {
284             Slogf.w(CarLog.TAG_TELEMETRY, "failed to write bundle to output stream", e);
285         }
286     }
287 
sendError(MetricsConfigKey key, TelemetryProto.TelemetryError error)288     private void sendError(MetricsConfigKey key, TelemetryProto.TelemetryError error) {
289         try {
290             mListener.onError(key, error.toByteArray());
291         } catch (RemoteException e) {
292             Slogf.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
293         }
294     }
295 
296     @VisibleForTesting
getTelemetryHandler()297     Handler getTelemetryHandler() {
298         return mTelemetryHandler;
299     }
300 
301     @VisibleForTesting
getResultStore()302     ResultStore getResultStore() {
303         return mResultStore;
304     }
305 
306     @VisibleForTesting
getMetricsConfigStore()307     MetricsConfigStore getMetricsConfigStore() {
308         return mMetricsConfigStore;
309     }
310 }
311