blob: 6f5ca1a2be49d07fac0421c6cdfd9a549ca0001d [file] [log] [blame]
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.car.telemetry;
import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_NONE;
import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_PARSE_FAILED;
import static android.car.telemetry.CarTelemetryManager.ERROR_METRICS_CONFIG_UNKNOWN;
import android.annotation.NonNull;
import android.app.StatsManager;
import android.car.Car;
import android.car.telemetry.ICarTelemetryService;
import android.car.telemetry.ICarTelemetryServiceListener;
import android.car.telemetry.MetricsConfigKey;
import android.content.Context;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.car.CarLocalServices;
import com.android.car.CarLog;
import com.android.car.CarPropertyService;
import com.android.car.CarServiceBase;
import com.android.car.CarServiceUtils;
import com.android.car.systeminterface.SystemInterface;
import com.android.car.telemetry.databroker.DataBroker;
import com.android.car.telemetry.databroker.DataBrokerController;
import com.android.car.telemetry.databroker.DataBrokerImpl;
import com.android.car.telemetry.publisher.PublisherFactory;
import com.android.car.telemetry.publisher.StatsManagerImpl;
import com.android.car.telemetry.publisher.StatsManagerProxy;
import com.android.car.telemetry.systemmonitor.SystemMonitor;
import com.android.internal.annotations.VisibleForTesting;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
/**
* CarTelemetryService manages OEM telemetry collection, processing and communication
* with a data upload service.
*/
public class CarTelemetryService extends ICarTelemetryService.Stub implements CarServiceBase {
private static final boolean DEBUG = false;
public static final String TELEMETRY_DIR = "telemetry";
private final Context mContext;
private final CarPropertyService mCarPropertyService;
private final HandlerThread mTelemetryThread = CarServiceUtils.getHandlerThread(
CarTelemetryService.class.getSimpleName());
private final Handler mTelemetryHandler = new Handler(mTelemetryThread.getLooper());
private ICarTelemetryServiceListener mListener;
private DataBroker mDataBroker;
private DataBrokerController mDataBrokerController;
private MetricsConfigStore mMetricsConfigStore;
private PublisherFactory mPublisherFactory;
private ResultStore mResultStore;
private StatsManagerProxy mStatsManagerProxy;
private SystemMonitor mSystemMonitor;
public CarTelemetryService(Context context, CarPropertyService carPropertyService) {
mContext = context;
mCarPropertyService = carPropertyService;
}
@Override
public void init() {
mTelemetryHandler.post(() -> {
SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
// full root directory path is /data/system/car/telemetry
File rootDirectory = new File(systemInterface.getSystemCarDir(), TELEMETRY_DIR);
// initialize all necessary components
mMetricsConfigStore = new MetricsConfigStore(rootDirectory);
mResultStore = new ResultStore(rootDirectory);
mStatsManagerProxy = new StatsManagerImpl(
mContext.getSystemService(StatsManager.class));
mPublisherFactory = new PublisherFactory(mCarPropertyService, mTelemetryHandler,
mStatsManagerProxy, rootDirectory);
mDataBroker = new DataBrokerImpl(mContext, mPublisherFactory, mResultStore);
mSystemMonitor = SystemMonitor.create(mContext, mTelemetryHandler);
// controller starts metrics collection after boot complete
mDataBrokerController = new DataBrokerController(mDataBroker, mTelemetryHandler,
mMetricsConfigStore, mSystemMonitor,
systemInterface.getSystemStateInterface());
});
}
@Override
public void release() {
// TODO(b/197969149): prevent threading issue, block main thread
mTelemetryHandler.post(() -> mResultStore.flushToDisk());
}
@Override
public void dump(IndentingPrintWriter writer) {
writer.println("Car Telemetry service");
}
/**
* Registers a listener with CarTelemetryService for the service to send data to cloud app.
*/
@Override
public void setListener(@NonNull ICarTelemetryServiceListener listener) {
// TODO(b/184890506): verify that only a hardcoded app can set the listener
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
mTelemetryHandler.post(() -> {
if (DEBUG) {
Slog.d(CarLog.TAG_TELEMETRY, "Setting the listener for car telemetry service");
}
mListener = listener;
});
}
/**
* Clears the listener registered with CarTelemetryService.
*/
@Override
public void clearListener() {
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "clearListener");
mTelemetryHandler.post(() -> {
if (DEBUG) {
Slog.d(CarLog.TAG_TELEMETRY, "Clearing the listener for car telemetry service");
}
mListener = null;
});
}
/**
* Send a telemetry metrics config to the service. This method assumes
* {@link #setListener(ICarTelemetryServiceListener)} is called. Otherwise it does nothing.
*
* @param key the unique key to identify the MetricsConfig.
* @param config the serialized bytes of a MetricsConfig object.
*/
@Override
public void addMetricsConfig(@NonNull MetricsConfigKey key, @NonNull byte[] config) {
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "addMetricsConfig");
mTelemetryHandler.post(() -> {
if (mListener == null) {
Slog.w(CarLog.TAG_TELEMETRY, "ICarTelemetryServiceListener is not set");
return;
}
Slog.d(CarLog.TAG_TELEMETRY, "Adding metrics config " + key.getName()
+ " to car telemetry service");
TelemetryProto.MetricsConfig metricsConfig = null;
int status = ERROR_METRICS_CONFIG_UNKNOWN;
try {
metricsConfig = TelemetryProto.MetricsConfig.parseFrom(config);
} catch (InvalidProtocolBufferException e) {
Slog.e(CarLog.TAG_TELEMETRY, "Failed to parse MetricsConfig.", e);
status = ERROR_METRICS_CONFIG_PARSE_FAILED;
}
// if config can be parsed, add it to persistent storage
if (metricsConfig != null) {
status = mMetricsConfigStore.addMetricsConfig(metricsConfig);
// TODO(b/199410900): update logic once metrics configs have expiration dates
mDataBroker.addMetricsConfiguration(metricsConfig);
}
// If no error (a config is successfully added), script results from an older version
// should be deleted
if (status == ERROR_METRICS_CONFIG_NONE) {
mResultStore.removeResult(key.getName());
}
try {
mListener.onAddMetricsConfigStatus(key, status);
} catch (RemoteException e) {
Slog.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
}
});
}
/**
* Removes a metrics config based on the key. This will also remove outputs produced by the
* MetricsConfig.
*
* @param key the unique identifier of a MetricsConfig.
*/
@Override
public void removeMetricsConfig(@NonNull MetricsConfigKey key) {
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "removeMetricsConfig");
mTelemetryHandler.post(() -> {
Slog.d(CarLog.TAG_TELEMETRY, "Removing metrics config " + key.getName()
+ " from car telemetry service");
// TODO(b/198792767): Check both config name and config version for removal
mDataBroker.removeMetricsConfiguration(key.getName());
mResultStore.removeResult(key.getName());
mMetricsConfigStore.removeMetricsConfig(key.getName());
});
}
/**
* Removes all MetricsConfigs. This will also remove all MetricsConfig outputs.
*/
@Override
public void removeAllMetricsConfigs() {
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "removeAllMetricsConfigs");
mTelemetryHandler.post(() -> {
Slog.d(CarLog.TAG_TELEMETRY,
"Removing all metrics config from car telemetry service");
mDataBroker.removeAllMetricsConfigurations();
mMetricsConfigStore.removeAllMetricsConfigs();
mResultStore.removeAllResults();
});
}
/**
* Sends script results associated with the given key using the
* {@link ICarTelemetryServiceListener}. This method assumes listener is set. Otherwise it
* does nothing.
*
* @param key the unique identifier of a MetricsConfig.
*/
@Override
public void sendFinishedReports(@NonNull MetricsConfigKey key) {
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "sendFinishedReports");
mTelemetryHandler.post(() -> {
if (mListener == null) {
Slog.w(CarLog.TAG_TELEMETRY, "ICarTelemetryServiceListener is not set");
return;
}
if (DEBUG) {
Slog.d(CarLog.TAG_TELEMETRY,
"Flushing reports for metrics config " + key.getName());
}
PersistableBundle result = mResultStore.getFinalResult(key.getName(), true);
TelemetryProto.TelemetryError error = mResultStore.getError(key.getName(), true);
if (result != null) {
sendFinalResult(key, result);
} else if (error != null) {
sendError(key, error);
} else {
Slog.w(CarLog.TAG_TELEMETRY, "config " + key.getName()
+ " did not produce any results");
}
});
}
/**
* Sends all script results or errors using the {@link ICarTelemetryServiceListener}.
*/
@Override
public void sendAllFinishedReports() {
// TODO(b/184087869): Implement
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "sendAllFinishedReports");
if (DEBUG) {
Slog.d(CarLog.TAG_TELEMETRY, "Flushing all reports");
}
}
private void sendFinalResult(MetricsConfigKey key, PersistableBundle result) {
try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
result.writeToStream(bos);
mListener.onResult(key, bos.toByteArray());
} catch (RemoteException e) {
Slog.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
} catch (IOException e) {
Slog.w(CarLog.TAG_TELEMETRY, "failed to write bundle to output stream", e);
}
}
private void sendError(MetricsConfigKey key, TelemetryProto.TelemetryError error) {
try {
mListener.onError(key, error.toByteArray());
} catch (RemoteException e) {
Slog.w(CarLog.TAG_TELEMETRY, "error with ICarTelemetryServiceListener", e);
}
}
@VisibleForTesting
Handler getTelemetryHandler() {
return mTelemetryHandler;
}
@VisibleForTesting
ResultStore getResultStore() {
return mResultStore;
}
@VisibleForTesting
MetricsConfigStore getMetricsConfigStore() {
return mMetricsConfigStore;
}
}