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.BluetoothGattCharacteristic;
20import android.bluetooth.BluetoothGattService;
21import android.content.Intent;
22import android.os.Binder;
23import android.os.IBinder;
24import android.os.ParcelUuid;
25import android.util.Log;
26import com.android.car.trust.comms.SimpleBleServer;
27
28import java.util.UUID;
29
30/**
31 * A service that receives unlock requests from remote devices.
32 */
33public class CarUnlockService extends SimpleBleServer {
34    /**
35     * A callback to receives callback
36     */
37    public interface UnlockServiceCallback {
38        void unlockDevice(byte[] token, long handle);
39    }
40
41    private static final String TAG = "CarUnlockService";
42
43    private BluetoothGattService mUnlockService;
44    private BluetoothGattCharacteristic mUnlockEscrowToken;
45    private BluetoothGattCharacteristic mUnlockTokenHandle;
46
47    private UnlockServiceCallback mCallback;
48
49    private byte[] mCurrentToken;
50    private Long mCurrentHandle;
51
52    private final IBinder mBinder = new UnlockServiceBinder();
53
54    public class UnlockServiceBinder extends Binder {
55        public CarUnlockService getService() {
56            return CarUnlockService.this;
57        }
58    }
59
60    @Override
61    public void onCreate() {
62        super.onCreate();
63        if (Log.isLoggable(TAG, Log.DEBUG)) {
64            Log.d(TAG, "CarUnlockService starting up, creating BLE service");
65        }
66        setupUnlockService();
67    }
68
69    /**
70     * Start advertising the BLE unlock service
71     */
72    public void start() {
73        ParcelUuid uuid = new ParcelUuid(
74                UUID.fromString(getString(R.string.unlock_service_uuid)));
75        start(uuid, mUnlockService);
76    }
77
78    public void addUnlockServiceCallback(UnlockServiceCallback callback) {
79        mCallback = callback;
80    }
81
82    @Override
83    public IBinder onBind(Intent intent) {
84        return mBinder;
85    }
86
87    @Override
88    public void onCharacteristicWrite(BluetoothDevice device,
89            int requestId, BluetoothGattCharacteristic characteristic,
90            boolean preparedWrite, boolean responseNeeded, int offset, byte[] value) {
91        UUID uuid = characteristic.getUuid();
92
93        if (uuid.equals(mUnlockTokenHandle.getUuid())) {
94            if (Log.isLoggable(TAG, Log.DEBUG)) {
95                Log.d(TAG, "Unlock handle received, value: " + Utils.getLong(value));
96            }
97            mCurrentHandle = Utils.getLong(value);
98            unlockDataReceived();
99        } else if (uuid.equals(mUnlockEscrowToken.getUuid())) {
100            if (Log.isLoggable(TAG, Log.DEBUG)) {
101                Log.d(TAG, "Unlock escrow token received, value: " + Utils.getLong(value));
102            }
103            mCurrentToken = value;
104            unlockDataReceived();
105        }
106    }
107
108    @Override
109    public void onCharacteristicRead(BluetoothDevice device,
110            int requestId, int offset, BluetoothGattCharacteristic characteristic) {
111        // The BLE unlock service should not receive any read requests.
112    }
113
114    private synchronized void unlockDataReceived() {
115        // If any piece of the unlocking data is not received, then do not unlock.
116        if (mCurrentHandle == null || mCurrentToken == null) {
117            return;
118        }
119
120        if (Log.isLoggable(TAG, Log.DEBUG)) {
121            Log.d(TAG, "Handle and token both received, requesting unlock. Time: "
122                    + System.currentTimeMillis());
123        }
124        // Both the handle and token has been received, try to unlock the device.
125
126
127        mCallback.unlockDevice(mCurrentToken, mCurrentHandle);
128
129        // Once we've notified the client of the unlocking data, clear it out.
130        mCurrentToken = null;
131        mCurrentHandle = null;
132    }
133
134
135    // Create services and characteristics to receive tokens and handles for unlocking the device.
136    private void setupUnlockService() {
137        mUnlockService = new BluetoothGattService(
138                UUID.fromString(getString(R.string.unlock_service_uuid)),
139                BluetoothGattService.SERVICE_TYPE_PRIMARY);
140
141        // Characteristic to describe the escrow token being used for unlock
142        mUnlockEscrowToken = new BluetoothGattCharacteristic(
143                UUID.fromString(getString(R.string.unlock_escrow_token_uiid)),
144                BluetoothGattCharacteristic.PROPERTY_WRITE,
145                BluetoothGattCharacteristic.PERMISSION_WRITE);
146
147        // Characteristic to describe the handle being used for this escrow token
148        mUnlockTokenHandle = new BluetoothGattCharacteristic(
149                UUID.fromString(getString(R.string.unlock_handle_uiid)),
150                BluetoothGattCharacteristic.PROPERTY_WRITE,
151                BluetoothGattCharacteristic.PERMISSION_WRITE);
152
153        mUnlockService.addCharacteristic(mUnlockEscrowToken);
154        mUnlockService.addCharacteristic(mUnlockTokenHandle);
155    }
156
157}
158