Merge "Add CarLatinIME to the base car make file."
diff --git a/TrustAgent/Android.mk b/TrustAgent/Android.mk
index f672e57..dc70ef3 100644
--- a/TrustAgent/Android.mk
+++ b/TrustAgent/Android.mk
@@ -16,6 +16,8 @@
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_PACKAGE_NAME := CarTrustAgentService
diff --git a/TrustAgent/AndroidManifest.xml b/TrustAgent/AndroidManifest.xml
index b499a73..1f5e6e6 100644
--- a/TrustAgent/AndroidManifest.xml
+++ b/TrustAgent/AndroidManifest.xml
@@ -16,26 +16,72 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.car.trust">
+ <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
+
+ <!-- Need Bluetooth LE -->
+ <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
+
<uses-permission android:name="android.permission.BLUETOOTH" />
- <uses-sdk android:minSdkVersion="10" android:targetSdkVersion="17"/>
+ <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
+ <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+
+ <!-- Needed to unlock user -->
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+ <uses-permission android:name="android.permission.MANAGE_USERS" />
<uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
<uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
+
<application android:label="@string/app_name">
<service
- android:name=".CarBluetoothTrustAgent"
+ android:name=".CarBleTrustAgent"
android:label="@string/app_name"
android:permission="android.permission.BIND_TRUST_AGENT"
+ android:directBootAware="true"
android:exported="true">
<intent-filter>
<action android:name="android.service.trust.TrustAgentService" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
+ <!-- Warning: the meta data must be included if the service is direct boot aware.
+ If not included, the device will crash before boot completes. Rendering the
+ device unusable. -->
+ <meta-data android:name="android.service.trust.trustagent"
+ android:resource="@xml/car_sample_trust_agent"/>
</service>
- <receiver android:name=".BootupReceiver">
+
+ <!-- CarUnlockService needs to be direct boot aware, since the trust agent
+ binds to it during direct boot.-->
+ <service android:name=".CarUnlockService"
+ android:directBootAware="true">
+ <!-- Warning: the meta data must be included if the service is direct boot aware.
+ If not included, the device will crash before boot completes. Rendering the device
+ unusable. -->
+ <meta-data android:name="android.service.trust.trustagent"
+ android:resource="@xml/car_sample_trust_agent"/>
+ </service>
+
+ <service android:name=".CarEnrolmentService"/>
+
+ <activity
+ android:name=".MainActivity"
+ android:label="@string/app_name"
+ android:exported="true"
+ android:launchMode="singleInstance">
<intent-filter>
- <action android:name="android.intent.action.BOOT_COMPLETED" />
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
- </receiver>
+ </activity>
+
+ <activity android:name=".CarEnrolmentActivity"
+ android:exported="false" />
+
+ <activity android:name=".PhoneEnrolmentActivity"
+ android:exported="false" />
+
+ <activity android:name=".PhoneUnlockActivity"
+ android:exported="false" />
</application>
</manifest>
diff --git a/TrustAgent/res/layout/car_client.xml b/TrustAgent/res/layout/car_client.xml
new file mode 100644
index 0000000..c5a27f0
--- /dev/null
+++ b/TrustAgent/res/layout/car_client.xml
@@ -0,0 +1,47 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:weightSum="1">
+
+ <ScrollView
+ android:id="@+id/scroll"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="vertical"
+ android:layout_weight="0.70"
+ android:fillViewport="true">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/textfield"/>
+ </ScrollView>
+ <Button
+ android:id="@+id/start_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/start_advertising"
+ android:layout_weight="0.15"/>
+ <Button
+ android:id="@+id/revoke_trust_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/revoke_trust"
+ android:layout_weight="0.15"/>
+</LinearLayout>
diff --git a/TrustAgent/res/layout/main_app.xml b/TrustAgent/res/layout/main_app.xml
new file mode 100644
index 0000000..5153ca8
--- /dev/null
+++ b/TrustAgent/res/layout/main_app.xml
@@ -0,0 +1,37 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:weightSum="1">
+
+ <Button android:id="@+id/car_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0.20"
+ android:text="@string/car"/>
+ <Button android:id="@+id/phone_enrolment_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0.20"
+ android:text="@string/enrollment_client"/>
+ <Button android:id="@+id/phone_unlock_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0.20"
+ android:text="@string/unlock_client"/>
+</LinearLayout>
diff --git a/TrustAgent/res/layout/phone_client.xml b/TrustAgent/res/layout/phone_client.xml
new file mode 100644
index 0000000..41ead63
--- /dev/null
+++ b/TrustAgent/res/layout/phone_client.xml
@@ -0,0 +1,45 @@
+<?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.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:weightSum="1">
+ <ScrollView
+ android:id="@+id/scroll"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:scrollbars="vertical"
+ android:layout_weight="0.60"
+ android:fillViewport="true">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:id="@+id/output"/>
+ </ScrollView>
+ <Button
+ android:id="@+id/ble_scan_btn"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/start_scanning"
+ android:layout_weight="0.20"/>
+ <Button
+ android:id="@+id/action_button"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_weight="0.20"/>
+</LinearLayout>
diff --git a/TrustAgent/res/values/strings.xml b/TrustAgent/res/values/strings.xml
index 3184184..d23a4b3 100644
--- a/TrustAgent/res/values/strings.xml
+++ b/TrustAgent/res/values/strings.xml
@@ -1,21 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
-<!--
- ~ Copyright (C) 2016 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
- -->
+<!-- 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="app_name">CarBluetoothTrustAgent</string>
- <string name="trust_granted_explanation">Paired Device was found</string>
-</resources>
\ No newline at end of file
+ <string name="app_name">CarBleTrustAgent</string>
+ <string name="trust_granted_explanation">Unlock via escrow token, now granting trust</string>
+
+ <!-- service/characteristics uuid for unlocking a device -->
+ <string name="unlock_service_uuid">5e2a68a1-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="unlock_escrow_token_uiid">5e2a68a2-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="unlock_handle_uiid">5e2a68a3-27be-43f9-8d1e-4546976fabd7</string>
+
+ <!-- service/characteristics uuid for adding new escrow token -->
+ <string name="enrollment_service_uuid">5e2a68a4-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="enrollment_handle_uuid">5e2a68a5-27be-43f9-8d1e-4546976fabd7</string>
+ <string name="enrollment_token_uuid">5e2a68a6-27be-43f9-8d1e-4546976fabd7</string>
+
+ <string name="pref_key_token_handle">token-handle-key</string>
+ <string name="pref_key_escrow_token">escrow-token-key</string>
+
+ <string name="unlock_button">Unlock Car</string>
+ <string name="enroll_button">Enroll New Token</string>
+
+ <string name="start_advertising">Start Advertising</string>
+ <string name="revoke_trust">Revoke Trust</string>
+
+ <string name="car">Car</string>
+ <string name="enrollment_client">Phone Enrolment Client</string>
+ <string name="unlock_client">Phone Unlock Client</string>
+ <string name="start_scanning">Start Scanning</string>
+</resources>
diff --git a/TrustAgent/res/xml/car_sample_trust_agent.xml b/TrustAgent/res/xml/car_sample_trust_agent.xml
new file mode 100644
index 0000000..0c7d91d
--- /dev/null
+++ b/TrustAgent/res/xml/car_sample_trust_agent.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
+ -->
+<trust-agent xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:priv-android="http://schemas.android.com/apk/prv/res/android"
+ android:settingsActivity=".MainActivity"
+ priv-android:unlockProfile="true" />
diff --git a/TrustAgent/src/com/android/car/trust/BootupReceiver.java b/TrustAgent/src/com/android/car/trust/BootupReceiver.java
deleted file mode 100644
index 352003f..0000000
--- a/TrustAgent/src/com/android/car/trust/BootupReceiver.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2016 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.trust;
-
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.util.Log;
-
-public class BootupReceiver extends BroadcastReceiver {
- private static final String TAG = "CarBTTrustAgent";
-
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "ACTION_BOOT_COMPLETED");
- }
- context.startService(new Intent(context, CarBluetoothTrustAgent.class));
- }
- }
-}
diff --git a/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java b/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java
new file mode 100644
index 0000000..99bbf22
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2016 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.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothManager;
+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.Handler;
+import android.os.IBinder;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.service.trust.TrustAgentService;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import com.android.car.trust.comms.SimpleBleServer;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A sample trust agent that demonstrates how to use the escrow token unlock APIs. </p>
+ *
+ * This trust agent runs during direct boot and binds to a BLE service that listens for remote
+ * devices to trigger an unlock.
+ */
+public class CarBleTrustAgent extends TrustAgentService {
+ public static final String ACTION_REVOKE_TRUST = "revoke-trust-action";
+ public static final String ACTION_ADD_TOKEN = "add-token-action";
+ public static final String ACTION_IS_TOKEN_ACTIVE = "is-token-active-action";
+ public static final String ACTION_REMOVE_TOKEN = "remove-token-action";
+ public static final String ACTION_UNLOCK_DEVICE = "unlock-device-action";
+
+ public static final String ACTION_TOKEN_STATUS_RESULT = "token-status-result-action";
+ public static final String ACTION_ADD_TOKEN_RESULT = "add-token-result-action";
+
+ public static final String INTENT_EXTRA_ESCROW_TOKEN = "extra-escrow-token";
+ public static final String INTENT_EXTRA_TOKEN_HANDLE = "extra-token-handle";
+ public static final String INTENT_EXTRA_TOKEN_STATUS = "extra-token-status";
+
+
+ private static final String TAG = "CarBleTrustAgent";
+
+ private static final long TRUST_DURATION_MS = TimeUnit.MINUTES.toMicros(5);
+ private static final long BLE_RETRY_MS = TimeUnit.SECONDS.toMillis(1);
+
+ private CarUnlockService mCarUnlockService;
+ private LocalBroadcastManager mLocalBroadcastManager;
+
+ private boolean mBleServiceBound;
+
+ // We cannot directly bind to TrustAgentService since the onBind method is final.
+ // As a result, we communicate with the various UI components using a LocalBroadcastManager.
+ private final BroadcastReceiver mTrustEventReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Received broadcast: " + action);
+ }
+ if (ACTION_REVOKE_TRUST.equals(action)) {
+ revokeTrust();
+ } else if (ACTION_ADD_TOKEN.equals(action)) {
+ byte[] token = intent.getByteArrayExtra(INTENT_EXTRA_ESCROW_TOKEN);
+ addEscrowToken(token, getCurrentUserHandle());
+ } else if (ACTION_IS_TOKEN_ACTIVE.equals(action)) {
+ long handle = intent.getLongExtra(INTENT_EXTRA_TOKEN_HANDLE, -1);
+ isEscrowTokenActive(handle, getCurrentUserHandle());
+ } else if (ACTION_REMOVE_TOKEN.equals(action)) {
+ long handle = intent.getLongExtra(INTENT_EXTRA_TOKEN_HANDLE, -1);
+ removeEscrowToken(handle, getCurrentUserHandle());
+ }
+ }
+ };
+
+ @Override
+ public void onTrustTimeout() {
+ super.onTrustTimeout();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onTrustTimeout(): timeout expired");
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Bluetooth trust agent starting up");
+ }
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_REVOKE_TRUST);
+ filter.addAction(ACTION_ADD_TOKEN);
+ filter.addAction(ACTION_IS_TOKEN_ACTIVE);
+ filter.addAction(ACTION_REMOVE_TOKEN);
+
+ mLocalBroadcastManager = LocalBroadcastManager.getInstance(this /* context */);
+ mLocalBroadcastManager.registerReceiver(mTrustEventReceiver, filter);
+
+ // If the user is already unlocked, don't bother starting the BLE service.
+ UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+ if (!um.isUserUnlocked()) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "User locked, will now bind CarUnlockService");
+ }
+ Intent intent = new Intent(this, CarUnlockService.class);
+
+ bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ } else {
+ setManagingTrust(true);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Car Trust agent shutting down");
+ }
+ mLocalBroadcastManager.unregisterReceiver(mTrustEventReceiver);
+
+ // Unbind the service to avoid leaks from BLE stack.
+ if (mBleServiceBound) {
+ unbindService(mServiceConnection);
+ }
+ super.onDestroy();
+ }
+
+ private SimpleBleServer.ConnectionListener mConnectionListener
+ = new SimpleBleServer.ConnectionListener() {
+ @Override
+ public void onServerStarted() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "BLE server started");
+ }
+ }
+
+ @Override
+ public void onServerStartFailed(int errorCode) {
+ Log.w(TAG, "BLE server failed to start. Error Code: " + errorCode);
+ }
+
+ @Override
+ public void onDeviceConnected(BluetoothDevice device) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "BLE device connected. Name: " + device.getName()
+ + " Address: " + device.getAddress());
+ }
+ }
+ };
+
+ private CarUnlockService.UnlockServiceCallback mUnlockCallback
+ = new CarUnlockService.UnlockServiceCallback() {
+ @Override
+ public void unlockDevice(byte[] token, long handle) {
+ unlock(token, handle);
+ }
+ };
+
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+
+ public void onServiceConnected(ComponentName className, IBinder service) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "CarUnlockService connected");
+ }
+
+ mBleServiceBound = true;
+ CarUnlockService.UnlockServiceBinder binder
+ = (CarUnlockService.UnlockServiceBinder) service;
+ mCarUnlockService = binder.getService();
+ mCarUnlockService.addUnlockServiceCallback(mUnlockCallback);
+ mCarUnlockService.addConnectionListener(mConnectionListener);
+ maybeStartBleUnlockService();
+ }
+
+ public void onServiceDisconnected(ComponentName arg0) {
+ mCarUnlockService = null;
+ mBleServiceBound = false;
+ }
+
+ };
+
+ private void maybeStartBleUnlockService() {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Trying to open a Ble GATT server");
+ }
+
+ BluetoothManager btManager =
+ (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+ BluetoothGattServer mGattServer
+ = btManager.openGattServer(this, new BluetoothGattServerCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothDevice device, int status, int newState) {
+ super.onConnectionStateChange(device, status, newState);
+ }
+ });
+
+ // The BLE stack is started up before the trust agent service, however Gatt capabilities
+ // might not be ready just yet. Keep trying until a GattServer can open up before proceeding
+ // to start the rest of the BLE services.
+ if (mGattServer == null) {
+ Log.e(TAG, "Gatt not available, will try again...in " + BLE_RETRY_MS + "ms");
+
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ maybeStartBleUnlockService();
+ }
+ }, BLE_RETRY_MS);
+ } else {
+ mGattServer.close();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "GATT available, starting up UnlockService");
+ }
+ mCarUnlockService.start();
+ }
+ }
+
+ private void unlock(byte[] token, long handle) {
+ UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "About to unlock user. Current handle: " + handle
+ + " Time: " + System.currentTimeMillis());
+ }
+ unlockUserWithToken(handle, token, getCurrentUserHandle());
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Attempted to unlock user, is user unlocked? " + um.isUserUnlocked()
+ + " Time: " + System.currentTimeMillis());
+ }
+ setManagingTrust(true);
+
+ if (um.isUserUnlocked()) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, getString(R.string.trust_granted_explanation));
+ }
+ grantTrust("Granting trust from escrow token",
+ TRUST_DURATION_MS, FLAG_GRANT_TRUST_DISMISS_KEYGUARD);
+ // Trust has been granted, disable the BLE server. This trust agent service does
+ // not need to receive additional BLE data.
+ unbindService(mServiceConnection);
+ }
+ }
+
+ @Override
+ public void onEscrowTokenRemoved(long handle, boolean successful) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onEscrowTokenRemoved. Handle: " + handle + " successful? " + successful);
+ }
+ }
+
+ @Override
+ public void onEscrowTokenStateReceived(long handle, int tokenState) {
+ boolean isActive = tokenState == TOKEN_STATE_ACTIVE;
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Token handle: " + handle + " isActive: " + isActive);
+ }
+
+ Intent intent = new Intent();
+ intent.setAction(ACTION_TOKEN_STATUS_RESULT);
+ intent.putExtra(INTENT_EXTRA_TOKEN_STATUS, isActive);
+
+ mLocalBroadcastManager.sendBroadcast(intent);
+ }
+
+ @Override
+ public void onEscrowTokenAdded(byte[] token, long handle, UserHandle user) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onEscrowTokenAdded, handle: " + handle);
+ }
+
+ Intent intent = new Intent();
+ intent.setAction(ACTION_ADD_TOKEN_RESULT);
+ intent.putExtra(INTENT_EXTRA_TOKEN_HANDLE, handle);
+
+ mLocalBroadcastManager.sendBroadcast(intent);
+ }
+
+ private UserHandle getCurrentUserHandle() {
+ return UserHandle.of(UserHandle.myUserId());
+ }
+}
diff --git a/TrustAgent/src/com/android/car/trust/CarBluetoothTrustAgent.java b/TrustAgent/src/com/android/car/trust/CarBluetoothTrustAgent.java
deleted file mode 100644
index fa52e1b..0000000
--- a/TrustAgent/src/com/android/car/trust/CarBluetoothTrustAgent.java
+++ /dev/null
@@ -1,190 +0,0 @@
-/*
- * Copyright (C) 2016 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.trust;
-
-import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
-import android.bluetooth.BluetoothManager;
-import android.content.BroadcastReceiver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.service.trust.TrustAgentService;
-import android.util.Log;
-
-import java.util.HashMap;
-import java.util.concurrent.TimeUnit;
-
-/**
- * A simple trust agent that grants trust when a paired bluetooth device is connected.
- */
-public class CarBluetoothTrustAgent extends TrustAgentService {
- private static final String TAG = "CarBTTrustAgent";
- private static final long TRUST_DURATION_MS = TimeUnit.MINUTES.toMicros(5);
-
- private String mTrustGrantedMessage;
-
- private BluetoothAdapter mBluetoothAdapter;
-
- // List of paired devices
- private HashMap<String, BluetoothDevice> mBondedDeviceMap = new HashMap<>();
-
- @Override
- public void onCreate() {
- super.onCreate();
-
- mTrustGrantedMessage = getString(R.string.trust_granted_explanation);
-
- registerReceiver(mBtReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_CONNECTED));
- registerReceiver(mBtReceiver, new IntentFilter(BluetoothDevice.ACTION_ACL_DISCONNECTED));
- registerReceiver(mBtReceiver, new IntentFilter(BluetoothDevice.ACTION_BOND_STATE_CHANGED));
- registerReceiver(mBtReceiver, new IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED));
-
- registerReceiver(mSreenOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_ON));
- registerReceiver(mSreenOnOffReceiver, new IntentFilter(Intent.ACTION_SCREEN_OFF));
-
- setManagingTrust(true);
- mBluetoothAdapter = ((BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE))
- .getAdapter();
-
- // Bonded/paired devices are only returned if bluetooth is enabled.
- // If not enabled, wait for ACTION_STATE_CHANGED to get bonded/paired devices.
- if (mBluetoothAdapter.isEnabled()) {
- updateBondedDevices();
- }
- }
-
- @Override
- public void onTrustTimeout() {
- super.onTrustTimeout();
- // If there is still a connected device, we can continue granting trust.
- for (BluetoothDevice device : mBondedDeviceMap.values()) {
- if (device.isConnected()) {
- grantTrust();
- break;
- }
- }
- }
-
- private final BroadcastReceiver mSreenOnOffReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "ACTION_SCREEN_ON");
- }
- updateTrustStatus();
- } else if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "ACTION_SCREEN_OFF: revoking trust");
- }
- revokeTrust();
- }
- }
- };
-
- /**
- * The BroadcastReceiver that listens for bluetooth broadcasts.
- */
- private final BroadcastReceiver mBtReceiver = new BroadcastReceiver() {
- @Override
- public void onReceive(Context context, Intent intent) {
- String action = intent.getAction();
- BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
-
- if (BluetoothDevice.ACTION_ACL_CONNECTED.equals(action)) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "ACTION_ACL_CONNECTED device: " + device.getName());
- }
- int state = device.getBondState();
- if (state == BluetoothDevice.BOND_BONDED) {
- mBondedDeviceMap.put(device.getAddress(), device);
- updateTrustStatus();
- }
-
- } else if (BluetoothDevice.ACTION_ACL_DISCONNECTED.equals(action)) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "ACTION_ACL_DISCONNECTED device: " + device.getName());
- }
-
- updateTrustStatus();
- } else if (BluetoothDevice.ACTION_BOND_STATE_CHANGED.equals(action)) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "ACTION_BOND_STATE_CHANGED device: " + device.getName());
- }
-
- int state = device.getBondState();
- if (state == BluetoothDevice.BOND_BONDED) {
- mBondedDeviceMap.put(device.getAddress(), device);
- grantTrust();
- } else if (state == BluetoothDevice.BOND_NONE) {
- mBondedDeviceMap.remove(device.getAddress());
- updateTrustStatus();
- }
- } else if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
- int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, 0);
- // Bluetooth was just turned on.
- if (state == BluetoothAdapter.STATE_ON) {
- updateBondedDevices();
- }
- }
- }
- };
-
- private void updateBondedDevices() {
- for (BluetoothDevice d : mBluetoothAdapter.getBondedDevices()) {
- mBondedDeviceMap.put(d.getAddress(), d);
-
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "ACTION_STATE_CHANGED device: " + d.getName());
- }
- }
- updateTrustStatus();
- }
-
- private void updateTrustStatus() {
- // Nothing is paired, this trust agent should not grant trust
- if (mBondedDeviceMap.size() == 0) {
- revokeTrust();
- }
-
- boolean deviceConnected = false;
- for (BluetoothDevice device : mBondedDeviceMap.values()) {
- if (device.isConnected()) {
- deviceConnected = true;
- break;
- }
- }
- if (!deviceConnected) {
- revokeTrust();
- } else {
- grantTrust();
- }
- }
-
- private void grantTrust() {
- try {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
- Log.d(TAG, "Granting Trust");
- }
- grantTrust(mTrustGrantedMessage, TRUST_DURATION_MS,
- TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD);
- } catch (IllegalStateException e) {
- Log.d(TAG, "IllegalStateException: " + e.getMessage());
- }
- }
-}
diff --git a/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java b/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java
new file mode 100644
index 0000000..89e68fe
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java
@@ -0,0 +1,246 @@
+/*
+ * 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.android.car.trust;
+
+import android.app.Activity;
+import android.bluetooth.BluetoothDevice;
+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.content.SharedPreferences;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.preference.PreferenceManager;
+import android.support.v4.content.LocalBroadcastManager;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import com.android.car.trust.CarEnrolmentService.EnrolmentCallback;
+import com.android.car.trust.comms.SimpleBleServer.ConnectionListener;
+
+import static com.android.car.trust.CarBleTrustAgent.ACTION_ADD_TOKEN_RESULT;
+import static com.android.car.trust.CarBleTrustAgent.ACTION_TOKEN_STATUS_RESULT;
+import static com.android.car.trust.CarBleTrustAgent.INTENT_EXTRA_TOKEN_HANDLE;
+import static com.android.car.trust.CarBleTrustAgent.INTENT_EXTRA_TOKEN_STATUS;
+
+/**
+ * Setup activity that binds {@link CarEnrolmentService} and starts the enrolment process.
+ */
+public class CarEnrolmentActivity extends Activity {
+ private static String TAG = "CarEnrolment";
+ private static String SP_HANDLE_KEY = "sp-test";
+
+ private TextView mOutputText;
+ private TextView mStartButton;
+
+ private long mHandle;
+
+ private CarEnrolmentService mEnrolmentService;
+
+ private BluetoothDevice mDevice;
+
+ private boolean mServiceBound;
+
+ private LocalBroadcastManager mLocalBroadcastManager;
+
+ private SharedPreferences mPrefs;
+
+ private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ intent.getPackage();
+
+ String action = intent.getAction();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Received broadcast: " + action);
+ }
+
+ if (ACTION_TOKEN_STATUS_RESULT.equals(action)) {
+ boolean tokenActive = intent.getBooleanExtra(INTENT_EXTRA_TOKEN_STATUS, false);
+ appendOutputText("Is token active? " + tokenActive + " handle: " + mHandle);
+ } else if (ACTION_ADD_TOKEN_RESULT.equals(action)) {
+ final long handle = intent.getLongExtra(INTENT_EXTRA_TOKEN_HANDLE, -1);
+
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mPrefs.edit().putLong(SP_HANDLE_KEY, handle).apply();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "stored new handle");
+ }
+ }
+ });
+
+ mEnrolmentService.sendHandle(handle, mDevice);
+ appendOutputText("Escrow Token Added. Handle: " + handle
+ + "\nLock and unlock the device to activate token");
+ }
+ }
+ };
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.car_client);
+ mOutputText = (TextView) findViewById(R.id.textfield);
+
+ final Intent intent = new Intent(this, CarEnrolmentService.class);
+
+ mPrefs = PreferenceManager.getDefaultSharedPreferences(this /* context */);
+
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(ACTION_TOKEN_STATUS_RESULT);
+ filter.addAction(ACTION_ADD_TOKEN_RESULT);
+
+ mLocalBroadcastManager = LocalBroadcastManager.getInstance(this /* context */);
+ mLocalBroadcastManager.registerReceiver(mReceiver, filter);
+
+ mStartButton = (Button) findViewById(R.id.start_button);
+ mStartButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // binding the service will start it if not started.
+ bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
+ }
+ });
+
+ Button revokeButton = (Button) findViewById(R.id.revoke_trust_button);
+ revokeButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(CarBleTrustAgent.ACTION_REVOKE_TRUST);
+ intent.setPackage(getPackageName());
+ sendBroadcast(intent);
+ }
+ });
+ }
+
+ private ServiceConnection mServiceConnection = new ServiceConnection() {
+ public void onServiceConnected(ComponentName className,
+ IBinder service) {
+ mServiceBound = true;
+ CarEnrolmentService.EnrolmentServiceBinder binder
+ = (CarEnrolmentService.EnrolmentServiceBinder) service;
+ mEnrolmentService = binder.getService();
+ mEnrolmentService.addEnrolmentCallback(mEnrolmentCallback);
+ mEnrolmentService.addConnectionListener(mConnectionListener);
+ mEnrolmentService.start();
+ }
+
+ public void onServiceDisconnected(ComponentName arg0) {
+ mEnrolmentService = null;
+ mServiceBound = false;
+ }
+ };
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ if (!mPrefs.contains(SP_HANDLE_KEY)) {
+ appendOutputText("No handles found.");
+ return;
+ }
+
+ try {
+ mHandle = mPrefs.getLong(SP_HANDLE_KEY, -1);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onResume, checking handle active: " + mHandle);
+ }
+ isTokenActive(mHandle);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Error checking if token is valid");
+ appendOutputText("Error checking if token is valid");
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (mServiceBound) {
+ unbindService(mServiceConnection);
+ }
+ super.onDestroy();
+ }
+
+ private void appendOutputText(final String text) {
+ runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mOutputText.append("\n" + text);
+ }
+ });
+ }
+
+ private ConnectionListener mConnectionListener = new ConnectionListener() {
+ @Override
+ public void onServerStarted() {
+ appendOutputText("Server started");
+ }
+
+ @Override
+ public void onServerStartFailed(int errorCode) {
+ appendOutputText("Server failed to start, error code: " + errorCode);
+ }
+
+ @Override
+ public void onDeviceConnected(BluetoothDevice device) {
+ mDevice = device;
+ appendOutputText("Device connected: " + device.getName()
+ + " addr: " + device.getAddress());
+ }
+ };
+
+ private EnrolmentCallback mEnrolmentCallback = new EnrolmentCallback() {
+ @Override
+ public void onEnrolmentDataReceived(byte[] token) {
+ appendOutputText("Enrolment data received ");
+ addEscrowToken(token);
+ }
+ };
+
+ private void isTokenActive(long handle) throws RemoteException {
+ Intent intent = new Intent();
+ intent.setAction(CarBleTrustAgent.ACTION_IS_TOKEN_ACTIVE);
+ intent.putExtra(CarBleTrustAgent.INTENT_EXTRA_TOKEN_HANDLE, handle);
+
+ mLocalBroadcastManager.sendBroadcast(intent);
+ }
+
+ private void addEscrowToken(byte[] token) {
+ long handle;
+
+ if (mPrefs.contains(SP_HANDLE_KEY)) {
+ handle = mPrefs.getLong(SP_HANDLE_KEY, -1);
+ appendOutputText("Removing old token, handle value: " + handle);
+ Intent intent = new Intent();
+ intent.setAction(CarBleTrustAgent.ACTION_REMOVE_TOKEN);
+ intent.putExtra(CarBleTrustAgent.INTENT_EXTRA_TOKEN_HANDLE, handle);
+ mLocalBroadcastManager.sendBroadcast(intent);
+ }
+
+ Intent intent = new Intent();
+ intent.setAction(CarBleTrustAgent.ACTION_ADD_TOKEN);
+ intent.putExtra(CarBleTrustAgent.INTENT_EXTRA_ESCROW_TOKEN, token);
+
+ mLocalBroadcastManager.sendBroadcast(intent);
+ }
+}
diff --git a/TrustAgent/src/com/android/car/trust/CarEnrolmentService.java b/TrustAgent/src/com/android/car/trust/CarEnrolmentService.java
new file mode 100644
index 0000000..e6bbee2
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarEnrolmentService.java
@@ -0,0 +1,129 @@
+/*
+ * 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.android.car.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.util.Log;
+import com.android.car.trust.comms.SimpleBleServer;
+
+import java.util.HashSet;
+import java.util.UUID;
+
+/**
+ * A service that receives escrow token enrollment requests from remote devices.
+ */
+public class CarEnrolmentService extends SimpleBleServer {
+ private static final String TAG = "CarEnrolmentService";
+
+ public interface EnrolmentCallback {
+ void onEnrolmentDataReceived(byte[] token);
+ }
+
+ private BluetoothGattService mEnrolmentService;
+ private BluetoothGattCharacteristic mEnrolmentEscrowToken;
+ private BluetoothGattCharacteristic mEnrolmentTokenHandle;
+
+ private HashSet<EnrolmentCallback> mCallbacks;
+
+ private final IBinder mBinder = new EnrolmentServiceBinder();
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ mCallbacks = new HashSet<>();
+ setupEnrolmentService();
+ }
+
+ public void start() {
+ ParcelUuid uuid = new ParcelUuid(
+ UUID.fromString(getString(R.string.enrollment_service_uuid)));
+ start(uuid, mEnrolmentService);
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothDevice device,
+ int requestId, BluetoothGattCharacteristic characteristic,
+ boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+ if (characteristic.getUuid().equals(mEnrolmentEscrowToken.getUuid())) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Enrolment token received, value: " + Utils.getLong(value));
+ }
+
+ for (EnrolmentCallback callback : mCallbacks) {
+ callback.onEnrolmentDataReceived(value);
+ }
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothDevice device,
+ int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+ //Enrolment service should not have any read requests.
+ }
+
+ public void addEnrolmentCallback(EnrolmentCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ public void sendHandle(long handle, BluetoothDevice device) {
+ mEnrolmentTokenHandle.setValue(Utils.getBytes(handle));
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Sending notification for EscrowToken Handle");
+ }
+ mGattServer.notifyCharacteristicChanged(device,
+ mEnrolmentTokenHandle, false /* confirm */);
+ }
+
+ public class EnrolmentServiceBinder extends Binder {
+ public CarEnrolmentService getService() {
+ return CarEnrolmentService.this;
+ }
+ }
+
+ // Create services and characteristics for enrolling new unlocking escrow tokens
+ private void setupEnrolmentService() {
+ mEnrolmentService = new BluetoothGattService(
+ UUID.fromString(getString(R.string.enrollment_service_uuid)),
+ BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+ // Characteristic to describe the escrow token being used for unlock
+ mEnrolmentEscrowToken = new BluetoothGattCharacteristic(
+ UUID.fromString(getString(R.string.enrollment_token_uuid)),
+ BluetoothGattCharacteristic.PROPERTY_WRITE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+ // Characteristic to describe the handle being used for this escrow token
+ mEnrolmentTokenHandle = new BluetoothGattCharacteristic(
+ UUID.fromString(getString(R.string.enrollment_handle_uuid)),
+ BluetoothGattCharacteristic.PROPERTY_NOTIFY,
+ BluetoothGattCharacteristic.PERMISSION_READ);
+
+ mEnrolmentService.addCharacteristic(mEnrolmentEscrowToken);
+ mEnrolmentService.addCharacteristic(mEnrolmentTokenHandle);
+ }
+}
diff --git a/TrustAgent/src/com/android/car/trust/CarUnlockService.java b/TrustAgent/src/com/android/car/trust/CarUnlockService.java
new file mode 100644
index 0000000..0c1bc89
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarUnlockService.java
@@ -0,0 +1,157 @@
+/*
+ * 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.android.car.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Intent;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.util.Log;
+import com.android.car.trust.comms.SimpleBleServer;
+
+import java.util.UUID;
+
+/**
+ * A service that receives unlock requests from remote devices.
+ */
+public class CarUnlockService extends SimpleBleServer {
+ /**
+ * A callback to receives callback
+ */
+ public interface UnlockServiceCallback {
+ void unlockDevice(byte[] token, long handle);
+ }
+
+ private static final String TAG = "CarUnlockService";
+
+ private BluetoothGattService mUnlockService;
+ private BluetoothGattCharacteristic mUnlockEscrowToken;
+ private BluetoothGattCharacteristic mUnlockTokenHandle;
+
+ private UnlockServiceCallback mCallback;
+
+ private byte[] mCurrentToken;
+ private Long mCurrentHandle;
+
+ private final IBinder mBinder = new UnlockServiceBinder();
+
+ public class UnlockServiceBinder extends Binder {
+ public CarUnlockService getService() {
+ return CarUnlockService.this;
+ }
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "CarUnlockService starting up, creating BLE service");
+ }
+ setupUnlockService();
+ }
+
+ /**
+ * Start advertising the BLE unlock service
+ */
+ public void start() {
+ ParcelUuid uuid = new ParcelUuid(
+ UUID.fromString(getString(R.string.unlock_service_uuid)));
+ start(uuid, mUnlockService);
+ }
+
+ public void addUnlockServiceCallback(UnlockServiceCallback callback) {
+ mCallback = callback;
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return mBinder;
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothDevice device,
+ int requestId, BluetoothGattCharacteristic characteristic,
+ boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
+ UUID uuid = characteristic.getUuid();
+
+ if (uuid.equals(mUnlockTokenHandle.getUuid())) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unlock handle received, value: " + Utils.getLong(value));
+ }
+ mCurrentHandle = Utils.getLong(value);
+ unlockDataReceived();
+ } else if (uuid.equals(mUnlockEscrowToken.getUuid())) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unlock escrow token received, value: " + Utils.getLong(value));
+ }
+ mCurrentToken = value;
+ unlockDataReceived();
+ }
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothDevice device,
+ int requestId, int offset, BluetoothGattCharacteristic characteristic) {
+ // The BLE unlock service should not receive any read requests.
+ }
+
+ private synchronized void unlockDataReceived() {
+ // If any piece of the unlocking data is not received, then do not unlock.
+ if (mCurrentHandle == null || mCurrentToken == null) {
+ return;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Handle and token both received, requesting unlock. Time: "
+ + System.currentTimeMillis());
+ }
+ // Both the handle and token has been received, try to unlock the device.
+
+
+ mCallback.unlockDevice(mCurrentToken, mCurrentHandle);
+
+ // Once we've notified the client of the unlocking data, clear it out.
+ mCurrentToken = null;
+ mCurrentHandle = null;
+ }
+
+
+ // Create services and characteristics to receive tokens and handles for unlocking the device.
+ private void setupUnlockService() {
+ mUnlockService = new BluetoothGattService(
+ UUID.fromString(getString(R.string.unlock_service_uuid)),
+ BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+ // Characteristic to describe the escrow token being used for unlock
+ mUnlockEscrowToken = new BluetoothGattCharacteristic(
+ UUID.fromString(getString(R.string.unlock_escrow_token_uiid)),
+ BluetoothGattCharacteristic.PROPERTY_WRITE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+ // Characteristic to describe the handle being used for this escrow token
+ mUnlockTokenHandle = new BluetoothGattCharacteristic(
+ UUID.fromString(getString(R.string.unlock_handle_uiid)),
+ BluetoothGattCharacteristic.PROPERTY_WRITE,
+ BluetoothGattCharacteristic.PERMISSION_WRITE);
+
+ mUnlockService.addCharacteristic(mUnlockEscrowToken);
+ mUnlockService.addCharacteristic(mUnlockTokenHandle);
+ }
+
+}
diff --git a/TrustAgent/src/com/android/car/trust/MainActivity.java b/TrustAgent/src/com/android/car/trust/MainActivity.java
new file mode 100644
index 0000000..8c1348b
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/MainActivity.java
@@ -0,0 +1,94 @@
+/*
+ * 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.android.car.trust;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+
+/**
+ * Selects whether the device is started as the car or remote device.
+ */
+public class MainActivity extends Activity {
+ private static final int FINE_LOCATION_REQUEST_CODE = 13;
+
+ private Button mCarEnrolmentButton;
+ private Button mPhoneEnrolmentButton;
+ private Button mPhoneUnlockButton;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.main_app);
+ mCarEnrolmentButton = (Button) findViewById(R.id.car_button);
+ mPhoneEnrolmentButton = (Button) findViewById(R.id.phone_enrolment_button);
+ mPhoneUnlockButton = (Button) findViewById(R.id.phone_unlock_button);
+
+ mCarEnrolmentButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(MainActivity.this /* context */,
+ CarEnrolmentActivity.class);
+ startActivity(intent);
+ }
+ });
+
+ mPhoneEnrolmentButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(MainActivity.this /* context */,
+ PhoneEnrolmentActivity.class);
+ startActivity(intent);
+ }
+ });
+
+ mPhoneUnlockButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Intent intent = new Intent(MainActivity.this /* context */,
+ PhoneUnlockActivity.class);
+ startActivity(intent);
+ }
+ });
+
+ if (!checkPermissionGranted()) {
+ requestPermissions(new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION},
+ FINE_LOCATION_REQUEST_CODE);
+ // If location access isn't granted, BLE scanning will fail.
+ mCarEnrolmentButton.setEnabled(false);
+ mPhoneEnrolmentButton.setEnabled(false);
+ mPhoneUnlockButton.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void onRequestPermissionsResult(int requestCode,
+ String permissions[], int[] grantResults) {
+ if (requestCode == FINE_LOCATION_REQUEST_CODE && checkPermissionGranted()) {
+ mCarEnrolmentButton.setEnabled(true);
+ mPhoneEnrolmentButton.setEnabled(true);
+ mPhoneUnlockButton.setEnabled(true);
+ }
+ }
+
+ private boolean checkPermissionGranted() {
+ return checkSelfPermission(android.Manifest.permission.ACCESS_FINE_LOCATION)
+ == PackageManager.PERMISSION_GRANTED;
+ }
+}
\ No newline at end of file
diff --git a/TrustAgent/src/com/android/car/trust/PhoneEnrolmentActivity.java b/TrustAgent/src/com/android/car/trust/PhoneEnrolmentActivity.java
new file mode 100644
index 0000000..dac4b79
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/PhoneEnrolmentActivity.java
@@ -0,0 +1,46 @@
+/*
+ * 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.android.car.trust;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Activity to allow the user to add an escrow token to a remote device.
+ */
+public class PhoneEnrolmentActivity extends Activity {
+ private Button mScanButton;
+ private Button mEnrollButton;
+
+ private TextView mTextOutput;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.phone_client);
+
+ mScanButton = (Button) findViewById(R.id.ble_scan_btn);
+ mEnrollButton = (Button) findViewById(R.id.action_button);
+ mEnrollButton.setText(getString(R.string.enroll_button));
+
+ mTextOutput = (TextView) findViewById(R.id.output);
+
+ PhoneEnrolmentController controller = new PhoneEnrolmentController(this /* context */);
+ controller.bind(mTextOutput, mScanButton, mEnrollButton);
+ }
+}
diff --git a/TrustAgent/src/com/android/car/trust/PhoneEnrolmentController.java b/TrustAgent/src/com/android/car/trust/PhoneEnrolmentController.java
new file mode 100644
index 0000000..b887dc4
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/PhoneEnrolmentController.java
@@ -0,0 +1,209 @@
+/*
+ * 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.android.car.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.preference.PreferenceManager;
+import android.util.Base64;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import com.android.car.trust.comms.SimpleBleClient;
+
+import java.nio.ByteBuffer;
+import java.util.Random;
+import java.util.UUID;
+
+/**
+ * A controller that sets up a {@link SimpleBleClient} to connect to the BLE enrollment service.
+ * It also binds the UI components to control the enrollment process.
+ */
+public class PhoneEnrolmentController {
+ private static final String TAG = "PhoneEnrolmentCtlr";
+ private String mTokenHandleKey;
+ private String mEscrowTokenKey;
+
+ // BLE characteristics associated with the enrollment/add escrow token service.
+ private BluetoothGattCharacteristic mEnrolmentTokenHandle;
+ private BluetoothGattCharacteristic mEnrolmentEscrowToken;
+
+ private ParcelUuid mEnrolmentServiceUuid;
+
+ private SimpleBleClient mClient;
+ private Context mContext;
+
+ private TextView mTextView;
+ private Handler mHandler;
+
+ private Button mScanButton;
+ private Button mEnrolButton;
+
+ public PhoneEnrolmentController(Context context) {
+ mContext = context;
+
+ mTokenHandleKey = context.getString(R.string.pref_key_token_handle);
+ mEscrowTokenKey = context.getString(R.string.pref_key_escrow_token);
+
+ mClient = new SimpleBleClient(context);
+ mEnrolmentServiceUuid = new ParcelUuid(
+ UUID.fromString(mContext.getString(R.string.enrollment_service_uuid)));
+ mClient.addCallback(mCallback /* callback */);
+
+ mHandler = new Handler(mContext.getMainLooper());
+ }
+
+ /**
+ * Binds the views to the actions that can be performed by this controller.
+ *
+ * @param textView A text view used to display results from various BLE actions
+ * @param scanButton Button used to start scanning for available BLE devices.
+ * @param enrolButton Button used to send new escrow token to remote device.
+ */
+ public void bind(TextView textView, Button scanButton, Button enrolButton) {
+ mTextView = textView;
+ mScanButton = scanButton;
+ mEnrolButton = enrolButton;
+
+ mScanButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mClient.start(mEnrolmentServiceUuid);
+ }
+ });
+
+ mEnrolButton.setEnabled(false);
+ mEnrolButton.setAlpha(0.3f);
+ mEnrolButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ appendOutputText("Sending new escrow token to remote device");
+
+ byte[] token = generateEscrowToken();
+ sendEnrolmentRequest(token);
+
+ // WARNING: Store the token so it can be used later for unlocking. This token
+ // should NEVER be stored on the device that is being unlocked. It should
+ // always be securely stored on a remote device that will trigger the unlock.
+ storeToken(token);
+ }
+ });
+ }
+
+ /**
+ * @return A random byte array that is used as the escrow token for remote device unlock.
+ */
+ private byte[] generateEscrowToken() {
+ Random random = new Random();
+ ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+ buffer.putLong(0, random.nextLong());
+ return buffer.array();
+ }
+
+ private void sendEnrolmentRequest(byte[] token) {
+ mEnrolmentEscrowToken.setValue(token);
+ mClient.writeCharacteristic(mEnrolmentEscrowToken);
+ storeToken(token);
+ }
+
+ private SimpleBleClient.ClientCallback mCallback = new SimpleBleClient.ClientCallback() {
+ @Override
+ public void onDeviceConnected(BluetoothDevice device) {
+ appendOutputText("Device connected: " + device.getName()
+ + " addr: " + device.getAddress());
+ }
+
+ @Override
+ public void onDeviceDisconnected() {
+ appendOutputText("Device disconnected");
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCharacteristicChanged: " + Utils.getLong(characteristic.getValue()));
+ }
+
+ if (characteristic.getUuid().equals(mEnrolmentTokenHandle.getUuid())) {
+ // Store the new token handle that the BLE server is sending us. This required
+ // to unlock the device.
+ long handle = Utils.getLong(characteristic.getValue());
+ storeHandle(handle);
+ appendOutputText("Token handle received: " + handle);
+ }
+ }
+
+ @Override
+ public void onServiceDiscovered(BluetoothGattService service) {
+ if (!service.getUuid().equals(mEnrolmentServiceUuid.getUuid())) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Service UUID: " + service.getUuid()
+ + " does not match Enrolment UUID " + mEnrolmentServiceUuid.getUuid());
+ }
+ return;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Enrolment Service # characteristics: "
+ + service.getCharacteristics().size());
+ }
+ mEnrolmentEscrowToken
+ = Utils.getCharacteristic(R.string.enrollment_token_uuid, service, mContext);
+ mEnrolmentTokenHandle
+ = Utils.getCharacteristic(R.string.enrollment_handle_uuid, service, mContext);
+ mClient.setCharacteristicNotification(mEnrolmentTokenHandle, true /* enable */);
+ appendOutputText("Enrolment BLE client successfully connected");
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Services are now set up, allow users to enrol new escrow tokens.
+ mEnrolButton.setEnabled(true);
+ mEnrolButton.setAlpha(1.0f);
+ }
+ });
+ }
+ };
+
+ private void storeHandle(long handle) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ prefs.edit().putLong(mTokenHandleKey, handle).apply();
+ }
+
+ private void storeToken(byte[] token) {
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ String byteArray = Base64.encodeToString(token, Base64.DEFAULT);
+ prefs.edit().putString(mEscrowTokenKey, byteArray).apply();
+ }
+
+ private void appendOutputText(final String text) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mTextView.append("\n" + text);
+ }
+ });
+ }
+}
diff --git a/TrustAgent/src/com/android/car/trust/PhoneUnlockActivity.java b/TrustAgent/src/com/android/car/trust/PhoneUnlockActivity.java
new file mode 100644
index 0000000..767e1c5
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/PhoneUnlockActivity.java
@@ -0,0 +1,46 @@
+/*
+ * 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.android.car.trust;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.Button;
+import android.widget.TextView;
+
+/**
+ * Activity to allow the user to unlock a remote devices with the stored escrow token.
+ */
+public class PhoneUnlockActivity extends Activity {
+ private Button mScannButton;
+ private Button mUnlockButton;
+
+ private TextView mTextOutput;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.phone_client);
+
+ mScannButton = (Button) findViewById(R.id.ble_scan_btn);
+ mUnlockButton = (Button) findViewById(R.id.action_button);
+ mUnlockButton.setText(getString(R.string.unlock_button));
+
+ mTextOutput = (TextView) findViewById(R.id.output);
+
+ PhoneUnlockController controller = new PhoneUnlockController(this /* context */);
+ controller.bind(mTextOutput, mScannButton, mUnlockButton);
+ }
+}
diff --git a/TrustAgent/src/com/android/car/trust/PhoneUnlockController.java b/TrustAgent/src/com/android/car/trust/PhoneUnlockController.java
new file mode 100644
index 0000000..c956333
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/PhoneUnlockController.java
@@ -0,0 +1,174 @@
+/*
+ * 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.android.car.trust;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.preference.PreferenceManager;
+import android.util.Base64;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.TextView;
+import com.android.car.trust.comms.SimpleBleClient;
+
+import java.util.UUID;
+
+/**
+ * A controller that sets up a {@link SimpleBleClient} to connect to the BLE unlock service.
+ */
+public class PhoneUnlockController {
+ private static final String TAG = "PhoneUnlockController";
+
+ private String mTokenHandleKey;
+ private String mEscrowTokenKey;
+
+ // BLE characteristics associated with the enrolment/add escrow token service.
+ private BluetoothGattCharacteristic mUnlockTokenHandle;
+ private BluetoothGattCharacteristic mUnlockEscrowToken;
+
+ private ParcelUuid mUnlockServiceUuid;
+
+ private SimpleBleClient mClient;
+ private Context mContext;
+
+ private TextView mTextView;
+ private Handler mHandler;
+
+ private Button mScanButton;
+ private Button mUnlockButton;
+
+ public PhoneUnlockController(Context context) {
+ mContext = context;
+
+ mTokenHandleKey = context.getString(R.string.pref_key_token_handle);
+ mEscrowTokenKey = context.getString(R.string.pref_key_escrow_token);
+
+ mClient = new SimpleBleClient(context);
+ mUnlockServiceUuid = new ParcelUuid(
+ UUID.fromString(mContext.getString(R.string.unlock_service_uuid)));
+ mClient.addCallback(mCallback /* callback */);
+
+ mHandler = new Handler(mContext.getMainLooper());
+ }
+
+ /**
+ * Binds the views to the actions that can be performed by this controller.
+ *
+ * @param textView A text view used to display results from various BLE actions
+ * @param scanButton Button used to start scanning for available BLE devices.
+ * @param enrolButton Button used to send new escrow token to remote device.
+ */
+ public void bind(TextView textView, Button scanButton, Button enrolButton) {
+ mTextView = textView;
+ mScanButton = scanButton;
+ mUnlockButton = enrolButton;
+
+ mScanButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mClient.start(mUnlockServiceUuid);
+ }
+ });
+
+ mUnlockButton.setEnabled(false);
+ mUnlockButton.setAlpha(0.3f);
+ mUnlockButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ appendOutputText("Sending unlock token and handle to remote device");
+ sendUnlockRequest();
+ }
+ });
+ }
+
+ private void sendUnlockRequest() {
+ // Retrieve stored token and handle and write to remote device.
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+ long handle = prefs.getLong(mTokenHandleKey, -1);
+ byte[] token = Base64.decode(prefs.getString(mEscrowTokenKey, null), Base64.DEFAULT);
+
+ mUnlockEscrowToken.setValue(token);
+ mUnlockTokenHandle.setValue(Utils.getBytes(handle));
+
+ mClient.writeCharacteristic(mUnlockEscrowToken);
+ mClient.writeCharacteristic(mUnlockTokenHandle);
+ }
+
+ private SimpleBleClient.ClientCallback mCallback = new SimpleBleClient.ClientCallback() {
+ @Override
+ public void onDeviceConnected(BluetoothDevice device) {
+ appendOutputText("Device connected: " + device.getName()
+ + " addr: " + device.getAddress());
+ }
+
+ @Override
+ public void onDeviceDisconnected() {
+ appendOutputText("Device disconnected");
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ // Not expecting any characteristics changes for the unlocking client.
+ }
+
+ @Override
+ public void onServiceDiscovered(BluetoothGattService service) {
+ if (!service.getUuid().equals(mUnlockServiceUuid.getUuid())) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Service UUID: " + service.getUuid()
+ + " does not match Enrolment UUID " + mUnlockServiceUuid.getUuid());
+ }
+ return;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Unlock Service # characteristics: "
+ + service.getCharacteristics().size());
+ }
+ mUnlockEscrowToken
+ = Utils.getCharacteristic(R.string.unlock_escrow_token_uiid, service, mContext);
+ mUnlockTokenHandle
+ = Utils.getCharacteristic(R.string.unlock_handle_uiid, service, mContext);
+ appendOutputText("Unlock BLE client successfully connected");
+
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ // Services are now set up, allow users to enrol new escrow tokens.
+ mUnlockButton.setEnabled(true);
+ mUnlockButton.setAlpha(1.0f);
+ }
+ });
+ }
+ };
+
+ private void appendOutputText(final String text) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mTextView.append("\n" + text);
+ }
+ });
+ }
+}
diff --git a/TrustAgent/src/com/android/car/trust/Utils.java b/TrustAgent/src/com/android/car/trust/Utils.java
new file mode 100644
index 0000000..3e9181f
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/Utils.java
@@ -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
+ */
+package com.android.car.trust;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.content.Context;
+
+import java.nio.ByteBuffer;
+import java.util.UUID;
+
+public class Utils {
+
+ public static byte[] getBytes(long l) {
+ ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+ buffer.putLong(0, l);
+ return buffer.array();
+ }
+
+ public static long getLong(byte[] bytes) {
+ ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
+ buffer.put(bytes);
+ buffer.flip();
+ return buffer.getLong();
+ }
+
+ public static BluetoothGattCharacteristic getCharacteristic(int uuidRes,
+ BluetoothGattService service, Context context) {
+ return service.getCharacteristic(UUID.fromString(context.getString(uuidRes)));
+ }
+}
diff --git a/TrustAgent/src/com/android/car/trust/comms/SimpleBleClient.java b/TrustAgent/src/com/android/car/trust/comms/SimpleBleClient.java
new file mode 100644
index 0000000..77bf2d3
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/comms/SimpleBleClient.java
@@ -0,0 +1,380 @@
+/*
+ * 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.android.car.trust.comms;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCallback;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.content.Context;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.support.annotation.NonNull;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+/**
+ * A simple client that supports the scanning and connecting to available BLE devices. Should be
+ * used along with {@link SimpleBleServer}.
+ */
+public class SimpleBleClient {
+ public interface ClientCallback {
+ /**
+ * Called when a device that has a matching service UUID is found.
+ **/
+ void onDeviceConnected(BluetoothDevice device);
+
+ void onDeviceDisconnected();
+
+ void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic);
+
+ /**
+ * Called for each {@link BluetoothGattService} that is discovered on the
+ * {@link BluetoothDevice} after a matching scan result and connection.
+ *
+ * @param service {@link BluetoothGattService} that has been discovered.
+ */
+ void onServiceDiscovered(BluetoothGattService service);
+ }
+
+ /**
+ * Wrapper class to allow queuing of BLE actions. The BLE stack allows only one action to be
+ * executed at a time.
+ */
+ public static class BleAction {
+ public static final int ACTION_WRITE = 0;
+ public static final int ACTION_READ = 1;
+
+ private int mAction;
+ private BluetoothGattCharacteristic mCharacteristic;
+
+ public BleAction(BluetoothGattCharacteristic characteristic, int action) {
+ mAction = action;
+ mCharacteristic = characteristic;
+ }
+
+ public int getAction() {
+ return mAction;
+ }
+
+ public BluetoothGattCharacteristic getCharacteristic() {
+ return mCharacteristic;
+ }
+ }
+
+ private static final String TAG = "SimpleBleClient";
+ private static final long SCAN_TIME_MS = 10000;
+
+ private Queue<BleAction> mBleActionQueue = new ConcurrentLinkedQueue<BleAction>();
+
+ private BluetoothManager mBtManager;
+ private BluetoothLeScanner mScanner;
+
+ protected BluetoothGatt mBtGatt;
+
+ private List<ClientCallback> mCallbacks;
+ private ParcelUuid mServiceUuid;
+ private Context mContext;
+
+ public SimpleBleClient(@NonNull Context context) {
+ mContext = context;
+ mBtManager = (BluetoothManager) mContext.getSystemService(Context.BLUETOOTH_SERVICE);
+ mScanner = mBtManager.getAdapter().getBluetoothLeScanner();
+ mCallbacks = new ArrayList<>();
+ }
+
+ /**
+ * Start scanning for a BLE devices with the specified service uuid.
+ *
+ * @param parcelUuid {@link ParcelUuid} used to identify the device that should be used for
+ * this client. This uuid should be the same as the one that is set in the
+ * {@link android.bluetooth.le.AdvertiseData.Builder} by the advertising
+ * device.
+ */
+ public void start(ParcelUuid parcelUuid) {
+ mServiceUuid = parcelUuid;
+
+ // We only want to scan for devices that have the correct uuid set in its advertise data.
+ List<ScanFilter> filters = new ArrayList<ScanFilter>();
+ ScanFilter.Builder serviceFilter = new ScanFilter.Builder();
+ serviceFilter.setServiceUuid(mServiceUuid);
+ filters.add(serviceFilter.build());
+
+ ScanSettings.Builder settings = new ScanSettings.Builder();
+ settings.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Start scanning for uuid: " + mServiceUuid.getUuid());
+ }
+ mScanner.startScan(filters, settings.build(), mScanCallback);
+
+ Handler handler = new Handler();
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ mScanner.stopScan(mScanCallback);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Stopping Scanner");
+ }
+ }
+ }, SCAN_TIME_MS);
+ }
+
+ private boolean hasServiceUuid(ScanResult result) {
+ if (result.getScanRecord() == null
+ || result.getScanRecord().getServiceUuids() == null
+ || result.getScanRecord().getServiceUuids().size() == 0) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Writes to a {@link BluetoothGattCharacteristic} if possible, or queues the action until
+ * other actions are complete.
+ *
+ * @param characteristic {@link BluetoothGattCharacteristic} to be written
+ */
+ public void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
+ processAction(new BleAction(characteristic, BleAction.ACTION_WRITE));
+ }
+
+ /**
+ * Reads a {@link BluetoothGattCharacteristic} if possible, or queues the read action until
+ * other actions are complete.
+ *
+ * @param characteristic {@link BluetoothGattCharacteristic} to be read.
+ */
+ public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
+ processAction(new BleAction(characteristic, BleAction.ACTION_READ));
+ }
+
+ /**
+ * Enable or disable notification for specified {@link BluetoothGattCharacteristic}.
+ *
+ * @param characteristic The {@link BluetoothGattCharacteristic} for which to enable
+ * notifications.
+ * @param enabled True if notifications should be enabled, false otherwise.
+ */
+ public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
+ boolean enabled) {
+ mBtGatt.setCharacteristicNotification(characteristic, enabled);
+ }
+
+ /**
+ * Add a {@link ClientCallback} to listen for updates from BLE components
+ */
+ public void addCallback(ClientCallback callback) {
+ mCallbacks.add(callback);
+ }
+
+ public void removeCallback(ClientCallback callback) {
+ mCallbacks.remove(callback);
+ }
+
+ private void processAction(BleAction action) {
+ // Only execute actions if the queue is empty.
+ if (mBleActionQueue.size() > 0) {
+ mBleActionQueue.add(action);
+ return;
+ }
+
+ mBleActionQueue.add(action);
+ executeAction(mBleActionQueue.peek());
+ }
+
+ private void processNextAction() {
+ mBleActionQueue.poll();
+ executeAction(mBleActionQueue.peek());
+ }
+
+ private void executeAction(BleAction action) {
+ if (action == null) {
+ return;
+ }
+
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Executing BLE Action type: " + action.getAction());
+ }
+
+ int actionType = action.getAction();
+ switch (actionType) {
+ case BleAction.ACTION_WRITE:
+ mBtGatt.writeCharacteristic(action.getCharacteristic());
+ break;
+ case BleAction.ACTION_READ:
+ mBtGatt.readCharacteristic(action.getCharacteristic());
+ break;
+ default:
+ }
+ }
+
+ private String getStatus(int status) {
+ switch (status) {
+ case BluetoothGatt.GATT_FAILURE:
+ return "Failure";
+ case BluetoothGatt.GATT_SUCCESS:
+ return "GATT_SUCCESS";
+ case BluetoothGatt.GATT_READ_NOT_PERMITTED:
+ return "GATT_READ_NOT_PERMITTED";
+ case BluetoothGatt.GATT_WRITE_NOT_PERMITTED:
+ return "GATT_WRITE_NOT_PERMITTED";
+ case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION:
+ return "GATT_INSUFFICIENT_AUTHENTICATION";
+ case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED:
+ return "GATT_REQUEST_NOT_SUPPORTED";
+ case BluetoothGatt.GATT_INVALID_OFFSET:
+ return "GATT_INVALID_OFFSET";
+ case BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH:
+ return "GATT_INVALID_ATTRIBUTE_LENGTH";
+ case BluetoothGatt.GATT_CONNECTION_CONGESTED:
+ return "GATT_CONNECTION_CONGESTED";
+ default:
+ return "unknown";
+ }
+ }
+
+ private ScanCallback mScanCallback = new ScanCallback() {
+ @Override
+ public void onScanResult(int callbackType, ScanResult result) {
+ BluetoothDevice device = result.getDevice();
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Scan result found: " + result.getScanRecord().getServiceUuids());
+ }
+
+ if (!hasServiceUuid(result)) {
+ return;
+ }
+
+ for (ParcelUuid uuid : result.getScanRecord().getServiceUuids()) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Scan result UUID: " + uuid);
+ }
+ if (uuid.equals(mServiceUuid)) {
+ // This client only supports connecting to one service.
+ // Once we find one, stop scanning and open a GATT connection to the device.
+ mScanner.stopScan(mScanCallback);
+ mBtGatt = device.connectGatt(mContext, false /* autoConnect */, mGattCallback);
+ return;
+ }
+ }
+ }
+
+ @Override
+ public void onBatchScanResults(List<ScanResult> results) {
+ for (ScanResult r : results) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Batch scanResult: " + r.getDevice().getName()
+ + " " + r.getDevice().getAddress());
+ }
+ }
+ }
+
+ @Override
+ public void onScanFailed(int errorCode) {
+ Log.w(TAG, "Scan failed: " + errorCode);
+ }
+ };
+
+ private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
+ super.onConnectionStateChange(gatt, status, newState);
+
+ String state = "";
+
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ state = "Connected";
+ mBtGatt.discoverServices();
+ for (ClientCallback callback : mCallbacks) {
+ callback.onDeviceConnected(gatt.getDevice());
+ }
+
+ } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
+ state = "Disconnected";
+ for (ClientCallback callback : mCallbacks) {
+ callback.onDeviceDisconnected();
+ }
+ }
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, " Gatt connection status: " + getStatus(status) + " newState: " + state);
+ }
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ super.onServicesDiscovered(gatt, status);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onServicesDiscovered: " + status);
+ }
+
+ List<BluetoothGattService> services = gatt.getServices();
+ if (services == null || services.size() <= 0) {
+ return;
+ }
+
+ // Notify clients of newly discovered services.
+ for (BluetoothGattService service : mBtGatt.getServices()) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Found service: " + service.getUuid() + " notifying clients");
+ }
+ for (ClientCallback callback : mCallbacks) {
+ callback.onServiceDiscovered(service);
+ }
+ }
+ }
+
+ @Override
+ public void onCharacteristicWrite(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic, int status) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCharacteristicWrite: " + status);
+ }
+ processNextAction();
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic, int status) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "onCharacteristicRead:" + new String(characteristic.getValue()));
+ }
+ processNextAction();
+ }
+
+ @Override
+ public void onCharacteristicChanged(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic) {
+ for (ClientCallback callback : mCallbacks) {
+ callback.onCharacteristicChanged(gatt, characteristic);
+ }
+ processNextAction();
+ }
+ };
+}
diff --git a/TrustAgent/src/com/android/car/trust/comms/SimpleBleServer.java b/TrustAgent/src/com/android/car/trust/comms/SimpleBleServer.java
new file mode 100644
index 0000000..da3f7ac
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/comms/SimpleBleServer.java
@@ -0,0 +1,251 @@
+/*
+ * 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.android.car.trust.comms;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattServer;
+import android.bluetooth.BluetoothGattServerCallback;
+import android.bluetooth.BluetoothGattService;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.le.AdvertiseCallback;
+import android.bluetooth.le.AdvertiseData;
+import android.bluetooth.le.AdvertiseSettings;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.util.HashSet;
+
+/**
+ * A generic service to start a BLE
+ */
+public abstract class SimpleBleServer extends Service {
+
+ /**
+ * Listener that is notified when the status of the BLE server changes.
+ */
+ public interface ConnectionListener {
+ /**
+ * Called when the GATT server is started and BLE is successfully advertising.
+ */
+ void onServerStarted();
+
+ /**
+ * Called when the BLE advertisement fails to start.
+ *
+ * @param errorCode Error code (see {@link AdvertiseCallback}#ADVERTISE_FAILED_* constants)
+ */
+ void onServerStartFailed(int errorCode);
+
+ /**
+ * Called when a device is connected.
+ * @param device
+ */
+ void onDeviceConnected(BluetoothDevice device);
+ }
+
+ private static final String TAG = "SimpleBleServer";
+
+ private BluetoothLeAdvertiser mAdvertiser;
+ protected BluetoothGattServer mGattServer;
+
+ private HashSet<ConnectionListener> mListeners = new HashSet<>();
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ // Override in child classes.
+ return null;
+ }
+
+ /**
+ * Starts the GATT server with the given {@link BluetoothGattService} and begins
+ * advertising with the {@link ParcelUuid}.
+ * @param advertiseUuid Service Uuid used in the {@link AdvertiseData}
+ * @param service {@link BluetoothGattService} that will be discovered by clients
+ */
+ protected void start(ParcelUuid advertiseUuid, BluetoothGattService service) {
+ if (!getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
+ Log.e(TAG, "System does not support BLE");
+ return;
+ }
+
+ BluetoothManager btManager =
+ (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
+
+ mGattServer = btManager.openGattServer(this, mGattServerCallback);
+ if (mGattServer == null) {
+ Log.e(TAG, "Gatt Server not created");
+ return;
+ }
+
+ // We only allow adding one service in this implementation. If multiple services need
+ // to be added, then they need to be queued up and added only after
+ // BluetoothGattServerCallback.onServiceAdded is called.
+ mGattServer.addService(service);
+
+ AdvertiseSettings settings = new AdvertiseSettings.Builder()
+ .setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY)
+ .setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_HIGH)
+ .setConnectable(true)
+ .build();
+
+ AdvertiseData data = new AdvertiseData.Builder()
+ .setIncludeDeviceName(true)
+ .addServiceUuid(advertiseUuid)
+ .build();
+
+ mAdvertiser
+ = BluetoothAdapter.getDefaultAdapter().getBluetoothLeAdvertiser();
+
+ mAdvertiser.startAdvertising(settings, data, mAdvertisingCallback);
+ }
+
+ /**
+ * Stops the advertiser and GATT server. This needs to be done to avoid leaks
+ */
+ protected void stop() {
+ if (mAdvertiser != null) {
+ mAdvertiser.stopAdvertising(mAdvertisingCallback);
+ mAdvertiser.cleanup();
+ }
+
+ if (mGattServer != null) {
+ mGattServer.clearServices();
+ try {
+ for (BluetoothDevice d : mGattServer.getConnectedDevices()) {
+ mGattServer.cancelConnection(d);
+ }
+ } catch (UnsupportedOperationException e) {
+ Log.e(TAG, "Error getting connected devices", e);
+ } finally {
+ mGattServer.close();
+ }
+ }
+
+ mListeners.clear();
+ }
+
+ @Override
+ public void onDestroy() {
+ stop();
+ super.onDestroy();
+ }
+
+ public void addConnectionListener(ConnectionListener listener) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Adding connection listener");
+ }
+ mListeners.add(listener);
+ }
+
+ /**
+ * Triggered when this BleService receives a write request from a remote
+ * device. Sub-classes should implement how to handle requests.
+ */
+ public abstract void onCharacteristicWrite(final BluetoothDevice device, int requestId,
+ BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
+ responseNeeded, int offset, byte[] value);
+
+ /**
+ * Triggered when this BleService receives a read request from a remote device.
+ */
+ public abstract void onCharacteristicRead(BluetoothDevice device,
+ int requestId, int offset, final BluetoothGattCharacteristic characteristic);
+
+ private AdvertiseCallback mAdvertisingCallback = new AdvertiseCallback() {
+ @Override
+ public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+ super.onStartSuccess(settingsInEffect);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Successfully started advertising service");
+ }
+ for (ConnectionListener listener : mListeners) {
+ listener.onServerStarted();
+ }
+ }
+
+ @Override
+ public void onStartFailure(int errorCode) {
+ super.onStartFailure(errorCode);
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Failed to advertise, errorCode: " + errorCode);
+ }
+ for (ConnectionListener listener : mListeners) {
+ listener.onServerStartFailed(errorCode);
+ }
+ }
+ };
+
+ private BluetoothGattServerCallback mGattServerCallback = new BluetoothGattServerCallback() {
+ @Override
+ public void onConnectionStateChange(BluetoothDevice device,
+ final int status, final int newState) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "GattServer connection change status: "
+ + newState + " newState: "
+ + newState + " device name: " + device.getName());
+ }
+ if (newState == BluetoothProfile.STATE_CONNECTED) {
+ for (ConnectionListener listener : mListeners) {
+ listener.onDeviceConnected(device);
+ }
+ }
+ }
+
+ @Override
+ public void onServiceAdded(final int status, BluetoothGattService service) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Service added status: " + status + " uuid: " + service.getUuid());
+ }
+ }
+
+ @Override
+ public void onCharacteristicReadRequest(BluetoothDevice device,
+ int requestId, int offset, final BluetoothGattCharacteristic characteristic) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Read request for characteristic: " + characteristic.getUuid());
+ }
+ mGattServer.sendResponse(device, requestId,
+ BluetoothGatt.GATT_SUCCESS, offset, characteristic.getValue());
+ SimpleBleServer.
+ this.onCharacteristicRead(device, requestId, offset, characteristic);
+ }
+
+ @Override
+ public void onCharacteristicWriteRequest(final BluetoothDevice device, int requestId,
+ BluetoothGattCharacteristic characteristic, boolean preparedWrite, boolean
+ responseNeeded, int offset, byte[] value) {
+ if (Log.isLoggable(TAG, Log.DEBUG)) {
+ Log.d(TAG, "Write request for characteristic: " + characteristic.getUuid());
+ }
+ mGattServer.sendResponse(device, requestId, BluetoothGatt.GATT_SUCCESS,
+ offset, value);
+
+ SimpleBleServer.
+ this.onCharacteristicWrite(device, requestId, characteristic,
+ preparedWrite, responseNeeded, offset, value);
+ }
+ };
+}
diff --git a/car-cluster-logging-renderer/AndroidManifest.xml b/car-cluster-logging-renderer/AndroidManifest.xml
index 7802988..5fc8d57 100644
--- a/car-cluster-logging-renderer/AndroidManifest.xml
+++ b/car-cluster-logging-renderer/AndroidManifest.xml
@@ -17,7 +17,10 @@
package="android.car.cluster.loggingrenderer"
android:versionCode="1"
android:versionName="1.0">
- <application android:label="@string/app_name" android:icon="@drawable/ic_launcher">
+ <application android:label="@string/app_name"
+ android:icon="@drawable/ic_launcher"
+ android:directBootAware="true"
+ android:persistent="true">
<service android:name=".LoggingClusterRenderingService"
android:exported="false"
android:permission="android.car.permission.BIND_INSTRUMENT_CLUSTER_RENDERER_SERVICE"
diff --git a/car-lib/src/android/car/hardware/CarDiagnosticEvent.java b/car-lib/src/android/car/hardware/CarDiagnosticEvent.java
index fc205ec..0c37537 100644
--- a/car-lib/src/android/car/hardware/CarDiagnosticEvent.java
+++ b/car-lib/src/android/car/hardware/CarDiagnosticEvent.java
@@ -636,12 +636,12 @@
*/
public static final class IgnitionMonitors {
public static final class IgnitionMonitor {
- public final boolean mAvailable;
- public final boolean mIncomplete;
+ public final boolean available;
+ public final boolean incomplete;
IgnitionMonitor(boolean available, boolean incomplete) {
- mAvailable = available;
- mIncomplete = incomplete;
+ this.available = available;
+ this.incomplete = incomplete;
}
public static final class Builder {
diff --git a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
index 21af78c..9cb0e79 100644
--- a/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
+++ b/car-lib/src/android/car/vms/IVmsSubscriberService.aidl
@@ -24,23 +24,32 @@
*/
interface IVmsSubscriberService {
/**
- * Subscribes the listener to receive messages from layer/version. If silent is true, the
- * service will not send subscription notifications to publishers (i.e. this is a passive
- * subscriber). Note that removeOnVmsMessageReceivedListener does not require the parameter
- * silent because the service keeps track of silent subscribes and it will not generate
- * unsubscribe events in this scenario.
+ * Subscribes the listener to receive messages from layer/version.
*/
void addOnVmsMessageReceivedListener(
- int layer,
- int version,
in IOnVmsMessageReceivedListener listener,
- boolean silent) = 0;
+ int layer,
+ int version) = 0;
+
+ /**
+ * Subscribes the listener to receive messages from all published layer/version. The
+ * service will not send any subscription notifications to publishers (i.e. this is a passive
+ * subscriber).
+ */
+ void addOnVmsMessageReceivedPassiveListener(in IOnVmsMessageReceivedListener listener) = 1;
/**
* Tells the VmsSubscriberService a client unsubscribes to layer messages.
*/
void removeOnVmsMessageReceivedListener(
+ in IOnVmsMessageReceivedListener listener,
int layer,
- int version,
- in IOnVmsMessageReceivedListener listener) = 1;
+ int version) = 2;
+
+ /**
+ * Tells the VmsSubscriberService a passive client unsubscribes. This will not unsubscribe
+ * the listener from any specific layer it has subscribed to.
+ */
+ void removeOnVmsMessageReceivedPassiveListener(
+ in IOnVmsMessageReceivedListener listener) = 3;
}
diff --git a/car-lib/src/android/car/vms/VmsPublisherClientService.java b/car-lib/src/android/car/vms/VmsPublisherClientService.java
index 092b397..4dc4997 100644
--- a/car-lib/src/android/car/vms/VmsPublisherClientService.java
+++ b/car-lib/src/android/car/vms/VmsPublisherClientService.java
@@ -94,6 +94,9 @@
* @return if the call to the method VmsPublisherService.publish was successful.
*/
public final boolean publish(int layerId, int layerVersion, byte[] payload) {
+ if (DBG) {
+ Log.d(TAG, "Publishing for layer ID: " + layerId + " Version: " + layerVersion);
+ }
if (mVmsPublisherService == null) {
throw new IllegalStateException("VmsPublisherService not set.");
}
diff --git a/car-lib/src/android/car/vms/VmsSubscriberManager.java b/car-lib/src/android/car/vms/VmsSubscriberManager.java
index 0fb32d6..87486a4 100644
--- a/car-lib/src/android/car/vms/VmsSubscriberManager.java
+++ b/car-lib/src/android/car/vms/VmsSubscriberManager.java
@@ -151,7 +151,7 @@
* it only listens passively.
* @throws IllegalStateException if the listener was not set via {@link #setListener}.
*/
- public void subscribe(int layer, int version, boolean silentSubscribe)
+ public void subscribe(int layer, int version)
throws CarNotConnectedException {
if (DBG) {
Log.d(TAG, "Subscribing to layer: " + layer + ", version: " + version);
@@ -166,8 +166,31 @@
throw new IllegalStateException("Listener was not set.");
}
try {
- mVmsSubscriberService.addOnVmsMessageReceivedListener(layer, version, mIListener,
- silentSubscribe);
+ mVmsSubscriberService.addOnVmsMessageReceivedListener(mIListener, layer, version);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Could not connect: ", e);
+ throw new CarNotConnectedException(e);
+ } catch (IllegalStateException ex) {
+ Car.checkCarNotConnectedExceptionFromCarService(ex);
+ }
+ }
+
+ public void subscribeAll()
+ throws CarNotConnectedException {
+ if (DBG) {
+ Log.d(TAG, "Subscribing passively to all data messages");
+ }
+ OnVmsMessageReceivedListener listener;
+ synchronized (mListenerLock) {
+ listener = mListener;
+ }
+ if (listener == null) {
+ Log.w(TAG, "subscribe: listener was not set, " +
+ "setListener must be called first.");
+ throw new IllegalStateException("Listener was not set.");
+ }
+ try {
+ mVmsSubscriberService.addOnVmsMessageReceivedPassiveListener(mIListener);
} catch (RemoteException e) {
Log.e(TAG, "Could not connect: ", e);
throw new CarNotConnectedException(e);
@@ -197,7 +220,7 @@
throw new IllegalStateException("Listener was not set.");
}
try {
- mVmsSubscriberService.removeOnVmsMessageReceivedListener(layer, version, mIListener);
+ mVmsSubscriberService.removeOnVmsMessageReceivedListener(mIListener, layer, version);
} catch (RemoteException e) {
Log.e(TAG, "Failed to unregister subscriber", e);
// ignore
diff --git a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
index 520b8b4..1a21ea4 100644
--- a/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
+++ b/service/src/com/android/car/BluetoothDeviceConnectionPolicy.java
@@ -24,6 +24,9 @@
import android.bluetooth.BluetoothPbapClient;
import android.bluetooth.BluetoothProfile;
import android.car.hardware.CarPropertyValue;
+import android.car.hardware.CarSensorEvent;
+import android.car.hardware.CarSensorManager;
+import android.car.hardware.ICarSensorEventListener;
import android.car.hardware.cabin.CarCabinManager;
import android.car.hardware.property.CarPropertyEvent;
import android.car.hardware.property.ICarPropertyEventListener;
@@ -69,7 +72,6 @@
* connected. The device that successfully connects on a profile is moved to the top of the list
* of devices for that profile, so the next time a connection attempt is made, the policy starts
* with the last connected device first.
- *
*/
public class BluetoothDeviceConnectionPolicy {
@@ -90,8 +92,11 @@
// Events that are listened to for triggering an auto-connect:
// Cabin events like Door unlock coming from the Cabin Service.
- private CarCabinService mCarCabinService;
- private ICarPropertyEventListener mCabinEventListener;
+ private final CarCabinService mCarCabinService;
+ private final CarPropertyListener mCabinEventListener;
+ // Sensor events like Ignition switch ON from the Car Sensor Service
+ private final CarSensorService mCarSensorService;
+ private final CarSensorEventListener mCarSensorEventListener;
// Profile Proxies.
private BluetoothHeadsetClient mBluetoothHeadsetClient;
@@ -111,17 +116,21 @@
private ConnectionParams mConnectionInFlight;
public static BluetoothDeviceConnectionPolicy create(Context context,
- CarCabinService carCabinService) {
- return new BluetoothDeviceConnectionPolicy(context, carCabinService);
+ CarCabinService carCabinService, CarSensorService carSensorService) {
+ return new BluetoothDeviceConnectionPolicy(context, carCabinService, carSensorService);
}
- private BluetoothDeviceConnectionPolicy(Context context, CarCabinService carCabinService) {
+ private BluetoothDeviceConnectionPolicy(Context context, CarCabinService carCabinService,
+ CarSensorService carSensorService) {
mContext = context;
mCarCabinService = carCabinService;
+ mCarSensorService = carSensorService;
mProfilesToConnect = Arrays.asList(new Integer[]
{BluetoothProfile.HEADSET_CLIENT,
BluetoothProfile.PBAP_CLIENT, BluetoothProfile.A2DP_SINK,
BluetoothProfile.MAP_CLIENT,});
+ mCabinEventListener = new CarPropertyListener();
+ mCarSensorEventListener = new CarSensorEventListener();
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (mBluetoothAdapter == null) {
Log.w(TAG, "No Bluetooth Adapter Available");
@@ -319,44 +328,66 @@
*/
private void setupEventListeners() {
// Setting up a listener for events from CarCabinService
- // For now, we only listen to door unlock signal coming from {@link CarCabinService},
- // but more events will be added here soon - b/34723490
- mCabinEventListener = new ICarPropertyEventListener.Stub() {
- @Override
- public void onEvent(CarPropertyEvent event) throws RemoteException {
- handleCabinChangeEvent(event);
- }
- };
+ // For now, we listen to door unlock signal coming from {@link CarCabinService},
+ // and Ignition state START from {@link CarSensorService}
mCarCabinService.registerListener(mCabinEventListener);
+ mCarSensorService.registerOrUpdateSensorListener(
+ CarSensorManager.SENSOR_TYPE_IGNITION_STATE, 0, mCarSensorEventListener);
}
/**
- * handleCabinChangeEvent handles events coming in from the {@link CarCabinService}
+ * Handles events coming in from the {@link CarCabinService}
* The events that can trigger Bluetooth Scanning from CarCabinService is Door Unlock.
* Upon receiving the event that is of interest, initiate a connection attempt by calling
* the policy {@link BluetoothDeviceConnectionPolicy}
*/
- private void handleCabinChangeEvent(CarPropertyEvent event) {
- if (DBG) {
- Log.d(TAG, "Cabin change Event : " + event.getEventType());
- }
- Boolean locked;
- CarPropertyValue value = event.getCarPropertyValue();
- Object o = value.getValue();
+ private class CarPropertyListener extends ICarPropertyEventListener.Stub {
+ @Override
+ public void onEvent(CarPropertyEvent event) throws RemoteException {
+ if (DBG) {
+ Log.d(TAG, "Cabin change Event : " + event.getEventType());
+ }
+ Boolean locked;
+ CarPropertyValue value = event.getCarPropertyValue();
+ Object o = value.getValue();
- if (value.getPropertyId() == CarCabinManager.ID_DOOR_LOCK) {
- if (o instanceof Boolean) {
- locked = (Boolean) o;
- // Attempting a connection only on a door unlock
- if (locked) {
+ if (value.getPropertyId() == CarCabinManager.ID_DOOR_LOCK) {
+ if (o instanceof Boolean) {
+ locked = (Boolean) o;
if (DBG) {
- Log.d(TAG, "Door locked");
+ Log.d(TAG, "Door Lock: " + locked);
}
- } else {
+ // Attempting a connection only on a door unlock
+ if (!locked) {
+ initiateConnection();
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Handles events coming in from the {@link CarSensorService}
+ * The events that can trigger Bluetooth Scanning from CarSensorService is Ignition START.
+ * Upon receiving the event that is of interest, initiate a connection attempt by calling
+ * the policy {@link BluetoothDeviceConnectionPolicy}
+ */
+
+ private class CarSensorEventListener extends ICarSensorEventListener.Stub {
+ @Override
+ public void onSensorChanged(List<CarSensorEvent> events) throws RemoteException {
+ if (events != null & events.size() > 0) {
+ CarSensorEvent event = events.get(0);
+ if (DBG) {
+ Log.d(TAG, "Sensor event Type : " + event.sensorType);
+ }
+ if (event.sensorType == CarSensorManager.SENSOR_TYPE_IGNITION_STATE) {
if (DBG) {
- Log.d(TAG, "Door Unlocked");
+ Log.d(TAG, "Sensor value : " + event.intValues[0]);
}
- initiateConnection();
+ if (event.intValues[0] == CarSensorEvent.IGNITION_STATE_START) {
+ initiateConnection();
+ }
}
}
}
@@ -671,7 +702,6 @@
BluetoothDevice devToConnect = devInfo.getNextDeviceInQueueLocked();
if (devToConnect != null) {
switch (profile) {
- // b/34723437 - Add MAP_CLIENT
case BluetoothProfile.A2DP_SINK:
if (mBluetoothA2dpSink != null) {
if (DBG) {
@@ -1095,11 +1125,11 @@
return false;
}
- if (mBluetoothAdapter != null ) {
+ if (mBluetoothAdapter != null) {
if (DBG) {
Log.d(TAG, "Bonded devices size:" + mBluetoothAdapter.getBondedDevices().size());
}
- if(mBluetoothAdapter.getBondedDevices().isEmpty()) {
+ if (mBluetoothAdapter.getBondedDevices().isEmpty()) {
if (DBG) {
Log.d(TAG, "No Bonded Devices available. Quit rebuilding");
}
@@ -1109,7 +1139,7 @@
boolean rebuildSuccess = true;
// Iterate through the Map's entries and build the {@link #mProfileToConnectableDevicesMap}
- if (mProfileToConnectableDevicesMap == null ) {
+ if (mProfileToConnectableDevicesMap == null) {
mProfileToConnectableDevicesMap = new HashMap<Integer, BluetoothDevicesInfo>();
}
diff --git a/service/src/com/android/car/CarBluetoothService.java b/service/src/com/android/car/CarBluetoothService.java
index 0d23b77..3efcda8 100644
--- a/service/src/com/android/car/CarBluetoothService.java
+++ b/service/src/com/android/car/CarBluetoothService.java
@@ -32,12 +32,13 @@
private static final String TAG = "CarBluetoothService";
private final Context mContext;
- private BluetoothDeviceConnectionPolicy mBluetoothDeviceConnectionPolicy = null;
+ private final BluetoothDeviceConnectionPolicy mBluetoothDeviceConnectionPolicy;
- public CarBluetoothService(Context context, CarCabinService carCabinService) {
+ public CarBluetoothService(Context context, CarCabinService carCabinService,
+ CarSensorService carSensorService) {
mContext = context;
mBluetoothDeviceConnectionPolicy = BluetoothDeviceConnectionPolicy.create(mContext,
- carCabinService);
+ carCabinService, carSensorService);
}
@Override
diff --git a/service/src/com/android/car/CarDiagnosticService.java b/service/src/com/android/car/CarDiagnosticService.java
index 5b4b675..c14a385 100644
--- a/service/src/com/android/car/CarDiagnosticService.java
+++ b/service/src/com/android/car/CarDiagnosticService.java
@@ -25,12 +25,8 @@
import android.car.hardware.ICarDiagnostic;
import android.car.hardware.ICarDiagnosticEventListener;
import android.content.Context;
-import android.os.Handler;
import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
import android.os.RemoteException;
-import android.os.SystemClock;
import android.util.ArrayMap;
import android.util.Log;
import com.android.car.internal.CarPermission;
@@ -53,9 +49,6 @@
/** @hide */
public class CarDiagnosticService extends ICarDiagnostic.Stub
implements CarServiceBase, DiagnosticHalService.DiagnosticListener {
- /** {@link #mDiagnosticLock} is not waited forever for handling disconnection */
- private static final long MAX_DIAGNOSTIC_LOCK_WAIT_MS = 1000;
-
/** lock to access diagnostic structures */
private final ReentrantLock mDiagnosticLock = new ReentrantLock();
/** hold clients callback */
@@ -487,82 +480,6 @@
}
}
- private class DiagnosticDispatchHandler extends Handler {
- private static final long DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS = 16; // over 60Hz
-
- private static final int MSG_DIAGNOSTIC_DATA = 0;
-
- private long mLastDiagnosticDispatchTime = -1;
- private int mFreeListIndex = 0;
- private final LinkedList<CarDiagnosticEvent>[] mDiagnosticDataList = new LinkedList[2];
-
- private DiagnosticDispatchHandler(Looper looper) {
- super(looper);
- for (int i = 0; i < mDiagnosticDataList.length; i++) {
- mDiagnosticDataList[i] = new LinkedList<CarDiagnosticEvent>();
- }
- }
-
- private synchronized void handleDiagnosticEvents(List<CarDiagnosticEvent> data) {
- LinkedList<CarDiagnosticEvent> list = mDiagnosticDataList[mFreeListIndex];
- list.addAll(data);
- requestDispatchLocked();
- }
-
- private synchronized void handleDiagnosticEvent(CarDiagnosticEvent event) {
- LinkedList<CarDiagnosticEvent> list = mDiagnosticDataList[mFreeListIndex];
- list.add(event);
- requestDispatchLocked();
- }
-
- private void requestDispatchLocked() {
- Message msg = obtainMessage(MSG_DIAGNOSTIC_DATA);
- long now = SystemClock.uptimeMillis();
- long delta = now - mLastDiagnosticDispatchTime;
- if (delta > DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS) {
- sendMessage(msg);
- } else {
- sendMessageDelayed(msg, DIAGNOSTIC_DISPATCH_MIN_INTERVAL_MS - delta);
- }
- }
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_DIAGNOSTIC_DATA:
- doHandleDiagnosticData();
- break;
- default:
- break;
- }
- }
-
- private void doHandleDiagnosticData() {
- List<CarDiagnosticEvent> listToDispatch = null;
- synchronized (this) {
- mLastDiagnosticDispatchTime = SystemClock.uptimeMillis();
- int nonFreeListIndex = mFreeListIndex ^ 0x1;
- List<CarDiagnosticEvent> nonFreeList = mDiagnosticDataList[nonFreeListIndex];
- List<CarDiagnosticEvent> freeList = mDiagnosticDataList[mFreeListIndex];
- if (nonFreeList.size() > 0) {
- // copy again, but this should not be normal case
- nonFreeList.addAll(freeList);
- listToDispatch = nonFreeList;
- freeList.clear();
- } else if (freeList.size() > 0) {
- listToDispatch = freeList;
- mFreeListIndex = nonFreeListIndex;
- }
- }
- // leave this part outside lock so that time-taking dispatching can be done without
- // blocking diagnostic event notification.
- if (listToDispatch != null) {
- processDiagnosticData(listToDispatch);
- listToDispatch.clear();
- }
- }
- }
-
/** internal instance for pending client request */
private class DiagnosticClient implements Listeners.IListener {
/** callback for diagnostic events */
diff --git a/service/src/com/android/car/CarInputService.java b/service/src/com/android/car/CarInputService.java
index 5ed9d49..b4846b7 100644
--- a/service/src/com/android/car/CarInputService.java
+++ b/service/src/com/android/car/CarInputService.java
@@ -396,8 +396,6 @@
extras.putBinder(CarInputHandlingService.INPUT_CALLBACK_BINDER_KEY, mCallback);
intent.putExtras(extras);
intent.setComponent(ComponentName.unflattenFromString(carInputService));
- // Explicitly start service as we do not use BIND_AUTO_CREATE flag to handle service crash.
- mContext.startService(intent);
- return mContext.bindService(intent, mInputServiceConnection, Context.BIND_IMPORTANT);
+ return mContext.bindService(intent, mInputServiceConnection, Context.BIND_AUTO_CREATE);
}
}
diff --git a/service/src/com/android/car/CarProjectionService.java b/service/src/com/android/car/CarProjectionService.java
index 52fd93f..89ee8b0 100644
--- a/service/src/com/android/car/CarProjectionService.java
+++ b/service/src/com/android/car/CarProjectionService.java
@@ -121,8 +121,8 @@
mRegisteredService = serviceIntent;
}
UserHandle userHandle = UserHandle.getUserHandleForUid(Binder.getCallingUid());
- mContext.startServiceAsUser(serviceIntent, userHandle);
- mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_IMPORTANT, userHandle);
+ mContext.bindServiceAsUser(serviceIntent, mConnection, Context.BIND_AUTO_CREATE,
+ userHandle);
}
private void unbindServiceIfBound() {
diff --git a/service/src/com/android/car/ICarImpl.java b/service/src/com/android/car/ICarImpl.java
index 61aae91..e671ec7 100644
--- a/service/src/com/android/car/ICarImpl.java
+++ b/service/src/com/android/car/ICarImpl.java
@@ -43,7 +43,7 @@
public class ICarImpl extends ICar.Stub {
- public static final String INTERNAL_INPUT_SERVICE = "internal_input";
+ public static final String INTERNAL_INPUT_SERVICE = "internal_input";
public static final String INTERNAL_SYSTEM_ACTIVITY_MONITORING_SERVICE =
"system_activity_monitoring";
@@ -120,14 +120,15 @@
mCarPowerManagementService, mCarAudioService, this);
mCarVendorExtensionService = new CarVendorExtensionService(serviceContext,
mHal.getVendorExtensionHal());
- mCarBluetoothService = new CarBluetoothService(serviceContext, mCarCabinService);
+ mCarBluetoothService = new CarBluetoothService(serviceContext, mCarCabinService,
+ mCarSensorService);
if (FeatureConfiguration.ENABLE_VEHICLE_MAP_SERVICE) {
mVmsSubscriberService = new VmsSubscriberService(serviceContext, mHal.getVmsHal());
mVmsPublisherService = new VmsPublisherService(serviceContext, mHal.getVmsHal());
}
if (FeatureConfiguration.ENABLE_DIAGNOSTIC) {
mCarDiagnosticService = new CarDiagnosticService(serviceContext,
- mHal.getDiagnosticHal());
+ mHal.getDiagnosticHal());
}
// Be careful with order. Service depending on other service should be inited later.
@@ -357,6 +358,7 @@
private static final String PARAM_NIGHT_MODE = "night";
private static final String PARAM_SENSOR_MODE = "sensor";
private static final String PARAM_ZONED_BOOLEAN = "zoned-boolean";
+ private static final String PARAM_GLOBAL_INT = "global-integer";
private void dumpHelp(PrintWriter pw) {
pw.println("Car service commands:");
@@ -380,17 +382,32 @@
break;
case COMMAND_INJECT_EVENT:
String eventType;
- if(args.length > 1) {
- eventType = args[1];
- // Zoned boolean event
- if(eventType.equalsIgnoreCase(PARAM_ZONED_BOOLEAN)) {
- if(args.length < 5) {
- writer.println("Incorrect number of arguments.");
- dumpHelp(writer);
- break;
- }
- inject_zoned_boolean_event(args[2], args[3], args[4], writer);
- }
+ if (args.length > 1) {
+ eventType = args[1].toLowerCase();
+ switch (eventType) {
+ case PARAM_ZONED_BOOLEAN:
+ if (args.length < 5) {
+ writer.println("Incorrect number of arguments.");
+ dumpHelp(writer);
+ break;
+ }
+ inject_zoned_boolean_event(args[2], args[3], args[4], writer);
+ break;
+
+ case PARAM_GLOBAL_INT:
+ if (args.length < 4) {
+ writer.println("Incorrect number of Arguments");
+ dumpHelp(writer);
+ break;
+ }
+ inject_global_integer_event(args[2], args[3], writer);
+ break;
+
+ default:
+ writer.println("Unsupported event type");
+ dumpHelp(writer);
+ break;
+ }
}
break;
default:
@@ -434,12 +451,13 @@
/**
* Inject a fake boolean HAL event to help testing.
- * Currently supporting Door Unlock/Lock
+ *
* @param property - Vehicle Property
- * @param value - boolean value for the property
- * @param writer - Printwriter
+ * @param value - boolean value for the property
+ * @param writer - Printwriter
*/
- private void inject_zoned_boolean_event(String property, String zone, String value, PrintWriter writer) {
+ private void inject_zoned_boolean_event(String property, String zone, String value,
+ PrintWriter writer) {
Log.d(CarLog.TAG_SERVICE, "Injecting Boolean event");
boolean event;
int propId;
@@ -459,5 +477,27 @@
mHal.injectBooleanEvent(propId, zoneId, event);
}
+ /**
+ * Inject a fake Integer HAL event to help testing.
+ *
+ * @param property - Vehicle Property
+ * @param value - Integer value to inject
+ * @param writer - PrintWriter
+ */
+ private void inject_global_integer_event(String property, String value,
+ PrintWriter writer) {
+ Log.d(CarLog.TAG_SERVICE, "Injecting integer event");
+ int propId;
+ int eventValue;
+ try {
+ propId = Integer.decode(property);
+ eventValue = Integer.decode(value);
+ } catch (NumberFormatException e) {
+ writer.println("Invalid property Id or event value. Prefix hex values with 0x");
+ return;
+ }
+ mHal.injectIntegerEvent(propId, eventValue);
+ }
+
}
}
\ No newline at end of file
diff --git a/service/src/com/android/car/VmsPublisherService.java b/service/src/com/android/car/VmsPublisherService.java
index 2d8f309..2c7215c 100644
--- a/service/src/com/android/car/VmsPublisherService.java
+++ b/service/src/com/android/car/VmsPublisherService.java
@@ -17,6 +17,8 @@
package com.android.car;
import android.car.annotation.FutureFeature;
+import android.car.vms.IOnVmsMessageReceivedListener;
+
import android.car.vms.IVmsPublisherClient;
import android.car.vms.IVmsPublisherService;
import android.content.ComponentName;
@@ -37,6 +39,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* + Receives HAL updates by implementing VmsHalService.VmsHalListener.
@@ -92,21 +95,50 @@
// Implements IVmsPublisherService interface.
@Override
public void publish(int layerId, int layerVersion, byte[] payload) {
+ if (DBG) {
+ Log.d(TAG, "Publishing for layer ID: " + layerId + " Version: " + layerVersion);
+ }
ICarImpl.assertVmsPublisherPermission(mContext);
- mHal.setDataMessage(layerId, layerVersion, payload);
+ VmsLayer layer = new VmsLayer(layerId, layerVersion);
+
+ // Sned the message to application listeners.
+ Set<IOnVmsMessageReceivedListener> listeners = mHal.getListeners(layer);
+
+ if (DBG) {
+ Log.d(TAG, "Number of subscribed apps: " + listeners.size());
+ }
+ for (IOnVmsMessageReceivedListener listener : listeners) {
+ try {
+ listener.onVmsMessageReceived(layerId, layerVersion, payload);
+ } catch (RemoteException ex) {
+ Log.e(TAG, "unable to publish to listener: " + listener);
+ }
+ }
+
+ // Send the message to HAL
+ if (mHal.isHalSubscribed(layer)) {
+ Log.d(TAG, "HAL is subscribed");
+ mHal.setDataMessage(layerId, layerVersion, payload);
+ } else {
+ Log.d(TAG, "HAL is NOT subscribed");
+ }
}
@Override
- public boolean hasSubscribers(int layer, int version) {
+ public boolean hasSubscribers(int layerId, int layerVersion) {
ICarImpl.assertVmsPublisherPermission(mContext);
- // TODO(antoniocortes): implement this logic.
- return true;
+ VmsLayer layer = new VmsLayer(layerId, layerVersion);
+ return mHal.isHalSubscribed(layer) || mHal.hasLayerSubscriptions(layer);
}
// Implements VmsHalListener interface
@Override
public void onChange(int layerId, int layerVersion, boolean hasSubscribers) {
- // TODO(antoniocortes): notify publishers if this message causes a subscription change.
+ if (hasSubscribers) {
+ mHal.addHalSubscription(new VmsLayer(layerId, layerVersion));
+ } else {
+ mHal.removeHalSubscription(new VmsLayer(layerId, layerVersion));
+ }
}
/**
diff --git a/service/src/com/android/car/VmsRouting.java b/service/src/com/android/car/VmsRouting.java
index 6f93783..333634d 100644
--- a/service/src/com/android/car/VmsRouting.java
+++ b/service/src/com/android/car/VmsRouting.java
@@ -150,6 +150,24 @@
}
/**
+ * Checks if a listener is subscribed to any messages.
+ * @param listener that may have subscription.
+ * @return true if the listener uis subscribed to messages.
+ */
+ public boolean containsListener(IOnVmsMessageReceivedListener listener) {
+ synchronized (mLock) {
+ // Check if listener is subscribed to a layer.
+ for (Set<IOnVmsMessageReceivedListener> layerListeners: mLayerSubscriptions.values()) {
+ if (layerListeners.contains(listener)) {
+ return true;
+ }
+ }
+ // Check is listener is subscribed to all data messages.
+ return mPromiscuousSubscribers.contains(listener);
+ }
+ }
+
+ /**
* Add a layer and version to the HAL subscriptions.
* @param layer the HAL subscribes to.
*/
@@ -170,6 +188,28 @@
}
/**
+ * checks if the HAL is subscribed to a layer.
+ * @param layer
+ * @return true if the HAL is subscribed to layer.
+ */
+ public boolean isHalSubscribed(VmsLayer layer) {
+ synchronized (mLock) {
+ return mHalSubscriptions.contains(layer);
+ }
+ }
+
+ /**
+ * checks if there are subscribers to a layer.
+ * @param layer
+ * @return true if there are subscribers to layer.
+ */
+ public boolean hasLayerSubscriptions(VmsLayer layer) {
+ synchronized (mLock) {
+ return mLayerSubscriptions.containsKey(layer);
+ }
+ }
+
+ /**
* @return a Set of layers and versions which VMS clients are subscribed to.
*/
public Collection<VmsLayer> getSubscribedLayers() {
diff --git a/service/src/com/android/car/VmsSubscriberService.java b/service/src/com/android/car/VmsSubscriberService.java
index 2a0d45a..679b07d 100644
--- a/service/src/com/android/car/VmsSubscriberService.java
+++ b/service/src/com/android/car/VmsSubscriberService.java
@@ -33,11 +33,11 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/**
* + Receives HAL updates by implementing VmsHalService.VmsHalListener.
* + Offers subscriber/publisher services by implementing IVmsService.Stub.
- * TODO(antoniocortes): implement layer subscription logic (i.e. routing).
*/
@FutureFeature
public class VmsSubscriberService extends IVmsSubscriberService.Stub
@@ -48,7 +48,10 @@
private final Context mContext;
private final VmsHalService mHal;
+
+ @GuardedBy("mSubscriberServiceLock")
private final VmsListenerManager mMessageReceivedManager = new VmsListenerManager();
+ private final Object mSubscriberServiceLock = new Object();
/**
* Keeps track of listeners of this service.
@@ -57,11 +60,11 @@
/**
* Allows to modify mListenerMap and mListenerDeathRecipientMap as a single unit.
*/
- private final Object mLock = new Object();
- @GuardedBy("mLock")
+ private final Object mListenerManagerLock = new Object();
+ @GuardedBy("mListenerManagerLock")
private final Map<IBinder, ListenerDeathRecipient> mListenerDeathRecipientMap =
new HashMap<>();
- @GuardedBy("mLock")
+ @GuardedBy("mListenerManagerLock")
private final Map<IBinder, IOnVmsMessageReceivedListener> mListenerMap = new HashMap<>();
class ListenerDeathRecipient implements IBinder.DeathRecipient {
@@ -79,6 +82,20 @@
if (DBG) {
Log.d(TAG, "binderDied " + mListenerBinder);
}
+
+ // Get the Listener from the Binder
+ IOnVmsMessageReceivedListener listener = mListenerMap.get(mListenerBinder);
+
+ // Remove the listener subscriptions.
+ if (listener != null) {
+ Log.d(TAG, "Removing subscriptions for dead listener: " + listener);
+ mHal.removeDeadListener(listener);
+ } else {
+ Log.d(TAG, "Handling dead binder with no matching listener");
+
+ }
+
+ // Remove binder
VmsListenerManager.this.removeListener(mListenerBinder);
}
@@ -113,7 +130,7 @@
Log.d(TAG, "register: " + listener);
}
IBinder listenerBinder = listener.asBinder();
- synchronized (mLock) {
+ synchronized (mListenerManagerLock) {
if (mListenerMap.containsKey(listenerBinder)) {
// Already registered, nothing to do.
return;
@@ -152,8 +169,7 @@
// Removes the listenerBinder from the current state.
// The function assumes that binder will exist both in listeners and death recipients list.
private void removeListener(IBinder listenerBinder) {
- synchronized (mLock) {
- //TODO(asafro): mHal.vms.routing.removeDeadListener(mListenerMap(listenerBinder))
+ synchronized (mListenerManagerLock) {
boolean found = mListenerMap.remove(listenerBinder) != null;
if (found) {
mListenerDeathRecipientMap.get(listenerBinder).release();
@@ -170,7 +186,7 @@
* @return list of listeners.
*/
public List<IOnVmsMessageReceivedListener> getListeners() {
- synchronized (mLock) {
+ synchronized (mListenerManagerLock) {
return new ArrayList<>(mListenerMap.values());
}
}
@@ -199,21 +215,70 @@
// Implements IVmsService interface.
@Override
- public void addOnVmsMessageReceivedListener(int layer, int version,
- IOnVmsMessageReceivedListener listener, boolean silent) {
- mMessageReceivedManager.add(listener);
+ public void addOnVmsMessageReceivedListener(IOnVmsMessageReceivedListener listener,
+ int layerId, int layerVersion) {
+ synchronized (mSubscriberServiceLock) {
+ // Add the listener so it can subscribe.
+ mMessageReceivedManager.add(listener);
+
+ // Add the subscription for the layer.
+ VmsLayer layer = new VmsLayer(layerId, layerVersion);
+ mHal.addSubscription(listener, layer);
+ }
}
@Override
- public void removeOnVmsMessageReceivedListener(int layer, int version,
- IOnVmsMessageReceivedListener listener) {
- mMessageReceivedManager.remove(listener);
+ public void removeOnVmsMessageReceivedListener(IOnVmsMessageReceivedListener listener,
+ int layerId, int layerVersion) {
+ synchronized (mSubscriberServiceLock) {
+ // Remove the subscription.
+ VmsLayer layer = new VmsLayer(layerId, layerVersion);
+ mHal.removeSubscription(listener, layer);
+
+ // Remove the listener if it has no more subscriptions.
+ if (!mHal.containsListener(listener)) {
+ mMessageReceivedManager.remove(listener);
+ }
+ }
+ }
+
+ @Override
+ public void addOnVmsMessageReceivedPassiveListener(IOnVmsMessageReceivedListener listener) {
+ synchronized (mSubscriberServiceLock) {
+ mMessageReceivedManager.add(listener);
+ mHal.addSubscription(listener);
+ }
+ }
+
+ @Override
+ public void removeOnVmsMessageReceivedPassiveListener(IOnVmsMessageReceivedListener listener) {
+ synchronized (mSubscriberServiceLock) {
+ // Remove the subscription.
+ mHal.removeSubscription(listener);
+
+ // Remove the listener if it has no more subscriptions.
+ if (!mHal.containsListener(listener)) {
+ mMessageReceivedManager.remove(listener);
+ }
+ }
}
// Implements VmsHalSubscriberListener interface
@Override
public void onChange(int layerId, int layerVersion, byte[] payload) {
- for (IOnVmsMessageReceivedListener subscriber : mMessageReceivedManager.getListeners()) {
+ VmsLayer layer = new VmsLayer(layerId, layerVersion);
+ if(DBG) {
+ Log.d(TAG, "Publishing a message for layer: " + layer);
+ }
+
+ Set<IOnVmsMessageReceivedListener> listeners = mHal.getListeners(layer);
+
+ // If there are no listeners we're done.
+ if ((listeners == null)) {
+ return;
+ }
+
+ for (IOnVmsMessageReceivedListener subscriber : listeners) {
try {
subscriber.onVmsMessageReceived(layerId, layerVersion, payload);
} catch (RemoteException e) {
@@ -222,5 +287,6 @@
Log.e(TAG, "onVmsMessageReceived calling failed: ", e);
}
}
+
}
}
diff --git a/service/src/com/android/car/hal/DiagnosticHalService.java b/service/src/com/android/car/hal/DiagnosticHalService.java
index f64e49c..84e3678 100644
--- a/service/src/com/android/car/hal/DiagnosticHalService.java
+++ b/service/src/com/android/car/hal/DiagnosticHalService.java
@@ -33,7 +33,10 @@
import com.android.car.CarServiceUtils;
import com.android.car.vehiclehal.VehiclePropValueBuilder;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
import java.util.BitSet;
+import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArraySet;
@@ -120,15 +123,36 @@
return mVehiclePropertyToConfig.get(halPropId, null);
}
+ private List<Integer> getPropConfigArray(int halPropId) {
+ VehiclePropConfig propConfig = getPropConfig(halPropId);
+ return propConfig.configArray;
+ }
+
private int getNumIntegerSensors(int halPropId) {
int count = Obd2IntegerSensorIndex.LAST_SYSTEM_INDEX + 1;
- count = count + getPropConfig(halPropId).configArray.get(0);
+ List<Integer> configArray = getPropConfigArray(halPropId);
+ if(configArray.size() < 2) {
+ Log.e(CarLog.TAG_DIAGNOSTIC, String.format(
+ "property 0x%x does not specify the number of vendor-specific properties." +
+ "assuming 0.", halPropId));
+ }
+ else {
+ count += configArray.get(0);
+ }
return count;
}
private int getNumFloatSensors(int halPropId) {
int count = Obd2FloatSensorIndex.LAST_SYSTEM_INDEX + 1;
- count = count + getPropConfig(halPropId).configArray.get(1);
+ List<Integer> configArray = getPropConfigArray(halPropId);
+ if(configArray.size() < 2) {
+ Log.e(CarLog.TAG_DIAGNOSTIC, String.format(
+ "property 0x%x does not specify the number of vendor-specific properties." +
+ "assuming 0.", halPropId));
+ }
+ else {
+ count += configArray.get(1);
+ }
return count;
}
diff --git a/service/src/com/android/car/hal/VehicleHal.java b/service/src/com/android/car/hal/VehicleHal.java
index 052f1da..fba375c 100644
--- a/service/src/com/android/car/hal/VehicleHal.java
+++ b/service/src/com/android/car/hal/VehicleHal.java
@@ -461,6 +461,12 @@
synchronized (this) {
for (VehiclePropValue v : propValues) {
HalServiceBase service = mPropertyHandlers.get(v.prop);
+ if(service == null) {
+ if (DBG) {
+ Log.d(CarLog.TAG_HAL, "HalService is null for " + v.prop);
+ }
+ return;
+ }
service.getDispatchList().add(v);
mServicesToDispatch.add(service);
VehiclePropertyEventInfo info = mEventLog.get(v.prop);
@@ -555,6 +561,18 @@
onPropertyEvent(Lists.newArrayList(v));
}
+ /**
+ * Inject a fake Integer HAL event - for testing purposes.
+ * @param propId - VehicleProperty ID
+ * @param value - Integer value to inject
+ */
+ public void injectIntegerEvent(int propId, int value) {
+ VehiclePropValue v = createPropValue(propId, 0);
+ v.value.int32Values.add(value);
+ v.timestamp = SystemClock.elapsedRealtimeNanos();
+ onPropertyEvent(Lists.newArrayList(v));
+ }
+
private static class VehiclePropertyEventInfo {
private int eventCount;
private VehiclePropValue lastEvent;
diff --git a/service/src/com/android/car/hal/VmsHalService.java b/service/src/com/android/car/hal/VmsHalService.java
index b69824b..c23bb6a 100644
--- a/service/src/com/android/car/hal/VmsHalService.java
+++ b/service/src/com/android/car/hal/VmsHalService.java
@@ -18,19 +18,19 @@
import static com.android.car.CarServiceUtils.toByteArray;
import static java.lang.Integer.toHexString;
-import android.annotation.Nullable;
import android.car.VehicleAreaType;
import android.car.annotation.FutureFeature;
+import android.car.vms.IOnVmsMessageReceivedListener;
import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageIntegerValuesIndex;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
+import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_1.VmsMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_1.VmsMessageType;
import android.util.Log;
-
import com.android.car.CarLog;
+import com.android.car.VmsLayer;
+import com.android.car.VmsRouting;
import com.android.internal.annotations.GuardedBy;
-
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
@@ -39,7 +39,6 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
-
import java.util.concurrent.CopyOnWriteArrayList;
/**
@@ -64,6 +63,9 @@
private CopyOnWriteArrayList<VmsHalSubscriberListener> mSubscriberListeners =
new CopyOnWriteArrayList<>();
private final VehicleHal mVehicleHal;
+ @GuardedBy("mLock")
+ private VmsRouting mRouting = new VmsRouting();
+ private final Object mLock = new Object();
/**
* The VmsPublisherService implements this interface to receive data from the HAL.
@@ -105,6 +107,128 @@
mSubscriberListeners.remove(listener);
}
+ public void addSubscription(IOnVmsMessageReceivedListener listener, VmsLayer layer) {
+ synchronized (mLock) {
+ // Check if publishers need to be notified about this change in subscriptions.
+ boolean firstSubscriptionForLayer = !mRouting.getSubscribedLayers().contains(layer);
+
+ // Add the listeners subscription to the layer
+ mRouting.addSubscription(listener, layer);
+
+ // Notify the publishers
+ if (firstSubscriptionForLayer) {
+ notifyPublishers(layer, true);
+ }
+ }
+ }
+
+ public void removeSubscription(IOnVmsMessageReceivedListener listener, VmsLayer layer) {
+ synchronized (mLock) {
+ if (!mRouting.hasLayerSubscriptions(layer)) {
+ Log.i(TAG, "Trying to remove a layer with no subscription: " + layer);
+ return;
+ }
+
+ // Remove the listeners subscription to the layer
+ mRouting.removeSubscription(listener, layer);
+
+ // Check if publishers need to be notified about this change in subscriptions.
+ boolean layerHasSubscribers = mRouting.getSubscribedLayers().contains(layer);
+
+ // Notify the publishers
+ if (!layerHasSubscribers) {
+ notifyPublishers(layer, false);
+ }
+ }
+ }
+
+ public void addSubscription(IOnVmsMessageReceivedListener listener) {
+ synchronized (mLock) {
+ mRouting.addSubscription(listener);
+ }
+ }
+
+ public void removeSubscription(IOnVmsMessageReceivedListener listener) {
+ synchronized (mLock) {
+ mRouting.removeSubscription(listener);
+ }
+ }
+
+ public void removeDeadListener(IOnVmsMessageReceivedListener listener) {
+ synchronized (mLock) {
+ mRouting.removeDeadListener(listener);
+ }
+ }
+
+ public Set<IOnVmsMessageReceivedListener> getListeners(VmsLayer layer) {
+ return mRouting.getListeners(layer);
+ }
+
+ public boolean isHalSubscribed(VmsLayer layer) {
+ return mRouting.isHalSubscribed(layer);
+ }
+
+ public boolean hasLayerSubscriptions(VmsLayer layer) {
+ return mRouting.hasLayerSubscriptions(layer);
+ }
+
+ public void addHalSubscription(VmsLayer layer) {
+ synchronized (mLock) {
+ // Check if publishers need to be notified about this change in subscriptions.
+ boolean firstSubscriptionForLayer = !mRouting.getSubscribedLayers().contains(layer);
+
+ // Add the listeners subscription to the layer
+ mRouting.addHalSubscription(layer);
+
+ if (firstSubscriptionForLayer) {
+ notifyPublishers(layer, true);
+ }
+ }
+ }
+
+ public void removeHalSubscription(VmsLayer layer) {
+ synchronized (mLock) {
+ if (!mRouting.hasLayerSubscriptions(layer)) {
+ Log.i(TAG, "Trying to remove a layer with no subscription: " + layer);
+ return;
+ }
+
+ // Remove the listeners subscription to the layer
+ mRouting.removeHalSubscription(layer);
+
+ // Check if publishers need to be notified about this change in subscriptions.
+ boolean layerHasSubscribers = mRouting.getSubscribedLayers().contains(layer);
+
+ // Notify the publishers
+ if (!layerHasSubscribers) {
+ notifyPublishers(layer, false);
+ }
+ }
+ }
+
+ public boolean containsListener(IOnVmsMessageReceivedListener listener) {
+ return mRouting.containsListener(listener);
+ }
+
+ /**
+ * Notify all the publishers and the HAL on subscription changes regardless of who triggered
+ * the change.
+ *
+ * @param layer which is being subscribed to or unsubscribed from.
+ * @param hasListeners indicates if the notification is for subscription or unsubscription.
+ */
+ public void notifyPublishers(VmsLayer layer, boolean hasSubscribers) {
+ synchronized (mLock) {
+ // notify the HAL
+ setSubscriptionRequest(layer.getId(), layer.getVersion(), hasSubscribers);
+
+ // Notify the App publishers
+ for (VmsHalPublisherListener listener : mPublisherListeners) {
+ listener.onChange(layer.getId(), layer.getVersion(), hasSubscribers);
+ }
+ }
+ }
+
@Override
public void init() {
if (DBG) {
@@ -146,6 +270,9 @@
@Override
public void handleHalEvents(List<VehiclePropValue> values) {
+ if (DBG) {
+ Log.d(TAG, "Handling a VMS property change");
+ }
for (VehiclePropValue v : values) {
ArrayList<Integer> vec = v.value.int32Values;
int messageType = vec.get(VmsMessageIntegerValuesIndex.VMS_MESSAGE_TYPE);
@@ -160,6 +287,12 @@
}
+ if (DBG) {
+ Log.d(TAG,
+ "Received message for Type: " + messageType +
+ " Layer Id: " + layerId +
+ "Version: " + layerVersion);
+ }
// This is a data message intended for subscribers.
if (messageType == VmsMessageType.DATA) {
// Get the payload.
@@ -169,14 +302,14 @@
for (VmsHalSubscriberListener listener : mSubscriberListeners) {
listener.onChange(layerId, layerVersion, payload);
}
- } else {
- //TODO(b/35386660): This is placeholder until implementing subscription manager.
- // Get subscribe or unsubscribe.
- boolean hasSubscribers = (messageType == VmsMessageType.SUBSCRIBE) ? true : false;
-
- // Send the message.
+ } else if (messageType == VmsMessageType.SUBSCRIBE) {
for (VmsHalPublisherListener listener : mPublisherListeners) {
- listener.onChange(layerId, layerVersion, hasSubscribers);
+ listener.onChange(layerId, layerVersion, true);
+ }
+ } else {
+ // messageType == VmsMessageType.UNSUBSCRIBE
+ for (VmsHalPublisherListener listener : mPublisherListeners) {
+ listener.onChange(layerId, layerVersion, false);
}
}
}
@@ -194,15 +327,9 @@
* @param property the value used to update the HAL property.
* @return true if the call to the HAL to update the property was successful.
*/
- public boolean setSubscribeRequest(int layerId, int layerVersion) {
- VehiclePropValue vehiclePropertyValue = toVehiclePropValue(VmsMessageType.SUBSCRIBE,
- layerId,
- layerVersion);
- return setPropertyValue(vehiclePropertyValue);
- }
-
- public boolean setUnsubscribeRequest(int layerId, int layerVersion) {
- VehiclePropValue vehiclePropertyValue = toVehiclePropValue(VmsMessageType.UNSUBSCRIBE,
+ public boolean setSubscriptionRequest(int layerId, int layerVersion, boolean hasSubscribers) {
+ VehiclePropValue vehiclePropertyValue = toVehiclePropValue(
+ hasSubscribers ? VmsMessageType.SUBSCRIBE : VmsMessageType.UNSUBSCRIBE,
layerId,
layerVersion);
return setPropertyValue(vehiclePropertyValue);
@@ -213,11 +340,6 @@
layerId,
layerVersion,
payload);
- // TODO(b/34977500): remove call to handleHalEvents once the routing is implemented.
- // This temporal code forwards messages from publishers to subscribers.
- List<VehiclePropValue> list = new ArrayList<>();
- list.add(vehiclePropertyValue);
- handleHalEvents(list);
return setPropertyValue(vehiclePropertyValue);
}
@@ -259,4 +381,4 @@
}
return vehicleProp;
}
-}
+}
\ No newline at end of file
diff --git a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
index c531e82..b4915cc 100644
--- a/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
+++ b/tests/EmbeddedKitchenSinkApp/src/com/google/android/car/kitchensink/bluetooth/MapMceTestFragment.java
@@ -243,8 +243,8 @@
mDelivered.setChecked(true);
} else if (action.equals(BluetoothMapClient.ACTION_MESSAGE_RECEIVED)) {
- String[] recipients = intent.getStringArrayExtra(android.provider
- .ContactsContract.Intents.EXTRA_RECIPIENT_CONTACT_URI);
+ String[] recipients = intent.getStringArrayExtra(
+ BluetoothMapClient.EXTRA_SENDER_CONTACT_URI);
StringBuilder stringBuilder = new StringBuilder();
if (recipients != null) {
for (String s : recipients) {
@@ -252,8 +252,8 @@
}
}
- String[] recipientsName = intent.getStringArrayExtra(android.provider
- .ContactsContract.Intents.EXTRA_RECIPIENT_CONTACT_NAME);
+ String[] recipientsName = intent.getStringArrayExtra(
+ BluetoothMapClient.EXTRA_SENDER_CONTACT_NAME);
StringBuilder stringBuilderName = new StringBuilder();
if (recipientsName != null) {
for (String s : recipientsName) {
diff --git a/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java b/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java
index 71f957e..39c0519 100644
--- a/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java
+++ b/tests/VmsSubscriberClientSample/src/com/google/android/car/vms/subscriber/VmsSubscriberClientSampleActivity.java
@@ -89,7 +89,7 @@
private void configureSubscriptions(VmsSubscriberManager vmsSubscriberManager) {
try {
vmsSubscriberManager.setListener(mListener);
- vmsSubscriberManager.subscribe(TEST_LAYER_ID, TEST_LAYER_VERSION, false);
+ vmsSubscriberManager.subscribe(TEST_LAYER_ID, TEST_LAYER_VERSION);
} catch (android.car.CarNotConnectedException e) {
Log.e(TAG, "Car is not connected!", e);
}
diff --git a/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java b/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
new file mode 100644
index 0000000..bc18c13
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/test/CarDiagnosticManagerTest.java
@@ -0,0 +1,663 @@
+/*
+ * 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.android.car.test;
+
+import static java.lang.Integer.toHexString;
+
+import android.car.Car;
+import android.car.hardware.CarDiagnosticEvent;
+import android.car.hardware.CarDiagnosticEvent.FuelSystemStatus;
+import android.car.hardware.CarDiagnosticEvent.FuelType;
+import android.car.hardware.CarDiagnosticEvent.IgnitionMonitors;
+import android.car.hardware.CarDiagnosticEvent.IgnitionMonitors.CommonIgnitionMonitors;
+import android.car.hardware.CarDiagnosticEvent.IgnitionMonitors.CompressionIgnitionMonitors;
+import android.car.hardware.CarDiagnosticEvent.IgnitionMonitors.SparkIgnitionMonitors;
+import android.car.hardware.CarDiagnosticEvent.Obd2FloatSensorIndex;
+import android.car.hardware.CarDiagnosticEvent.Obd2IntegerSensorIndex;
+import android.car.hardware.CarDiagnosticEvent.SecondaryAirStatus;
+import android.car.hardware.CarDiagnosticManager;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
+import android.os.SystemClock;
+import android.test.suitebuilder.annotation.MediumTest;
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+/** Test the public entry points for the CarDiagnosticManager */
+@MediumTest
+public class CarDiagnosticManagerTest extends MockedCarTestBase {
+ private static final String TAG = CarDiagnosticManagerTest.class.getSimpleName();
+
+ private final DiagnosticEventBuilder mLiveFrameEventBuilder =
+ new DiagnosticEventBuilder(VehicleProperty.OBD2_LIVE_FRAME);
+ private final DiagnosticEventBuilder mFreezeFrameEventBuilder =
+ new DiagnosticEventBuilder(VehicleProperty.OBD2_FREEZE_FRAME);
+ private final FreezeFrameProperties mFreezeFrameProperties = new FreezeFrameProperties();
+
+ private CarDiagnosticManager mCarDiagnosticManager;
+
+ private static final String DTC = "P1010";
+
+ /**
+ * This class is a central repository for freeze frame data. It ensures that timestamps and
+ * events are kept in sync and provides a consistent access model for diagnostic properties.
+ */
+ class FreezeFrameProperties {
+ private final HashMap<Long, VehiclePropValue> mEvents = new HashMap<>();
+
+ public final VehicleHalPropertyHandler mFreezeFrameInfoHandler =
+ new FreezeFrameInfoHandler();
+ public final VehicleHalPropertyHandler mFreezeFrameHandler = new FreezeFrameHandler();
+ public final VehicleHalPropertyHandler mFreezeFrameClearHandler =
+ new FreezeFrameClearHandler();
+
+ synchronized VehiclePropValue addNewEvent(DiagnosticEventBuilder builder) {
+ long timestamp = SystemClock.elapsedRealtimeNanos();
+ return addNewEvent(builder, timestamp);
+ }
+
+ synchronized VehiclePropValue addNewEvent(DiagnosticEventBuilder builder, long timestamp) {
+ VehiclePropValue newEvent = builder.build(timestamp);
+ mEvents.put(timestamp, newEvent);
+ return newEvent;
+ }
+
+ synchronized VehiclePropValue removeEvent(long timestamp) {
+ return mEvents.remove(timestamp);
+ }
+
+ synchronized void removeEvents() {
+ mEvents.clear();
+ }
+
+ synchronized long[] getTimestamps() {
+ return mEvents.keySet().stream().mapToLong(Long::longValue).toArray();
+ }
+
+ synchronized VehiclePropValue getEvent(long timestamp) {
+ return mEvents.get(timestamp);
+ }
+
+ class FreezeFramePropertyHandler implements VehicleHalPropertyHandler {
+ private boolean mSubscribed = false;
+
+ protected final int VEHICLE_PROPERTY;
+
+ protected FreezeFramePropertyHandler(int propertyId) {
+ VEHICLE_PROPERTY = propertyId;
+ }
+
+ @Override
+ public synchronized void onPropertySet(VehiclePropValue value) {
+ assertEquals(VEHICLE_PROPERTY, value.prop);
+ }
+
+ @Override
+ public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
+ assertEquals(VEHICLE_PROPERTY, value.prop);
+ return null;
+ }
+
+ @Override
+ public synchronized void onPropertySubscribe(
+ int property, int zones, float sampleRate) {
+ assertEquals(VEHICLE_PROPERTY, property);
+ mSubscribed = true;
+ }
+
+ @Override
+ public synchronized void onPropertyUnsubscribe(int property) {
+ assertEquals(VEHICLE_PROPERTY, property);
+ if (!mSubscribed) {
+ throw new IllegalArgumentException(
+ "Property was not subscribed 0x" + toHexString(property));
+ }
+ mSubscribed = false;
+ }
+ }
+
+ class FreezeFrameInfoHandler extends FreezeFramePropertyHandler {
+ FreezeFrameInfoHandler() {
+ super(VehicleProperty.OBD2_FREEZE_FRAME_INFO);
+ }
+
+ @Override
+ public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
+ super.onPropertyGet(value);
+ VehiclePropValueBuilder builder =
+ VehiclePropValueBuilder.newBuilder(VEHICLE_PROPERTY);
+ builder.setInt64Value(getTimestamps());
+ return builder.build();
+ }
+ }
+
+ class FreezeFrameHandler extends FreezeFramePropertyHandler {
+ FreezeFrameHandler() {
+ super(VehicleProperty.OBD2_FREEZE_FRAME);
+ }
+
+ @Override
+ public synchronized VehiclePropValue onPropertyGet(VehiclePropValue value) {
+ super.onPropertyGet(value);
+ long timestamp = value.value.int64Values.get(0);
+ return getEvent(timestamp);
+ }
+ }
+
+ class FreezeFrameClearHandler extends FreezeFramePropertyHandler {
+ FreezeFrameClearHandler() {
+ super(VehicleProperty.OBD2_FREEZE_FRAME_CLEAR);
+ }
+
+ @Override
+ public synchronized void onPropertySet(VehiclePropValue value) {
+ super.onPropertySet(value);
+ if (0 == value.value.int64Values.size()) {
+ removeEvents();
+ } else {
+ for (long timestamp : value.value.int64Values) {
+ removeEvent(timestamp);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ protected synchronized void configureMockedHal() {
+ java.util.Collection<Integer> numVendorSensors = Arrays.asList(0, 0);
+ addProperty(VehicleProperty.OBD2_LIVE_FRAME, mLiveFrameEventBuilder.build())
+ .setConfigArray(numVendorSensors);
+ addProperty(
+ VehicleProperty.OBD2_FREEZE_FRAME_INFO,
+ mFreezeFrameProperties.mFreezeFrameInfoHandler);
+ addProperty(VehicleProperty.OBD2_FREEZE_FRAME, mFreezeFrameProperties.mFreezeFrameHandler)
+ .setConfigArray(numVendorSensors);
+ addProperty(
+ VehicleProperty.OBD2_FREEZE_FRAME_CLEAR,
+ mFreezeFrameProperties.mFreezeFrameClearHandler);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.AMBIENT_AIR_TEMPERATURE, 30);
+ mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.FUEL_SYSTEM_STATUS,
+ FuelSystemStatus.OPEN_ENGINE_LOAD_OR_DECELERATION);
+ mLiveFrameEventBuilder.addIntSensor(
+ Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START, 5000);
+ mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.CONTROL_MODULE_VOLTAGE, 2);
+ mLiveFrameEventBuilder.addFloatSensor(Obd2FloatSensorIndex.CALCULATED_ENGINE_LOAD, 0.125f);
+ mLiveFrameEventBuilder.addFloatSensor(Obd2FloatSensorIndex.VEHICLE_SPEED, 12.5f);
+
+ mFreezeFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.AMBIENT_AIR_TEMPERATURE, 30);
+ mFreezeFrameEventBuilder.addIntSensor(
+ Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START, 5000);
+ mFreezeFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.CONTROL_MODULE_VOLTAGE, 2);
+ mFreezeFrameEventBuilder.addFloatSensor(
+ Obd2FloatSensorIndex.CALCULATED_ENGINE_LOAD, 0.125f);
+ mFreezeFrameEventBuilder.addFloatSensor(Obd2FloatSensorIndex.VEHICLE_SPEED, 12.5f);
+ mFreezeFrameEventBuilder.setDTC(DTC);
+
+ super.setUp();
+
+ mCarDiagnosticManager =
+ (CarDiagnosticManager) getCar().getCarManager(Car.DIAGNOSTIC_SERVICE);
+ }
+
+ public void testLiveFrameRead() throws Exception {
+ CarDiagnosticEvent liveFrame = mCarDiagnosticManager.getLatestLiveFrame();
+
+ assertNotNull(liveFrame);
+ assertTrue(liveFrame.isLiveFrame());
+ assertFalse(liveFrame.isFreezeFrame());
+ assertFalse(liveFrame.isEmptyFrame());
+
+ assertEquals(
+ 5000,
+ liveFrame
+ .getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START)
+ .intValue());
+ assertEquals(
+ 30,
+ liveFrame
+ .getSystemIntegerSensor(Obd2IntegerSensorIndex.AMBIENT_AIR_TEMPERATURE)
+ .intValue());
+ assertEquals(
+ 2,
+ liveFrame
+ .getSystemIntegerSensor(Obd2IntegerSensorIndex.CONTROL_MODULE_VOLTAGE)
+ .intValue());
+ assertEquals(
+ 0.125f,
+ liveFrame
+ .getSystemFloatSensor(Obd2FloatSensorIndex.CALCULATED_ENGINE_LOAD)
+ .floatValue());
+ assertEquals(12.5f,
+ liveFrame
+ .getSystemFloatSensor(Obd2FloatSensorIndex.VEHICLE_SPEED)
+ .floatValue());
+ }
+
+ public void testLiveFrameEvent() throws Exception {
+ Listener listener = new Listener();
+ mCarDiagnosticManager.registerListener(
+ listener,
+ CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+ android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+ listener.reset();
+ long time = SystemClock.elapsedRealtimeNanos();
+ mLiveFrameEventBuilder.addIntSensor(
+ Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START, 5100);
+
+ getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(time));
+ assertTrue(listener.waitForEvent(time));
+
+ CarDiagnosticEvent liveFrame = listener.getLastEvent();
+
+ assertEquals(
+ 5100,
+ liveFrame
+ .getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START)
+ .intValue());
+ }
+
+ public void testMissingSensorRead() throws Exception {
+ Listener listener = new Listener();
+ mCarDiagnosticManager.registerListener(
+ listener,
+ CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+ android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+ getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build());
+ assertTrue(listener.waitForEvent());
+
+ CarDiagnosticEvent liveFrame = listener.getLastEvent();
+ assertNotNull(liveFrame);
+
+ assertNull(liveFrame.getSystemIntegerSensor(
+ Obd2IntegerSensorIndex.DRIVER_DEMAND_PERCENT_TORQUE));
+ assertEquals(-1, liveFrame.getSystemIntegerSensor(
+ Obd2IntegerSensorIndex.DRIVER_DEMAND_PERCENT_TORQUE,
+ -1));
+
+ assertNull(liveFrame.getSystemFloatSensor(
+ Obd2FloatSensorIndex.OXYGEN_SENSOR6_VOLTAGE));
+ assertEquals(0.25f, liveFrame.getSystemFloatSensor(
+ Obd2FloatSensorIndex.OXYGEN_SENSOR5_VOLTAGE,
+ 0.25f));
+
+ assertNull(liveFrame.getVendorIntegerSensor(Obd2IntegerSensorIndex.VENDOR_START));
+ assertEquals(-1, liveFrame.getVendorIntegerSensor(
+ Obd2IntegerSensorIndex.VENDOR_START, -1));
+
+ assertNull(liveFrame.getVendorFloatSensor(Obd2FloatSensorIndex.VENDOR_START));
+ assertEquals(0.25f, liveFrame.getVendorFloatSensor(
+ Obd2FloatSensorIndex.VENDOR_START, 0.25f));
+ }
+
+ public void testFuelSystemStatus() throws Exception {
+ Listener listener = new Listener();
+ mCarDiagnosticManager.registerListener(
+ listener,
+ CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+ android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+ getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build());
+ assertTrue(listener.waitForEvent());
+
+ CarDiagnosticEvent liveFrame = listener.getLastEvent();
+ assertNotNull(liveFrame);
+
+ assertEquals(FuelSystemStatus.OPEN_ENGINE_LOAD_OR_DECELERATION,
+ liveFrame.getSystemIntegerSensor(Obd2IntegerSensorIndex.FUEL_SYSTEM_STATUS).intValue());
+ assertEquals(FuelSystemStatus.OPEN_ENGINE_LOAD_OR_DECELERATION,
+ liveFrame.getFuelSystemStatus().intValue());
+ }
+
+ public void testSecondaryAirStatus() throws Exception {
+ Listener listener = new Listener();
+ mCarDiagnosticManager.registerListener(
+ listener,
+ CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+ android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+ mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.COMMANDED_SECONDARY_AIR_STATUS,
+ SecondaryAirStatus.FROM_OUTSIDE_OR_OFF);
+ long timestamp = SystemClock.elapsedRealtimeNanos();
+ getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(timestamp));
+
+ assertTrue(listener.waitForEvent(timestamp));
+
+ CarDiagnosticEvent liveFrame = listener.getLastEvent();
+ assertNotNull(liveFrame);
+
+ assertEquals(SecondaryAirStatus.FROM_OUTSIDE_OR_OFF,
+ liveFrame.getSystemIntegerSensor(
+ Obd2IntegerSensorIndex.COMMANDED_SECONDARY_AIR_STATUS).intValue());
+ assertEquals(SecondaryAirStatus.FROM_OUTSIDE_OR_OFF,
+ liveFrame.getSecondaryAirStatus().intValue());
+ }
+
+ public void testIgnitionMonitors() throws Exception {
+ Listener listener = new Listener();
+ mCarDiagnosticManager.registerListener(
+ listener,
+ CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+ android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+ // cfr. CarDiagnosticEvent for the meaning of the several bits
+ final int sparkMonitorsValue = 0x1 | (0x1 << 2) | (0x1 << 3) | (0x1 << 6)
+ | (0x1 << 10) | (0x1 << 11);
+
+ final int compressionMonitorsValue = (0x1 << 2) | (0x1 << 3) | (0x1 << 6)
+ | (0x1 << 12) | (0x1 << 13);
+
+ mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.IGNITION_MONITORS_SUPPORTED,0);
+ mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.IGNITION_SPECIFIC_MONITORS,
+ sparkMonitorsValue);
+
+ long timestamp = SystemClock.elapsedRealtimeNanos();
+ getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(timestamp));
+
+ assertTrue(listener.waitForEvent(timestamp));
+
+ CarDiagnosticEvent liveFrame = listener.getLastEvent();
+ assertNotNull(liveFrame);
+
+ CommonIgnitionMonitors commonIgnitionMonitors = liveFrame.getIgnitionMonitors();
+ assertNotNull(commonIgnitionMonitors);
+ assertTrue(commonIgnitionMonitors.components.available);
+ assertFalse(commonIgnitionMonitors.components.incomplete);
+ assertTrue(commonIgnitionMonitors.fuelSystem.available);
+ assertTrue(commonIgnitionMonitors.fuelSystem.incomplete);
+ assertFalse(commonIgnitionMonitors.misfire.available);
+ assertFalse(commonIgnitionMonitors.misfire.incomplete);
+
+ SparkIgnitionMonitors sparkIgnitionMonitors =
+ commonIgnitionMonitors.asSparkIgnitionMonitors();
+ assertNotNull(sparkIgnitionMonitors);
+ assertNull(commonIgnitionMonitors.asCompressionIgnitionMonitors());
+
+ assertTrue(sparkIgnitionMonitors.EGR.available);
+ assertFalse(sparkIgnitionMonitors.EGR.incomplete);
+ assertFalse(sparkIgnitionMonitors.oxygenSensorHeater.available);
+ assertFalse(sparkIgnitionMonitors.oxygenSensorHeater.incomplete);
+ assertTrue(sparkIgnitionMonitors.oxygenSensor.available);
+ assertTrue(sparkIgnitionMonitors.oxygenSensor.incomplete);
+ assertFalse(sparkIgnitionMonitors.ACRefrigerant.available);
+ assertFalse(sparkIgnitionMonitors.ACRefrigerant.incomplete);
+ assertFalse(sparkIgnitionMonitors.secondaryAirSystem.available);
+ assertFalse(sparkIgnitionMonitors.secondaryAirSystem.incomplete);
+ assertFalse(sparkIgnitionMonitors.evaporativeSystem.available);
+ assertFalse(sparkIgnitionMonitors.evaporativeSystem.incomplete);
+ assertFalse(sparkIgnitionMonitors.heatedCatalyst.available);
+ assertFalse(sparkIgnitionMonitors.heatedCatalyst.incomplete);
+ assertFalse(sparkIgnitionMonitors.catalyst.available);
+ assertFalse(sparkIgnitionMonitors.catalyst.incomplete);
+
+ mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.IGNITION_MONITORS_SUPPORTED,1);
+ mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.IGNITION_SPECIFIC_MONITORS,
+ compressionMonitorsValue);
+
+ timestamp += 1000;
+ getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(timestamp));
+
+ assertTrue(listener.waitForEvent(timestamp));
+
+ liveFrame = listener.getLastEvent();
+ assertNotNull(liveFrame);
+ assertEquals(timestamp, liveFrame.timestamp);
+
+ commonIgnitionMonitors = liveFrame.getIgnitionMonitors();
+ assertNotNull(commonIgnitionMonitors);
+ assertFalse(commonIgnitionMonitors.components.available);
+ assertFalse(commonIgnitionMonitors.components.incomplete);
+ assertTrue(commonIgnitionMonitors.fuelSystem.available);
+ assertTrue(commonIgnitionMonitors.fuelSystem.incomplete);
+ assertFalse(commonIgnitionMonitors.misfire.available);
+ assertFalse(commonIgnitionMonitors.misfire.incomplete);
+ CompressionIgnitionMonitors compressionIgnitionMonitors =
+ commonIgnitionMonitors.asCompressionIgnitionMonitors();
+ assertNull(commonIgnitionMonitors.asSparkIgnitionMonitors());
+ assertNotNull(compressionIgnitionMonitors);
+
+ assertTrue(compressionIgnitionMonitors.EGROrVVT.available);
+ assertFalse(compressionIgnitionMonitors.EGROrVVT.incomplete);
+ assertFalse(compressionIgnitionMonitors.PMFilter.available);
+ assertFalse(compressionIgnitionMonitors.PMFilter.incomplete);
+ assertFalse(compressionIgnitionMonitors.exhaustGasSensor.available);
+ assertFalse(compressionIgnitionMonitors.exhaustGasSensor.incomplete);
+ assertTrue(compressionIgnitionMonitors.boostPressure.available);
+ assertTrue(compressionIgnitionMonitors.boostPressure.incomplete);
+ assertFalse(compressionIgnitionMonitors.NOxSCR.available);
+ assertFalse(compressionIgnitionMonitors.NOxSCR.incomplete);
+ assertFalse(compressionIgnitionMonitors.NMHCCatalyst.available);
+ assertFalse(compressionIgnitionMonitors.NMHCCatalyst.incomplete);
+ }
+
+ public void testFuelType() throws Exception {
+ Listener listener = new Listener();
+ mCarDiagnosticManager.registerListener(
+ listener,
+ CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+ android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+ mLiveFrameEventBuilder.addIntSensor(Obd2IntegerSensorIndex.FUEL_TYPE,
+ FuelType.BIFUEL_RUNNING_LPG);
+ long timestamp = SystemClock.elapsedRealtimeNanos();
+ getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(timestamp));
+
+ assertTrue(listener.waitForEvent(timestamp));
+
+ CarDiagnosticEvent liveFrame = listener.getLastEvent();
+ assertNotNull(liveFrame);
+
+ assertEquals(FuelType.BIFUEL_RUNNING_LPG,
+ liveFrame.getSystemIntegerSensor(
+ Obd2IntegerSensorIndex.FUEL_TYPE).intValue());
+ assertEquals(FuelType.BIFUEL_RUNNING_LPG,
+ liveFrame.getFuelType().intValue());
+ }
+
+ public void testMultipleListeners() throws Exception {
+ Listener listener1 = new Listener();
+ Listener listener2 = new Listener();
+
+ mCarDiagnosticManager.registerListener(
+ listener1,
+ CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+ android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+ mCarDiagnosticManager.registerListener(
+ listener2,
+ CarDiagnosticManager.FRAME_TYPE_FLAG_LIVE,
+ android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+ listener1.reset();
+ listener2.reset();
+
+ long time = SystemClock.elapsedRealtimeNanos();
+ getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(time));
+ assertTrue(listener1.waitForEvent(time));
+ assertTrue(listener2.waitForEvent(time));
+
+ CarDiagnosticEvent event1 = listener1.getLastEvent();
+ CarDiagnosticEvent event2 = listener2.getLastEvent();
+ assertEquals(
+ 5000,
+ event1
+ .getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START)
+ .intValue());
+ assertEquals(
+ 5000,
+ event2
+ .getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START)
+ .intValue());
+
+ listener1.reset();
+ listener2.reset();
+
+ mCarDiagnosticManager.unregisterListener(listener1);
+
+ time += 1000;
+ getMockedVehicleHal().injectEvent(mLiveFrameEventBuilder.build(time));
+ assertFalse(listener1.waitForEvent(time));
+ assertTrue(listener2.waitForEvent(time));
+
+ assertNull(listener1.getLastEvent());
+ event2 = listener2.getLastEvent();
+
+ assertTrue(event1.isEarlierThan(event2));
+
+ assertEquals(
+ 5000,
+ event2
+ .getSystemIntegerSensor(Obd2IntegerSensorIndex.RUNTIME_SINCE_ENGINE_START)
+ .intValue());
+ }
+
+ public void testFreezeFrameEvent() throws Exception {
+ Listener listener = new Listener();
+ mCarDiagnosticManager.registerListener(
+ listener,
+ CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE,
+ android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+ listener.reset();
+ VehiclePropValue injectedEvent =
+ mFreezeFrameProperties.addNewEvent(mFreezeFrameEventBuilder);
+ getMockedVehicleHal().injectEvent(injectedEvent);
+ assertTrue(listener.waitForEvent(injectedEvent.timestamp));
+
+ CarDiagnosticEvent freezeFrame = listener.getLastEvent();
+
+ assertEquals(DTC, freezeFrame.dtc);
+
+ mFreezeFrameEventBuilder.addIntSensor(
+ Obd2IntegerSensorIndex.ABSOLUTE_BAROMETRIC_PRESSURE, 22);
+ injectedEvent = mFreezeFrameProperties.addNewEvent(mFreezeFrameEventBuilder);
+ getMockedVehicleHal().injectEvent(injectedEvent);
+ assertTrue(listener.waitForEvent(injectedEvent.timestamp));
+
+ freezeFrame = listener.getLastEvent();
+
+ assertNotNull(freezeFrame);
+ assertFalse(freezeFrame.isLiveFrame());
+ assertTrue(freezeFrame.isFreezeFrame());
+ assertFalse(freezeFrame.isEmptyFrame());
+
+ assertEquals(DTC, freezeFrame.dtc);
+ assertEquals(
+ 22,
+ freezeFrame
+ .getSystemIntegerSensor(Obd2IntegerSensorIndex.ABSOLUTE_BAROMETRIC_PRESSURE)
+ .intValue());
+ }
+
+ public void testFreezeFrameTimestamps() throws Exception {
+ Listener listener = new Listener();
+ mCarDiagnosticManager.registerListener(
+ listener,
+ CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE,
+ android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+ Set<Long> generatedTimestamps = new HashSet<>();
+
+ VehiclePropValue injectedEvent =
+ mFreezeFrameProperties.addNewEvent(mFreezeFrameEventBuilder);
+ getMockedVehicleHal().injectEvent(injectedEvent);
+ generatedTimestamps.add(injectedEvent.timestamp);
+ assertTrue(listener.waitForEvent(injectedEvent.timestamp));
+
+ injectedEvent =
+ mFreezeFrameProperties.addNewEvent(
+ mFreezeFrameEventBuilder, injectedEvent.timestamp + 1000);
+ getMockedVehicleHal().injectEvent(injectedEvent);
+ generatedTimestamps.add(injectedEvent.timestamp);
+ assertTrue(listener.waitForEvent(injectedEvent.timestamp));
+
+ long[] acquiredTimestamps = mCarDiagnosticManager.getFreezeFrameTimestamps();
+ assertEquals(generatedTimestamps.size(), acquiredTimestamps.length);
+ for (long acquiredTimestamp : acquiredTimestamps) {
+ assertTrue(generatedTimestamps.contains(acquiredTimestamp));
+ }
+ }
+
+ public void testClearFreezeFrameTimestamps() throws Exception {
+ Listener listener = new Listener();
+ mCarDiagnosticManager.registerListener(
+ listener,
+ CarDiagnosticManager.FRAME_TYPE_FLAG_FREEZE,
+ android.car.hardware.CarSensorManager.SENSOR_RATE_NORMAL);
+
+ VehiclePropValue injectedEvent =
+ mFreezeFrameProperties.addNewEvent(mFreezeFrameEventBuilder);
+ getMockedVehicleHal().injectEvent(injectedEvent);
+ assertTrue(listener.waitForEvent(injectedEvent.timestamp));
+
+ assertNotNull(mCarDiagnosticManager.getFreezeFrame(injectedEvent.timestamp));
+ mCarDiagnosticManager.clearFreezeFrames(injectedEvent.timestamp);
+ assertNull(mCarDiagnosticManager.getFreezeFrame(injectedEvent.timestamp));
+ }
+
+ class Listener implements CarDiagnosticManager.OnDiagnosticEventListener {
+ private final Object mSync = new Object();
+
+ private CarDiagnosticEvent mLastEvent = null;
+
+ CarDiagnosticEvent getLastEvent() {
+ return mLastEvent;
+ }
+
+ void reset() {
+ synchronized (mSync) {
+ mLastEvent = null;
+ }
+ }
+
+ boolean waitForEvent() throws InterruptedException {
+ return waitForEvent(0);
+ }
+
+ boolean waitForEvent(long eventTimeStamp) throws InterruptedException {
+ long start = SystemClock.elapsedRealtime();
+ boolean matchTimeStamp = eventTimeStamp != 0;
+ synchronized (mSync) {
+ while ((mLastEvent == null
+ || (matchTimeStamp && mLastEvent.timestamp != eventTimeStamp))
+ && (start + SHORT_WAIT_TIMEOUT_MS > SystemClock.elapsedRealtime())) {
+ mSync.wait(10L);
+ }
+ return mLastEvent != null
+ && (!matchTimeStamp || mLastEvent.timestamp == eventTimeStamp);
+ }
+ }
+
+ @Override
+ public void onDiagnosticEvent(CarDiagnosticEvent event) {
+ synchronized (mSync) {
+ // We're going to hold a reference to this object
+ mLastEvent = event;
+ mSync.notify();
+ }
+ }
+ }
+}
diff --git a/tests/carservice_test/src/com/android/car/test/DiagnosticEventBuilder.java b/tests/carservice_test/src/com/android/car/test/DiagnosticEventBuilder.java
new file mode 100644
index 0000000..ba511c8
--- /dev/null
+++ b/tests/carservice_test/src/com/android/car/test/DiagnosticEventBuilder.java
@@ -0,0 +1,148 @@
+/*
+ * 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.android.car.test;
+
+import android.car.hardware.CarDiagnosticEvent.Obd2FloatSensorIndex;
+import android.car.hardware.CarDiagnosticEvent.Obd2IntegerSensorIndex;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropConfig;
+import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
+import android.util.SparseArray;
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+import java.util.BitSet;
+import java.util.Iterator;
+
+/**
+ * A builder class for a VehiclePropValue that encapsulates a diagnostic event. This is the Java
+ * equivalent of Obd2SensorStore.cpp in the native layer.
+ *
+ * @hide
+ */
+public class DiagnosticEventBuilder {
+ /**
+ * An array-like container that knows to return a default value for any unwritten-to index.
+ *
+ * @param <T> the element type
+ */
+ class DefaultedArray<T> implements Iterable<T> {
+ private final SparseArray<T> mElements = new SparseArray<>();
+ private final int mSize;
+ private final T mDefaultValue;
+
+ DefaultedArray(int size, T defaultValue) {
+ mSize = size;
+ mDefaultValue = defaultValue;
+ }
+
+ private int checkIndex(int index) {
+ if (index < 0 || index >= mSize)
+ throw new IndexOutOfBoundsException(
+ String.format("Index: %d, Size: %d", index, mSize));
+ return index;
+ }
+
+ DefaultedArray<T> set(int index, T element) {
+ checkIndex(index);
+ mElements.put(index, element);
+ return this;
+ }
+
+ T get(int index) {
+ checkIndex(index);
+ return mElements.get(index, mDefaultValue);
+ }
+
+ int size() {
+ return mSize;
+ }
+
+ @Override
+ public Iterator<T> iterator() {
+ return new Iterator<T>() {
+ private int mIndex = 0;
+
+ @Override
+ public boolean hasNext() {
+ return (mIndex >= 0) && (mIndex < mSize);
+ }
+
+ @Override
+ public T next() {
+ int index = mIndex++;
+ return get(index);
+ }
+ };
+ }
+ }
+
+ private final int mPropertyId;
+ private final int mNumIntSensors;
+ private final DefaultedArray<Integer> mIntValues;
+ private final DefaultedArray<Float> mFloatValues;
+ private final BitSet mBitmask;
+ private String mDtc = null;
+
+ public DiagnosticEventBuilder(VehiclePropConfig propConfig) {
+ this(propConfig.prop, propConfig.configArray.get(0), propConfig.configArray.get(1));
+ }
+
+ public DiagnosticEventBuilder(int propertyId) {
+ this(propertyId, 0, 0);
+ }
+
+ public DiagnosticEventBuilder(
+ int propertyId, int numVendorIntSensors, int numVendorFloatSensors) {
+ mPropertyId = propertyId;
+ mNumIntSensors = Obd2IntegerSensorIndex.LAST_SYSTEM + 1 + numVendorIntSensors;
+ final int numFloatSensors = Obd2FloatSensorIndex.LAST_SYSTEM + 1 + numVendorFloatSensors;
+ mBitmask = new BitSet(mNumIntSensors + numFloatSensors);
+ mIntValues = new DefaultedArray<>(mNumIntSensors, 0);
+ mFloatValues = new DefaultedArray<>(numFloatSensors, 0.0f);
+ }
+
+ public DiagnosticEventBuilder addIntSensor(int index, int value) {
+ mIntValues.set(index, value);
+ mBitmask.set(index);
+ return this;
+ }
+
+ public DiagnosticEventBuilder addFloatSensor(int index, float value) {
+ mFloatValues.set(index, value);
+ mBitmask.set(mNumIntSensors + index);
+ return this;
+ }
+
+ public DiagnosticEventBuilder setDTC(String dtc) {
+ mDtc = dtc;
+ return this;
+ }
+
+ public VehiclePropValue build() {
+ return build(0);
+ }
+
+ public VehiclePropValue build(long timestamp) {
+ VehiclePropValueBuilder propValueBuilder = VehiclePropValueBuilder.newBuilder(mPropertyId);
+ if (0 == timestamp) {
+ propValueBuilder.setTimestamp();
+ } else {
+ propValueBuilder.setTimestamp(timestamp);
+ }
+ mIntValues.forEach(propValueBuilder::addIntValue);
+ mFloatValues.forEach(propValueBuilder::addFloatValue);
+ return propValueBuilder.addByteValue(mBitmask.toByteArray()).setStringValue(mDtc).build();
+ }
+}
diff --git a/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java b/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java
index a146e73..84f564d 100644
--- a/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java
+++ b/tests/carservice_test/src/com/android/car/test/VmsPublisherClientServiceTest.java
@@ -24,17 +24,20 @@
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_1.VmsMessageIntegerValuesIndex;
+import android.hardware.automotive.vehicle.V2_1.VmsMessageType;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import com.android.car.R;
+import com.android.car.vehiclehal.VehiclePropValueBuilder;
+import com.android.car.vehiclehal.test.MockedVehicleHal;
import com.android.car.vehiclehal.test.MockedVehicleHal.VehicleHalPropertyHandler;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
@@ -43,6 +46,8 @@
@MediumTest
public class VmsPublisherClientServiceTest extends MockedCarTestBase {
private static final String TAG = "VmsPublisherTest";
+ private static final int MOCK_PUBLISHER_LAYER_ID = 12;
+ private static final int MOCK_PUBLISHER_LAYER_VERSION = 34;
private HalHandler mHalHandler;
// Used to block until the HAL property is updated in HalHandler.onPropertySet.
@@ -85,6 +90,14 @@
return wrapper;
}
+ private VehiclePropValue getHalSubscriptionRequest() {
+ return VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+ .addIntValue(VmsMessageType.SUBSCRIBE)
+ .addIntValue(MOCK_PUBLISHER_LAYER_ID)
+ .addIntValue(MOCK_PUBLISHER_LAYER_VERSION)
+ .build();
+ }
+
@Override
protected void setUp() throws Exception {
/**
@@ -93,6 +106,10 @@
*/
mHalHandlerSemaphore = new Semaphore(0);
super.setUp();
+
+ // Inject a subscribe event which simulates the HAL is subscribed to the Mock Publisher.
+ MockedVehicleHal mHal = getMockedVehicleHal();
+ mHal.injectEvent(getHalSubscriptionRequest());
}
/**
@@ -104,6 +121,10 @@
* this test.
*/
public void testPublish() throws Exception {
+ //TODO: This test is using minial synchronisation between clients.
+ // If more complexity is added this may result in publisher
+ // publishing before the subscriber subscribed, in which case
+ // the semaphore will not be released.
assertTrue(mHalHandlerSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
VehiclePropValue.RawValue rawValue = mHalHandler.getValue().value;
int messageType = rawValue.int32Values.get(VmsMessageIntegerValuesIndex.VMS_MESSAGE_TYPE);
@@ -125,7 +146,13 @@
@Override
public synchronized void onPropertySet(VehiclePropValue value) {
mValue = value;
- mHalHandlerSemaphore.release();
+
+ // If this is the data message release the semaphone so the test can continue.
+ ArrayList<Integer> int32Values = value.value.int32Values;
+ if (int32Values.get(VmsMessageIntegerValuesIndex.VMS_MESSAGE_TYPE) ==
+ VmsMessageType.DATA) {
+ mHalHandlerSemaphore.release();
+ }
}
@Override
diff --git a/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java b/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java
index 97215e2..d0b3ac8 100644
--- a/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java
+++ b/tests/carservice_test/src/com/android/car/test/VmsSubscriberManagerTest.java
@@ -22,10 +22,10 @@
import android.car.vms.VmsSubscriberManager.OnVmsMessageReceivedListener;
import android.car.vms.VmsSubscriberManager;
import android.hardware.automotive.vehicle.V2_0.VehiclePropValue;
-import android.hardware.automotive.vehicle.V2_0.VehicleProperty;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyAccess;
import android.hardware.automotive.vehicle.V2_0.VehiclePropertyChangeMode;
-import android.hardware.automotive.vehicle.V2_0.VmsMessageType;
+import android.hardware.automotive.vehicle.V2_1.VehicleProperty;
+import android.hardware.automotive.vehicle.V2_1.VmsMessageType;
import android.os.SystemClock;
import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
@@ -41,6 +41,8 @@
@MediumTest
public class VmsSubscriberManagerTest extends MockedCarTestBase {
private static final String TAG = "VmsSubscriberManagerTest";
+ private static final int SUBSCRIPTION_LAYER_ID = 2;
+ private static final int SUBSCRIPTION_LAYER_VERSION = 3;
private HalHandler mHalHandler;
// Used to block until the HAL property is updated in HalHandler.onPropertySet.
@@ -70,7 +72,7 @@
Car.VMS_SUBSCRIBER_SERVICE);
TestListener listener = new TestListener();
vmsSubscriberManager.setListener(listener);
- vmsSubscriberManager.subscribe(0, 0, false);
+ vmsSubscriberManager.subscribe(SUBSCRIPTION_LAYER_ID, SUBSCRIPTION_LAYER_VERSION);
// Inject a value and wait for its callback in TestListener.onVmsMessageReceived.
VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
@@ -78,16 +80,45 @@
.setTimestamp(SystemClock.elapsedRealtimeNanos())
.build();
v.value.int32Values.add(VmsMessageType.DATA); // MessageType
- v.value.int32Values.add(2); // Layer ID
- v.value.int32Values.add(3); // Layer Version
+ v.value.int32Values.add(SUBSCRIPTION_LAYER_ID);
+ v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
v.value.bytes.add((byte) 0xa);
v.value.bytes.add((byte) 0xb);
assertEquals(0, mSubscriberSemaphore.availablePermits());
getMockedVehicleHal().injectEvent(v);
assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
- assertEquals(2, listener.getLayerId());
- assertEquals(3, listener.getLayerVersion());
+ assertEquals(SUBSCRIPTION_LAYER_ID, listener.getLayerId());
+ assertEquals(SUBSCRIPTION_LAYER_VERSION, listener.getLayerVersion());
+ byte[] expectedPayload = {(byte) 0xa, (byte) 0xb};
+ assertTrue(Arrays.equals(expectedPayload, listener.getPayload()));
+ }
+
+
+ // Test injecting a value in the HAL and verifying it propagates to a subscriber.
+ public void testSubscribeAll() throws Exception {
+ VmsSubscriberManager vmsSubscriberManager = (VmsSubscriberManager) getCar().getCarManager(
+ Car.VMS_SUBSCRIBER_SERVICE);
+ TestListener listener = new TestListener();
+ vmsSubscriberManager.setListener(listener);
+ vmsSubscriberManager.subscribeAll();
+
+ // Inject a value and wait for its callback in TestListener.onVmsMessageReceived.
+ VehiclePropValue v = VehiclePropValueBuilder.newBuilder(VehicleProperty.VEHICLE_MAP_SERVICE)
+ .setAreaId(VehicleAreaType.VEHICLE_AREA_TYPE_NONE)
+ .setTimestamp(SystemClock.elapsedRealtimeNanos())
+ .build();
+ v.value.int32Values.add(VmsMessageType.DATA); // MessageType
+ v.value.int32Values.add(SUBSCRIPTION_LAYER_ID);
+ v.value.int32Values.add(SUBSCRIPTION_LAYER_VERSION);
+ v.value.bytes.add((byte) 0xa);
+ v.value.bytes.add((byte) 0xb);
+ assertEquals(0, mSubscriberSemaphore.availablePermits());
+
+ getMockedVehicleHal().injectEvent(v);
+ assertTrue(mSubscriberSemaphore.tryAcquire(2L, TimeUnit.SECONDS));
+ assertEquals(SUBSCRIPTION_LAYER_ID, listener.getLayerId());
+ assertEquals(SUBSCRIPTION_LAYER_VERSION, listener.getLayerVersion());
byte[] expectedPayload = {(byte) 0xa, (byte) 0xb};
assertTrue(Arrays.equals(expectedPayload, listener.getPayload()));
}
diff --git a/tools/bootanalyze/bootanalyze.py b/tools/bootanalyze/bootanalyze.py
index d05aa4d..d685209 100755
--- a/tools/bootanalyze/bootanalyze.py
+++ b/tools/bootanalyze/bootanalyze.py
@@ -39,6 +39,7 @@
BOOT_ANIM_END_TIME_KEY = "BootAnimEnd"
KERNEL_BOOT_COMPLETE = "BootComplete_kernel"
LOGCAT_BOOT_COMPLETE = "BootComplete"
+LAUNCHER_START = "LauncherStart"
BOOT_TIME_TOO_BIG = 200.0
MAX_RETRIES = 5
DEBUG = False
@@ -170,13 +171,13 @@
if args.reboot:
reboot()
- logcat_events, logcat_timing_events = collect_events(
- search_events, ADB_CMD + ' logcat -b all -v epoch', timings, [ LOGCAT_BOOT_COMPLETE,\
- KERNEL_BOOT_COMPLETE ])
-
dmesg_events, e = collect_events(search_events, ADB_CMD + ' shell su root dmesg -w', {},\
[ KERNEL_BOOT_COMPLETE ])
+ logcat_events, logcat_timing_events = collect_events(
+ search_events, ADB_CMD + ' logcat -b all -v epoch', timings, [ LOGCAT_BOOT_COMPLETE,\
+ KERNEL_BOOT_COMPLETE, \
+ LAUNCHER_START ])
logcat_event_time = extract_time(
logcat_events, TIME_LOGCAT, float);
logcat_original_time = extract_time(
diff --git a/tools/bootanalyze/config.yaml b/tools/bootanalyze/config.yaml
index 1afcd60..85f648e 100644
--- a/tools/bootanalyze/config.yaml
+++ b/tools/bootanalyze/config.yaml
@@ -36,9 +36,10 @@
PackageManagerInit_ready: StartPackageManagerService took to complete
BluetoothService_start: Starting com.android.server.BluetoothService
SystemUi_start: for service com.android.systemui/.
- LauncherReady: Em.Overview:\s*onResume
+ CarLauncherReady: Em.Overview:\s*onResume
CarService_start: for service com.android.car/.CarService
BootAnimStart: starting service 'bootanim'
BootAnimEnd: Service 'bootanim'
BootComplete: Starting phase 1000
BootComplete_kernel: processing action \(sys\.boot_completed=1\)
+ LauncherStart: START.*HOME.*NexusLauncherActivity
diff --git a/tools/emulator/VehicleHalProto_pb2.py b/tools/emulator/VehicleHalProto_pb2.py
new file mode 100644
index 0000000..aaad547
--- /dev/null
+++ b/tools/emulator/VehicleHalProto_pb2.py
@@ -0,0 +1,537 @@
+# Generated by the protocol buffer compiler. DO NOT EDIT!
+# source: VehicleHalProto.proto
+
+from google.protobuf.internal import enum_type_wrapper
+from google.protobuf import descriptor as _descriptor
+from google.protobuf import message as _message
+from google.protobuf import reflection as _reflection
+from google.protobuf import descriptor_pb2
+# @@protoc_insertion_point(imports)
+
+
+
+
+DESCRIPTOR = _descriptor.FileDescriptor(
+ name='VehicleHalProto.proto',
+ package='emulator',
+ serialized_pb='\n\x15VehicleHalProto.proto\x12\x08\x65mulator\"\xba\x01\n\x11VehicleAreaConfig\x12\x0f\n\x07\x61rea_id\x18\x01 \x02(\x05\x12\x17\n\x0fmin_int32_value\x18\x02 \x01(\x11\x12\x17\n\x0fmax_int32_value\x18\x03 \x01(\x11\x12\x17\n\x0fmin_int64_value\x18\x04 \x01(\x12\x12\x17\n\x0fmax_int64_value\x18\x05 \x01(\x12\x12\x17\n\x0fmin_float_value\x18\x06 \x01(\x02\x12\x17\n\x0fmax_float_value\x18\x07 \x01(\x02\"\x9b\x02\n\x11VehiclePropConfig\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x0e\n\x06\x61\x63\x63\x65ss\x18\x02 \x01(\x05\x12\x13\n\x0b\x63hange_mode\x18\x03 \x01(\x05\x12\x12\n\nvalue_type\x18\x04 \x01(\x05\x12\x17\n\x0fsupported_areas\x18\x05 \x01(\x05\x12\x31\n\x0c\x61rea_configs\x18\x06 \x03(\x0b\x32\x1b.emulator.VehicleAreaConfig\x12\x14\n\x0c\x63onfig_flags\x18\x07 \x01(\x05\x12\x14\n\x0c\x63onfig_array\x18\x08 \x03(\x05\x12\x15\n\rconfig_string\x18\t \x01(\t\x12\x17\n\x0fmin_sample_rate\x18\n \x01(\x02\x12\x17\n\x0fmax_sample_rate\x18\x0b \x01(\x02\"\xc5\x01\n\x10VehiclePropValue\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x12\n\nvalue_type\x18\x02 \x01(\x05\x12\x11\n\ttimestamp\x18\x03 \x01(\x03\x12\x0f\n\x07\x61rea_id\x18\x04 \x01(\x05\x12\x14\n\x0cint32_values\x18\x05 \x03(\x11\x12\x14\n\x0cint64_values\x18\x06 \x03(\x12\x12\x14\n\x0c\x66loat_values\x18\x07 \x03(\x02\x12\x14\n\x0cstring_value\x18\x08 \x01(\t\x12\x13\n\x0b\x62ytes_value\x18\t \x01(\x0c\"/\n\x0eVehiclePropGet\x12\x0c\n\x04prop\x18\x01 \x02(\x05\x12\x0f\n\x07\x61rea_id\x18\x02 \x01(\x05\"\xd8\x01\n\x0f\x45mulatorMessage\x12#\n\x08msg_type\x18\x01 \x02(\x0e\x32\x11.emulator.MsgType\x12 \n\x06status\x18\x02 \x01(\x0e\x32\x10.emulator.Status\x12&\n\x04prop\x18\x03 \x03(\x0b\x32\x18.emulator.VehiclePropGet\x12+\n\x06\x63onfig\x18\x04 \x03(\x0b\x32\x1b.emulator.VehiclePropConfig\x12)\n\x05value\x18\x05 \x03(\x0b\x32\x1a.emulator.VehiclePropValue*\x8a\x02\n\x07MsgType\x12\x12\n\x0eGET_CONFIG_CMD\x10\x00\x12\x13\n\x0fGET_CONFIG_RESP\x10\x01\x12\x16\n\x12GET_CONFIG_ALL_CMD\x10\x02\x12\x17\n\x13GET_CONFIG_ALL_RESP\x10\x03\x12\x14\n\x10GET_PROPERTY_CMD\x10\x04\x12\x15\n\x11GET_PROPERTY_RESP\x10\x05\x12\x18\n\x14GET_PROPERTY_ALL_CMD\x10\x06\x12\x19\n\x15GET_PROPERTY_ALL_RESP\x10\x07\x12\x14\n\x10SET_PROPERTY_CMD\x10\x08\x12\x15\n\x11SET_PROPERTY_RESP\x10\t\x12\x16\n\x12SET_PROPERTY_ASYNC\x10\n*\xfb\x01\n\x06Status\x12\r\n\tRESULT_OK\x10\x00\x12\x11\n\rERROR_UNKNOWN\x10\x01\x12\x1b\n\x17\x45RROR_UNIMPLEMENTED_CMD\x10\x02\x12\x1a\n\x16\x45RROR_INVALID_PROPERTY\x10\x03\x12\x19\n\x15\x45RROR_INVALID_AREA_ID\x10\x04\x12 \n\x1c\x45RROR_PROPERTY_UNINITIALIZED\x10\x05\x12\x1d\n\x19\x45RROR_WRITE_ONLY_PROPERTY\x10\x06\x12\x1d\n\x19\x45RROR_MEMORY_ALLOC_FAILED\x10\x07\x12\x1b\n\x17\x45RROR_INVALID_OPERATION\x10\x08\x42\x02H\x03')
+
+_MSGTYPE = _descriptor.EnumDescriptor(
+ name='MsgType',
+ full_name='emulator.MsgType',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='GET_CONFIG_CMD', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='GET_CONFIG_RESP', index=1, number=1,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='GET_CONFIG_ALL_CMD', index=2, number=2,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='GET_CONFIG_ALL_RESP', index=3, number=3,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='GET_PROPERTY_CMD', index=4, number=4,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='GET_PROPERTY_RESP', index=5, number=5,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='GET_PROPERTY_ALL_CMD', index=6, number=6,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='GET_PROPERTY_ALL_RESP', index=7, number=7,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SET_PROPERTY_CMD', index=8, number=8,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SET_PROPERTY_RESP', index=9, number=9,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='SET_PROPERTY_ASYNC', index=10, number=10,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=979,
+ serialized_end=1245,
+)
+
+MsgType = enum_type_wrapper.EnumTypeWrapper(_MSGTYPE)
+_STATUS = _descriptor.EnumDescriptor(
+ name='Status',
+ full_name='emulator.Status',
+ filename=None,
+ file=DESCRIPTOR,
+ values=[
+ _descriptor.EnumValueDescriptor(
+ name='RESULT_OK', index=0, number=0,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ERROR_UNKNOWN', index=1, number=1,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ERROR_UNIMPLEMENTED_CMD', index=2, number=2,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ERROR_INVALID_PROPERTY', index=3, number=3,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ERROR_INVALID_AREA_ID', index=4, number=4,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ERROR_PROPERTY_UNINITIALIZED', index=5, number=5,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ERROR_WRITE_ONLY_PROPERTY', index=6, number=6,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ERROR_MEMORY_ALLOC_FAILED', index=7, number=7,
+ options=None,
+ type=None),
+ _descriptor.EnumValueDescriptor(
+ name='ERROR_INVALID_OPERATION', index=8, number=8,
+ options=None,
+ type=None),
+ ],
+ containing_type=None,
+ options=None,
+ serialized_start=1248,
+ serialized_end=1499,
+)
+
+Status = enum_type_wrapper.EnumTypeWrapper(_STATUS)
+GET_CONFIG_CMD = 0
+GET_CONFIG_RESP = 1
+GET_CONFIG_ALL_CMD = 2
+GET_CONFIG_ALL_RESP = 3
+GET_PROPERTY_CMD = 4
+GET_PROPERTY_RESP = 5
+GET_PROPERTY_ALL_CMD = 6
+GET_PROPERTY_ALL_RESP = 7
+SET_PROPERTY_CMD = 8
+SET_PROPERTY_RESP = 9
+SET_PROPERTY_ASYNC = 10
+RESULT_OK = 0
+ERROR_UNKNOWN = 1
+ERROR_UNIMPLEMENTED_CMD = 2
+ERROR_INVALID_PROPERTY = 3
+ERROR_INVALID_AREA_ID = 4
+ERROR_PROPERTY_UNINITIALIZED = 5
+ERROR_WRITE_ONLY_PROPERTY = 6
+ERROR_MEMORY_ALLOC_FAILED = 7
+ERROR_INVALID_OPERATION = 8
+
+
+
+_VEHICLEAREACONFIG = _descriptor.Descriptor(
+ name='VehicleAreaConfig',
+ full_name='emulator.VehicleAreaConfig',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='area_id', full_name='emulator.VehicleAreaConfig.area_id', index=0,
+ number=1, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='min_int32_value', full_name='emulator.VehicleAreaConfig.min_int32_value', index=1,
+ number=2, type=17, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='max_int32_value', full_name='emulator.VehicleAreaConfig.max_int32_value', index=2,
+ number=3, type=17, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='min_int64_value', full_name='emulator.VehicleAreaConfig.min_int64_value', index=3,
+ number=4, type=18, cpp_type=2, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='max_int64_value', full_name='emulator.VehicleAreaConfig.max_int64_value', index=4,
+ number=5, type=18, cpp_type=2, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='min_float_value', full_name='emulator.VehicleAreaConfig.min_float_value', index=5,
+ number=6, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='max_float_value', full_name='emulator.VehicleAreaConfig.max_float_value', index=6,
+ number=7, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=36,
+ serialized_end=222,
+)
+
+
+_VEHICLEPROPCONFIG = _descriptor.Descriptor(
+ name='VehiclePropConfig',
+ full_name='emulator.VehiclePropConfig',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='prop', full_name='emulator.VehiclePropConfig.prop', index=0,
+ number=1, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='access', full_name='emulator.VehiclePropConfig.access', index=1,
+ number=2, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='change_mode', full_name='emulator.VehiclePropConfig.change_mode', index=2,
+ number=3, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='value_type', full_name='emulator.VehiclePropConfig.value_type', index=3,
+ number=4, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='supported_areas', full_name='emulator.VehiclePropConfig.supported_areas', index=4,
+ number=5, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='area_configs', full_name='emulator.VehiclePropConfig.area_configs', index=5,
+ number=6, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='config_flags', full_name='emulator.VehiclePropConfig.config_flags', index=6,
+ number=7, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='config_array', full_name='emulator.VehiclePropConfig.config_array', index=7,
+ number=8, type=5, cpp_type=1, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='config_string', full_name='emulator.VehiclePropConfig.config_string', index=8,
+ number=9, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='min_sample_rate', full_name='emulator.VehiclePropConfig.min_sample_rate', index=9,
+ number=10, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='max_sample_rate', full_name='emulator.VehiclePropConfig.max_sample_rate', index=10,
+ number=11, type=2, cpp_type=6, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=225,
+ serialized_end=508,
+)
+
+
+_VEHICLEPROPVALUE = _descriptor.Descriptor(
+ name='VehiclePropValue',
+ full_name='emulator.VehiclePropValue',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='prop', full_name='emulator.VehiclePropValue.prop', index=0,
+ number=1, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='value_type', full_name='emulator.VehiclePropValue.value_type', index=1,
+ number=2, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='timestamp', full_name='emulator.VehiclePropValue.timestamp', index=2,
+ number=3, type=3, cpp_type=2, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='area_id', full_name='emulator.VehiclePropValue.area_id', index=3,
+ number=4, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='int32_values', full_name='emulator.VehiclePropValue.int32_values', index=4,
+ number=5, type=17, cpp_type=1, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='int64_values', full_name='emulator.VehiclePropValue.int64_values', index=5,
+ number=6, type=18, cpp_type=2, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='float_values', full_name='emulator.VehiclePropValue.float_values', index=6,
+ number=7, type=2, cpp_type=6, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='string_value', full_name='emulator.VehiclePropValue.string_value', index=7,
+ number=8, type=9, cpp_type=9, label=1,
+ has_default_value=False, default_value=unicode("", "utf-8"),
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='bytes_value', full_name='emulator.VehiclePropValue.bytes_value', index=8,
+ number=9, type=12, cpp_type=9, label=1,
+ has_default_value=False, default_value="",
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=511,
+ serialized_end=708,
+)
+
+
+_VEHICLEPROPGET = _descriptor.Descriptor(
+ name='VehiclePropGet',
+ full_name='emulator.VehiclePropGet',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='prop', full_name='emulator.VehiclePropGet.prop', index=0,
+ number=1, type=5, cpp_type=1, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='area_id', full_name='emulator.VehiclePropGet.area_id', index=1,
+ number=2, type=5, cpp_type=1, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=710,
+ serialized_end=757,
+)
+
+
+_EMULATORMESSAGE = _descriptor.Descriptor(
+ name='EmulatorMessage',
+ full_name='emulator.EmulatorMessage',
+ filename=None,
+ file=DESCRIPTOR,
+ containing_type=None,
+ fields=[
+ _descriptor.FieldDescriptor(
+ name='msg_type', full_name='emulator.EmulatorMessage.msg_type', index=0,
+ number=1, type=14, cpp_type=8, label=2,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='status', full_name='emulator.EmulatorMessage.status', index=1,
+ number=2, type=14, cpp_type=8, label=1,
+ has_default_value=False, default_value=0,
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='prop', full_name='emulator.EmulatorMessage.prop', index=2,
+ number=3, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='config', full_name='emulator.EmulatorMessage.config', index=3,
+ number=4, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ _descriptor.FieldDescriptor(
+ name='value', full_name='emulator.EmulatorMessage.value', index=4,
+ number=5, type=11, cpp_type=10, label=3,
+ has_default_value=False, default_value=[],
+ message_type=None, enum_type=None, containing_type=None,
+ is_extension=False, extension_scope=None,
+ options=None),
+ ],
+ extensions=[
+ ],
+ nested_types=[],
+ enum_types=[
+ ],
+ options=None,
+ is_extendable=False,
+ extension_ranges=[],
+ serialized_start=760,
+ serialized_end=976,
+)
+
+_VEHICLEPROPCONFIG.fields_by_name['area_configs'].message_type = _VEHICLEAREACONFIG
+_EMULATORMESSAGE.fields_by_name['msg_type'].enum_type = _MSGTYPE
+_EMULATORMESSAGE.fields_by_name['status'].enum_type = _STATUS
+_EMULATORMESSAGE.fields_by_name['prop'].message_type = _VEHICLEPROPGET
+_EMULATORMESSAGE.fields_by_name['config'].message_type = _VEHICLEPROPCONFIG
+_EMULATORMESSAGE.fields_by_name['value'].message_type = _VEHICLEPROPVALUE
+DESCRIPTOR.message_types_by_name['VehicleAreaConfig'] = _VEHICLEAREACONFIG
+DESCRIPTOR.message_types_by_name['VehiclePropConfig'] = _VEHICLEPROPCONFIG
+DESCRIPTOR.message_types_by_name['VehiclePropValue'] = _VEHICLEPROPVALUE
+DESCRIPTOR.message_types_by_name['VehiclePropGet'] = _VEHICLEPROPGET
+DESCRIPTOR.message_types_by_name['EmulatorMessage'] = _EMULATORMESSAGE
+
+class VehicleAreaConfig(_message.Message):
+ __metaclass__ = _reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _VEHICLEAREACONFIG
+
+ # @@protoc_insertion_point(class_scope:emulator.VehicleAreaConfig)
+
+class VehiclePropConfig(_message.Message):
+ __metaclass__ = _reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _VEHICLEPROPCONFIG
+
+ # @@protoc_insertion_point(class_scope:emulator.VehiclePropConfig)
+
+class VehiclePropValue(_message.Message):
+ __metaclass__ = _reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _VEHICLEPROPVALUE
+
+ # @@protoc_insertion_point(class_scope:emulator.VehiclePropValue)
+
+class VehiclePropGet(_message.Message):
+ __metaclass__ = _reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _VEHICLEPROPGET
+
+ # @@protoc_insertion_point(class_scope:emulator.VehiclePropGet)
+
+class EmulatorMessage(_message.Message):
+ __metaclass__ = _reflection.GeneratedProtocolMessageType
+ DESCRIPTOR = _EMULATORMESSAGE
+
+ # @@protoc_insertion_point(class_scope:emulator.EmulatorMessage)
+
+
+DESCRIPTOR.has_options = True
+DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), 'H\003')
+# @@protoc_insertion_point(module_scope)
diff --git a/tools/emulator/vhal_consts_2_0.py b/tools/emulator/vhal_consts_2_0.py
new file mode 100644
index 0000000..f2433f6
--- /dev/null
+++ b/tools/emulator/vhal_consts_2_0.py
@@ -0,0 +1,175 @@
+# Copyright 2017 Google Inc.
+#
+# 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.
+#
+
+"""
+ This file contains constants defined in hardware/interfaces/vehicle/2.0/types.hal
+
+ Constants in this file are parsed from:
+ out/soong/.intermediates/hardware/interfaces/automotive/vehicle/2.0/android.hardware.automotive.vehicle@2.0_genc++_headers/gen/android/hardware/automotive/vehicle/2.0/types.h
+
+ Currently, there is no script to auto-generate this constants file. The file is generated by
+ copying enum fields into an editor and running a macro to format it. The elements being used
+ are shown in the following table:
+
+ type.h file: this file:
+ VehiclePropertyType enum --> VEHICLE_VALUE_TYPE_*
+ VehicleProperty enum --> VEHICLE_PROPERTY_*
+ VehicleAreaZone enum --> VEHICLE_ZONE_*
+ VehiclePropertyType enum --> class vhal_types_2_0
+"""
+
+# Vehicle Property ID
+VEHICLE_PROPERTY_INFO_VIN = 286261504
+VEHICLE_PROPERTY_INFO_MAKE = 286261505
+VEHICLE_PROPERTY_INFO_MODEL = 286261506
+VEHICLE_PROPERTY_INFO_MODEL_YEAR = 289407235
+VEHICLE_PROPERTY_INFO_FUEL_CAPACITY = 291504388
+VEHICLE_PROPERTY_PERF_ODOMETER = 291504644
+VEHICLE_PROPERTY_PERF_VEHICLE_SPEED = 291504647
+VEHICLE_PROPERTY_ENGINE_COOLANT_TEMP = 291504897
+VEHICLE_PROPERTY_ENGINE_OIL_TEMP = 291504900
+VEHICLE_PROPERTY_ENGINE_RPM = 291504901
+VEHICLE_PROPERTY_GEAR_SELECTION = 289408000
+VEHICLE_PROPERTY_CURRENT_GEAR = 289408001
+VEHICLE_PROPERTY_PARKING_BRAKE_ON = 287310850
+VEHICLE_PROPERTY_DRIVING_STATUS = 289408004
+VEHICLE_PROPERTY_FUEL_LEVEL_LOW = 287310853
+VEHICLE_PROPERTY_NIGHT_MODE = 287310855
+VEHICLE_PROPERTY_TURN_SIGNAL_STATE = 289408008
+VEHICLE_PROPERTY_IGNITION_STATE = 289408009
+VEHICLE_PROPERTY_HVAC_FAN_SPEED = 306185472
+VEHICLE_PROPERTY_HVAC_FAN_DIRECTION = 306185473
+VEHICLE_PROPERTY_HVAC_TEMPERATURE_CURRENT = 308282626
+VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET = 308282627
+VEHICLE_PROPERTY_HVAC_DEFROSTER = 320865540
+VEHICLE_PROPERTY_HVAC_AC_ON = 304088325
+VEHICLE_PROPERTY_HVAC_MAX_AC_ON = 304088326
+VEHICLE_PROPERTY_HVAC_MAX_DEFROST_ON = 304088327
+VEHICLE_PROPERTY_HVAC_RECIRC_ON = 304088328
+VEHICLE_PROPERTY_HVAC_DUAL_ON = 304088329
+VEHICLE_PROPERTY_HVAC_AUTO_ON = 304088330
+VEHICLE_PROPERTY_HVAC_SEAT_TEMPERATURE = 356517131
+VEHICLE_PROPERTY_HVAC_SIDE_MIRROR_HEAT = 339739916
+VEHICLE_PROPERTY_HVAC_STEERING_WHEEL_TEMP = 289408269
+VEHICLE_PROPERTY_HVAC_TEMPERATURE_UNITS = 306185486
+VEHICLE_PROPERTY_HVAC_ACTUAL_FAN_SPEED_RPM = 306185487
+VEHICLE_PROPERTY_HVAC_FAN_DIRECTION_AVAILABLE = 306185489
+VEHICLE_PROPERTY_HVAC_POWER_ON = 304088336
+VEHICLE_PROPERTY_ENV_OUTSIDE_TEMPERATURE = 291505923
+VEHICLE_PROPERTY_ENV_CABIN_TEMPERATURE = 291505924
+VEHICLE_PROPERTY_RADIO_PRESET = 289474561
+VEHICLE_PROPERTY_AUDIO_FOCUS = 289474816
+VEHICLE_PROPERTY_AUDIO_FOCUS_EXT_SYNC = 289474832
+VEHICLE_PROPERTY_AUDIO_VOLUME = 289474817
+VEHICLE_PROPERTY_AUDIO_VOLUME_EXT_SYNC = 289474833
+VEHICLE_PROPERTY_AUDIO_VOLUME_LIMIT = 289474818
+VEHICLE_PROPERTY_AUDIO_ROUTING_POLICY = 289474819
+VEHICLE_PROPERTY_AUDIO_HW_VARIANT = 289409284
+VEHICLE_PROPERTY_AUDIO_EXT_ROUTING_HINT = 289474821
+VEHICLE_PROPERTY_AUDIO_STREAM_STATE = 289474822
+VEHICLE_PROPERTY_AUDIO_PARAMETERS = 286263559
+VEHICLE_PROPERTY_AP_POWER_STATE = 2560
+VEHICLE_PROPERTY_DISPLAY_BRIGHTNESS = 289409537
+VEHICLE_PROPERTY_AP_POWER_BOOTUP_REASON = 289409538
+VEHICLE_PROPERTY_HW_KEY_INPUT = 289475088
+VEHICLE_PROPERTY_INSTRUMENT_CLUSTER_INFO = 289475104
+VEHICLE_PROPERTY_UNIX_TIME = 290458160
+VEHICLE_PROPERTY_CURRENT_TIME_IN_SECONDS = 289409585
+VEHICLE_PROPERTY_DOOR_POS = 373295872
+VEHICLE_PROPERTY_DOOR_MOVE = 373295873
+VEHICLE_PROPERTY_DOOR_LOCK = 371198722
+VEHICLE_PROPERTY_MIRROR_Z_POS = 339741504
+VEHICLE_PROPERTY_MIRROR_Z_MOVE = 339741505
+VEHICLE_PROPERTY_MIRROR_Y_POS = 339741506
+VEHICLE_PROPERTY_MIRROR_Y_MOVE = 339741507
+VEHICLE_PROPERTY_MIRROR_LOCK = 287312708
+VEHICLE_PROPERTY_MIRROR_FOLD = 287312709
+VEHICLE_PROPERTY_SEAT_MEMORY_SELECT = 356518784
+VEHICLE_PROPERTY_SEAT_MEMORY_SET = 356518785
+VEHICLE_PROPERTY_SEAT_BELT_BUCKLED = 354421634
+VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_POS = 356518787
+VEHICLE_PROPERTY_SEAT_BELT_HEIGHT_MOVE = 356518788
+VEHICLE_PROPERTY_SEAT_FORE_AFT_POS = 356518789
+VEHICLE_PROPERTY_SEAT_FORE_AFT_MOVE = 356518790
+VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_POS = 356518791
+VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_1_MOVE = 356518792
+VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_POS = 356518793
+VEHICLE_PROPERTY_SEAT_BACKREST_ANGLE_2_MOVE = 356518794
+VEHICLE_PROPERTY_SEAT_HEIGHT_POS = 356518795
+VEHICLE_PROPERTY_SEAT_HEIGHT_MOVE = 356518796
+VEHICLE_PROPERTY_SEAT_DEPTH_POS = 356518797
+VEHICLE_PROPERTY_SEAT_DEPTH_MOVE = 356518798
+VEHICLE_PROPERTY_SEAT_TILT_POS = 356518799
+VEHICLE_PROPERTY_SEAT_TILT_MOVE = 356518800
+VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_POS = 356518801
+VEHICLE_PROPERTY_SEAT_LUMBAR_FORE_AFT_MOVE = 356518802
+VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_POS = 356518803
+VEHICLE_PROPERTY_SEAT_LUMBAR_SIDE_SUPPORT_MOVE = 356518804
+VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_POS = 289409941
+VEHICLE_PROPERTY_SEAT_HEADREST_HEIGHT_MOVE = 356518806
+VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_POS = 356518807
+VEHICLE_PROPERTY_SEAT_HEADREST_ANGLE_MOVE = 356518808
+VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_POS = 356518809
+VEHICLE_PROPERTY_SEAT_HEADREST_FORE_AFT_MOVE = 356518810
+VEHICLE_PROPERTY_WINDOW_POS = 289409984
+VEHICLE_PROPERTY_WINDOW_MOVE = 289409985
+VEHICLE_PROPERTY_WINDOW_VENT_POS = 289409986
+VEHICLE_PROPERTY_WINDOW_VENT_MOVE = 289409987
+VEHICLE_PROPERTY_WINDOW_LOCK = 287312836
+VEHICLE_PROPERTY_VEHICLE_MAPS_DATA_SERVICE = 299895808
+VEHICLE_PROPERTY_OBD2_LIVE_FRAME = 299896064
+VEHICLE_PROPERTY_OBD2_FREEZE_FRAME = 299896065
+
+# Vehicle Value Type
+VEHICLE_VALUE_TYPE_STRING = 0x00100000
+VEHICLE_VALUE_TYPE_BOOLEAN = 0x00200000
+VEHICLE_VALUE_TYPE_INT32 = 0x00400000
+VEHICLE_VALUE_TYPE_INT32_VEC = 0x00410000
+VEHICLE_VALUE_TYPE_INT64 = 0x00500000
+VEHICLE_VALUE_TYPE_FLOAT = 0x00600000
+VEHICLE_VALUE_TYPE_FLOAT_VEC = 0x00610000
+VEHICLE_VALUE_TYPE_BYTES = 0x00700000
+VEHICLE_VALUE_TYPE_COMPLEX = 0x00E00000
+
+# Vehicle zone / area definitions
+VEHICLE_ZONE_ROW_1_LEFT = 0x00000001
+VEHICLE_ZONE_ROW_1_CENTER = 0x00000002
+VEHICLE_ZONE_ROW_1_RIGHT = 0x00000004
+VEHICLE_ZONE_ROW_1_ALL = 0x00000008
+VEHICLE_ZONE_ROW_2_LEFT = 0x00000010
+VEHICLE_ZONE_ROW_2_CENTER = 0x00000020
+VEHICLE_ZONE_ROW_2_RIGHT = 0x00000040
+VEHICLE_ZONE_ROW_2_ALL = 0x00000080
+VEHICLE_ZONE_ROW_3_LEFT = 0x00000100
+VEHICLE_ZONE_ROW_3_CENTER = 0x00000200
+VEHICLE_ZONE_ROW_3_RIGHT = 0x00000400
+VEHICLE_ZONE_ROW_3_ALL = 0x00000800
+VEHICLE_ZONE_ROW_4_LEFT = 0x00001000
+VEHICLE_ZONE_ROW_4_CENTER = 0x00002000
+VEHICLE_ZONE_ROW_4_RIGHT = 0x00004000
+VEHICLE_ZONE_ROW_4_ALL = 0x00008000
+VEHICLE_ZONE_ALL = 0x80000000
+
+# Create a container of value_type constants to be used by vhal_emulator
+class vhal_types_2_0:
+ TYPE_STRING = [VEHICLE_VALUE_TYPE_STRING]
+ TYPE_BYTES = [VEHICLE_VALUE_TYPE_BYTES]
+ TYPE_INT32 = [VEHICLE_VALUE_TYPE_BOOLEAN,
+ VEHICLE_VALUE_TYPE_INT32]
+ TYPE_INT64 = [VEHICLE_VALUE_TYPE_INT64]
+ TYPE_FLOAT = [VEHICLE_VALUE_TYPE_FLOAT]
+ TYPE_INT32S = [VEHICLE_VALUE_TYPE_INT32_VEC]
+ TYPE_FLOATS = [VEHICLE_VALUE_TYPE_FLOAT_VEC]
+
diff --git a/tools/emulator/vhal_emulator.py b/tools/emulator/vhal_emulator.py
new file mode 100644
index 0000000..44e5566
--- /dev/null
+++ b/tools/emulator/vhal_emulator.py
@@ -0,0 +1,234 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2016 Google Inc.
+#
+# 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.
+#
+
+"""
+ This module provides a vhal class which sends and receives messages to the vehicle HAL module
+ on an Android Auto device. It uses port forwarding via ADB to communicted with the Android
+ device.
+
+ Example Usage:
+
+ import vhal_consts_1_0 as c
+ from vhal_emulator import Vhal
+
+ # Create an instance of vhal class. Need to pass the vhal_types constants.
+ v = Vhal(c.vhal_types_1_0)
+
+ # Get the property config (if desired)
+ v.getConfig(c.VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET)
+
+ # Get the response message to getConfig()
+ reply = v.rxMsg()
+ print reply
+
+ # Set left temperature to 70 degrees
+ v.setProperty(c.VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLE_ZONE_ROW_1_LEFT, 70)
+
+ # Get the response message to setProperty()
+ reply = v.rxMsg()
+ print reply
+
+ # Get the left temperature value
+ v.getProperty(c.VEHICLE_PROPERTY_HVAC_TEMPERATURE_SET, c.VEHICLE_ZONE_ROW_1_LEFT)
+
+ # Get the response message to getProperty()
+ reply = v.rxMsg()
+ print reply
+
+ NOTE: The rxMsg() is a blocking call, so it may be desirable to set up a separate RX thread
+ to handle any asynchronous messages coming from the device.
+
+ Example for creating RX thread (assumes vhal has already been instantiated):
+
+ from threading import Thread
+
+ # Define a simple thread that receives messags from a vhal object (v) and prints them
+ def rxThread(v):
+ while(1):
+ print v.rxMsg()
+
+ rx = Thread(target=rxThread, args=(v,))
+ rx.start()
+
+ Protocol Buffer:
+ This module relies on VehicleHalProto_pb2.py being in sync with the protobuf in the VHAL.
+ If the VehicleHalProto.proto file has changed, re-generate the python version using:
+
+ protoc -I=<proto_dir> --python_out=<out_dir> <proto_dir>/VehicleHalProto.proto
+"""
+
+# Suppress .pyc files
+import sys
+sys.dont_write_bytecode = True
+
+import socket
+import struct
+import subprocess
+
+# Generate the protobuf file from vendor/auto/embedded/lib/vehicle_hal:
+# protoc -I=proto --python_out=proto proto/VehicleHalProto.proto
+import VehicleHalProto_pb2
+
+
+class Vhal:
+ """
+ Dictionary of prop_id to value_type. Used by setProperty() to properly format data.
+ """
+ _propToType = {}
+
+ ### Private Functions
+ def _txCmd(self, cmd):
+ """
+ Transmits a protobuf to Android Auto device. Should not be called externally.
+ """
+ # Serialize the protobuf into a string
+ msgStr = cmd.SerializeToString()
+ msgLen = len(msgStr)
+ # Convert the message length into int32 byte array
+ msgHdr = struct.pack('!I', msgLen)
+ # Send the message length first
+ self.sock.send(msgHdr)
+ # Then send the protobuf
+ self.sock.send(msgStr)
+
+ ### Public Functions
+ def printHex(self, data):
+ """
+ For debugging, print the protobuf message string in hex.
+ """
+ print "len = ", len(data), "str = ", ":".join("{:02x}".format(ord(d)) for d in data)
+
+ def openSocket(self):
+ """
+ Connects to an Android Auto device running a Vehicle HAL with simulator.
+ """
+ # Hard-coded socket port needs to match the one in DefaultVehicleHal
+ portNumber = 33452
+ # Setup ADB port forwarding
+ subprocess.call("adb forward tcp:%d tcp:%d" % (portNumber, portNumber), shell=True)
+ # Open the socket and connect
+ self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.sock.connect(('localhost', portNumber))
+
+ def rxMsg(self):
+ """
+ Receive a message over the socket. This function blocks if a message is not available.
+ May want to wrap this function inside of an rx thread to also collect asynchronous
+ messages generated by the device.
+ """
+ # Receive the message length (int32) first
+ b = self.sock.recv(4)
+ if (len(b) == 4):
+ msgLen, = struct.unpack('!I', b)
+ if (msgLen > 0):
+ # Receive the actual message
+ b = self.sock.recv(msgLen)
+ if (len(b) == msgLen):
+ # Unpack the protobuf
+ msg = VehicleHalProto_pb2.EmulatorMessage()
+ msg.ParseFromString(b)
+ return msg
+
+ def getConfig(self, prop):
+ """
+ Sends a getConfig message for the specified property.
+ """
+ cmd = VehicleHalProto_pb2.EmulatorMessage()
+ cmd.msg_type = VehicleHalProto_pb2.GET_CONFIG_CMD
+ propGet = cmd.prop.add()
+ propGet.prop = prop
+ self._txCmd(cmd)
+
+ def getConfigAll(self):
+ """
+ Sends a getConfigAll message to the host. This will return all configs avaialable.
+ """
+ cmd = VehicleHalProto_pb2.EmulatorMessage()
+ cmd.msg_type = VehicleHalProto_pb2.GET_CONFIG_ALL_CMD
+ self._txCmd(cmd)
+
+ def getProperty(self, prop, area_id):
+ """
+ Sends a getProperty command for the specified property ID and area ID.
+ """
+ cmd = VehicleHalProto_pb2.EmulatorMessage()
+ cmd.msg_type = VehicleHalProto_pb2.GET_PROPERTY_CMD
+ propGet = cmd.prop.add()
+ propGet.prop = prop
+ propGet.area_id = area_id
+ self._txCmd(cmd)
+
+ def getPropertyAll(self):
+ """
+ Sends a getPropertyAll message to the host. This will return all properties avaialable.
+ """
+ cmd = VehicleHalProto_pb2.EmulatorMessage()
+ cmd.msg_type = VehicleHalProto_pb2.GET_PROPERTY_ALL_CMD
+ self._txCmd(cmd)
+
+ def setProperty(self, prop, area_id, value):
+ """
+ Sends a setProperty command for the specified property ID, area ID, and value.
+ This function chooses the proper value field to populate based on the config for the
+ property. It is the caller's responsibility to ensure the value data is the proper
+ type.
+ """
+ cmd = VehicleHalProto_pb2.EmulatorMessage()
+ cmd.msg_type = VehicleHalProto_pb2.SET_PROPERTY_CMD
+ propValue = cmd.value.add()
+ propValue.prop = prop
+ # Insert value into the proper area
+ propValue.area_id = area_id
+ # Determine the value_type and populate the correct value field in protoBuf
+ try:
+ valType = self._propToType[prop]
+ except KeyError:
+ raise ValueError('propId is invalid:', prop)
+ return
+ propValue.value_type = valType
+ if valType in self._types.TYPE_STRING:
+ propValue.string_value = value
+ elif valType in self._types.TYPE_BYTES:
+ propValue.bytes_value = value
+ elif valType in self._types.TYPE_INT32:
+ propValue.int32_values.append(value)
+ elif valType in self._types.TYPE_INT64:
+ propValue.int64_values.append(value)
+ elif valType in self._types.TYPE_FLOAT:
+ propValue.float_values.append(value)
+ elif valType in self._types.TYPE_INT32S:
+ propValue.int32_values.extend(value)
+ elif valType in self._types.TYPE_FLOATS:
+ propValue.float_values.extend(value)
+ else:
+ raise ValueError('value type not recognized:', valType)
+ return
+ self._txCmd(cmd)
+
+ def __init__(self, types):
+ # Save the list of types constants
+ self._types = types
+ # Open the socket
+ self.openSocket()
+ # Get the list of configs
+ self.getConfigAll()
+ msg = self.rxMsg()
+ # Parse the list of configs to generate a dictionary of prop_id to type
+ for cfg in msg.config:
+ self._propToType[cfg.prop] = cfg.value_type
+
diff --git a/tools/emulator/vhal_emulator_test.py b/tools/emulator/vhal_emulator_test.py
new file mode 100644
index 0000000..325e0a8
--- /dev/null
+++ b/tools/emulator/vhal_emulator_test.py
@@ -0,0 +1,290 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright 2017 Google Inc.
+#
+# 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.
+#
+
+"""
+ This module tests the Vehicle HAL using adb socket.
+
+ Protocol Buffer:
+ This module relies on VehicleHalProto_pb2.py being in sync with the protobuf in the VHAL.
+ If the VehicleHalProto.proto file has changed, re-generate the python version using:
+
+ protoc -I=<proto_dir> --python_out=<out_dir> <proto_dir>/VehicleHalProto.proto
+ protoc -I=proto --python_out=proto proto/VehicleHalProto.proto
+"""
+
+# Suppress .pyc files
+import sys
+sys.dont_write_bytecode = True
+
+import VehicleHalProto_pb2
+import vhal_consts_2_0
+import vhal_emulator
+import logging
+
+class VhalTest:
+ # Global vars
+ _badProps = [0, 0x3FFFFFFF] # List of bad properties to try for negative tests
+ _configs = 0 # List of configs from DUT
+ _log = 0 # Logger module
+ _vhal = 0 # Handle to VHAL object that communicates over socket to DUT
+
+ def _getMidpoint(self, minVal, maxVal):
+ retVal = minVal + (maxVal - minVal)/2
+ return retVal
+
+ # Generates a test value based on the config
+ def _generateTestValue(self, cfg, idx, origValue):
+ valType = cfg.value_type
+ if valType in self._types.TYPE_STRING:
+ testValue = "test string"
+ elif valType in self._types.TYPE_BYTES:
+ # Generate array of integers counting from 0
+ testValue = range(len(origValue))
+ elif valType == vhal_consts_2_0.VEHICLE_VALUE_TYPE_BOOLEAN:
+ testValue = origValue ^ 1
+ elif valType in self._types.TYPE_INT32:
+ try:
+ testValue = self._getMidpoint(cfg.area_configs[idx].min_int32_value,
+ cfg.area_configs[idx].max_int32_value)
+ except:
+ # min/max values aren't set. Set a hard-coded value
+ testValue = 123
+ elif valType in self._types.TYPE_INT64:
+ try:
+ testValue = self._getMidpoint(cfg.area_configs[idx].min_int64_value,
+ cfg.area_configs[idx].max_int64_value)
+ except:
+ # min/max values aren't set. Set a large hard-coded value
+ testValue = 1 << 50
+ elif valType in self._types.TYPE_FLOAT:
+ try:
+ testValue = self._getMidpoint(cfg.area_configs[idx].min_float_value,
+ cfg.area_configs[idx].max_float_value)
+ except:
+ # min/max values aren't set. Set a hard-coded value
+ testValue = 123.456
+ # Truncate float to 5 decimal places
+ testValue = "%.5f" % testValue
+ testValue = float(testValue)
+ else:
+ self._log.error("generateTestValue: valType=%d is not handled", valType)
+ testValue = None
+ return testValue
+
+ # Helper function to extract values array from rxMsg
+ def _getValueFromMsg(self, rxMsg):
+ # Check to see only one property value is returned
+ if len(rxMsg.value) != 1:
+ self._log.error("getValueFromMsg: Received invalid value")
+ value = None
+ else:
+ valType = rxMsg.value[0].value_type
+ if valType in self._types.TYPE_STRING:
+ value = rxMsg.value[0].string_value
+ elif valType in self._types.TYPE_BYTES:
+ value = rxMsg.value[0].bytes_value
+ elif valType == vhal_consts_2_0.VEHICLE_VALUE_TYPE_BOOLEAN:
+ value = rxMsg.value[0].int32_values[0]
+ elif valType in self._types.TYPE_INT32:
+ value = rxMsg.value[0].int32_values[0]
+ elif valType in self._types.TYPE_INT64:
+ value = rxMsg.value[0].int64_values[0]
+ elif valType in self._types.TYPE_FLOAT:
+ value = rxMsg.value[0].float_values[0]
+ # Truncate float to 5 decimal places
+ value = "%.5f" % value
+ value = float(value)
+ else:
+ self._log.error("getValuesFromMsg: valType=%d is not handled", valType)
+ value = None
+ return value
+
+ # Helper function to receive a message and validate the type and status
+ # retVal = 1 if no errors
+ # retVal = 0 if errors detected
+ def _rxMsgAndValidate(self, expectedType, expectedStatus):
+ retVal = 1
+ rxMsg = self._vhal.rxMsg()
+ if rxMsg.msg_type != expectedType:
+ self._log.error("rxMsg Type expected: %d, received: %d", expectedType, rxMsg.msg_type)
+ retVal = 0
+ if rxMsg.status != expectedStatus:
+ self._log.error("rxMsg Status expected: %d, received: %d", expectedStatus, rxMsg.status)
+ retVal = 0
+ return rxMsg, retVal
+
+ # Calls getConfig() on each individual property ID and verifies it matches with the config
+ # received in getConfigAll()
+ def testGetConfig(self):
+ self._log.info("Starting testGetConfig...")
+ for cfg in self._configs:
+ self._log.debug(" Getting config for propId=%d", cfg.prop)
+ self._vhal.getConfig(cfg.prop)
+ rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_CONFIG_RESP,
+ VehicleHalProto_pb2.RESULT_OK)
+ if retVal:
+ if rxMsg.config[0] != cfg:
+ self._log.error("testGetConfig failed. prop=%d, expected:\n%s\nreceived:\n%s",
+ cfg.prop, str(cfg), str(rxMsg.config))
+ self._log.info(" Finished testGetConfig!")
+
+ # Calls getConfig() on invalid property ID and verifies it generates an error
+ def testGetBadConfig(self):
+ self._log.info("Starting testGetBadConfig...")
+ for prop in self._badProps:
+ self._log.debug(" Testing bad propId=%d", prop)
+ self._vhal.getConfig(prop)
+ rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_CONFIG_RESP,
+ VehicleHalProto_pb2.ERROR_INVALID_PROPERTY)
+ if retVal:
+ for cfg in rxMsg.config:
+ self._log.error("testGetBadConfig prop=%d, expected:None, received:\n%s",
+ cfg.prop, str(rxMsg.config))
+ self._log.info(" Finished testGetBadConfig!")
+
+ def testGetPropertyAll(self):
+ self._log.info("Starting testGetPropertyAll...")
+ self._vhal.getPropertyAll()
+ rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_ALL_RESP,
+ VehicleHalProto_pb2.RESULT_OK)
+ if retVal == 0:
+ self._log.error("testGetPropertyAll: Failed to receive proper rxMsg")
+
+ # TODO: Finish writing this test. What should we be testing, anyway?
+
+ self._log.info(" Finished testGetPropertyAll!")
+
+ def testGetSet(self):
+ self._log.info("Starting testGetSet()...")
+ for cfg in self._configs:
+ areas = cfg.supported_areas
+ idx = -1
+ while (idx == -1) | (areas != 0):
+ idx += 1
+ # Get the area to test
+ area = areas & (areas -1)
+ area ^= areas
+
+ # Remove the area from areas
+ areas ^= area
+
+ self._log.debug(" Testing propId=%d, area=%d", cfg.prop, area)
+
+ # Get the current value
+ self._vhal.getProperty(cfg.prop, area)
+ rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
+ VehicleHalProto_pb2.RESULT_OK)
+
+ # Save the original value
+ origValue = self._getValueFromMsg(rxMsg)
+ if origValue == None:
+ self._log.error("testGetSet: Could not get value for prop=%d, area=%d",
+ cfg.prop, area)
+ continue
+
+ # Generate the test value
+ testValue = self._generateTestValue(cfg, idx, origValue)
+ if testValue == None:
+ self._log.error("testGetSet: Cannot generate test value for prop=%d, area=%d",
+ cfg.prop, area)
+ continue
+
+ # Send the new value
+ self._vhal.setProperty(cfg.prop, area, testValue)
+ rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_RESP,
+ VehicleHalProto_pb2.RESULT_OK)
+
+ # Get the new value and verify it
+ self._vhal.getProperty(cfg.prop, area)
+ rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
+ VehicleHalProto_pb2.RESULT_OK)
+ newValue = self._getValueFromMsg(rxMsg)
+ if newValue != testValue:
+ self._log.error("testGetSet: set failed for propId=%d, area=%d", cfg.prop, area)
+ print "testValue= ", testValue, "newValue= ", newValue
+ continue
+
+ # Reset the value to what it was before
+ self._vhal.setProperty(cfg.prop, area, origValue)
+ rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.SET_PROPERTY_RESP,
+ VehicleHalProto_pb2.RESULT_OK)
+ self._log.info(" Finished testGetSet()!")
+
+ def testGetBadProperty(self):
+ self._log.info("Starting testGetBadProperty()...")
+ for prop in self._badProps:
+ self._log.debug(" Testing bad propId=%d", prop)
+ self._vhal.getProperty(prop, 0)
+ rxMsg, retVal = self._rxMsgAndValidate(VehicleHalProto_pb2.GET_PROPERTY_RESP,
+ VehicleHalProto_pb2.ERROR_INVALID_PROPERTY)
+ if retVal:
+ for value in rxMsg.value:
+ self._log.error("testGetBadProperty prop=%d, expected:None, received:\n%s",
+ prop, str(rxMsg))
+ self._log.info(" Finished testGetBadProperty()!")
+
+ def testSetBadProperty(self):
+ self._log.info("Starting testSetBadProperty()...")
+ area = 1
+ value = 100
+ for prop in self._badProps:
+ self._log.debug(" Testing bad propId=%d", prop)
+ area = area + 1
+ value = value + 1
+ try:
+ self._vhal.setProperty(prop, area, value)
+ self._log.error("testGetBadProperty failed. prop=%d, area=%d, value=%d",
+ prop, area, value)
+ except ValueError as e:
+ # Received expected error
+ pass
+ self._log.info(" Finished testSetBadProperty()!")
+
+ def runTests(self):
+ self.testGetConfig()
+ self.testGetBadConfig()
+ self.testGetPropertyAll()
+ self.testGetSet()
+ self.testGetBadProperty()
+ self.testSetBadProperty()
+ # Add new tests here to be run
+
+
+ # Valid logLevels:
+ # CRITICAL 50
+ # ERRROR 40
+ # WARNING 30
+ # INFO 20
+ # DEBUG 10
+ # NOTSET 0
+ def __init__(self, types, logLevel=20):
+ self._types = types
+ # Configure the logger
+ logging.basicConfig()
+ self._log = logging.getLogger('vhal_emulator_test')
+ self._log.setLevel(logLevel)
+ # Start the VHAL Emulator
+ self._vhal = vhal_emulator.Vhal(types)
+ # Get the list of configs
+ self._vhal.getConfigAll()
+ self._configs = self._vhal.rxMsg().config
+
+if __name__ == '__main__':
+ v = VhalTest(vhal_consts_2_0.vhal_types_2_0)
+ v.runTests()
+