aboutsummaryrefslogtreecommitdiff
path: root/tests/CarDiagnosticVerifier
diff options
context:
space:
mode:
authorChao Yan <aceyansf@google.com>2017-07-13 15:09:16 -0700
committerChao Yan <aceyansf@google.com>2017-07-14 15:18:34 -0700
commit7582f2e1cc5df01f51b97554a00d44f393c922b6 (patch)
tree62c4033b49e61e352f93b666656083fb4ab19cbf /tests/CarDiagnosticVerifier
parent9f6f2546729b204c69195e0908a8ea750e7ce9dd (diff)
downloadCar-7582f2e1cc5df01f51b97554a00d44f393c922b6.tar.gz
Added car diagnostic API test application
Added the test application which verify if received car diagnostic events are correct by checking against the true (golden) event list. Test: manual with preparing golden event list while injecting wrong ones Change-Id: I7ea85f2558fd1cbc7e302eb424f40301402a58a7
Diffstat (limited to 'tests/CarDiagnosticVerifier')
-rw-r--r--tests/CarDiagnosticVerifier/Android.mk44
-rw-r--r--tests/CarDiagnosticVerifier/AndroidManifest.xml30
-rw-r--r--tests/CarDiagnosticVerifier/res/layout/verifier_activity.xml34
-rw-r--r--tests/CarDiagnosticVerifier/res/values/strings.xml20
-rw-r--r--tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/DiagnosticJsonConverter.java85
-rw-r--r--tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/DiagnosticVerifier.java244
-rw-r--r--tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/MainActivity.java254
7 files changed, 711 insertions, 0 deletions
diff --git a/tests/CarDiagnosticVerifier/Android.mk b/tests/CarDiagnosticVerifier/Android.mk
new file mode 100644
index 0000000000..98758a5a24
--- /dev/null
+++ b/tests/CarDiagnosticVerifier/Android.mk
@@ -0,0 +1,44 @@
+# Copyright (C) 2017 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.
+#
+#
+
+LOCAL_PATH := $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := optional
+
+# Only compile source java files in this apk.
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+
+LOCAL_PACKAGE_NAME := CarDiagnosticVerifier
+
+LOCAL_JAVA_VERSION := 1.8
+
+LOCAL_PROGUARD_ENABLED := disabled
+
+LOCAL_DEX_PREOPT := false
+
+LOCAL_PRIVILEGED_MODULE := true
+
+LOCAL_STATIC_JAVA_LIBRARIES += vehicle-hal-support-lib
+
+LOCAL_JAVA_LIBRARIES += android.car
+
+include $(BUILD_PACKAGE)
+
+# Use the following include to make our test apk.
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/tests/CarDiagnosticVerifier/AndroidManifest.xml b/tests/CarDiagnosticVerifier/AndroidManifest.xml
new file mode 100644
index 0000000000..443825a577
--- /dev/null
+++ b/tests/CarDiagnosticVerifier/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.google.android.car.diagnosticverifier">
+
+ <uses-permission android:name="android.car.permission.DIAGNOSTIC_READ_ALL" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
+ <application android:label="Car Diagnostic Verification">
+ <activity android:name=".MainActivity">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+ </application>
+</manifest>
diff --git a/tests/CarDiagnosticVerifier/res/layout/verifier_activity.xml b/tests/CarDiagnosticVerifier/res/layout/verifier_activity.xml
new file mode 100644
index 0000000000..7edf5bce30
--- /dev/null
+++ b/tests/CarDiagnosticVerifier/res/layout/verifier_activity.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:paddingLeft="16dp"
+ android:paddingRight="16dp" >
+
+ <TextView
+ android:id="@+id/status_bar"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content" />
+
+ <TextView
+ android:id="@+id/results"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/status_bar"
+ android:layout_marginTop="16dp" />
+
+</RelativeLayout>
diff --git a/tests/CarDiagnosticVerifier/res/values/strings.xml b/tests/CarDiagnosticVerifier/res/values/strings.xml
new file mode 100644
index 0000000000..ca6b33a1e8
--- /dev/null
+++ b/tests/CarDiagnosticVerifier/res/values/strings.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2017 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.
+-->
+<resources>
+ <string name="status_receiving">Receiving car diagnostic events...</string>
+ <string name="status_verifying">Verifying car diagnostic events...</string>
+ <string name="status_done">Done with verification</string>
+</resources>
diff --git a/tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/DiagnosticJsonConverter.java b/tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/DiagnosticJsonConverter.java
new file mode 100644
index 0000000000..fd78c7f24a
--- /dev/null
+++ b/tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/DiagnosticJsonConverter.java
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2017 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.google.android.car.diagnosticverifier;
+
+import android.car.hardware.CarDiagnosticEvent;
+import android.util.JsonReader;
+
+import com.android.car.vehiclehal.DiagnosticJson;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides method to convert JSON into car diagnostic event object.
+ */
+public class DiagnosticJsonConverter {
+
+ public static List<CarDiagnosticEvent> readFromJson(InputStream in) throws IOException {
+ JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
+
+ try {
+ return readEventsArray(reader);
+ } finally {
+ reader.close();
+ }
+ }
+
+ private static List<CarDiagnosticEvent> readEventsArray(JsonReader reader) throws IOException {
+ List<CarDiagnosticEvent> events = new ArrayList<>();
+
+ reader.beginArray();
+ while (reader.hasNext()) {
+ events.add(readEventAndCanonicalize(reader));
+ }
+ reader.endArray();
+ return events;
+ }
+
+ public static CarDiagnosticEvent readEventAndCanonicalize(InputStream in) throws IOException {
+ JsonReader reader = new JsonReader(new InputStreamReader(in, "UTF-8"));
+ return readEventAndCanonicalize(reader);
+ }
+
+ /**
+ * This method convert JSON to a car diagnostic event object.
+ * Note: it will always set timestamp to 0 and set dtc to null if it is empty string.
+ */
+ private static CarDiagnosticEvent readEventAndCanonicalize(JsonReader reader)
+ throws IOException {
+ DiagnosticJson diagnosticJson = DiagnosticJson.build(reader);
+ //Build event
+ CarDiagnosticEvent.Builder builder = "freeze".equals(diagnosticJson.type) ?
+ CarDiagnosticEvent.Builder.newFreezeFrameBuilder() :
+ CarDiagnosticEvent.Builder.newLiveFrameBuilder();
+ //Always skip timestamp because it is not useful for test
+ builder.atTimestamp(0);
+ for (int i = 0; i < diagnosticJson.intValues.size(); i++) {
+ builder.withIntValue(diagnosticJson.intValues.keyAt(i),
+ diagnosticJson.intValues.valueAt(i));
+ }
+ for (int i = 0; i < diagnosticJson.floatValues.size(); i++) {
+ builder.withFloatValue(diagnosticJson.floatValues.keyAt(i),
+ diagnosticJson.floatValues.valueAt(i));
+ }
+ //Always set dtc to null if it is empty string
+ builder.withDTC("".equals(diagnosticJson.dtc) ? null : diagnosticJson.dtc);
+
+ return builder.build();
+ }
+}
diff --git a/tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/DiagnosticVerifier.java b/tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/DiagnosticVerifier.java
new file mode 100644
index 0000000000..8f547b1877
--- /dev/null
+++ b/tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/DiagnosticVerifier.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2017 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.google.android.car.diagnosticverifier;
+
+import android.car.hardware.CarDiagnosticEvent;
+import android.util.JsonWriter;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * DiagVerifier implements verification logic for car diagnostic events.
+ *
+ * The main idea for the verification is similar to a "diff" command on two files, whereas here
+ * is a diff on two event lists. The available diff operations are: "add", "delete", "modify"
+ *
+ * For example, think about doing a diff on two sequences:
+ *
+ * Truth: A B C D E
+ * Received: A C D E F
+ *
+ * The goal is to find the minimal number of diff operations applied on Truth list in order to
+ * become Received list. It is the same problem to find edit distance between two sequences and keep
+ * track of the corresponding edit operations. This verifier applies dynamic programming algorithm
+ * to find the minimal set of diff operations. And the result would be:
+ *
+ * Truth: A - C D E +
+ * Received: A C D E F
+ *
+ * It means in order to become Received list, "B" will be missing and an extra "F" will be added
+ * at the end of list.
+ */
+public class DiagnosticVerifier {
+
+ private static final String TAG = "DiagnosticVerifier";
+ /**
+ * Below are 4 diff operations when comparing two event lists
+ */
+ private static final int DELETE = 0;
+ private static final int ADD = 1;
+ private static final int MODIFY = 2;
+ private static final int KEEP = 3;
+
+ /**
+ * A list of truth diagnostic events for comparison.
+ */
+ private final List<CarDiagnosticEvent> mTruthEventList = new ArrayList<>();
+ /**
+ * A list of received diagnostic events from car service.
+ */
+ private final List<CarDiagnosticEvent> mReceivedEventList = new ArrayList<>();
+
+ /**
+ * Definition of the verification result
+ */
+ static class VerificationResult {
+ public final String testCase;
+ public final boolean success;
+ public final String errorMessage;
+
+ private VerificationResult(String testCase, boolean success, String errorMessage) {
+ this.testCase = testCase;
+ this.success = success;
+ this.errorMessage = errorMessage;
+ }
+
+ public static VerificationResult fromMessage(String testCase, String message) {
+ return new VerificationResult(testCase, message.length() == 0, message);
+ }
+
+ public void writeToJson(JsonWriter jsonWriter) throws IOException {
+ jsonWriter.beginObject();
+
+ jsonWriter.name("testCase");
+ jsonWriter.value(this.testCase);
+
+ jsonWriter.name("success");
+ jsonWriter.value(this.success);
+
+ jsonWriter.name("errorMessage");
+ jsonWriter.value(this.errorMessage);
+
+ jsonWriter.endObject();
+ }
+ }
+
+ public DiagnosticVerifier(List<CarDiagnosticEvent> truthEvents) {
+ if (truthEvents != null) {
+ for (CarDiagnosticEvent event : truthEvents) {
+ CarDiagnosticEvent canonicalEvent = canonicalize(event);
+ mTruthEventList.add(canonicalEvent);
+ }
+ }
+ }
+
+ public void receiveEvent(CarDiagnosticEvent event) {
+ CarDiagnosticEvent newEvent = canonicalize(event);
+ mReceivedEventList.add(newEvent);
+ }
+
+ public List<VerificationResult> verify() {
+ List<Integer> diff = calculateDiffOperations();
+ StringBuilder missingEventMsgBuilder = new StringBuilder();
+ StringBuilder extraEventMsgBuilder = new StringBuilder();
+ StringBuilder mismatchEventMsgBuilder = new StringBuilder();
+ for (int i = 0, j = 0, k = diff.size() - 1; k >= 0; k--) {
+ if (diff.get(k) == DELETE) {
+ missingEventMsgBuilder.append(String.format(
+ "Missing event at position %d: %s\n", i, mTruthEventList.get(i)));
+ i++;
+ } else if (diff.get(k) == ADD) {
+ extraEventMsgBuilder.append(String.format(
+ "Extra event at position %d: %s\n", i, mReceivedEventList.get(j)));
+ j++;
+ } else if (diff.get(k) == MODIFY) {
+ mismatchEventMsgBuilder.append(String.format(
+ "Mismatched event pair at position %d:\n" +
+ "True event -- %s\nWrong event -- %s\n",
+ i, mTruthEventList.get(i), mReceivedEventList.get(j)));
+ i++;
+ j++;
+ } else {
+ i++;
+ j++;
+ }
+ }
+ List<VerificationResult> results = new ArrayList<>();
+ results.add(VerificationResult.fromMessage(
+ "test_mismatched_event", mismatchEventMsgBuilder.toString()));
+ results.add(VerificationResult.fromMessage(
+ "test_missing_event", missingEventMsgBuilder.toString()));
+ results.add(VerificationResult.fromMessage(
+ "test_extra_event", extraEventMsgBuilder.toString()));
+ return results;
+ }
+
+ /**
+ * The function applies a dynamic programming algorithm to find the minimal set of diff
+ * operations that applied on truth event list in order to become received event list
+ */
+ private List<Integer> calculateDiffOperations() {
+ final int n = mTruthEventList.size();
+ final int m = mReceivedEventList.size();
+
+ int[][] diffTable = new int[n + 1][m + 1];
+ int[][] costTable = new int[n + 1][m + 1];
+
+ for (int i = 1; i <= n; i++) {
+ costTable[i][0] = i;
+ diffTable[i][0] = DELETE;
+ }
+
+ for (int i = 1; i <= m; i++) {
+ costTable[0][i] = i;
+ diffTable[0][i] = ADD;
+ }
+
+ for (int i = 1; i <= n; i++) {
+ for (int j = 1; j <= m; j++) {
+ int deleteCost = costTable[i - 1][j] + 1;
+ int addCost = costTable[i][j - 1] + 1;
+ int modifyCost = costTable[i - 1][j - 1];
+
+ CarDiagnosticEvent trueEvent = mTruthEventList.get(i - 1);
+ CarDiagnosticEvent receivedEvent = mReceivedEventList.get(j - 1);
+
+ //TODO: Use a more meaningful comparison. Instead of strict object level equality,
+ //can check logical equality and allow an acceptable difference.
+ boolean isEqual = trueEvent.equals(receivedEvent);
+ modifyCost += isEqual ? 0 : 1;
+
+ int minCost = modifyCost;
+ int move = isEqual ? KEEP : MODIFY;
+ if (minCost > addCost) {
+ minCost = addCost;
+ move = ADD;
+ }
+ if (minCost > deleteCost) {
+ minCost = deleteCost;
+ move = DELETE;
+ }
+
+ costTable[i][j] = minCost;
+ diffTable[i][j] = move;
+ }
+ }
+ List<Integer> diff = new ArrayList<>();
+
+ for (int i = n, j = m; i > 0 || j > 0; ) {
+ diff.add(diffTable[i][j]);
+ if (diffTable[i][j] == DELETE) {
+ i--;
+ } else if (diffTable[i][j] == ADD) {
+ j--;
+ } else {
+ i--;
+ j--;
+ }
+ }
+ return diff;
+ }
+
+ /**
+ * The function will canonicalize a given event by using JSON converter which will reset event
+ * timestamp to 0 and set DTC field with empty string to null. Doing JSON conversion is because
+ * CarDiagnosticEvent does not provide direct accessor for intValues and floatValues.
+ */
+ private CarDiagnosticEvent canonicalize(CarDiagnosticEvent event) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ JsonWriter writer = new JsonWriter(new OutputStreamWriter(out));
+ CarDiagnosticEvent newEvent = event;
+ try {
+ event.writeToJson(writer);
+ writer.flush();
+ writer.close();
+ byte[] rawJson = out.toByteArray();
+ ByteArrayInputStream in = new ByteArrayInputStream(rawJson);
+ newEvent = DiagnosticJsonConverter.readEventAndCanonicalize(in);
+ in.close();
+ out.close();
+ } catch (IOException e) {
+ Log.w(TAG, "Failed to clear timestamp ");
+ }
+ return newEvent;
+ }
+}
diff --git a/tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/MainActivity.java b/tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/MainActivity.java
new file mode 100644
index 0000000000..14c5e076f3
--- /dev/null
+++ b/tests/CarDiagnosticVerifier/src/com/google/android/car/diagnosticverifier/MainActivity.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2017 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.google.android.car.diagnosticverifier;
+
+import android.app.Activity;
+import android.car.Car;
+import android.car.CarNotConnectedException;
+import android.car.hardware.CarDiagnosticEvent;
+import android.car.hardware.CarDiagnosticManager;
+import android.car.hardware.CarSensorManager;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.IBinder;
+import android.util.JsonWriter;
+import android.util.Log;
+import android.widget.TextView;
+
+import com.google.android.car.diagnosticverifier.DiagnosticVerifier.VerificationResult;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.util.List;
+
+/**
+ * The test app that does the verification of car diagnostic event data. It first reads the
+ * truth (golden) event data from a JSON file upon starting. Then a broadcast intent such as:
+ *
+ * am broadcast -a com.google.android.car.diagnosticverifier.action.START_LISTEN
+ *
+ * will activate the car diagnostics listener. The test app will receive events from diagnostic API.
+ * Once it receives all the events, a broadcast intent with "stop" action such as:
+ *
+ * am broadcast -a com.google.android.car.diagnosticverifier.action.STOP_LISTEN
+ *
+ * will deactivate the listener and start the verification process (see {@link DiagnosticVerifier}).
+ *
+ * Verification result will be output to a JSON file on device.
+ */
+public class MainActivity extends Activity {
+ public static final String TAG = "DiagnosticVerifier";
+
+ public static final String ACTION_START_LISTEN =
+ "com.google.android.car.diagnosticverifier.action.START_LISTEN";
+ public static final String ACTION_STOP_LISTEN =
+ "com.google.android.car.diagnosticverifier.action.STOP_LISTEN";
+
+ private static final String DEFAULT_JSON_PATH = "/data/local/tmp/diag.json";
+
+ private static final String JSON_PATH_KEY = "jsonPath";
+ private static final String JSON_RESULT = "verification_result.json";
+
+ private Car mCar;
+ private CarDiagnosticManager mCarDiagnosticManager;
+ private DiagnosticListener mDiagnosticListener;
+ private BroadcastReceiver mBroadcastReceiver;
+ private DiagnosticVerifier mVerifier;
+ private TextView mStatusBar;
+ private boolean mListening = false;
+
+ private final ServiceConnection mCarConnectionListener =
+ new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder iBinder) {
+ Log.d(TAG, "Connected to " + name.flattenToString());
+ try {
+ mCarDiagnosticManager =
+ (CarDiagnosticManager) mCar.getCarManager(Car.DIAGNOSTIC_SERVICE);
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Failed to get a connection", e);
+ }
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ Log.d(TAG, "Disconnected from " + name.flattenToString());
+
+ mCar = null;
+ mCarDiagnosticManager = null;
+ }
+ };
+
+ class DiagnosticListener implements CarDiagnosticManager.OnDiagnosticEventListener {
+
+ @Override
+ public void onDiagnosticEvent(CarDiagnosticEvent carDiagnosticEvent) {
+ Log.v(TAG, "Received Car Diagnostic Event: " + carDiagnosticEvent.toString());
+ mVerifier.receiveEvent(carDiagnosticEvent);
+ }
+ }
+
+ class VerifierMsgReceiver extends BroadcastReceiver {
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ Log.d(TAG, "Received intent with action: " + action);
+ if (ACTION_START_LISTEN.equals(action)) {
+ try {
+ startListen();
+ } catch (CarNotConnectedException e) {
+ Log.e(TAG, "Failed to listen for car diagnostic event", e);
+ }
+ } else if (ACTION_STOP_LISTEN.equals(action)) {
+ stopListen();
+ verify();
+ }
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.verifier_activity);
+
+ mStatusBar = (TextView) findViewById(R.id.status_bar);
+
+ mCar = Car.createCar(this, mCarConnectionListener);
+ mCar.connect();
+
+ mBroadcastReceiver = new VerifierMsgReceiver();
+ IntentFilter filter = new IntentFilter(ACTION_START_LISTEN);
+ filter.addAction(ACTION_STOP_LISTEN);
+ this.registerReceiver(mBroadcastReceiver, filter);
+
+ String jsonPath = this.getIntent().getStringExtra(JSON_PATH_KEY);
+ if (jsonPath == null || jsonPath.isEmpty()) {
+ jsonPath = DEFAULT_JSON_PATH;
+ }
+
+ List<CarDiagnosticEvent> events;
+ try {
+ events = DiagnosticJsonConverter.readFromJson(new FileInputStream(jsonPath));
+ } catch (IOException e) {
+ throw new RuntimeException("Failed to read diagnostic JSON file", e);
+ }
+ Log.d(TAG, String.format("Read %d events from JSON file %s.", events.size(), jsonPath));
+
+ mVerifier = new DiagnosticVerifier(events);
+ }
+
+ @Override
+ protected void onDestroy() {
+ if (mCar != null) {
+ mCar.disconnect();
+ }
+ mVerifier = null;
+ this.unregisterReceiver(mBroadcastReceiver);
+ }
+
+ private void startListen() throws CarNotConnectedException {
+ if (mListening) {
+ return;
+ }
+ if (mDiagnosticListener == null) {
+ mDiagnosticListener = new DiagnosticListener();
+ }
+ Log.i(TAG, "Start listening for car diagnostics events");
+ mCarDiagnosticManager.registerListener(
+ mDiagnosticListener,
+ CarDiagnosticManager.FRAME_TYPE_LIVE,
+ CarSensorManager.SENSOR_RATE_NORMAL);
+ mCarDiagnosticManager.registerListener(
+ mDiagnosticListener,
+ CarDiagnosticManager.FRAME_TYPE_FREEZE,
+ CarSensorManager.SENSOR_RATE_NORMAL);
+
+ mListening = true;
+ mStatusBar.setText(R.string.status_receiving);
+ }
+
+ private void stopListen() {
+ Log.i(TAG, "Stop listening for car diagnostics events");
+ mCarDiagnosticManager.unregisterListener(mDiagnosticListener);
+ mListening = false;
+ }
+
+ private boolean isExternalStorageWritable() {
+ String state = Environment.getExternalStorageState();
+ return Environment.MEDIA_MOUNTED.equals(state);
+ }
+
+ private File getResultJsonFile() throws IOException {
+ if (!isExternalStorageWritable()) {
+ throw new IOException("External storage is not writable. Cannot save content");
+ }
+
+ File resultJson = new File(Environment.getExternalStoragePublicDirectory(
+ Environment.DIRECTORY_DOCUMENTS), JSON_RESULT);
+ if (!resultJson.getParentFile().mkdirs()) {
+ Log.w(TAG, "Parent directory may already exist");
+ }
+ return resultJson;
+ }
+
+ private void verify() {
+ Log.d(TAG, "Start verifying car diagnostics events");
+ mStatusBar.setText(R.string.status_verifying);
+ List<VerificationResult> results = mVerifier.verify();
+ mStatusBar.setText(R.string.status_done);
+
+ if (results.isEmpty()) {
+ Log.d(TAG, "Verification result is empty.");
+ return;
+ }
+
+ //TODO: Use a scrollable view
+ TextView resultView = (TextView) findViewById(R.id.results);
+ resultView.setText("");
+
+ try {
+ File resultJson = getResultJsonFile();
+ JsonWriter writer = new JsonWriter(
+ new OutputStreamWriter(new FileOutputStream(resultJson)));
+
+ writer.beginArray();
+ for (VerificationResult result : results) {
+ resultView.append("Test case: " + result.testCase + "\n");
+ resultView.append("Result: " + result.success + "\n");
+ resultView.append(result.errorMessage);
+ resultView.append("\n");
+ result.writeToJson(writer);
+ }
+ writer.endArray();
+ writer.flush();
+ writer.close();
+ Log.i(TAG, "Verification result: " + resultJson.getAbsolutePath());
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to save verification result.", e);
+ }
+ }
+}
+