| /* |
| * 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.BluetoothClass; |
| import android.bluetooth.BluetoothDevice; |
| import android.bluetooth.BluetoothError; |
| import android.bluetooth.BluetoothIntent; |
| import android.bluetooth.IBluetoothDeviceCallback; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.RemoteException; |
| import android.util.Log; |
| |
| import java.util.HashMap; |
| |
| /** |
| * 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 HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks; |
| private final BluetoothDeviceService mBluetoothService; |
| private final Context mContext; |
| |
| private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1; |
| private static final int EVENT_RESTART_BLUETOOTH = 2; |
| |
| // 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) { |
| switch (msg.what) { |
| case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: |
| String address = (String)msg.obj; |
| if (address != null) { |
| mBluetoothService.createBond(address); |
| return; |
| } |
| break; |
| case EVENT_RESTART_BLUETOOTH: |
| mBluetoothService.restart(); |
| break; |
| } |
| } |
| }; |
| |
| static { classInitNative(); } |
| private static native void classInitNative(); |
| |
| /* pacakge */ BluetoothEventLoop(Context context, BluetoothDeviceService bluetoothService) { |
| mBluetoothService = bluetoothService; |
| mContext = context; |
| mPasskeyAgentRequestData = new HashMap(); |
| mGetRemoteServiceChannelCallbacks = new HashMap(); |
| initializeNativeDataNative(); |
| } |
| private native void initializeNativeDataNative(); |
| |
| protected void finalize() throws Throwable { |
| try { |
| cleanupNativeDataNative(); |
| } finally { |
| super.finalize(); |
| } |
| } |
| private native void cleanupNativeDataNative(); |
| |
| /* pacakge */ HashMap<String, IBluetoothDeviceCallback> getRemoteServiceChannelCallbacks() { |
| return mGetRemoteServiceChannelCallbacks; |
| } |
| |
| /* pacakge */ HashMap<String, Integer> getPasskeyAgentRequestData() { |
| return mPasskeyAgentRequestData; |
| } |
| |
| private native void startEventLoopNative(); |
| private native void stopEventLoopNative(); |
| private native boolean isEventLoopRunningNative(); |
| |
| /* 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(); |
| } |
| |
| /*package*/ void onModeChanged(String bluezMode) { |
| int mode = BluetoothDeviceService.bluezStringToScanMode(bluezMode); |
| if (mode >= 0) { |
| Intent intent = new Intent(BluetoothIntent.SCAN_MODE_CHANGED_ACTION); |
| intent.putExtra(BluetoothIntent.SCAN_MODE, mode); |
| intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| } |
| |
| private void onDiscoveryStarted() { |
| mBluetoothService.setIsDiscovering(true); |
| Intent intent = new Intent(BluetoothIntent.DISCOVERY_STARTED_ACTION); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| private void onDiscoveryCompleted() { |
| mBluetoothService.setIsDiscovering(false); |
| Intent intent = new Intent(BluetoothIntent.DISCOVERY_COMPLETED_ACTION); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| |
| private void onRemoteDeviceFound(String address, int deviceClass, short rssi) { |
| Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_FOUND_ACTION); |
| intent.putExtra(BluetoothIntent.ADDRESS, address); |
| intent.putExtra(BluetoothIntent.CLASS, deviceClass); |
| intent.putExtra(BluetoothIntent.RSSI, rssi); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| private void onRemoteDeviceDisappeared(String address) { |
| Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISAPPEARED_ACTION); |
| intent.putExtra(BluetoothIntent.ADDRESS, address); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| private void onRemoteClassUpdated(String address, int deviceClass) { |
| Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CLASS_UPDATED_ACTION); |
| intent.putExtra(BluetoothIntent.ADDRESS, address); |
| intent.putExtra(BluetoothIntent.CLASS, deviceClass); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| private void onRemoteDeviceConnected(String address) { |
| Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_CONNECTED_ACTION); |
| intent.putExtra(BluetoothIntent.ADDRESS, address); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| private void onRemoteDeviceDisconnectRequested(String address) { |
| Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECT_REQUESTED_ACTION); |
| intent.putExtra(BluetoothIntent.ADDRESS, address); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| private void onRemoteDeviceDisconnected(String address) { |
| Intent intent = new Intent(BluetoothIntent.REMOTE_DEVICE_DISCONNECTED_ACTION); |
| intent.putExtra(BluetoothIntent.ADDRESS, address); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| private void onRemoteNameUpdated(String address, String name) { |
| Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION); |
| intent.putExtra(BluetoothIntent.ADDRESS, address); |
| intent.putExtra(BluetoothIntent.NAME, name); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| private void onRemoteNameFailed(String address) { |
| Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_FAILED_ACTION); |
| intent.putExtra(BluetoothIntent.ADDRESS, address); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| private void onRemoteNameChanged(String address, String name) { |
| Intent intent = new Intent(BluetoothIntent.REMOTE_NAME_UPDATED_ACTION); |
| intent.putExtra(BluetoothIntent.ADDRESS, address); |
| intent.putExtra(BluetoothIntent.NAME, name); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| |
| private void onCreateBondingResult(String address, int result) { |
| address = address.toUpperCase(); |
| if (result == BluetoothError.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_NOT_BONDED, 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_NOT_BONDED, 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_NOT_BONDED, result); |
| return; |
| } |
| mBluetoothService.getBondState().attempt(address); |
| } |
| |
| private void onBondingCreated(String address) { |
| mBluetoothService.getBondState().setBondState(address.toUpperCase(), |
| BluetoothDevice.BOND_BONDED); |
| } |
| |
| private void onBondingRemoved(String address) { |
| mBluetoothService.getBondState().setBondState(address.toUpperCase(), |
| BluetoothDevice.BOND_NOT_BONDED, BluetoothDevice.UNBOND_REASON_REMOVED); |
| } |
| |
| private void onNameChanged(String name) { |
| Intent intent = new Intent(BluetoothIntent.NAME_CHANGED_ACTION); |
| intent.putExtra(BluetoothIntent.NAME, name); |
| mContext.sendBroadcast(intent, BLUETOOTH_PERM); |
| } |
| |
| private void onPasskeyAgentRequest(String address, int nativeData) { |
| address = address.toUpperCase(); |
| mPasskeyAgentRequestData.put(address, new Integer(nativeData)); |
| |
| if (mBluetoothService.getBluetoothState() == BluetoothDevice.BLUETOOTH_STATE_TURNING_OFF) { |
| // shutdown path |
| mBluetoothService.cancelPin(address); |
| return; |
| } |
| |
| if (mBluetoothService.getBondState().getBondState(address) == |
| BluetoothDevice.BOND_BONDING) { |
| // we initiated the bonding |
| int btClass = mBluetoothService.getRemoteClass(address); |
| |
| // try 0000 once if the device looks dumb |
| switch (BluetoothClass.Device.getDevice(btClass)) { |
| 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; |
| } |
| } |
| } |
| Intent intent = new Intent(BluetoothIntent.PAIRING_REQUEST_ACTION); |
| intent.putExtra(BluetoothIntent.ADDRESS, address); |
| mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); |
| } |
| |
| private void onPasskeyAgentCancel(String address) { |
| address = address.toUpperCase(); |
| mBluetoothService.cancelPin(address); |
| Intent intent = new Intent(BluetoothIntent.PAIRING_CANCEL_ACTION); |
| intent.putExtra(BluetoothIntent.ADDRESS, address); |
| mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM); |
| mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_NOT_BONDED, |
| BluetoothDevice.UNBOND_REASON_AUTH_CANCELED); |
| } |
| |
| private boolean onAuthAgentAuthorize(String address, String service, String uuid) { |
| boolean authorized = false; |
| if (mBluetoothService.isEnabled() && service.endsWith("service_audio")) { |
| BluetoothA2dp a2dp = new BluetoothA2dp(mContext); |
| authorized = a2dp.getSinkPriority(address) > BluetoothA2dp.PRIORITY_OFF; |
| if (authorized) { |
| Log.i(TAG, "Allowing incoming A2DP connection from " + address); |
| } else { |
| Log.i(TAG, "Rejecting incoming A2DP connection from " + address); |
| } |
| } else { |
| Log.i(TAG, "Rejecting incoming " + service + " connection from " + address); |
| } |
| return authorized; |
| } |
| |
| private void onAuthAgentCancel(String address, String service, String uuid) { |
| // We immediately response to DBUS Authorize() so this should not |
| // usually happen |
| log("onAuthAgentCancel(" + address + ", " + service + ", " + uuid + ")"); |
| } |
| |
| private void onGetRemoteServiceChannelResult(String address, int channel) { |
| IBluetoothDeviceCallback callback = mGetRemoteServiceChannelCallbacks.get(address); |
| if (callback != null) { |
| mGetRemoteServiceChannelCallbacks.remove(address); |
| try { |
| callback.onGetRemoteServiceChannelResult(address, channel); |
| } catch (RemoteException e) {} |
| } |
| } |
| |
| private void onRestartRequired() { |
| if (mBluetoothService.isEnabled()) { |
| Log.e(TAG, "*** A serious error occured (did hcid crash?) - restarting Bluetooth ***"); |
| mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH); |
| } |
| } |
| |
| private static void log(String msg) { |
| Log.d(TAG, msg); |
| } |
| } |