blob: 59b5a680a2ecffcc18e4226fd2dfcd2a862e305b [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_NEWER_MANIFEST_EXISTS;
import static android.car.telemetry.CarTelemetryManager.ERROR_NONE;
import static android.car.telemetry.CarTelemetryManager.ERROR_PARSE_MANIFEST_FAILED;
import static android.car.telemetry.CarTelemetryManager.ERROR_SAME_MANIFEST_EXISTS;
import android.annotation.NonNull;
import android.app.StatsManager;
import android.car.Car;
import android.car.telemetry.CarTelemetryManager.AddManifestError;
import android.car.telemetry.ICarTelemetryService;
import android.car.telemetry.ICarTelemetryServiceListener;
import android.car.telemetry.ManifestKey;
import android.content.Context;
import android.content.SharedPreferences;
import android.os.Handler;
import android.os.HandlerThread;
import android.util.IndentingPrintWriter;
import android.util.Slog;
import com.android.car.CarLocalServices;
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.GuardedBy;
import com.google.protobuf.InvalidProtocolBufferException;
import java.io.File;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* CarTelemetryService manages OEM telemetry collection, processing and communication
* with a data upload service.
*/
public class CarTelemetryService extends ICarTelemetryService.Stub implements CarServiceBase {
// TODO(b/189340793): Rename Manifest to MetricsConfig
private static final boolean DEBUG = false;
private static final int DEFAULT_VERSION = 0;
private static final String TAG = CarTelemetryService.class.getSimpleName();
public static final String TELEMETRY_DIR = "telemetry";
private final Context mContext;
private final CarPropertyService mCarPropertyService;
private final File mRootDirectory;
private final HandlerThread mTelemetryThread = CarServiceUtils.getHandlerThread(
CarTelemetryService.class.getSimpleName());
private final Handler mTelemetryHandler = new Handler(mTelemetryThread.getLooper());
@GuardedBy("mLock")
private final Map<String, Integer> mNameVersionMap = new HashMap<>();
private final Object mLock = new Object();
@GuardedBy("mLock")
private ICarTelemetryServiceListener mListener;
private DataBroker mDataBroker;
private DataBrokerController mDataBrokerController;
private MetricsConfigStore mMetricsConfigStore;
private PublisherFactory mPublisherFactory;
private ResultStore mResultStore;
private SharedPreferences mSharedPrefs;
private StatsManagerProxy mStatsManagerProxy;
private SystemMonitor mSystemMonitor;
public CarTelemetryService(Context context, CarPropertyService carPropertyService) {
mContext = context;
mCarPropertyService = carPropertyService;
SystemInterface systemInterface = CarLocalServices.getService(SystemInterface.class);
// full root directory path is /data/system/car/telemetry
mRootDirectory = new File(systemInterface.getSystemCarDir(), TELEMETRY_DIR);
}
@Override
public void init() {
mTelemetryHandler.post(() -> {
// initialize all necessary components
mMetricsConfigStore = new MetricsConfigStore(mRootDirectory);
mResultStore = new ResultStore(mRootDirectory);
mStatsManagerProxy = new StatsManagerImpl(
mContext.getSystemService(StatsManager.class));
// TODO(b/197968695): delay initialization of stats publisher
mPublisherFactory = new PublisherFactory(mCarPropertyService, mTelemetryHandler,
mStatsManagerProxy, null);
mDataBroker = new DataBrokerImpl(mContext, mPublisherFactory, mResultStore);
mSystemMonitor = SystemMonitor.create(mContext, mTelemetryHandler);
mDataBrokerController = new DataBrokerController(mDataBroker, mSystemMonitor);
// start collecting data. once data is sent by publisher, scripts will be able to run
List<TelemetryProto.MetricsConfig> activeConfigs =
mMetricsConfigStore.getActiveMetricsConfigs();
for (TelemetryProto.MetricsConfig config : activeConfigs) {
mDataBroker.addMetricsConfiguration(config);
}
});
}
@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");
synchronized (mLock) {
setListenerLocked(listener);
}
}
/**
* Clears the listener registered with CarTelemetryService.
*/
@Override
public void clearListener() {
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
synchronized (mLock) {
clearListenerLocked();
}
}
/**
* Allows client to send telemetry manifests.
*
* @param key the unique key to identify the manifest.
* @param config the serialized bytes of a Manifest object.
* @return {@link AddManifestError} the error code.
*/
@Override
public @AddManifestError int addManifest(@NonNull ManifestKey key, @NonNull byte[] config) {
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
synchronized (mLock) {
return addManifestLocked(key, config);
}
}
/**
* Removes a manifest based on the key.
*/
@Override
public boolean removeManifest(@NonNull ManifestKey key) {
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
synchronized (mLock) {
return removeManifestLocked(key);
}
}
/**
* Removes all manifests.
*/
@Override
public void removeAllManifests() {
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
synchronized (mLock) {
removeAllManifestsLocked();
}
}
/**
* Sends script results associated with the given key using the
* {@link ICarTelemetryServiceListener}.
*/
@Override
public void sendFinishedReports(@NonNull ManifestKey key) {
// TODO(b/184087869): Implement
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
if (DEBUG) {
Slog.d(TAG, "Flushing reports for a manifest");
}
}
/**
* Sends all script results associated using the {@link ICarTelemetryServiceListener}.
*/
@Override
public void sendAllFinishedReports() {
// TODO(b/184087869): Implement
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
if (DEBUG) {
Slog.d(TAG, "Flushing all reports");
}
}
/**
* Sends all errors using the {@link ICarTelemetryServiceListener}.
*/
@Override
public void sendScriptExecutionErrors() {
// TODO(b/184087869): Implement
mContext.enforceCallingOrSelfPermission(
Car.PERMISSION_USE_CAR_TELEMETRY_SERVICE, "setListener");
if (DEBUG) {
Slog.d(TAG, "Flushing script execution errors");
}
}
@GuardedBy("mLock")
private void setListenerLocked(@NonNull ICarTelemetryServiceListener listener) {
if (DEBUG) {
Slog.d(TAG, "Setting the listener for car telemetry service");
}
mListener = listener;
}
@GuardedBy("mLock")
private void clearListenerLocked() {
if (DEBUG) {
Slog.d(TAG, "Clearing listener");
}
mListener = null;
}
@GuardedBy("mLock")
private @AddManifestError int addManifestLocked(ManifestKey key, byte[] configProto) {
if (DEBUG) {
Slog.d(TAG, "Adding MetricsConfig to car telemetry service");
}
int currentVersion = mNameVersionMap.getOrDefault(key.getName(), DEFAULT_VERSION);
if (currentVersion > key.getVersion()) {
return ERROR_NEWER_MANIFEST_EXISTS;
} else if (currentVersion == key.getVersion()) {
return ERROR_SAME_MANIFEST_EXISTS;
}
TelemetryProto.MetricsConfig metricsConfig;
try {
metricsConfig = TelemetryProto.MetricsConfig.parseFrom(configProto);
} catch (InvalidProtocolBufferException e) {
Slog.e(TAG, "Failed to parse MetricsConfig.", e);
return ERROR_PARSE_MANIFEST_FAILED;
}
mNameVersionMap.put(key.getName(), key.getVersion());
// TODO(b/186047142): Store the MetricsConfig to disk
// TODO(b/186047142): Send metricsConfig to a script manager or a queue
return ERROR_NONE;
}
@GuardedBy("mLock")
private boolean removeManifestLocked(@NonNull ManifestKey key) {
Integer version = mNameVersionMap.remove(key.getName());
if (version == null) {
return false;
}
// TODO(b/186047142): Delete manifest from disk and remove it from queue
return true;
}
@GuardedBy("mLock")
private void removeAllManifestsLocked() {
if (DEBUG) {
Slog.d(TAG, "Removing all manifest from car telemetry service");
}
mNameVersionMap.clear();
// TODO(b/186047142): Delete all manifests from disk & queue
}
}