aboutsummaryrefslogtreecommitdiff
path: root/TrustAgent/src
diff options
context:
space:
mode:
authorVictor Chan <victorchan@google.com>2017-03-01 19:03:10 -0800
committerVictor Chan <victorchan@google.com>2017-03-02 14:49:44 -0800
commit77e5e49cf9dcceb69b07510c380ae2a9285ebfee (patch)
tree27f4c7db60f42ac5b1fddd8489f20ccd20661079 /TrustAgent/src
parent3c5e8e9041a255a141ccf34de827d4ab0180d369 (diff)
downloadCar-77e5e49cf9dcceb69b07510c380ae2a9285ebfee.tar.gz
First pass of BLE escrow token user unlock.
The CarBleTrustAgent has now been updated to use the latest escrow token and synthetic password framework API's. The tokens are transfered via BLE between a phone and a car head unit. Note this only works on FBE enabled devices. config_strongAuthRequiredOnBoot should also be disabled so that unlocking the user will also cause the keyguard to be dropped. Test: Manual on sailfish build. Not tested on Mojave Change-Id: I6aba6cd3299e34389dca68c78047b8ebb9f1a884
Diffstat (limited to 'TrustAgent/src')
-rw-r--r--TrustAgent/src/com/android/car/trust/BootupReceiver.java36
-rw-r--r--TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java269
-rw-r--r--TrustAgent/src/com/android/car/trust/CarBluetoothTrustAgent.java190
-rw-r--r--TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java240
-rw-r--r--TrustAgent/src/com/android/car/trust/CarEnrolmentService.java125
-rw-r--r--TrustAgent/src/com/android/car/trust/CarUnlockService.java148
-rw-r--r--TrustAgent/src/com/android/car/trust/MainActivity.java94
-rw-r--r--TrustAgent/src/com/android/car/trust/PhoneEnrolmentActivity.java46
-rw-r--r--TrustAgent/src/com/android/car/trust/PhoneEnrolmentController.java201
-rw-r--r--TrustAgent/src/com/android/car/trust/PhoneUnlockActivity.java46
-rw-r--r--TrustAgent/src/com/android/car/trust/PhoneUnlockController.java169
-rw-r--r--TrustAgent/src/com/android/car/trust/Utils.java29
-rw-r--r--TrustAgent/src/com/android/car/trust/comms/SimpleBleClient.java359
-rw-r--r--TrustAgent/src/com/android/car/trust/comms/SimpleBleServer.java237
14 files changed, 1963 insertions, 226 deletions
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 352003f3c1..0000000000
--- 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 0000000000..cc7c1c1647
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarBleTrustAgent.java
@@ -0,0 +1,269 @@
+/*
+ * 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();
+ 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();
+ Log.d(TAG, "onTrustTimeout(): timeout expired");
+ }
+
+ @Override
+ public void onCreate() {
+ super.onCreate();
+
+ 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()) {
+ 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() {
+ 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() {
+ Log.d(TAG, "BLE server started");
+ }
+
+ @Override
+ public void onServerStartFailed(int errorCode) {
+ Log.d(TAG, "BLE server failed to start. Error Code: " + errorCode);
+ }
+
+ @Override
+ public void onDeviceConnected(BluetoothDevice device) {
+ 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) {
+ 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() {
+ 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();
+ Log.d(TAG, "GATT available, starting up UnlockService");
+ mCarUnlockService.start();
+ }
+ }
+
+ private void unlock(byte[] token, long handle) {
+ UserManager um = (UserManager) getSystemService(Context.USER_SERVICE);
+
+ Log.d(TAG, "About to unlock user. Current handle: " + handle
+ + " Time: " + System.currentTimeMillis());
+ unlockUserWithToken(handle, token, getCurrentUserHandle());
+ Log.d(TAG, "Attempted to unlock user, is user unlocked? " + um.isUserUnlocked()
+ + " Time: " + System.currentTimeMillis());
+ setManagingTrust(true);
+
+ if (um.isUserUnlocked()) {
+ 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) {
+ Log.d(TAG, "onEscrowTokenRemoved. Handle: " + handle + " successful? " + successful);
+ }
+
+ @Override
+ public void onEscrowTokenStateReceived(long handle, int tokenState) {
+ boolean isActive = tokenState == TOKEN_STATE_ACTIVE;
+ 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) {
+ 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 fa52e1bfc7..0000000000
--- 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 0000000000..175430487a
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarEnrolmentActivity.java
@@ -0,0 +1,240 @@
+/*
+ * 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();
+ 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() {
+ Log.d(TAG, "about to store new handle");
+ mPrefs.edit().putLong(SP_HANDLE_KEY, handle).apply();
+ 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);
+ 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 0000000000..4dd15b6fb1
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarEnrolmentService.java
@@ -0,0 +1,125 @@
+/*
+ * 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 enrolment 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.enrolment_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())) {
+ 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));
+
+ 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.enrolment_service_uuid)),
+ BluetoothGattService.SERVICE_TYPE_PRIMARY);
+
+ // Characteristic to describe the escrow token being used for unlock
+ mEnrolmentEscrowToken = new BluetoothGattCharacteristic(
+ UUID.fromString(getString(R.string.enrolment_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.enrolment_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 0000000000..cd0774e160
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/CarUnlockService.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.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();
+ 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())) {
+ Log.d(TAG, "Unlock handle received, value: " + Utils.getLong(value));
+ mCurrentHandle = Utils.getLong(value);
+ unlockDataReceived();
+ } else if (uuid.equals(mUnlockEscrowToken.getUuid())) {
+ 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;
+ }
+ 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 0000000000..8c1348bc23
--- /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 0000000000..27c7f90927
--- /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 mScannButton;
+ private Button mEnrolButton;
+
+ 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);
+ mEnrolButton = (Button) findViewById(R.id.action_button);
+ mEnrolButton.setText(getString(R.string.enrol_button));
+
+ mTextOutput = (TextView) findViewById(R.id.output);
+
+ PhoneEnrolmentController controller = new PhoneEnrolmentController(this /* context */);
+ controller.bind(mTextOutput, mScannButton, mEnrolButton);
+ }
+}
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 0000000000..aec7043a45
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/PhoneEnrolmentController.java
@@ -0,0 +1,201 @@
+/*
+ * 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 enrolment service.
+ * It also binds the UI components to control the enrolment process.
+ */
+public class PhoneEnrolmentController {
+ private static final String TAG = "PhoneEnrolmentCtlr";
+ private String mTokenHandleKey;
+ private String mEscrowTokenKey;
+
+ // BLE characteristics associated with the enrolment/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.enrolment_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) {
+ 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())) {
+ Log.d(TAG, "Service UUID: " + service.getUuid() + " does not match Enrolment UUID "
+ + mEnrolmentServiceUuid.getUuid());
+ return;
+ }
+
+ Log.d(TAG, "Enrolment Service # characteristics: " + service.getCharacteristics().size());
+ mEnrolmentEscrowToken
+ = Utils.getCharacteristic(R.string.enrolment_token_uuid, service, mContext);
+ mEnrolmentTokenHandle
+ = Utils.getCharacteristic(R.string.enrolment_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 0000000000..767e1c5fd4
--- /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 0000000000..de31ce5521
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/PhoneUnlockController.java
@@ -0,0 +1,169 @@
+/*
+ * 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())) {
+ Log.d(TAG, "Service UUID: " + service.getUuid() + " does not match Enrolment UUID "
+ + mUnlockServiceUuid.getUuid());
+ return;
+ }
+
+ 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 0000000000..f47a8d4361
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/Utils.java
@@ -0,0 +1,29 @@
+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 0000000000..ec5e5cabc2
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/comms/SimpleBleClient.java
@@ -0,0 +1,359 @@
+/*
+ * 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);
+
+ 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);
+ 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;
+ }
+
+ 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();
+ Log.d(TAG, "Scan result found: " + result.getScanRecord().getServiceUuids());
+
+ if (!hasServiceUuid(result)) {
+ return;
+ }
+
+ for (ParcelUuid uuid : result.getScanRecord().getServiceUuids()) {
+ 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) {
+ Log.d(TAG, "Batch scanResult: "
+ + r.getDevice().getName() + " " + r.getDevice().getAddress());
+ }
+ }
+
+ @Override
+ public void onScanFailed(int errorCode) {
+ Log.d(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();
+ }
+ }
+ Log.d(TAG, " Gatt connection status: " + getStatus(status) + " newState: " + state);
+ }
+
+ @Override
+ public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+ super.onServicesDiscovered(gatt, status);
+ 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()) {
+ 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) {
+ Log.d(TAG, "onCharacteristicWrite: " + status);
+ processNextAction();
+ }
+
+ @Override
+ public void onCharacteristicRead(BluetoothGatt gatt,
+ BluetoothGattCharacteristic characteristic, int status) {
+ 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 0000000000..d55db9e165
--- /dev/null
+++ b/TrustAgent/src/com/android/car/trust/comms/SimpleBleServer.java
@@ -0,0 +1,237 @@
+/*
+ * 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) {
+ 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);
+ Log.d(TAG, "Successfully started advertising service");
+ for (ConnectionListener listener : mListeners) {
+ listener.onServerStarted();
+ }
+ }
+
+ @Override
+ public void onStartFailure(int errorCode) {
+ super.onStartFailure(errorCode);
+ 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) {
+ 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) {
+ Log.d(TAG, "Service added status: " + status + " uuid: " + service.getUuid());
+ }
+
+ @Override
+ public void onCharacteristicReadRequest(BluetoothDevice device,
+ int requestId, int offset, final BluetoothGattCharacteristic characteristic) {
+ 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) {
+ 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);
+ }
+ };
+}