1 /*
2  * Copyright (C) 2022 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 
17 package com.android.pandora
18 
19 import android.bluetooth.BluetoothAdapter
20 import android.bluetooth.BluetoothDevice
21 import android.bluetooth.BluetoothDevice.BOND_NONE
22 import android.bluetooth.BluetoothManager
23 import android.content.Context
24 import android.content.Intent
25 import android.content.IntentFilter
26 import android.util.Log
27 import com.google.protobuf.BoolValue
28 import com.google.protobuf.Empty
29 import io.grpc.Status
30 import io.grpc.stub.StreamObserver
31 import java.io.Closeable
32 import kotlinx.coroutines.CoroutineScope
33 import kotlinx.coroutines.Dispatchers
34 import kotlinx.coroutines.async
35 import kotlinx.coroutines.cancel
36 import kotlinx.coroutines.flow.Flow
37 import kotlinx.coroutines.flow.SharingStarted
38 import kotlinx.coroutines.flow.filter
39 import kotlinx.coroutines.flow.first
40 import kotlinx.coroutines.flow.shareIn
41 import pandora.HostProto.*
42 import pandora.SecurityProto.*
43 import pandora.SecurityStorageGrpc.SecurityStorageImplBase
44 
45 private const val TAG = "PandoraSecurityStorage"
46 
47 @kotlinx.coroutines.ExperimentalCoroutinesApi
48 class SecurityStorage(private val context: Context) : SecurityStorageImplBase(), Closeable {
49 
50     private val globalScope: CoroutineScope = CoroutineScope(Dispatchers.Default.limitedParallelism(1))
51     private val flow: Flow<Intent>
52 
53     private val bluetoothManager = context.getSystemService(BluetoothManager::class.java)!!
54     private val bluetoothAdapter = bluetoothManager.adapter
55 
56     init {
57         val intentFilter = IntentFilter()
58         intentFilter.addAction(BluetoothDevice.ACTION_BOND_STATE_CHANGED)
59 
60         flow = intentFlow(context, intentFilter, globalScope).shareIn(globalScope, SharingStarted.Eagerly)
61     }
62 
closenull63     override fun close() {
64         globalScope.cancel()
65     }
66 
isBondednull67     override fun isBonded(request: IsBondedRequest, responseObserver: StreamObserver<BoolValue>) {
68         grpcUnary(globalScope, responseObserver) {
69             val bondedDevices = bluetoothAdapter.getBondedDevices()
70             val bondedDevice =
71                 when (request.getAddressCase()!!) {
72                     IsBondedRequest.AddressCase.PUBLIC ->
73                         bondedDevices.firstOrNull { it.toByteString() == request.public }
74                     IsBondedRequest.AddressCase.RANDOM ->
75                         bondedDevices.firstOrNull { it.toByteString() == request.random }
76                     IsBondedRequest.AddressCase.ADDRESS_NOT_SET ->
77                         throw Status.UNKNOWN.asException()
78                 }
79             Log.i(TAG, "isBonded: device=$bondedDevice")
80             BoolValue.newBuilder().setValue(bondedDevice != null).build()
81         }
82     }
83 
deleteBondnull84     override fun deleteBond(request: DeleteBondRequest, responseObserver: StreamObserver<Empty>) {
85         grpcUnary(globalScope, responseObserver) {
86             val (address, type) =
87                 when (request.getAddressCase()!!) {
88                     DeleteBondRequest.AddressCase.PUBLIC ->
89                         Pair(request.public, BluetoothDevice.ADDRESS_TYPE_PUBLIC)
90                     DeleteBondRequest.AddressCase.RANDOM ->
91                         Pair(request.random, BluetoothDevice.ADDRESS_TYPE_RANDOM)
92                     DeleteBondRequest.AddressCase.ADDRESS_NOT_SET ->
93                         throw Status.UNKNOWN.asException()
94                 }
95             val bluetoothDevice =
96                 bluetoothAdapter.getRemoteLeDevice(address.decodeAsMacAddressToString(), type)
97             Log.i(TAG, "deleteBond: device=$bluetoothDevice")
98 
99             val unbonded =
100                 globalScope.async {
101                     flow
102                         .filter { it.action == BluetoothDevice.ACTION_BOND_STATE_CHANGED }
103                         .filter { it.getBluetoothDeviceExtra() == bluetoothDevice }
104                         .filter {
105                             it.getIntExtra(
106                                 BluetoothDevice.EXTRA_BOND_STATE,
107                                 BluetoothAdapter.ERROR
108                             ) == BluetoothDevice.BOND_NONE
109                         }
110                         .first()
111                 }
112 
113             if (bluetoothDevice.removeBond()) {
114                 Log.i(TAG, "deleteBond: device=$bluetoothDevice - wait BOND_NONE intent")
115                 unbonded.await()
116             } else {
117                 Log.i(TAG, "deleteBond: device=$bluetoothDevice - Already unpaired")
118                 unbonded.cancel()
119             }
120             Empty.getDefaultInstance()
121         }
122     }
123 }
124