summaryrefslogtreecommitdiffstats
path: root/core/java/android/server/BluetoothEventLoop.java
diff options
context:
space:
mode:
authorThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
committerThe Android Open Source Project <initial-contribution@android.com>2009-03-03 19:31:44 -0800
commit9066cfe9886ac131c34d59ed0e2d287b0e3c0087 (patch)
treed88beb88001f2482911e3d28e43833b50e4b4e97 /core/java/android/server/BluetoothEventLoop.java
parentd83a98f4ce9cfa908f5c54bbd70f03eec07e7553 (diff)
downloadframeworks_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.java373
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);
+ }
+}