| /* |
| * Copyright (C) 2008 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 android.server; |
| |
| import android.bluetooth.BluetoothA2dp; |
| import android.bluetooth.BluetoothAdapter; |
| import android.bluetooth.BluetoothClass; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothInputDevice; |
| import android.bluetooth.BluetoothPan; |
| import android.bluetooth.BluetoothUuid; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.ParcelUuid; |
| import android.util.Log; |
| |
| import java.util.HashMap; |
| import java.util.Set; |
| import android.os.PowerManager; |
| |
| |
| /** |
| * TODO: Move this to |
| * java/services/com/android/server/BluetoothEventLoop.java |
| * and make the contructor package private again. |
| * |
| * @hide |
| */ |
| class BluetoothEventLoop { |
| private static final String TAG = "BluetoothEventLoop"; |
| private static final boolean DBG = false; |
| |
| private int mNativeData; |
| private Thread mThread; |
| private boolean mStarted; |
| private boolean mInterrupted; |
| |
| private final HashMap<String, Integer> mPasskeyAgentRequestData; |
| private final BluetoothService mBluetoothService; |
| private final BluetoothAdapter mAdapter; |
| private final Context mContext; |
| // The WakeLock is used for bringing up the LCD during a pairing request |
| // from remote device when Android is in Suspend state. |
| private PowerManager.WakeLock mWakeLock; |
| |
| private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1; |
| private static final int EVENT_RESTART_BLUETOOTH = 2; |
| private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 3; |
| private static final int EVENT_AGENT_CANCEL = 4; |
| |
| private static final int CREATE_DEVICE_ALREADY_EXISTS = 1; |
| private static final int CREATE_DEVICE_SUCCESS = 0; |
| private static final int CREATE_DEVICE_FAILED = -1; |
| |
| // The time (in millisecs) to delay the pairing attempt after the first |
| // auto pairing attempt fails. We use an exponential delay with |
| // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and |
| // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value. |
| private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000; |
| private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000; |
| |
| private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN; |
| private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH; |
| |
| private final Handler mHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| String address = null; |
| switch (msg.what) { |
| case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: |
| address = (String)msg.obj; |
| if (address != null) { |
| mBluetoothService.createBond(address); |
| return; |
| } |
| break; |
| case EVENT_RESTART_BLUETOOTH: |
| mBluetoothService.restart(); |
| break; |
| case EVENT_PAIRING_CONSENT_DELAYED_ACCEPT: |
| address = (String)msg.obj; |
| if (address != null) { |
| mBluetoothService.setPairingConfirmation(address, true); |
| } |
| break; |
| case EVENT_AGENT_CANCEL: |
| // Set the Bond State to BOND_NONE. |
| // We always have only 1 device in BONDING state. |
| String[] devices = |
| mBluetoothService.getBondState().listInState(BluetoothDevice.BOND_BONDING); |
| if (devices.length == 0) { |
| break; |
| } else if (devices.length > 1) { |
| Log.e(TAG, " There is more than one device in the Bonding State"); |
| break; |
| } |
| address = devices[0]; |
| mBluetoothService.getBondState().setBondState(address, |
| BluetoothDevice.BOND_NONE, |
| BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED); |
| break; |
| } |
| } |
| }; |
| |
| static { classInitNative(); } |
| private static native void classInitNative(); |
| |
| /* pacakge */ BluetoothEventLoop(Context context, BluetoothAdapter adapter, |
| BluetoothService bluetoothService) { |
| mBluetoothService = bluetoothService; |
| mContext = context; |
| mPasskeyAgentRequestData = new HashMap(); |
| mAdapter = adapter; |
| //WakeLock instantiation in BluetoothEventLoop class |
| PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); |
| mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP |
| | PowerManager.ON_AFTER_RELEASE, TAG); |
| mWakeLock.setReferenceCounted(false); |
| initializeNativeDataNative(); |
| } |
| |
| protected void finalize() throws Throwable { |
| try { |
| cleanupNativeDataNative(); |
| } finally { |
| super.finalize(); |
| } |
| } |
| |
| /* package */ HashMap<String, Integer> getPasskeyAgentRequestData() { |
| return mPasskeyAgentRequestData; |
| } |
| |
| /* package */ void start() { |
| |
| if (!isEventLoopRunningNative()) { |
| if (DBG) log("Starting Event Loop thread"); |
| startEventLoopNative(); |
| } |
| } |
| |
| public void stop() { |
| if (isEventLoopRunningNative()) { |
| if (DBG) log("Stopping Event Loop thread"); |
| stopEventLoopNative(); |
| } |
| } |
| |
| public boolean isEventLoopRunning() { |
| return isEventLoopRunningNative(); |
| } |
| |
| private void addDevice(String address, String[] properties) { |
| mBluetoothService.addRemoteDeviceProperties(address, properties); |
| String rssi = mBluetoothService.getRemoteDeviceProperty(address, "RSSI"); |
| String classValue = mBluetoothService.getRemoteDeviceProperty(address, "Class"); |
| String name = mBluetoothService.getRemoteDeviceProperty(address, "Name"); |
| short rssiValue; |
| // For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE. |
| // If we accept the pairing, we will automatically show it at the top of the list. |
| if (rssi != null) { |
| rssiValue = (short)Integer.valueOf(rssi).intValue(); |
| } else { |
| rssiValue = Short.MIN_VALUE; |
| } |
| if (classValue != null) { |
| Intent intent = new Intent(BluetoothDevice.ACTION_FOUND); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); |
| intent.putExtra(BluetoothDevice.EXTRA_CLASS, |
| new BluetoothClass(Integer.valueOf(classValue))); |
| intent.putExtra(BluetoothDevice.EXTRA_RSSI, rssiValue); |
| intent.putExtra(BluetoothDevice.EXTRA_NAME, name); |
| |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } else { |
| log ("ClassValue: " + classValue + " for remote device: " + address + " is null"); |
| } |
| } |
| |
| private void onDeviceFound(String address, String[] properties) { |
| if (properties == null) { |
| Log.e(TAG, "ERROR: Remote device properties are null"); |
| return; |
| } |
| addDevice(address, properties); |
| } |
| |
| private void onDeviceDisappeared(String address) { |
| Intent intent = new Intent(BluetoothDevice.ACTION_DISAPPEARED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| |
| private void onDeviceDisconnectRequested(String deviceObjectPath) { |
| String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); |
| if (address == null) { |
| Log.e(TAG, "onDeviceDisconnectRequested: Address of the remote device in null"); |
| return; |
| } |
| Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| |
| private void onCreatePairedDeviceResult(String address, int result) { |
| address = address.toUpperCase(); |
| if (result == BluetoothDevice.BOND_SUCCESS) { |
| mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); |
| if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { |
| mBluetoothService.getBondState().clearPinAttempts(address); |
| } |
| } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED && |
| mBluetoothService.getBondState().getAttempt(address) == 1) { |
| mBluetoothService.getBondState().addAutoPairingFailure(address); |
| pairingAttempt(address, result); |
| } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN && |
| mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { |
| pairingAttempt(address, result); |
| } else { |
| mBluetoothService.getBondState().setBondState(address, |
| BluetoothDevice.BOND_NONE, result); |
| if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) { |
| mBluetoothService.getBondState().clearPinAttempts(address); |
| } |
| } |
| } |
| |
| private void pairingAttempt(String address, int result) { |
| // This happens when our initial guess of "0000" as the pass key |
| // fails. Try to create the bond again and display the pin dialog |
| // to the user. Use back-off while posting the delayed |
| // message. The initial value is |
| // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is |
| // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is |
| // reached, display an error to the user. |
| int attempt = mBluetoothService.getBondState().getAttempt(address); |
| if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY > |
| MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) { |
| mBluetoothService.getBondState().clearPinAttempts(address); |
| mBluetoothService.getBondState().setBondState(address, |
| BluetoothDevice.BOND_NONE, result); |
| return; |
| } |
| |
| Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); |
| message.obj = address; |
| boolean postResult = mHandler.sendMessageDelayed(message, |
| attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY); |
| if (!postResult) { |
| mBluetoothService.getBondState().clearPinAttempts(address); |
| mBluetoothService.getBondState().setBondState(address, |
| BluetoothDevice.BOND_NONE, result); |
| return; |
| } |
| mBluetoothService.getBondState().attempt(address); |
| } |
| |
| private void onDeviceCreated(String deviceObjectPath) { |
| String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); |
| if (!mBluetoothService.isRemoteDeviceInCache(address)) { |
| // Incoming connection, we haven't seen this device, add to cache. |
| String[] properties = mBluetoothService.getRemoteDeviceProperties(address); |
| if (properties != null) { |
| addDevice(address, properties); |
| } |
| } |
| return; |
| } |
| |
| private void onDeviceRemoved(String deviceObjectPath) { |
| String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); |
| if (address != null) { |
| mBluetoothService.getBondState().setBondState(address.toUpperCase(), |
| BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED); |
| mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null); |
| } |
| } |
| |
| /*package*/ void onPropertyChanged(String[] propValues) { |
| if (mBluetoothService.isAdapterPropertiesEmpty()) { |
| // We have got a property change before |
| // we filled up our cache. |
| mBluetoothService.getAllProperties(); |
| } |
| String name = propValues[0]; |
| if (name.equals("Name")) { |
| Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED); |
| intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| mBluetoothService.setProperty(name, propValues[1]); |
| } else if (name.equals("Pairable") || name.equals("Discoverable")) { |
| String pairable = name.equals("Pairable") ? propValues[1] : |
| mBluetoothService.getPropertyInternal("Pairable"); |
| String discoverable = name.equals("Discoverable") ? propValues[1] : |
| mBluetoothService.getPropertyInternal("Discoverable"); |
| |
| // This shouldn't happen, unless Adapter Properties are null. |
| if (pairable == null || discoverable == null) |
| return; |
| |
| int mode = BluetoothService.bluezStringToScanMode( |
| pairable.equals("true"), |
| discoverable.equals("true")); |
| if (mode >= 0) { |
| Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED); |
| intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mode); |
| intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| mBluetoothService.setProperty(name, propValues[1]); |
| } else if (name.equals("Discovering")) { |
| Intent intent; |
| if (propValues[1].equals("true")) { |
| mBluetoothService.setIsDiscovering(true); |
| intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED); |
| } else { |
| // Stop the discovery. |
| mBluetoothService.cancelDiscovery(); |
| mBluetoothService.setIsDiscovering(false); |
| intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED); |
| } |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| mBluetoothService.setProperty(name, propValues[1]); |
| } else if (name.equals("Devices")) { |
| String value = null; |
| int len = Integer.valueOf(propValues[1]); |
| if (len > 0) { |
| StringBuilder str = new StringBuilder(); |
| for (int i = 2; i < propValues.length; i++) { |
| str.append(propValues[i]); |
| str.append(","); |
| } |
| value = str.toString(); |
| } |
| mBluetoothService.setProperty(name, value); |
| } else if (name.equals("Powered")) { |
| // bluetoothd has restarted, re-read all our properties. |
| // Note: bluez only sends this property change when it restarts. |
| if (propValues[1].equals("true")) |
| onRestartRequired(); |
| } |
| } |
| |
| private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) { |
| String name = propValues[0]; |
| String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); |
| if (address == null) { |
| Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null"); |
| return; |
| } |
| if (DBG) { |
| log("Device property changed: " + address + " property: " |
| + name + " value: " + propValues[1]); |
| } |
| BluetoothDevice device = mAdapter.getRemoteDevice(address); |
| if (name.equals("Name")) { |
| Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); |
| } else if (name.equals("Class")) { |
| Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| intent.putExtra(BluetoothDevice.EXTRA_CLASS, |
| new BluetoothClass(Integer.valueOf(propValues[1]))); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); |
| } else if (name.equals("Connected")) { |
| Intent intent = null; |
| if (propValues[1].equals("true")) { |
| intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED); |
| // Set the link timeout to 8000 slots (5 sec timeout) |
| // for bluetooth docks. |
| if (mBluetoothService.isBluetoothDock(address)) { |
| mBluetoothService.setLinkTimeout(address, 8000); |
| } |
| } else { |
| intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED); |
| } |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); |
| } else if (name.equals("UUIDs")) { |
| String uuid = null; |
| int len = Integer.valueOf(propValues[1]); |
| if (len > 0) { |
| StringBuilder str = new StringBuilder(); |
| for (int i = 2; i < propValues.length; i++) { |
| str.append(propValues[i]); |
| str.append(","); |
| } |
| uuid = str.toString(); |
| } |
| mBluetoothService.setRemoteDeviceProperty(address, name, uuid); |
| |
| // UUIDs have changed, query remote service channel and update cache. |
| mBluetoothService.updateDeviceServiceChannelCache(address); |
| |
| mBluetoothService.sendUuidIntent(address); |
| } else if (name.equals("Paired")) { |
| if (propValues[1].equals("true")) { |
| mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED); |
| } else { |
| mBluetoothService.getBondState().setBondState(address, |
| BluetoothDevice.BOND_NONE); |
| mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false"); |
| } |
| } else if (name.equals("Trusted")) { |
| if (DBG) |
| log("set trust state succeded, value is " + propValues[1]); |
| mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]); |
| } |
| } |
| |
| private void onInputDevicePropertyChanged(String path, String[] propValues) { |
| String address = mBluetoothService.getAddressFromObjectPath(path); |
| if (address == null) { |
| Log.e(TAG, "onInputDevicePropertyChanged: Address of the remote device in null"); |
| return; |
| } |
| log(" Input Device : Name of Property is:" + propValues[0]); |
| boolean state = false; |
| if (propValues[1].equals("true")) { |
| state = true; |
| } |
| mBluetoothService.handleInputDevicePropertyChange(address, state); |
| } |
| |
| private void onPanDevicePropertyChanged(String deviceObjectPath, String[] propValues) { |
| String name = propValues[0]; |
| String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); |
| if (address == null) { |
| Log.e(TAG, "onPanDevicePropertyChanged: Address of the remote device in null"); |
| return; |
| } |
| if (DBG) { |
| log("Pan Device property changed: " + address + " property: " |
| + name + " value: "+ propValues[1]); |
| } |
| BluetoothDevice device = mAdapter.getRemoteDevice(address); |
| if (name.equals("Connected")) { |
| int state = propValues[1].equals("true") ? BluetoothInputDevice.STATE_CONNECTED : |
| BluetoothInputDevice.STATE_DISCONNECTED; |
| mBluetoothService.handlePanDeviceStateChange(device, state); |
| } |
| } |
| |
| private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) { |
| String address = mBluetoothService.getAddressFromObjectPath(objectPath); |
| if (address == null) { |
| Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " + |
| "returning null"); |
| return null; |
| } |
| address = address.toUpperCase(); |
| mPasskeyAgentRequestData.put(address, new Integer(nativeData)); |
| |
| if (mBluetoothService.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) { |
| // shutdown path |
| mBluetoothService.cancelPairingUserInput(address); |
| return null; |
| } |
| // Set state to BONDING. For incoming connections it will be set here. |
| // For outgoing connections, it gets set when we call createBond. |
| // Also set it only when the state is not already Bonded, we can sometimes |
| // get an authorization request from the remote end if it doesn't have the link key |
| // while we still have it. |
| if (mBluetoothService.getBondState().getBondState(address) != BluetoothDevice.BOND_BONDED) |
| mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDING); |
| return address; |
| } |
| |
| private void onRequestPairingConsent(String objectPath, int nativeData) { |
| String address = checkPairingRequestAndGetAddress(objectPath, nativeData); |
| if (address == null) return; |
| |
| /* The link key will not be stored if the incoming request has MITM |
| * protection switched on. Unfortunately, some devices have MITM |
| * switched on even though their capabilities are NoInputNoOutput, |
| * so we may get this request many times. Also if we respond immediately, |
| * the other end is unable to handle it. Delay sending the message. |
| */ |
| if (mBluetoothService.getBondState().getBondState(address) == BluetoothDevice.BOND_BONDED) { |
| Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT); |
| message.obj = address; |
| mHandler.sendMessageDelayed(message, 1500); |
| return; |
| } |
| // Acquire wakelock during PIN code request to bring up LCD display |
| mWakeLock.acquire(); |
| Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); |
| intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, |
| BluetoothDevice.PAIRING_VARIANT_CONSENT); |
| mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); |
| // Release wakelock to allow the LCD to go off after the PIN popup notifcation. |
| mWakeLock.release(); |
| return; |
| } |
| |
| private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) { |
| String address = checkPairingRequestAndGetAddress(objectPath, nativeData); |
| if (address == null) return; |
| // Acquire wakelock during PIN code request to bring up LCD display |
| mWakeLock.acquire(); |
| Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); |
| intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey); |
| intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, |
| BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION); |
| mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); |
| // Release wakelock to allow the LCD to go off after the PIN popup notifcation. |
| mWakeLock.release(); |
| return; |
| } |
| |
| private void onRequestPasskey(String objectPath, int nativeData) { |
| String address = checkPairingRequestAndGetAddress(objectPath, nativeData); |
| if (address == null) return; |
| // Acquire wakelock during PIN code request to bring up LCD display |
| mWakeLock.acquire(); |
| Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); |
| intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, |
| BluetoothDevice.PAIRING_VARIANT_PASSKEY); |
| mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); |
| // Release wakelock to allow the LCD to go off after the PIN popup notifcation. |
| mWakeLock.release(); |
| return; |
| } |
| |
| private void onRequestPinCode(String objectPath, int nativeData) { |
| String address = checkPairingRequestAndGetAddress(objectPath, nativeData); |
| if (address == null) return; |
| |
| String pendingOutgoingAddress = |
| mBluetoothService.getBondState().getPendingOutgoingBonding(); |
| if (address.equals(pendingOutgoingAddress)) { |
| // we initiated the bonding |
| |
| // Check if its a dock |
| if (mBluetoothService.isBluetoothDock(address)) { |
| String pin = mBluetoothService.getDockPin(); |
| mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes(pin)); |
| return; |
| } |
| |
| BluetoothClass btClass = new BluetoothClass(mBluetoothService.getRemoteClass(address)); |
| |
| // try 0000 once if the device looks dumb |
| switch (btClass.getDeviceClass()) { |
| case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET: |
| case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE: |
| case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES: |
| case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO: |
| case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO: |
| case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO: |
| if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) && |
| !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) { |
| mBluetoothService.getBondState().attempt(address); |
| mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000")); |
| return; |
| } |
| } |
| } |
| // Acquire wakelock during PIN code request to bring up LCD display |
| mWakeLock.acquire(); |
| Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); |
| intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN); |
| mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); |
| // Release wakelock to allow the LCD to go off after the PIN popup notifcation. |
| mWakeLock.release(); |
| return; |
| } |
| |
| private void onDisplayPasskey(String objectPath, int passkey, int nativeData) { |
| String address = checkPairingRequestAndGetAddress(objectPath, nativeData); |
| if (address == null) return; |
| |
| // Acquire wakelock during PIN code request to bring up LCD display |
| mWakeLock.acquire(); |
| Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST); |
| intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address)); |
| intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey); |
| intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, |
| BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY); |
| mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); |
| //Release wakelock to allow the LCD to go off after the PIN popup notifcation. |
| mWakeLock.release(); |
| } |
| |
| private boolean onAgentAuthorize(String objectPath, String deviceUuid) { |
| if (!mBluetoothService.isEnabled()) return false; |
| |
| String address = mBluetoothService.getAddressFromObjectPath(objectPath); |
| if (address == null) { |
| Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize"); |
| return false; |
| } |
| |
| boolean authorized = false; |
| ParcelUuid uuid = ParcelUuid.fromString(deviceUuid); |
| BluetoothDevice device = mAdapter.getRemoteDevice(address); |
| |
| // Bluez sends the UUID of the local service being accessed, _not_ the |
| // remote service |
| if ((BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid) |
| || BluetoothUuid.isAdvAudioDist(uuid)) && |
| !isOtherSinkInNonDisconnectingState(address)) { |
| BluetoothA2dp a2dp = new BluetoothA2dp(mContext); |
| |
| authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF; |
| if (authorized) { |
| Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address); |
| mBluetoothService.notifyIncomingA2dpConnection(address); |
| } else { |
| Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address); |
| } |
| } else if (BluetoothUuid.isInputDevice(uuid) && !isOtherInputDeviceConnected(address)) { |
| BluetoothInputDevice inputDevice = new BluetoothInputDevice(mContext); |
| authorized = inputDevice.getInputDevicePriority(device) > |
| BluetoothInputDevice.PRIORITY_OFF; |
| if (authorized) { |
| Log.i(TAG, "Allowing incoming HID connection from " + address); |
| } else { |
| Log.i(TAG, "Rejecting incoming HID connection from " + address); |
| } |
| } else if (BluetoothUuid.isBnep(uuid) || BluetoothUuid.isNap(uuid)){ |
| authorized = true; |
| } else { |
| Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address); |
| } |
| log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized); |
| return authorized; |
| } |
| |
| private boolean isOtherInputDeviceConnected(String address) { |
| Set<BluetoothDevice> devices = |
| mBluetoothService.lookupInputDevicesMatchingStates(new int[] { |
| BluetoothInputDevice.STATE_CONNECTING, |
| BluetoothInputDevice.STATE_CONNECTED}); |
| |
| for (BluetoothDevice device : devices) { |
| if (!device.getAddress().equals(address)) return true; |
| } |
| return false; |
| } |
| |
| private boolean isOtherSinkInNonDisconnectingState(String address) { |
| BluetoothA2dp a2dp = new BluetoothA2dp(mContext); |
| Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks(); |
| if (devices.size() == 0) return false; |
| for(BluetoothDevice dev: devices) { |
| if (!dev.getAddress().equals(address)) return true; |
| } |
| return false; |
| } |
| |
| private void onAgentCancel() { |
| Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL); |
| mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); |
| |
| mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_AGENT_CANCEL), |
| 1500); |
| |
| return; |
| } |
| |
| private void onDiscoverServicesResult(String deviceObjectPath, boolean result) { |
| String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath); |
| // We don't parse the xml here, instead just query Bluez for the properties. |
| if (result) { |
| mBluetoothService.updateRemoteDevicePropertiesCache(address); |
| } |
| mBluetoothService.sendUuidIntent(address); |
| mBluetoothService.makeServiceChannelCallbacks(address); |
| } |
| |
| private void onCreateDeviceResult(String address, int result) { |
| if (DBG) log("Result of onCreateDeviceResult:" + result); |
| |
| switch (result) { |
| case CREATE_DEVICE_ALREADY_EXISTS: |
| String path = mBluetoothService.getObjectPathFromAddress(address); |
| if (path != null) { |
| mBluetoothService.discoverServicesNative(path, ""); |
| break; |
| } |
| Log.w(TAG, "Device exists, but we dont have the bluez path, failing"); |
| // fall-through |
| case CREATE_DEVICE_FAILED: |
| mBluetoothService.sendUuidIntent(address); |
| mBluetoothService.makeServiceChannelCallbacks(address); |
| break; |
| case CREATE_DEVICE_SUCCESS: |
| // nothing to do, UUID intent's will be sent via property changed |
| } |
| } |
| |
| private void onInputDeviceConnectionResult(String path, boolean result) { |
| // Success case gets handled by Property Change signal |
| if (!result) { |
| String address = mBluetoothService.getAddressFromObjectPath(path); |
| if (address == null) return; |
| |
| boolean connected = false; |
| BluetoothDevice device = mAdapter.getRemoteDevice(address); |
| int state = mBluetoothService.getInputDeviceState(device); |
| if (state == BluetoothInputDevice.STATE_CONNECTING) { |
| connected = false; |
| } else if (state == BluetoothInputDevice.STATE_DISCONNECTING) { |
| connected = true; |
| } else { |
| Log.e(TAG, "Error onInputDeviceConnectionResult. State is:" + state); |
| } |
| mBluetoothService.handleInputDevicePropertyChange(address, connected); |
| } |
| } |
| |
| private void onPanDeviceConnectionResult(String path, boolean result) { |
| log ("onPanDeviceConnectionResult " + path + " " + result); |
| // Success case gets handled by Property Change signal |
| if (!result) { |
| String address = mBluetoothService.getAddressFromObjectPath(path); |
| if (address == null) return; |
| |
| boolean connected = false; |
| BluetoothDevice device = mAdapter.getRemoteDevice(address); |
| int state = mBluetoothService.getPanDeviceState(device); |
| if (state == BluetoothPan.STATE_CONNECTING) { |
| connected = false; |
| } else if (state == BluetoothPan.STATE_DISCONNECTING) { |
| connected = true; |
| } else { |
| Log.e(TAG, "Error onPanDeviceConnectionResult. State is: " |
| + state + " result: "+ result); |
| } |
| int newState = connected? BluetoothPan.STATE_CONNECTED : |
| BluetoothPan.STATE_DISCONNECTED; |
| mBluetoothService.handlePanDeviceStateChange(device, newState); |
| } |
| } |
| |
| private void onRestartRequired() { |
| if (mBluetoothService.isEnabled()) { |
| Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " + |
| "restarting Bluetooth ***"); |
| mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH); |
| } |
| } |
| |
| private static void log(String msg) { |
| Log.d(TAG, msg); |
| } |
| |
| private native void initializeNativeDataNative(); |
| private native void startEventLoopNative(); |
| private native void stopEventLoopNative(); |
| private native boolean isEventLoopRunningNative(); |
| private native void cleanupNativeDataNative(); |
| } |