From 9066cfe9886ac131c34d59ed0e2d287b0e3c0087 Mon Sep 17 00:00:00 2001
From: The Android Open Source Project <initial-contribution@android.com>
Date: Tue, 3 Mar 2009 19:31:44 -0800
Subject: auto import from //depot/cupcake/@135843

---
 core/java/android/server/BluetoothEventLoop.java | 373 +++++++++++++++++++++++
 1 file changed, 373 insertions(+)
 create mode 100644 core/java/android/server/BluetoothEventLoop.java

(limited to 'core/java/android/server/BluetoothEventLoop.java')

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);
+    }
+}
-- 
cgit v1.1