summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorandroid-build-team Robot2018-08-15 22:06:20 -0500
committerandroid-build-team Robot2018-08-15 22:06:20 -0500
commit911e6566751a60c29eada6ad0679694bed11be4f (patch)
treed7b300e81f05e45345ada1ffcaf39b2512dd8f6f
parent25dec6ed54139a358e57026ef733a9c7166472e7 (diff)
parent95c93939201441d8b5eaba422ccc7c1bea47e574 (diff)
downloadplatform-packages-services-car-pie-qpr1-s2-release.tar.gz
platform-packages-services-car-pie-qpr1-s2-release.tar.xz
platform-packages-services-car-pie-qpr1-s2-release.zip
Change-Id: I13691362b626005dc503e12312f76a0aad67e315
-rw-r--r--tests/CarTrustAgentClientApp/Android.mk15
-rw-r--r--tests/CarTrustAgentClientApp/AndroidManifest.xml34
-rw-r--r--tests/CarTrustAgentClientApp/README.txt2
-rw-r--r--tests/CarTrustAgentClientApp/res/layout/phone_enrolment_activity.xml54
-rw-r--r--tests/CarTrustAgentClientApp/res/values/strings.xml22
-rw-r--r--tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentActivity.java61
-rw-r--r--tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentController.java183
-rw-r--r--tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneUnlockController.java149
-rw-r--r--tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java355
-rw-r--r--tests/CarTrustAgentClientApp/src/com/android/car/trust/client/Utils.java46
10 files changed, 921 insertions, 0 deletions
diff --git a/tests/CarTrustAgentClientApp/Android.mk b/tests/CarTrustAgentClientApp/Android.mk
new file mode 100644
index 00000000..7945ee5f
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/Android.mk
@@ -0,0 +1,15 @@
1LOCAL_PATH:= $(call my-dir)
2include $(CLEAR_VARS)
3
4LOCAL_PACKAGE_NAME := CarTrustAgentClient
5
6LOCAL_USE_AAPT2 := true
7LOCAL_SRC_FILES := $(call all-java-files-under, src)
8LOCAL_STATIC_ANDROID_LIBRARIES := androidx.legacy_legacy-support-v4
9
10LOCAL_CERTIFICATE := platform
11LOCAL_MODULE_TAGS := optional
12LOCAL_MIN_SDK_VERSION := 23
13LOCAL_SDK_VERSION := current
14
15include $(BUILD_PACKAGE)
diff --git a/tests/CarTrustAgentClientApp/AndroidManifest.xml b/tests/CarTrustAgentClientApp/AndroidManifest.xml
new file mode 100644
index 00000000..35a9a6db
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/AndroidManifest.xml
@@ -0,0 +1,34 @@
1<?xml version="1.0" encoding="utf-8"?>
2<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3 package="com.android.car.trust.client">
4
5 <uses-sdk android:minSdkVersion="23" android:targetSdkVersion="23"/>
6
7 <!-- Need Bluetooth LE -->
8 <uses-feature android:name="android.hardware.bluetooth_le" android:required="true" />
9
10 <uses-permission android:name="android.permission.BLUETOOTH" />
11 <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" />
12 <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>
13 <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
14
15 <!-- Needed to unlock user -->
16 <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
17 <uses-permission android:name="android.permission.MANAGE_USERS" />
18 <uses-permission android:name="android.permission.CONTROL_KEYGUARD" />
19 <uses-permission android:name="android.permission.PROVIDE_TRUST_AGENT" />
20 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
21
22 <application android:label="@string/app_name">
23 <activity
24 android:name=".PhoneEnrolmentActivity"
25 android:label="@string/app_name"
26 android:exported="true"
27 android:launchMode="singleInstance">
28 <intent-filter>
29 <action android:name="android.intent.action.MAIN" />
30 <category android:name="android.intent.category.LAUNCHER" />
31 </intent-filter>
32 </activity>
33 </application>
34</manifest>
diff --git a/tests/CarTrustAgentClientApp/README.txt b/tests/CarTrustAgentClientApp/README.txt
new file mode 100644
index 00000000..bf6c4444
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/README.txt
@@ -0,0 +1,2 @@
1IMPORTANT NOTE: This is a reference app to smart unlock paired HU during development.
2Consider moving the functionality to a more proper place.
diff --git a/tests/CarTrustAgentClientApp/res/layout/phone_enrolment_activity.xml b/tests/CarTrustAgentClientApp/res/layout/phone_enrolment_activity.xml
new file mode 100644
index 00000000..620e04e8
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/res/layout/phone_enrolment_activity.xml
@@ -0,0 +1,54 @@
1<?xml version="1.0" encoding="utf-8"?>
2<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
3 android:orientation="vertical"
4 android:layout_width="match_parent"
5 android:layout_height="match_parent"
6 android:weightSum="1">
7 <ScrollView
8 android:id="@+id/scroll"
9 android:layout_width="match_parent"
10 android:layout_height="0dp"
11 android:scrollbars="vertical"
12 android:layout_weight="0.80">
13 <TextView
14 android:layout_width="match_parent"
15 android:layout_height="wrap_content"
16 android:id="@+id/output"/>
17 </ScrollView>
18 <LinearLayout
19 android:layout_width="match_parent"
20 android:layout_height="0dp"
21 android:layout_weight="0.10"
22 android:orientation="horizontal">
23 <Button
24 android:id="@+id/enroll_scan"
25 android:layout_width="0dp"
26 android:layout_height="match_parent"
27 android:layout_weight="2"
28 android:text="@string/enroll_scan"/>
29 <Button
30 android:id="@+id/enroll_button"
31 android:layout_width="0dp"
32 android:layout_height="match_parent"
33 android:layout_weight="3"
34 android:text="@string/enroll_button"/>
35 </LinearLayout>
36 <LinearLayout
37 android:layout_width="match_parent"
38 android:layout_height="0dp"
39 android:layout_weight="0.10"
40 android:orientation="horizontal">
41 <Button
42 android:id="@+id/unlock_scan"
43 android:layout_width="0dp"
44 android:layout_height="match_parent"
45 android:layout_weight="2"
46 android:text="@string/unlock_scan"/>
47 <Button
48 android:id="@+id/unlock_button"
49 android:layout_width="0dp"
50 android:layout_height="match_parent"
51 android:layout_weight="3"
52 android:text="@string/unlock_button"/>
53 </LinearLayout>
54</LinearLayout>
diff --git a/tests/CarTrustAgentClientApp/res/values/strings.xml b/tests/CarTrustAgentClientApp/res/values/strings.xml
new file mode 100644
index 00000000..5c9b4db7
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/res/values/strings.xml
@@ -0,0 +1,22 @@
1<?xml version="1.0" encoding="utf-8"?>
2<resources>
3 <string name="app_name">CarTrustAgentClient</string>
4
5 <!-- service/characteristics uuid for unlocking a device -->
6 <string name="unlock_service_uuid">5e2a68a1-27be-43f9-8d1e-4546976fabd7</string>
7 <string name="unlock_escrow_token_uiid">5e2a68a2-27be-43f9-8d1e-4546976fabd7</string>
8 <string name="unlock_handle_uiid">5e2a68a3-27be-43f9-8d1e-4546976fabd7</string>
9
10 <!-- service/characteristics uuid for adding new escrow token -->
11 <string name="enrollment_service_uuid">5e2a68a4-27be-43f9-8d1e-4546976fabd7</string>
12 <string name="enrollment_handle_uuid">5e2a68a5-27be-43f9-8d1e-4546976fabd7</string>
13 <string name="enrollment_token_uuid">5e2a68a6-27be-43f9-8d1e-4546976fabd7</string>
14
15 <string name="pref_key_token_handle">token-handle-key</string>
16 <string name="pref_key_escrow_token">escrow-token-key</string>
17
18 <string name="enroll_button">Enroll new token</string>
19 <string name="enroll_scan">Scan to enroll</string>
20 <string name="unlock_button">Unlock</string>
21 <string name="unlock_scan">Scan to unlock</string>
22</resources>
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentActivity.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentActivity.java
new file mode 100644
index 00000000..c1d30c18
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentActivity.java
@@ -0,0 +1,61 @@
1/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.car.trust.client;
17
18import android.Manifest;
19import android.app.Activity;
20import android.content.pm.PackageManager;
21import android.os.Bundle;
22
23/**
24 * Activity to allow the user to add an escrow token to a remote device. <p/>
25 *
26 * For this to work properly, the correct permissions must be set in the system config. In AOSP,
27 * this config is in frameworks/base/core/res/res/values/config.xml <p/>
28 *
29 * The config must set config_allowEscrowTokenForTrustAgent to true. For the desired car
30 * experience, the config should also set config_strongAuthRequiredOnBoot to false.
31 */
32public class PhoneEnrolmentActivity extends Activity {
33
34 private static final int FINE_LOCATION_REQUEST_CODE = 42;
35
36 @Override
37 protected void onCreate(Bundle savedInstanceState) {
38 super.onCreate(savedInstanceState);
39 setContentView(R.layout.phone_enrolment_activity);
40
41 PhoneEnrolmentController enrolmentController = new PhoneEnrolmentController(this);
42 enrolmentController.bind(findViewById(R.id.output), findViewById(R.id.enroll_scan),
43 findViewById(R.id.enroll_button));
44
45 PhoneUnlockController unlockController = new PhoneUnlockController(this);
46 unlockController.bind(findViewById(R.id.output), findViewById(R.id.unlock_scan),
47 findViewById(R.id.unlock_button));
48 }
49
50 @Override
51 protected void onResume() {
52 super.onResume();
53
54 if (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION)
55 != PackageManager.PERMISSION_GRANTED) {
56 requestPermissions(
57 new String[] { android.Manifest.permission.ACCESS_FINE_LOCATION },
58 FINE_LOCATION_REQUEST_CODE);
59 }
60 }
61}
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentController.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentController.java
new file mode 100644
index 00000000..030e3d2a
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneEnrolmentController.java
@@ -0,0 +1,183 @@
1/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.car.trust.client;
17
18import android.bluetooth.BluetoothDevice;
19import android.bluetooth.BluetoothGatt;
20import android.bluetooth.BluetoothGattCharacteristic;
21import android.bluetooth.BluetoothGattService;
22import android.content.Context;
23import android.content.SharedPreferences;
24import android.os.Handler;
25import android.os.ParcelUuid;
26import android.preference.PreferenceManager;
27import android.util.Base64;
28import android.util.Log;
29import android.widget.Button;
30import android.widget.TextView;
31
32import java.nio.ByteBuffer;
33import java.util.Random;
34import java.util.UUID;
35
36/**
37 * A controller that sets up a {@link SimpleBleClient} to connect to the BLE enrollment service.
38 * It also binds the UI components to control the enrollment process.
39 */
40public class PhoneEnrolmentController {
41
42 private final SimpleBleClient.ClientCallback mCallback = new SimpleBleClient.ClientCallback() {
43 @Override
44 public void onDeviceConnected(BluetoothDevice device) {
45 appendOutputText("Device connected: " + device.getName()
46 + " addr: " + device.getAddress());
47 }
48
49 @Override
50 public void onDeviceDisconnected() {
51 appendOutputText("Device disconnected");
52 }
53
54 @Override
55 public void onCharacteristicChanged(BluetoothGatt gatt,
56 BluetoothGattCharacteristic characteristic) {
57
58 Log.d(Utils.LOG_TAG, "onCharacteristicChanged: "
59 + Utils.getLong(characteristic.getValue()));
60 if (characteristic.getUuid().equals(mEnrolmentTokenHandle.getUuid())) {
61 // Store the new token handle that the BLE server is sending us. This required
62 // to unlock the device.
63 long handle = Utils.getLong(characteristic.getValue());
64 storeHandle(handle);
65 appendOutputText("Token handle received: " + handle);
66 }
67 }
68
69 @Override
70 public void onServiceDiscovered(BluetoothGattService service) {
71 if (!service.getUuid().equals(mEnrolmentServiceUuid.getUuid())) {
72 Log.d(Utils.LOG_TAG, "Service UUID: " + service.getUuid()
73 + " does not match Enrolment UUID " + mEnrolmentServiceUuid.getUuid());
74 return;
75 }
76
77 Log.d(Utils.LOG_TAG, "Enrolment Service # characteristics: "
78 + service.getCharacteristics().size());
79 mEnrolmentEscrowToken = Utils.getCharacteristic(
80 R.string.enrollment_token_uuid, service, mContext);
81 mEnrolmentTokenHandle = Utils.getCharacteristic(
82 R.string.enrollment_handle_uuid, service, mContext);
83 mClient.setCharacteristicNotification(mEnrolmentTokenHandle, true /* enable */);
84 appendOutputText("Enrolment BLE client successfully connected");
85
86 mHandler.post(() -> {
87 // Services are now set up, allow users to enrol new escrow tokens.
88 mEnrolButton.setEnabled(true);
89 mEnrolButton.setAlpha(1.0f);
90 });
91 }
92 };
93
94 private String mTokenHandleKey;
95 private String mEscrowTokenKey;
96
97 // BLE characteristics associated with the enrollment/add escrow token service.
98 private BluetoothGattCharacteristic mEnrolmentTokenHandle;
99 private BluetoothGattCharacteristic mEnrolmentEscrowToken;
100
101 private ParcelUuid mEnrolmentServiceUuid;
102
103 private SimpleBleClient mClient;
104 private Context mContext;
105
106 private TextView mTextView;
107 private Handler mHandler;
108
109 private Button mEnrolButton;
110
111 public PhoneEnrolmentController(Context context) {
112 mContext = context;
113
114 mTokenHandleKey = context.getString(R.string.pref_key_token_handle);
115 mEscrowTokenKey = context.getString(R.string.pref_key_escrow_token);
116
117 mClient = new SimpleBleClient(context);
118 mEnrolmentServiceUuid = new ParcelUuid(
119 UUID.fromString(mContext.getString(R.string.enrollment_service_uuid)));
120 mClient.addCallback(mCallback /* callback */);
121
122 mHandler = new Handler(mContext.getMainLooper());
123 }
124
125 /**
126 * Binds the views to the actions that can be performed by this controller.
127 *
128 * @param textView A text view used to display results from various BLE actions
129 * @param scanButton Button used to start scanning for available BLE devices.
130 * @param enrolButton Button used to send new escrow token to remote device.
131 */
132 public void bind(TextView textView, Button scanButton, Button enrolButton) {
133 mTextView = textView;
134 mEnrolButton = enrolButton;
135
136 scanButton.setOnClickListener((view) -> mClient.start(mEnrolmentServiceUuid));
137
138 mEnrolButton.setEnabled(false);
139 mEnrolButton.setAlpha(0.3f);
140 mEnrolButton.setOnClickListener((view) -> {
141 appendOutputText("Sending new escrow token to remote device");
142
143 byte[] token = generateEscrowToken();
144 sendEnrolmentRequest(token);
145
146 // WARNING: Store the token so it can be used later for unlocking. This token
147 // should NEVER be stored on the device that is being unlocked. It should
148 // always be securely stored on a remote device that will trigger the unlock.
149 storeToken(token);
150 });
151 }
152
153 /**
154 * @return A random byte array that is used as the escrow token for remote device unlock.
155 */
156 private byte[] generateEscrowToken() {
157 Random random = new Random();
158 ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
159 buffer.putLong(0, random.nextLong());
160 return buffer.array();
161 }
162
163 private void sendEnrolmentRequest(byte[] token) {
164 mEnrolmentEscrowToken.setValue(token);
165 mClient.writeCharacteristic(mEnrolmentEscrowToken);
166 storeToken(token);
167 }
168
169 private void storeHandle(long handle) {
170 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
171 prefs.edit().putLong(mTokenHandleKey, handle).apply();
172 }
173
174 private void storeToken(byte[] token) {
175 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
176 String byteArray = Base64.encodeToString(token, Base64.DEFAULT);
177 prefs.edit().putString(mEscrowTokenKey, byteArray).apply();
178 }
179
180 private void appendOutputText(final String text) {
181 mHandler.post(() -> mTextView.append("\n" + text));
182 }
183}
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneUnlockController.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneUnlockController.java
new file mode 100644
index 00000000..78e50b41
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/PhoneUnlockController.java
@@ -0,0 +1,149 @@
1/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.car.trust.client;
17
18import android.bluetooth.BluetoothDevice;
19import android.bluetooth.BluetoothGatt;
20import android.bluetooth.BluetoothGattCharacteristic;
21import android.bluetooth.BluetoothGattService;
22import android.content.Context;
23import android.content.SharedPreferences;
24import android.os.Handler;
25import android.os.ParcelUuid;
26import android.preference.PreferenceManager;
27import android.util.Base64;
28import android.util.Log;
29import android.widget.Button;
30import android.widget.TextView;
31
32import java.util.UUID;
33
34/**
35 * A controller that sets up a {@link SimpleBleClient} to connect to the BLE unlock service.
36 */
37public class PhoneUnlockController {
38
39 private final SimpleBleClient.ClientCallback mCallback = new SimpleBleClient.ClientCallback() {
40 @Override
41 public void onDeviceConnected(BluetoothDevice device) {
42 appendOutputText("Device connected: " + device.getName()
43 + " addr: " + device.getAddress());
44 }
45
46 @Override
47 public void onDeviceDisconnected() {
48 appendOutputText("Device disconnected");
49 }
50
51 @Override
52 public void onCharacteristicChanged(BluetoothGatt gatt,
53 BluetoothGattCharacteristic characteristic) {
54 // Not expecting any characteristics changes for the unlocking client.
55 }
56
57 @Override
58 public void onServiceDiscovered(BluetoothGattService service) {
59 if (!service.getUuid().equals(mUnlockServiceUuid.getUuid())) {
60 Log.d(Utils.LOG_TAG, "Service UUID: " + service.getUuid()
61 + " does not match Enrolment UUID " + mUnlockServiceUuid.getUuid());
62 return;
63 }
64
65 Log.d(Utils.LOG_TAG, "Unlock Service # characteristics: "
66 + service.getCharacteristics().size());
67 mUnlockEscrowToken = Utils.getCharacteristic(
68 R.string.unlock_escrow_token_uiid, service, mContext);
69 mUnlockTokenHandle = Utils.getCharacteristic(
70 R.string.unlock_handle_uiid, service, mContext);
71 appendOutputText("Unlock BLE client successfully connected");
72
73 mHandler.post(() -> {
74 // Services are now set up, allow users to enrol new escrow tokens.
75 mUnlockButton.setEnabled(true);
76 mUnlockButton.setAlpha(1.0f);
77 });
78 }
79 };
80
81 private String mTokenHandleKey;
82 private String mEscrowTokenKey;
83
84 // BLE characteristics associated with the enrolment/add escrow token service.
85 private BluetoothGattCharacteristic mUnlockTokenHandle;
86 private BluetoothGattCharacteristic mUnlockEscrowToken;
87
88 private ParcelUuid mUnlockServiceUuid;
89
90 private SimpleBleClient mClient;
91 private Context mContext;
92
93 private TextView mTextView;
94 private Handler mHandler;
95
96 private Button mUnlockButton;
97
98 public PhoneUnlockController(Context context) {
99 mContext = context;
100
101 mTokenHandleKey = context.getString(R.string.pref_key_token_handle);
102 mEscrowTokenKey = context.getString(R.string.pref_key_escrow_token);
103
104 mClient = new SimpleBleClient(context);
105 mUnlockServiceUuid = new ParcelUuid(
106 UUID.fromString(mContext.getString(R.string.unlock_service_uuid)));
107 mClient.addCallback(mCallback /* callback */);
108
109 mHandler = new Handler(mContext.getMainLooper());
110 }
111
112 /**
113 * Binds the views to the actions that can be performed by this controller.
114 *
115 * @param textView A text view used to display results from various BLE actions
116 * @param scanButton Button used to start scanning for available BLE devices.
117 * @param enrolButton Button used to send new escrow token to remote device.
118 */
119 public void bind(TextView textView, Button scanButton, Button enrolButton) {
120 mTextView = textView;
121 mUnlockButton = enrolButton;
122
123 scanButton.setOnClickListener((view) -> mClient.start(mUnlockServiceUuid));
124
125 mUnlockButton.setEnabled(false);
126 mUnlockButton.setAlpha(0.3f);
127 mUnlockButton.setOnClickListener((view) -> {
128 appendOutputText("Sending unlock token and handle to remote device");
129 sendUnlockRequest();
130 });
131 }
132
133 private void sendUnlockRequest() {
134 // Retrieve stored token and handle and write to remote device.
135 SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
136 long handle = prefs.getLong(mTokenHandleKey, -1);
137 byte[] token = Base64.decode(prefs.getString(mEscrowTokenKey, null), Base64.DEFAULT);
138
139 mUnlockEscrowToken.setValue(token);
140 mUnlockTokenHandle.setValue(Utils.getBytes(handle));
141
142 mClient.writeCharacteristic(mUnlockEscrowToken);
143 mClient.writeCharacteristic(mUnlockTokenHandle);
144 }
145
146 private void appendOutputText(final String text) {
147 mHandler.post(() -> mTextView.append("\n" + text));
148 }
149}
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java
new file mode 100644
index 00000000..c0fecb31
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/SimpleBleClient.java
@@ -0,0 +1,355 @@
1/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.car.trust.client;
17
18import android.bluetooth.BluetoothDevice;
19import android.bluetooth.BluetoothGatt;
20import android.bluetooth.BluetoothGattCallback;
21import android.bluetooth.BluetoothGattCharacteristic;
22import android.bluetooth.BluetoothGattService;
23import android.bluetooth.BluetoothManager;
24import android.bluetooth.BluetoothProfile;
25import android.bluetooth.le.BluetoothLeScanner;
26import android.bluetooth.le.ScanCallback;
27import android.bluetooth.le.ScanFilter;
28import android.bluetooth.le.ScanResult;
29import android.bluetooth.le.ScanSettings;
30import android.content.Context;
31import android.os.Handler;
32import android.os.ParcelUuid;
33import android.util.Log;
34
35import androidx.annotation.NonNull;
36
37import java.util.ArrayList;
38import java.util.List;
39import java.util.Queue;
40import java.util.concurrent.ConcurrentLinkedQueue;
41
42/**
43 * A simple client that supports the scanning and connecting to available BLE devices. Should be
44 * used along with {@link SimpleBleServer}.
45 */
46public class SimpleBleClient {
47 public interface ClientCallback {
48 /**
49 * Called when a device that has a matching service UUID is found.
50 **/
51 void onDeviceConnected(BluetoothDevice device);
52
53 void onDeviceDisconnected();
54
55 void onCharacteristicChanged(BluetoothGatt gatt,
56 BluetoothGattCharacteristic characteristic);
57
58 /**
59 * Called for each {@link BluetoothGattService} that is discovered on the
60 * {@link BluetoothDevice} after a matching scan result and connection.
61 *
62 * @param service {@link BluetoothGattService} that has been discovered.
63 */
64 void onServiceDiscovered(BluetoothGattService service);
65 }
66
67 /**
68 * Wrapper class to allow queuing of BLE actions. The BLE stack allows only one action to be
69 * executed at a time.
70 */
71 public static class BleAction {
72 public static final int ACTION_WRITE = 0;
73 public static final int ACTION_READ = 1;
74
75 private int mAction;
76 private BluetoothGattCharacteristic mCharacteristic;
77
78 public BleAction(BluetoothGattCharacteristic characteristic, int action) {
79 mAction = action;
80 mCharacteristic = characteristic;
81 }
82
83 public int getAction() {
84 return mAction;
85 }
86
87 public BluetoothGattCharacteristic getCharacteristic() {
88 return mCharacteristic;
89 }
90 }
91
92 private static final long SCAN_TIME_MS = 10000;
93
94 private final Queue<BleAction> mBleActionQueue = new ConcurrentLinkedQueue<BleAction>();
95 private final List<ClientCallback> mCallbacks = new ArrayList<>();
96 private final Context mContext;
97 private final BluetoothLeScanner mScanner;
98
99 private BluetoothGatt mBtGatt;
100 private ParcelUuid mServiceUuid;
101
102 public SimpleBleClient(@NonNull Context context) {
103 mContext = context;
104 BluetoothManager btManager = (BluetoothManager) mContext.getSystemService(
105 Context.BLUETOOTH_SERVICE);
106 mScanner = btManager.getAdapter().getBluetoothLeScanner();
107 }
108
109 /**
110 * Start scanning for a BLE devices with the specified service uuid.
111 *
112 * @param parcelUuid {@link ParcelUuid} used to identify the device that should be used for
113 * this client. This uuid should be the same as the one that is set in the
114 * {@link android.bluetooth.le.AdvertiseData.Builder} by the advertising
115 * device.
116 */
117 public void start(ParcelUuid parcelUuid) {
118 mServiceUuid = parcelUuid;
119
120 // We only want to scan for devices that have the correct uuid set in its advertise data.
121 List<ScanFilter> filters = new ArrayList<ScanFilter>();
122 ScanFilter.Builder serviceFilter = new ScanFilter.Builder();
123 serviceFilter.setServiceUuid(mServiceUuid);
124 filters.add(serviceFilter.build());
125
126 ScanSettings.Builder settings = new ScanSettings.Builder();
127 settings.setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY);
128
129 Log.d(Utils.LOG_TAG, "Start scanning for uuid: " + mServiceUuid.getUuid());
130 mScanner.startScan(filters, settings.build(), mScanCallback);
131
132 Handler handler = new Handler();
133 handler.postDelayed(new Runnable() {
134 @Override
135 public void run() {
136 mScanner.stopScan(mScanCallback);
137 Log.d(Utils.LOG_TAG, "Stopping Scanner");
138 }
139 }, SCAN_TIME_MS);
140 }
141
142 private boolean hasServiceUuid(ScanResult result) {
143 if (result.getScanRecord() == null
144 || result.getScanRecord().getServiceUuids() == null
145 || result.getScanRecord().getServiceUuids().size() == 0) {
146 return false;
147 }
148 return true;
149 }
150
151 /**
152 * Writes to a {@link BluetoothGattCharacteristic} if possible, or queues the action until
153 * other actions are complete.
154 *
155 * @param characteristic {@link BluetoothGattCharacteristic} to be written
156 */
157 public void writeCharacteristic(BluetoothGattCharacteristic characteristic) {
158 processAction(new BleAction(characteristic, BleAction.ACTION_WRITE));
159 }
160
161 /**
162 * Reads a {@link BluetoothGattCharacteristic} if possible, or queues the read action until
163 * other actions are complete.
164 *
165 * @param characteristic {@link BluetoothGattCharacteristic} to be read.
166 */
167 public void readCharacteristic(BluetoothGattCharacteristic characteristic) {
168 processAction(new BleAction(characteristic, BleAction.ACTION_READ));
169 }
170
171 /**
172 * Enable or disable notification for specified {@link BluetoothGattCharacteristic}.
173 *
174 * @param characteristic The {@link BluetoothGattCharacteristic} for which to enable
175 * notifications.
176 * @param enabled True if notifications should be enabled, false otherwise.
177 */
178 public void setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
179 boolean enabled) {
180 mBtGatt.setCharacteristicNotification(characteristic, enabled);
181 }
182
183 /**
184 * Add a {@link ClientCallback} to listen for updates from BLE components
185 */
186 public void addCallback(ClientCallback callback) {
187 mCallbacks.add(callback);
188 }
189
190 public void removeCallback(ClientCallback callback) {
191 mCallbacks.remove(callback);
192 }
193
194 private void processAction(BleAction action) {
195 // Only execute actions if the queue is empty.
196 if (mBleActionQueue.size() > 0) {
197 mBleActionQueue.add(action);
198 return;
199 }
200
201 mBleActionQueue.add(action);
202 executeAction(mBleActionQueue.peek());
203 }
204
205 private void processNextAction() {
206 mBleActionQueue.poll();
207 executeAction(mBleActionQueue.peek());
208 }
209
210 private void executeAction(BleAction action) {
211 if (action == null) {
212 return;
213 }
214
215 Log.d(Utils.LOG_TAG, "Executing BLE Action type: " + action.getAction());
216 int actionType = action.getAction();
217 switch (actionType) {
218 case BleAction.ACTION_WRITE:
219 mBtGatt.writeCharacteristic(action.getCharacteristic());
220 break;
221 case BleAction.ACTION_READ:
222 mBtGatt.readCharacteristic(action.getCharacteristic());
223 break;
224 default:
225 }
226 }
227
228 private String getStatus(int status) {
229 switch (status) {
230 case BluetoothGatt.GATT_FAILURE:
231 return "Failure";
232 case BluetoothGatt.GATT_SUCCESS:
233 return "GATT_SUCCESS";
234 case BluetoothGatt.GATT_READ_NOT_PERMITTED:
235 return "GATT_READ_NOT_PERMITTED";
236 case BluetoothGatt.GATT_WRITE_NOT_PERMITTED:
237 return "GATT_WRITE_NOT_PERMITTED";
238 case BluetoothGatt.GATT_INSUFFICIENT_AUTHENTICATION:
239 return "GATT_INSUFFICIENT_AUTHENTICATION";
240 case BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED:
241 return "GATT_REQUEST_NOT_SUPPORTED";
242 case BluetoothGatt.GATT_INVALID_OFFSET:
243 return "GATT_INVALID_OFFSET";
244 case BluetoothGatt.GATT_INVALID_ATTRIBUTE_LENGTH:
245 return "GATT_INVALID_ATTRIBUTE_LENGTH";
246 case BluetoothGatt.GATT_CONNECTION_CONGESTED:
247 return "GATT_CONNECTION_CONGESTED";
248 default:
249 return "unknown";
250 }
251 }
252
253 private ScanCallback mScanCallback = new ScanCallback() {
254 @Override
255 public void onScanResult(int callbackType, ScanResult result) {
256 BluetoothDevice device = result.getDevice();
257 Log.d(Utils.LOG_TAG, "Scan result found: " + result.getScanRecord().getServiceUuids());
258
259 if (!hasServiceUuid(result)) {
260 return;
261 }
262
263 for (ParcelUuid uuid : result.getScanRecord().getServiceUuids()) {
264 Log.d(Utils.LOG_TAG, "Scan result UUID: " + uuid);
265 if (uuid.equals(mServiceUuid)) {
266 // This client only supports connecting to one service.
267 // Once we find one, stop scanning and open a GATT connection to the device.
268 mScanner.stopScan(mScanCallback);
269 mBtGatt = device.connectGatt(mContext, false /* autoConnect */, mGattCallback);
270 return;
271 }
272 }
273 }
274
275 @Override
276 public void onBatchScanResults(List<ScanResult> results) {
277 for (ScanResult r : results) {
278 Log.d(Utils.LOG_TAG, "Batch scanResult: " + r.getDevice().getName()
279 + " " + r.getDevice().getAddress());
280 }
281 }
282
283 @Override
284 public void onScanFailed(int errorCode) {
285 Log.e(Utils.LOG_TAG, "Scan failed: " + errorCode);
286 }
287 };
288
289 private BluetoothGattCallback mGattCallback = new BluetoothGattCallback() {
290 @Override
291 public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
292 super.onConnectionStateChange(gatt, status, newState);
293
294 String state = "";
295
296 if (newState == BluetoothProfile.STATE_CONNECTED) {
297 state = "Connected";
298 mBtGatt.discoverServices();
299 for (ClientCallback callback : mCallbacks) {
300 callback.onDeviceConnected(gatt.getDevice());
301 }
302
303 } else if (newState == BluetoothProfile.STATE_DISCONNECTED) {
304 state = "Disconnected";
305 for (ClientCallback callback : mCallbacks) {
306 callback.onDeviceDisconnected();
307 }
308 }
309 Log.d(Utils.LOG_TAG, "Gatt connection status: " + getStatus(status)
310 + " newState: " + state);
311 }
312
313 @Override
314 public void onServicesDiscovered(BluetoothGatt gatt, int status) {
315 super.onServicesDiscovered(gatt, status);
316 Log.d(Utils.LOG_TAG, "onServicesDiscovered: " + status);
317
318 List<BluetoothGattService> services = gatt.getServices();
319 if (services == null || services.size() <= 0) {
320 return;
321 }
322
323 // Notify clients of newly discovered services.
324 for (BluetoothGattService service : mBtGatt.getServices()) {
325 Log.d(Utils.LOG_TAG, "Found service: " + service.getUuid() + " notifying clients");
326 for (ClientCallback callback : mCallbacks) {
327 callback.onServiceDiscovered(service);
328 }
329 }
330 }
331
332 @Override
333 public void onCharacteristicWrite(BluetoothGatt gatt,
334 BluetoothGattCharacteristic characteristic, int status) {
335 Log.d(Utils.LOG_TAG, "onCharacteristicWrite: " + status);
336 processNextAction();
337 }
338
339 @Override
340 public void onCharacteristicRead(BluetoothGatt gatt,
341 BluetoothGattCharacteristic characteristic, int status) {
342 Log.d(Utils.LOG_TAG, "onCharacteristicRead:" + new String(characteristic.getValue()));
343 processNextAction();
344 }
345
346 @Override
347 public void onCharacteristicChanged(BluetoothGatt gatt,
348 BluetoothGattCharacteristic characteristic) {
349 for (ClientCallback callback : mCallbacks) {
350 callback.onCharacteristicChanged(gatt, characteristic);
351 }
352 processNextAction();
353 }
354 };
355}
diff --git a/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/Utils.java b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/Utils.java
new file mode 100644
index 00000000..003a86cc
--- /dev/null
+++ b/tests/CarTrustAgentClientApp/src/com/android/car/trust/client/Utils.java
@@ -0,0 +1,46 @@
1/*
2 * Copyright (C) 2018 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.car.trust.client;
17
18import android.bluetooth.BluetoothGattCharacteristic;
19import android.bluetooth.BluetoothGattService;
20import android.content.Context;
21
22import java.nio.ByteBuffer;
23import java.util.UUID;
24
25public class Utils {
26
27 public static final String LOG_TAG = "CarTrustAgentClient";
28
29 public static byte[] getBytes(long l) {
30 ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
31 buffer.putLong(0, l);
32 return buffer.array();
33 }
34
35 public static long getLong(byte[] bytes) {
36 ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
37 buffer.put(bytes);
38 buffer.flip();
39 return buffer.getLong();
40 }
41
42 public static BluetoothGattCharacteristic getCharacteristic(int uuidRes,
43 BluetoothGattService service, Context context) {
44 return service.getCharacteristic(UUID.fromString(context.getString(uuidRes)));
45 }
46}