| /* |
| * Copyright (C) 2012 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.bluetooth.btservice; |
| |
| import static android.Manifest.permission.BLUETOOTH_CONNECT; |
| |
| import android.annotation.RequiresPermission; |
| import android.app.Activity; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothClass; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothProfile; |
| import android.bluetooth.BluetoothProtoEnums; |
| import android.bluetooth.OobData; |
| import android.content.Intent; |
| import android.os.Build; |
| import android.os.Bundle; |
| import android.os.Message; |
| import android.os.UserHandle; |
| import android.util.Log; |
| |
| import com.android.bluetooth.BluetoothStatsLog; |
| import com.android.bluetooth.Utils; |
| import com.android.bluetooth.a2dp.A2dpService; |
| import com.android.bluetooth.a2dpsink.A2dpSinkService; |
| import com.android.bluetooth.btservice.RemoteDevices.DeviceProperties; |
| import com.android.bluetooth.hfp.HeadsetService; |
| import com.android.bluetooth.hfpclient.HeadsetClientService; |
| import com.android.bluetooth.hid.HidHostService; |
| import com.android.bluetooth.pbapclient.PbapClientService; |
| import com.android.internal.annotations.VisibleForTesting; |
| import com.android.internal.util.State; |
| import com.android.internal.util.StateMachine; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Set; |
| |
| /** |
| * This state machine handles Bluetooth Adapter State. |
| * States: |
| * {@link StableState} : No device is in bonding / unbonding state. |
| * {@link PendingCommandState} : Some device is in bonding / unbonding state. |
| * TODO(BT) This class can be removed and this logic moved to the stack. |
| */ |
| |
| final class BondStateMachine extends StateMachine { |
| private static final String TAG = "BluetoothBondStateMachine"; |
| |
| static final int CREATE_BOND = 1; |
| static final int CANCEL_BOND = 2; |
| static final int REMOVE_BOND = 3; |
| static final int BONDING_STATE_CHANGE = 4; |
| static final int SSP_REQUEST = 5; |
| static final int PIN_REQUEST = 6; |
| static final int UUID_UPDATE = 10; |
| static final int BONDED_INTENT_DELAY = 11; |
| static final int BOND_STATE_NONE = 0; |
| static final int BOND_STATE_BONDING = 1; |
| static final int BOND_STATE_BONDED = 2; |
| |
| static int sPendingUuidUpdateTimeoutMillis = 3000; // 3s |
| |
| private AdapterService mAdapterService; |
| private AdapterProperties mAdapterProperties; |
| private RemoteDevices mRemoteDevices; |
| private BluetoothAdapter mAdapter; |
| |
| private PendingCommandState mPendingCommandState = new PendingCommandState(); |
| private StableState mStableState = new StableState(); |
| |
| public static final String OOBDATAP192 = "oobdatap192"; |
| public static final String OOBDATAP256 = "oobdatap256"; |
| public static final String DISPLAY_PASSKEY = "display_passkey"; |
| |
| @VisibleForTesting Set<BluetoothDevice> mPendingBondedDevices = new HashSet<>(); |
| |
| private BondStateMachine(AdapterService service, AdapterProperties prop, |
| RemoteDevices remoteDevices) { |
| super("BondStateMachine:"); |
| addState(mStableState); |
| addState(mPendingCommandState); |
| mRemoteDevices = remoteDevices; |
| mAdapterService = service; |
| mAdapterProperties = prop; |
| mAdapter = BluetoothAdapter.getDefaultAdapter(); |
| setInitialState(mStableState); |
| } |
| |
| public static BondStateMachine make(AdapterService service, AdapterProperties prop, |
| RemoteDevices remoteDevices) { |
| Log.d(TAG, "make"); |
| BondStateMachine bsm = new BondStateMachine(service, prop, remoteDevices); |
| bsm.start(); |
| return bsm; |
| } |
| |
| public synchronized void doQuit() { |
| quitNow(); |
| } |
| |
| private void cleanup() { |
| mAdapterService = null; |
| mRemoteDevices = null; |
| mAdapterProperties = null; |
| } |
| |
| @Override |
| protected void onQuitting() { |
| cleanup(); |
| } |
| |
| private class StableState extends State { |
| @Override |
| public void enter() { |
| infoLog("StableState(): Entering Off State"); |
| } |
| |
| @Override |
| public synchronized boolean processMessage(Message msg) { |
| |
| BluetoothDevice dev = (BluetoothDevice) msg.obj; |
| |
| switch (msg.what) { |
| |
| case CREATE_BOND: |
| OobData p192Data = (msg.getData() != null) |
| ? msg.getData().getParcelable(OOBDATAP192) : null; |
| OobData p256Data = (msg.getData() != null) |
| ? msg.getData().getParcelable(OOBDATAP256) : null; |
| createBond(dev, msg.arg1, p192Data, p256Data, true); |
| break; |
| case REMOVE_BOND: |
| removeBond(dev, true); |
| break; |
| case BONDING_STATE_CHANGE: |
| int newState = msg.arg1; |
| /* if incoming pairing, transition to pending state */ |
| if (newState == BluetoothDevice.BOND_BONDING) { |
| deferMessage(msg); |
| transitionTo(mPendingCommandState); |
| } else if (newState == BluetoothDevice.BOND_NONE) { |
| /* if the link key was deleted by the stack */ |
| sendIntent(dev, newState, 0, false); |
| } else { |
| Log.e(TAG, "In stable state, received invalid newState: " |
| + state2str(newState)); |
| } |
| break; |
| case BONDED_INTENT_DELAY: |
| if (mPendingBondedDevices.contains(dev)) { |
| sendIntent(dev, BluetoothDevice.BOND_BONDED, 0, true); |
| } |
| break; |
| case UUID_UPDATE: |
| if (mPendingBondedDevices.contains(dev)) { |
| sendIntent(dev, BluetoothDevice.BOND_BONDED, 0, false); |
| } |
| break; |
| case CANCEL_BOND: |
| default: |
| Log.e(TAG, "Received unhandled state: " + msg.what); |
| return false; |
| } |
| return true; |
| } |
| } |
| |
| private class PendingCommandState extends State { |
| private final ArrayList<BluetoothDevice> mDevices = new ArrayList<BluetoothDevice>(); |
| |
| @Override |
| public void enter() { |
| infoLog("Entering PendingCommandState State"); |
| } |
| |
| @Override |
| public synchronized boolean processMessage(Message msg) { |
| BluetoothDevice dev = (BluetoothDevice) msg.obj; |
| |
| DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev); |
| boolean result = false; |
| if ((mDevices.contains(dev) || mPendingBondedDevices.contains(dev)) |
| && msg.what != CANCEL_BOND && msg.what != BONDING_STATE_CHANGE |
| && msg.what != SSP_REQUEST && msg.what != PIN_REQUEST) { |
| deferMessage(msg); |
| return true; |
| } |
| |
| switch (msg.what) { |
| case CREATE_BOND: |
| OobData p192Data = (msg.getData() != null) |
| ? msg.getData().getParcelable(OOBDATAP192) : null; |
| OobData p256Data = (msg.getData() != null) |
| ? msg.getData().getParcelable(OOBDATAP256) : null; |
| result = createBond(dev, msg.arg1, p192Data, p256Data, false); |
| break; |
| case REMOVE_BOND: |
| result = removeBond(dev, false); |
| break; |
| case CANCEL_BOND: |
| result = cancelBond(dev); |
| break; |
| case BONDING_STATE_CHANGE: |
| int newState = msg.arg1; |
| int reason = getUnbondReasonFromHALCode(msg.arg2); |
| // Bond is explicitly removed if we are in pending command state |
| if (newState == BluetoothDevice.BOND_NONE |
| && reason == BluetoothDevice.BOND_SUCCESS) { |
| reason = BluetoothDevice.UNBOND_REASON_REMOVED; |
| } |
| sendIntent(dev, newState, reason, false); |
| if (newState != BluetoothDevice.BOND_BONDING) { |
| // This is either none/bonded, remove and transition, and also set |
| // result=false to avoid adding the device to mDevices. |
| mDevices.remove(dev); |
| result = false; |
| if (mDevices.isEmpty()) { |
| transitionTo(mStableState); |
| } |
| if (newState == BluetoothDevice.BOND_NONE) { |
| mAdapterService.setPhonebookAccessPermission(dev, |
| BluetoothDevice.ACCESS_UNKNOWN); |
| mAdapterService.setMessageAccessPermission(dev, |
| BluetoothDevice.ACCESS_UNKNOWN); |
| mAdapterService.setSimAccessPermission(dev, |
| BluetoothDevice.ACCESS_UNKNOWN); |
| // Set the profile Priorities to undefined |
| clearProfilePriority(dev); |
| } |
| } else if (!mDevices.contains(dev)) { |
| result = true; |
| } |
| break; |
| case SSP_REQUEST: |
| if (devProp == null) { |
| errorLog("devProp is null, maybe the device is disconnected"); |
| break; |
| } |
| |
| int passkey = msg.arg1; |
| int variant = msg.arg2; |
| boolean displayPasskey = |
| (msg.getData() != null) |
| ? msg.getData().getByte(DISPLAY_PASSKEY) == 1 /* 1 == true */ |
| : false; |
| sendDisplayPinIntent( |
| devProp.getAddress(), |
| displayPasskey ? Optional.of(passkey) : Optional.empty(), |
| variant); |
| break; |
| case PIN_REQUEST: |
| if (devProp == null) { |
| errorLog("devProp is null, maybe the device is disconnected"); |
| break; |
| } |
| |
| BluetoothClass btClass = dev.getBluetoothClass(); |
| int btDeviceClass = btClass.getDeviceClass(); |
| if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD || btDeviceClass |
| == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) { |
| // Its a keyboard. Follow the HID spec recommendation of creating the |
| // passkey and displaying it to the user. If the keyboard doesn't follow |
| // the spec recommendation, check if the keyboard has a fixed PIN zero |
| // and pair. |
| //TODO: Maintain list of devices that have fixed pin |
| // Generate a variable 6-digit PIN in range of 100000-999999 |
| // This is not truly random but good enough. |
| int pin = 100000 + (int) Math.floor((Math.random() * (999999 - 100000))); |
| sendDisplayPinIntent( |
| devProp.getAddress(), |
| Optional.of(pin), |
| BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN); |
| break; |
| } |
| |
| if (msg.arg2 == 1) { // Minimum 16 digit pin required here |
| sendDisplayPinIntent( |
| devProp.getAddress(), |
| Optional.empty(), |
| BluetoothDevice.PAIRING_VARIANT_PIN_16_DIGITS); |
| } else { |
| // In PIN_REQUEST, there is no passkey to display.So do not send the |
| // EXTRA_PAIRING_KEY type in the intent |
| sendDisplayPinIntent( |
| devProp.getAddress(), |
| Optional.empty(), |
| BluetoothDevice.PAIRING_VARIANT_PIN); |
| } |
| break; |
| default: |
| Log.e(TAG, "Received unhandled event:" + msg.what); |
| return false; |
| } |
| if (result) { |
| mDevices.add(dev); |
| } |
| return true; |
| } |
| } |
| |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) |
| private boolean cancelBond(BluetoothDevice dev) { |
| if (dev.getBondState() == BluetoothDevice.BOND_BONDING) { |
| byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); |
| if (!mAdapterService.cancelBondNative(addr)) { |
| Log.e(TAG, "Unexpected error while cancelling bond:"); |
| } else { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean removeBond(BluetoothDevice dev, boolean transition) { |
| DeviceProperties devProp = mRemoteDevices.getDeviceProperties(dev); |
| if (devProp != null && devProp.getBondState() == BluetoothDevice.BOND_BONDED) { |
| byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); |
| if (!mAdapterService.removeBondNative(addr)) { |
| Log.e(TAG, "Unexpected error while removing bond:"); |
| } else { |
| if (transition) { |
| transitionTo(mPendingCommandState); |
| } |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| @RequiresPermission(allOf = { |
| android.Manifest.permission.BLUETOOTH_CONNECT, |
| android.Manifest.permission.INTERACT_ACROSS_USERS, |
| }) |
| private boolean createBond(BluetoothDevice dev, int transport, OobData remoteP192Data, |
| OobData remoteP256Data, boolean transition) { |
| if (dev.getBondState() == BluetoothDevice.BOND_NONE) { |
| infoLog("Bond address is:" + dev); |
| byte[] addr = Utils.getBytesFromAddress(dev.getAddress()); |
| int addrType = dev.getAddressType(); |
| boolean result; |
| // If we have some data |
| if (remoteP192Data != null || remoteP256Data != null) { |
| BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED, |
| mAdapterService.obfuscateAddress(dev), transport, dev.getType(), |
| BluetoothDevice.BOND_BONDING, |
| BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_START_PAIRING_OOB, |
| BluetoothProtoEnums.UNBOND_REASON_UNKNOWN, mAdapterService.getMetricId(dev)); |
| result = mAdapterService.createBondOutOfBandNative(addr, transport, |
| remoteP192Data, remoteP256Data); |
| } else { |
| BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED, |
| mAdapterService.obfuscateAddress(dev), transport, dev.getType(), |
| BluetoothDevice.BOND_BONDING, |
| BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_START_PAIRING, |
| BluetoothProtoEnums.UNBOND_REASON_UNKNOWN, mAdapterService.getMetricId(dev)); |
| result = mAdapterService.createBondNative(addr, addrType, transport); |
| } |
| BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_DEVICE_NAME_REPORTED, |
| mAdapterService.getMetricId(dev), dev.getName()); |
| BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED, |
| mAdapterService.obfuscateAddress(dev), transport, dev.getType(), |
| BluetoothDevice.BOND_BONDING, |
| remoteP192Data == null && remoteP256Data == null |
| ? BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN |
| : BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_OOB_DATA_PROVIDED, |
| BluetoothProtoEnums.UNBOND_REASON_UNKNOWN); |
| |
| if (!result) { |
| BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED, |
| mAdapterService.obfuscateAddress(dev), transport, dev.getType(), |
| BluetoothDevice.BOND_NONE, BluetoothProtoEnums.BOND_SUB_STATE_UNKNOWN, |
| BluetoothDevice.UNBOND_REASON_REPEATED_ATTEMPTS); |
| // Using UNBOND_REASON_REMOVED for legacy reason |
| sendIntent(dev, BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED, |
| false); |
| return false; |
| } else if (transition) { |
| transitionTo(mPendingCommandState); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| private void sendDisplayPinIntent(byte[] address, Optional<Integer> maybePin, int variant) { |
| Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mRemoteDevices.getDevice(address)); |
| maybePin.ifPresent(pin -> intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin)); |
| intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, variant); |
| intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND); |
| // Workaround for Android Auto until pre-accepting pairing requests is added. |
| intent.addFlags(Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND); |
| Utils.sendOrderedBroadcast(mAdapterService, intent, BLUETOOTH_CONNECT, |
| Utils.getTempAllowlistBroadcastOptions(), null/* resultReceiver */, |
| null/* scheduler */, Activity.RESULT_OK/* initialCode */, null/* initialData */, |
| null/* initialExtras */); |
| } |
| |
| @VisibleForTesting |
| @RequiresPermission(allOf = { |
| android.Manifest.permission.BLUETOOTH_CONNECT, |
| android.Manifest.permission.INTERACT_ACROSS_USERS, |
| }) |
| void sendIntent(BluetoothDevice device, int newState, int reason, |
| boolean isTriggerFromDelayMessage) { |
| DeviceProperties devProp = mRemoteDevices.getDeviceProperties(device); |
| int oldState = BluetoothDevice.BOND_NONE; |
| if (newState != BluetoothDevice.BOND_NONE |
| && newState != BluetoothDevice.BOND_BONDING |
| && newState != BluetoothDevice.BOND_BONDED) { |
| infoLog("Invalid bond state " + newState); |
| return; |
| } |
| if (devProp != null) { |
| oldState = devProp.getBondState(); |
| } |
| if (isTriggerFromDelayMessage && (oldState != BluetoothDevice.BOND_BONDED |
| || newState != BluetoothDevice.BOND_BONDED |
| || !mPendingBondedDevices.contains(device))) { |
| infoLog("Invalid state when doing delay send bonded intent, oldState: " + oldState |
| + ", newState: " + newState + ", in PendingBondedDevices list? " |
| + mPendingBondedDevices.contains(device)); |
| return; |
| } |
| if (mPendingBondedDevices.contains(device)) { |
| mPendingBondedDevices.remove(device); |
| if (oldState == BluetoothDevice.BOND_BONDED) { |
| if (newState == BluetoothDevice.BOND_BONDING) { |
| mAdapterProperties.onBondStateChanged(device, newState); |
| } |
| oldState = BluetoothDevice.BOND_BONDING; |
| } else { |
| // Should not enter here. |
| throw new IllegalArgumentException("Invalid old state " + oldState); |
| } |
| } |
| if (oldState == newState) { |
| return; |
| } |
| BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED, |
| mAdapterService.obfuscateAddress(device), 0, device.getType(), |
| newState, BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_BOND_STATE_INTENT_SENT, reason, |
| mAdapterService.getMetricId(device)); |
| BluetoothClass deviceClass = device.getBluetoothClass(); |
| int classOfDevice = deviceClass == null ? 0 : deviceClass.getClassOfDevice(); |
| BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_CLASS_OF_DEVICE_REPORTED, |
| mAdapterService.obfuscateAddress(device), classOfDevice, |
| mAdapterService.getMetricId(device)); |
| mAdapterProperties.onBondStateChanged(device, newState); |
| |
| if (!isTriggerFromDelayMessage && newState == BluetoothDevice.BOND_BONDED |
| && devProp != null && devProp.getUuids() == null) { |
| infoLog(device + " is bonded, wait for SDP complete to broadcast bonded intent"); |
| if (!mPendingBondedDevices.contains(device)) { |
| mPendingBondedDevices.add(device); |
| Message msg = obtainMessage(BONDED_INTENT_DELAY); |
| msg.obj = device; |
| sendMessageDelayed(msg, sPendingUuidUpdateTimeoutMillis); |
| } |
| if (oldState == BluetoothDevice.BOND_NONE) { |
| // Broadcast NONE->BONDING for NONE->BONDED case. |
| newState = BluetoothDevice.BOND_BONDING; |
| } else { |
| return; |
| } |
| } |
| |
| Intent intent = new Intent(BluetoothDevice.ACTION_BOND_STATE_CHANGED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| intent.putExtra(BluetoothDevice.EXTRA_BOND_STATE, newState); |
| intent.putExtra(BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE, oldState); |
| if (newState == BluetoothDevice.BOND_NONE) { |
| intent.putExtra(BluetoothDevice.EXTRA_UNBOND_REASON, reason); |
| } |
| mAdapterService.onBondStateChanged(device, newState); |
| mAdapterService.sendBroadcastAsUser(intent, UserHandle.ALL, BLUETOOTH_CONNECT, |
| Utils.getTempAllowlistBroadcastOptions()); |
| infoLog("Bond State Change Intent:" + device + " " + state2str(oldState) + " => " |
| + state2str(newState)); |
| } |
| |
| void bondStateChangeCallback(int status, byte[] address, int newState, int hciReason) { |
| BluetoothDevice device = mRemoteDevices.getDevice(address); |
| |
| if (device == null) { |
| infoLog("No record of the device:" + device); |
| // This device will be added as part of the BONDING_STATE_CHANGE intent processing |
| // in sendIntent above |
| device = mAdapter.getRemoteDevice(Utils.getAddressStringFromByte(address)); |
| } |
| |
| infoLog("bondStateChangeCallback: Status: " + status + " Address: " + device + " newState: " |
| + newState + " hciReason: " + hciReason); |
| |
| Message msg = obtainMessage(BONDING_STATE_CHANGE); |
| msg.obj = device; |
| |
| if (newState == BOND_STATE_BONDED) { |
| msg.arg1 = BluetoothDevice.BOND_BONDED; |
| } else if (newState == BOND_STATE_BONDING) { |
| msg.arg1 = BluetoothDevice.BOND_BONDING; |
| } else { |
| msg.arg1 = BluetoothDevice.BOND_NONE; |
| } |
| msg.arg2 = status; |
| |
| sendMessage(msg); |
| } |
| |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) |
| void sspRequestCallback(byte[] address, byte[] name, int cod, int pairingVariant, int passkey) { |
| //TODO(BT): Get wakelock and update name and cod |
| BluetoothDevice bdDevice = mRemoteDevices.getDevice(address); |
| if (bdDevice == null) { |
| mRemoteDevices.addDeviceProperties(address); |
| } |
| infoLog("sspRequestCallback: " + Utils.getRedactedAddressStringFromByte(address) |
| + " name: " + Arrays.toString(name) |
| + " cod: " + cod |
| + " pairingVariant " + pairingVariant |
| + " passkey: " + (Build.isDebuggable() ? passkey : "******")); |
| int variant; |
| boolean displayPasskey = false; |
| switch (pairingVariant) { |
| |
| case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_CONFIRMATION: |
| variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION; |
| displayPasskey = true; |
| break; |
| |
| case AbstractionLayer.BT_SSP_VARIANT_CONSENT: |
| variant = BluetoothDevice.PAIRING_VARIANT_CONSENT; |
| break; |
| |
| case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_ENTRY: |
| variant = BluetoothDevice.PAIRING_VARIANT_PASSKEY; |
| break; |
| |
| case AbstractionLayer.BT_SSP_VARIANT_PASSKEY_NOTIFICATION: |
| variant = BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY; |
| displayPasskey = true; |
| break; |
| |
| default: |
| errorLog("SSP Pairing variant not present"); |
| return; |
| } |
| BluetoothDevice device = mRemoteDevices.getDevice(address); |
| if (device == null) { |
| warnLog("Device is not known for:" + Utils.getRedactedAddressStringFromByte(address)); |
| mRemoteDevices.addDeviceProperties(address); |
| device = Objects.requireNonNull(mRemoteDevices.getDevice(address)); |
| } |
| |
| BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED, |
| mAdapterService.obfuscateAddress(device), 0, device.getType(), |
| BluetoothDevice.BOND_BONDING, |
| BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_SSP_REQUESTED, 0); |
| |
| Message msg = obtainMessage(SSP_REQUEST); |
| msg.obj = device; |
| if (displayPasskey) { |
| msg.arg1 = passkey; |
| Bundle bundle = new Bundle(); |
| bundle.putByte(BondStateMachine.DISPLAY_PASSKEY, (byte) 1 /* true */); |
| msg.setData(bundle); |
| } |
| msg.arg2 = variant; |
| sendMessage(msg); |
| } |
| |
| @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) |
| void pinRequestCallback(byte[] address, byte[] name, int cod, boolean min16Digits) { |
| //TODO(BT): Get wakelock and update name and cod |
| |
| BluetoothDevice bdDevice = mRemoteDevices.getDevice(address); |
| if (bdDevice == null) { |
| mRemoteDevices.addDeviceProperties(address); |
| bdDevice = Objects.requireNonNull(mRemoteDevices.getDevice(address)); |
| } |
| |
| BluetoothStatsLog.write(BluetoothStatsLog.BLUETOOTH_BOND_STATE_CHANGED, |
| mAdapterService.obfuscateAddress(bdDevice), 0, bdDevice.getType(), |
| BluetoothDevice.BOND_BONDING, |
| BluetoothProtoEnums.BOND_SUB_STATE_LOCAL_PIN_REQUESTED, 0); |
| |
| infoLog("pinRequestCallback: " + bdDevice |
| + " name:" + Utils.getName(bdDevice) + " cod:" + new BluetoothClass(cod)); |
| |
| Message msg = obtainMessage(PIN_REQUEST); |
| msg.obj = bdDevice; |
| msg.arg2 = min16Digits ? 1 : 0; // Use arg2 to pass the min16Digit boolean |
| |
| sendMessage(msg); |
| } |
| |
| /* |
| * Check whether has the specific message in message queue |
| */ |
| @VisibleForTesting |
| public boolean hasMessage(int what) { |
| return hasMessages(what); |
| } |
| |
| /* |
| * Remove the specific message from message queue |
| */ |
| @VisibleForTesting |
| public void removeMessage(int what) { |
| removeMessages(what); |
| } |
| |
| @RequiresPermission(allOf = { |
| android.Manifest.permission.BLUETOOTH_PRIVILEGED, |
| android.Manifest.permission.MODIFY_PHONE_STATE, |
| }) |
| private void clearProfilePriority(BluetoothDevice device) { |
| HidHostService hidService = HidHostService.getHidHostService(); |
| A2dpService a2dpService = A2dpService.getA2dpService(); |
| HeadsetService headsetService = HeadsetService.getHeadsetService(); |
| HeadsetClientService headsetClientService = HeadsetClientService.getHeadsetClientService(); |
| A2dpSinkService a2dpSinkService = A2dpSinkService.getA2dpSinkService(); |
| PbapClientService pbapClientService = PbapClientService.getPbapClientService(); |
| |
| if (hidService != null) { |
| hidService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| } |
| if (a2dpService != null) { |
| a2dpService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| } |
| if (headsetService != null) { |
| headsetService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| } |
| if (headsetClientService != null) { |
| headsetClientService.setConnectionPolicy(device, |
| BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| } |
| if (a2dpSinkService != null) { |
| a2dpSinkService.setConnectionPolicy(device, BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| } |
| if (pbapClientService != null) { |
| pbapClientService.setConnectionPolicy(device, |
| BluetoothProfile.CONNECTION_POLICY_UNKNOWN); |
| } |
| } |
| |
| private String state2str(int state) { |
| if (state == BluetoothDevice.BOND_NONE) { |
| return "BOND_NONE"; |
| } else if (state == BluetoothDevice.BOND_BONDING) { |
| return "BOND_BONDING"; |
| } else if (state == BluetoothDevice.BOND_BONDED) { |
| return "BOND_BONDED"; |
| } else return "UNKNOWN(" + state + ")"; |
| } |
| |
| private void infoLog(String msg) { |
| Log.i(TAG, msg); |
| } |
| |
| private void errorLog(String msg) { |
| Log.e(TAG, msg); |
| } |
| |
| private void warnLog(String msg) { |
| Log.w(TAG, msg); |
| } |
| |
| private int getUnbondReasonFromHALCode(int reason) { |
| if (reason == AbstractionLayer.BT_STATUS_SUCCESS) { |
| return BluetoothDevice.BOND_SUCCESS; |
| } else if (reason == AbstractionLayer.BT_STATUS_RMT_DEV_DOWN) { |
| return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN; |
| } else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE) { |
| return BluetoothDevice.UNBOND_REASON_AUTH_FAILED; |
| } else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED) { |
| return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED; |
| } else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT) { |
| return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT; |
| } |
| |
| /* default */ |
| return BluetoothDevice.UNBOND_REASON_REMOVED; |
| } |
| } |