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