diff options
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 @@ | |||
1 | LOCAL_PATH:= $(call my-dir) | ||
2 | include $(CLEAR_VARS) | ||
3 | |||
4 | LOCAL_PACKAGE_NAME := CarTrustAgentClient | ||
5 | |||
6 | LOCAL_USE_AAPT2 := true | ||
7 | LOCAL_SRC_FILES := $(call all-java-files-under, src) | ||
8 | LOCAL_STATIC_ANDROID_LIBRARIES := androidx.legacy_legacy-support-v4 | ||
9 | |||
10 | LOCAL_CERTIFICATE := platform | ||
11 | LOCAL_MODULE_TAGS := optional | ||
12 | LOCAL_MIN_SDK_VERSION := 23 | ||
13 | LOCAL_SDK_VERSION := current | ||
14 | |||
15 | include $(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 @@ | |||
1 | IMPORTANT NOTE: This is a reference app to smart unlock paired HU during development. | ||
2 | Consider 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 | */ | ||
16 | package com.android.car.trust.client; | ||
17 | |||
18 | import android.Manifest; | ||
19 | import android.app.Activity; | ||
20 | import android.content.pm.PackageManager; | ||
21 | import 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 | */ | ||
32 | public 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 | */ | ||
16 | package com.android.car.trust.client; | ||
17 | |||
18 | import android.bluetooth.BluetoothDevice; | ||
19 | import android.bluetooth.BluetoothGatt; | ||
20 | import android.bluetooth.BluetoothGattCharacteristic; | ||
21 | import android.bluetooth.BluetoothGattService; | ||
22 | import android.content.Context; | ||
23 | import android.content.SharedPreferences; | ||
24 | import android.os.Handler; | ||
25 | import android.os.ParcelUuid; | ||
26 | import android.preference.PreferenceManager; | ||
27 | import android.util.Base64; | ||
28 | import android.util.Log; | ||
29 | import android.widget.Button; | ||
30 | import android.widget.TextView; | ||
31 | |||
32 | import java.nio.ByteBuffer; | ||
33 | import java.util.Random; | ||
34 | import 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 | */ | ||
40 | public 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 | */ | ||
16 | package com.android.car.trust.client; | ||
17 | |||
18 | import android.bluetooth.BluetoothDevice; | ||
19 | import android.bluetooth.BluetoothGatt; | ||
20 | import android.bluetooth.BluetoothGattCharacteristic; | ||
21 | import android.bluetooth.BluetoothGattService; | ||
22 | import android.content.Context; | ||
23 | import android.content.SharedPreferences; | ||
24 | import android.os.Handler; | ||
25 | import android.os.ParcelUuid; | ||
26 | import android.preference.PreferenceManager; | ||
27 | import android.util.Base64; | ||
28 | import android.util.Log; | ||
29 | import android.widget.Button; | ||
30 | import android.widget.TextView; | ||
31 | |||
32 | import java.util.UUID; | ||
33 | |||
34 | /** | ||
35 | * A controller that sets up a {@link SimpleBleClient} to connect to the BLE unlock service. | ||
36 | */ | ||
37 | public 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 | */ | ||
16 | package com.android.car.trust.client; | ||
17 | |||
18 | import android.bluetooth.BluetoothDevice; | ||
19 | import android.bluetooth.BluetoothGatt; | ||
20 | import android.bluetooth.BluetoothGattCallback; | ||
21 | import android.bluetooth.BluetoothGattCharacteristic; | ||
22 | import android.bluetooth.BluetoothGattService; | ||
23 | import android.bluetooth.BluetoothManager; | ||
24 | import android.bluetooth.BluetoothProfile; | ||
25 | import android.bluetooth.le.BluetoothLeScanner; | ||
26 | import android.bluetooth.le.ScanCallback; | ||
27 | import android.bluetooth.le.ScanFilter; | ||
28 | import android.bluetooth.le.ScanResult; | ||
29 | import android.bluetooth.le.ScanSettings; | ||
30 | import android.content.Context; | ||
31 | import android.os.Handler; | ||
32 | import android.os.ParcelUuid; | ||
33 | import android.util.Log; | ||
34 | |||
35 | import androidx.annotation.NonNull; | ||
36 | |||
37 | import java.util.ArrayList; | ||
38 | import java.util.List; | ||
39 | import java.util.Queue; | ||
40 | import 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 | */ | ||
46 | public 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 | */ | ||
16 | package com.android.car.trust.client; | ||
17 | |||
18 | import android.bluetooth.BluetoothGattCharacteristic; | ||
19 | import android.bluetooth.BluetoothGattService; | ||
20 | import android.content.Context; | ||
21 | |||
22 | import java.nio.ByteBuffer; | ||
23 | import java.util.UUID; | ||
24 | |||
25 | public 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 | } | ||