diff options
author | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
---|---|---|
committer | The Android Open Source Project <initial-contribution@android.com> | 2009-03-03 19:31:44 -0800 |
commit | 9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch) | |
tree | d88beb88001f2482911e3d28e43833b50e4b4e97 /core/java/android/server/BluetoothEventLoop.java | |
parent | d83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff) | |
download | frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.zip frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.gz frameworks_base-9066cfe9886ac131c34d59ed0e2d287b0e3c0087.tar.bz2 |
auto import from //depot/cupcake/@135843
Diffstat (limited to 'core/java/android/server/BluetoothEventLoop.java')
-rw-r--r-- | core/java/android/server/BluetoothEventLoop.java | 373 |
1 files changed, 373 insertions, 0 deletions
diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java new file mode 100644 index 0000000..8b09583 --- /dev/null +++ b/core/java/android/server/BluetoothEventLoop.java @@ -0,0 +1,373 @@ +/* + * 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 mInterrupted; + private HashMap<String, Integer> mPasskeyAgentRequestData; + private HashMap<String, IBluetoothDeviceCallback> mGetRemoteServiceChannelCallbacks; + private BluetoothDeviceService mBluetoothService; + private Context mContext; + + private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 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) { + switch (msg.what) { + case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY: + String address = (String)msg.obj; + if (address != null) { + mBluetoothService.createBond(address); + return; + } + 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 synchronized boolean waitForAndDispatchEvent(int timeout_ms) { + return waitForAndDispatchEventNative(timeout_ms); + } + private native boolean waitForAndDispatchEventNative(int timeout_ms); + + /* package */ synchronized void start() { + + if (mThread != null) { + // Already running. + return; + } + mThread = new Thread("Bluetooth Event Loop") { + @Override + public void run() { + try { + if (setUpEventLoopNative()) { + while (!mInterrupted) { + waitForAndDispatchEvent(0); + sleep(500); + } + tearDownEventLoopNative(); + } + } catch (InterruptedException e) { } + if (DBG) log("Event Loop thread finished"); + } + }; + if (DBG) log("Starting Event Loop thread"); + mInterrupted = false; + mThread.start(); + } + private native boolean setUpEventLoopNative(); + private native void tearDownEventLoopNative(); + + public synchronized void stop() { + if (mThread != null) { + mInterrupted = true; + try { + mThread.join(); + mThread = null; + } catch (InterruptedException e) { + Log.i(TAG, "Interrupted waiting for Event Loop thread to join"); + } + } + } + + public synchronized boolean isEventLoopRunning() { + return mThread != null; + } + + /*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); + 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.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(); + mPasskeyAgentRequestData.remove(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 (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 static void log(String msg) { + Log.d(TAG, msg); + } +} |