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.util.UUID;
35
36/**
37 * A controller that sets up a {@link SimpleBleClient} to connect to the BLE unlock service.
38 */
39public class PhoneUnlockController {
40    private static final String TAG = "PhoneUnlockController";
41
42    private String mTokenHandleKey;
43    private String mEscrowTokenKey;
44
45    // BLE characteristics associated with the enrolment/add escrow token service.
46    private BluetoothGattCharacteristic mUnlockTokenHandle;
47    private BluetoothGattCharacteristic mUnlockEscrowToken;
48
49    private ParcelUuid mUnlockServiceUuid;
50
51    private SimpleBleClient mClient;
52    private Context mContext;
53
54    private TextView mTextView;
55    private Handler mHandler;
56
57    private Button mScanButton;
58    private Button mUnlockButton;
59
60    public PhoneUnlockController(Context context) {
61        mContext = context;
62
63        mTokenHandleKey = context.getString(R.string.pref_key_token_handle);
64        mEscrowTokenKey = context.getString(R.string.pref_key_escrow_token);
65
66        mClient = new SimpleBleClient(context);
67        mUnlockServiceUuid = new ParcelUuid(
68                UUID.fromString(mContext.getString(R.string.unlock_service_uuid)));
69        mClient.addCallback(mCallback /* callback */);
70
71        mHandler = new Handler(mContext.getMainLooper());
72    }
73
74    /**
75     * Binds the views to the actions that can be performed by this controller.
76     *
77     * @param textView    A text view used to display results from various BLE actions
78     * @param scanButton  Button used to start scanning for available BLE devices.
79     * @param enrolButton Button used to send new escrow token to remote device.
80     */
81    public void bind(TextView textView, Button scanButton, Button enrolButton) {
82        mTextView = textView;
83        mScanButton = scanButton;
84        mUnlockButton = enrolButton;
85
86        mScanButton.setOnClickListener(new View.OnClickListener() {
87            @Override
88            public void onClick(View v) {
89                mClient.start(mUnlockServiceUuid);
90            }
91        });
92
93        mUnlockButton.setEnabled(false);
94        mUnlockButton.setAlpha(0.3f);
95        mUnlockButton.setOnClickListener(new View.OnClickListener() {
96            @Override
97            public void onClick(View v) {
98                appendOutputText("Sending unlock token and handle to remote device");
99                sendUnlockRequest();
100            }
101        });
102    }
103
104    private void sendUnlockRequest() {
105        // Retrieve stored token and handle and write to remote device.
106        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
107        long handle = prefs.getLong(mTokenHandleKey, -1);
108        byte[] token = Base64.decode(prefs.getString(mEscrowTokenKey, null), Base64.DEFAULT);
109
110        mUnlockEscrowToken.setValue(token);
111        mUnlockTokenHandle.setValue(Utils.getBytes(handle));
112
113        mClient.writeCharacteristic(mUnlockEscrowToken);
114        mClient.writeCharacteristic(mUnlockTokenHandle);
115    }
116
117    private SimpleBleClient.ClientCallback mCallback = new SimpleBleClient.ClientCallback() {
118        @Override
119        public void onDeviceConnected(BluetoothDevice device) {
120            appendOutputText("Device connected: " + device.getName()
121                    + " addr: " + device.getAddress());
122        }
123
124        @Override
125        public void onDeviceDisconnected() {
126            appendOutputText("Device disconnected");
127        }
128
129        @Override
130        public void onCharacteristicChanged(BluetoothGatt gatt,
131                BluetoothGattCharacteristic characteristic) {
132            // Not expecting any characteristics changes for the unlocking client.
133        }
134
135        @Override
136        public void onServiceDiscovered(BluetoothGattService service) {
137            if (!service.getUuid().equals(mUnlockServiceUuid.getUuid())) {
138                if (Log.isLoggable(TAG, Log.DEBUG)) {
139                    Log.d(TAG, "Service UUID: " + service.getUuid()
140                        + " does not match Enrolment UUID " + mUnlockServiceUuid.getUuid());
141                }
142                return;
143            }
144
145            if (Log.isLoggable(TAG, Log.DEBUG)) {
146                Log.d(TAG, "Unlock Service # characteristics: "
147                        + service.getCharacteristics().size());
148            }
149            mUnlockEscrowToken
150                    = Utils.getCharacteristic(R.string.unlock_escrow_token_uiid, service, mContext);
151            mUnlockTokenHandle
152                    = Utils.getCharacteristic(R.string.unlock_handle_uiid, service, mContext);
153            appendOutputText("Unlock BLE client successfully connected");
154
155            mHandler.post(new Runnable() {
156                @Override
157                public void run() {
158                    // Services are now set up, allow users to enrol new escrow tokens.
159                    mUnlockButton.setEnabled(true);
160                    mUnlockButton.setAlpha(1.0f);
161                }
162            });
163        }
164    };
165
166    private void appendOutputText(final String text) {
167        mHandler.post(new Runnable() {
168            @Override
169            public void run() {
170                mTextView.append("\n" + text);
171            }
172        });
173    }
174}
175