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