PhoneEnrolmentController.java revision 77e5e49cf9dcceb69b07510c380ae2a9285ebfee
1/*
2 * Copyright (C) 2017 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;
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.view.View;
30import android.widget.Button;
31import android.widget.TextView;
32import com.android.car.trust.comms.SimpleBleClient;
33
34import java.nio.ByteBuffer;
35import java.util.Random;
36import java.util.UUID;
37
38/**
39 * A controller that sets up a {@link SimpleBleClient} to connect to the BLE enrolment service.
40 * It also binds the UI components to control the enrolment process.
41 */
42public class PhoneEnrolmentController {
43    private static final String TAG = "PhoneEnrolmentCtlr";
44    private String mTokenHandleKey;
45    private String mEscrowTokenKey;
46
47    // BLE characteristics associated with the enrolment/add escrow token service.
48    private BluetoothGattCharacteristic mEnrolmentTokenHandle;
49    private BluetoothGattCharacteristic mEnrolmentEscrowToken;
50
51    private ParcelUuid mEnrolmentServiceUuid;
52
53    private SimpleBleClient mClient;
54    private Context mContext;
55
56    private TextView mTextView;
57    private Handler mHandler;
58
59    private Button mScanButton;
60    private Button mEnrolButton;
61
62    public PhoneEnrolmentController(Context context) {
63        mContext = context;
64
65        mTokenHandleKey = context.getString(R.string.pref_key_token_handle);
66        mEscrowTokenKey = context.getString(R.string.pref_key_escrow_token);
67
68        mClient = new SimpleBleClient(context);
69        mEnrolmentServiceUuid = new ParcelUuid(
70                UUID.fromString(mContext.getString(R.string.enrolment_service_uuid)));
71        mClient.addCallback(mCallback /* callback */);
72
73        mHandler = new Handler(mContext.getMainLooper());
74    }
75
76    /**
77     * Binds the views to the actions that can be performed by this controller.
78     *
79     * @param textView    A text view used to display results from various BLE actions
80     * @param scanButton  Button used to start scanning for available BLE devices.
81     * @param enrolButton Button used to send new escrow token to remote device.
82     */
83    public void bind(TextView textView, Button scanButton, Button enrolButton) {
84        mTextView = textView;
85        mScanButton = scanButton;
86        mEnrolButton = enrolButton;
87
88        mScanButton.setOnClickListener(new View.OnClickListener() {
89            @Override
90            public void onClick(View v) {
91                mClient.start(mEnrolmentServiceUuid);
92            }
93        });
94
95        mEnrolButton.setEnabled(false);
96        mEnrolButton.setAlpha(0.3f);
97        mEnrolButton.setOnClickListener(new View.OnClickListener() {
98            @Override
99            public void onClick(View v) {
100                appendOutputText("Sending new escrow token to remote device");
101
102                byte[] token = generateEscrowToken();
103                sendEnrolmentRequest(token);
104
105                // WARNING: Store the token so it can be used later for unlocking. This token
106                // should NEVER be stored on the device that is being unlocked. It should
107                // always be securely stored on a remote device that will trigger the unlock.
108                storeToken(token);
109            }
110        });
111    }
112
113    /**
114     * @return A random byte array that is used as the escrow token for remote device unlock.
115     */
116    private byte[] generateEscrowToken() {
117        Random random = new Random();
118        ByteBuffer buffer = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
119        buffer.putLong(0, random.nextLong());
120        return buffer.array();
121    }
122
123    private void sendEnrolmentRequest(byte[] token) {
124        mEnrolmentEscrowToken.setValue(token);
125        mClient.writeCharacteristic(mEnrolmentEscrowToken);
126        storeToken(token);
127    }
128
129    private SimpleBleClient.ClientCallback mCallback = new SimpleBleClient.ClientCallback() {
130        @Override
131        public void onDeviceConnected(BluetoothDevice device) {
132            appendOutputText("Device connected: " + device.getName()
133                    + " addr: " + device.getAddress());
134        }
135
136        @Override
137        public void onDeviceDisconnected() {
138            appendOutputText("Device disconnected");
139        }
140
141        @Override
142        public void onCharacteristicChanged(BluetoothGatt gatt,
143                BluetoothGattCharacteristic characteristic) {
144            Log.d(TAG, "onCharacteristicChanged: " + Utils.getLong(characteristic.getValue()));
145
146            if (characteristic.getUuid().equals(mEnrolmentTokenHandle.getUuid())) {
147                // Store the new token handle that the BLE server is sending us. This required
148                // to unlock the device.
149                long handle = Utils.getLong(characteristic.getValue());
150                storeHandle(handle);
151                appendOutputText("Token handle received: " + handle);
152            }
153        }
154
155        @Override
156        public void onServiceDiscovered(BluetoothGattService service) {
157            if (!service.getUuid().equals(mEnrolmentServiceUuid.getUuid())) {
158                Log.d(TAG, "Service UUID: " + service.getUuid() + " does not match Enrolment UUID "
159                        + mEnrolmentServiceUuid.getUuid());
160                return;
161            }
162
163            Log.d(TAG, "Enrolment Service # characteristics: " + service.getCharacteristics().size());
164            mEnrolmentEscrowToken
165                    = Utils.getCharacteristic(R.string.enrolment_token_uuid, service, mContext);
166            mEnrolmentTokenHandle
167                    = Utils.getCharacteristic(R.string.enrolment_handle_uuid, service, mContext);
168            mClient.setCharacteristicNotification(mEnrolmentTokenHandle, true /* enable */);
169            appendOutputText("Enrolment BLE client successfully connected");
170
171            mHandler.post(new Runnable() {
172                @Override
173                public void run() {
174                    // Services are now set up, allow users to enrol new escrow tokens.
175                    mEnrolButton.setEnabled(true);
176                    mEnrolButton.setAlpha(1.0f);
177                }
178            });
179        }
180    };
181
182    private void storeHandle(long handle) {
183        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
184        prefs.edit().putLong(mTokenHandleKey, handle).apply();
185    }
186
187    private void storeToken(byte[] token) {
188        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
189        String byteArray = Base64.encodeToString(token, Base64.DEFAULT);
190        prefs.edit().putString(mEscrowTokenKey, byteArray).apply();
191    }
192
193    private void appendOutputText(final String text) {
194        mHandler.post(new Runnable() {
195            @Override
196            public void run() {
197                mTextView.append("\n" + text);
198            }
199        });
200    }
201}
202