Home | History | Annotate | Download | only in server
      1 /*
      2  * Copyright (C) 2008 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.server;
     18 
     19 import android.bluetooth.BluetoothA2dp;
     20 import android.bluetooth.BluetoothAdapter;
     21 import android.bluetooth.BluetoothClass;
     22 import android.bluetooth.BluetoothDevice;
     23 import android.bluetooth.BluetoothUuid;
     24 import android.content.Context;
     25 import android.content.Intent;
     26 import android.os.Handler;
     27 import android.os.Message;
     28 import android.os.ParcelUuid;
     29 import android.util.Log;
     30 
     31 import java.util.HashMap;
     32 import java.util.Set;
     33 
     34 /**
     35  * TODO: Move this to
     36  * java/services/com/android/server/BluetoothEventLoop.java
     37  * and make the contructor package private again.
     38  *
     39  * @hide
     40  */
     41 class BluetoothEventLoop {
     42     private static final String TAG = "BluetoothEventLoop";
     43     private static final boolean DBG = false;
     44 
     45     private int mNativeData;
     46     private Thread mThread;
     47     private boolean mStarted;
     48     private boolean mInterrupted;
     49 
     50     private final HashMap<String, Integer> mPasskeyAgentRequestData;
     51     private final BluetoothService mBluetoothService;
     52     private final BluetoothAdapter mAdapter;
     53     private final Context mContext;
     54 
     55     private static final int EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 1;
     56     private static final int EVENT_RESTART_BLUETOOTH = 2;
     57     private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 3;
     58     private static final int EVENT_AGENT_CANCEL = 4;
     59 
     60     private static final int CREATE_DEVICE_ALREADY_EXISTS = 1;
     61     private static final int CREATE_DEVICE_SUCCESS = 0;
     62     private static final int CREATE_DEVICE_FAILED = -1;
     63 
     64     // The time (in millisecs) to delay the pairing attempt after the first
     65     // auto pairing attempt fails. We use an exponential delay with
     66     // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the initial value and
     67     // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY as the max value.
     68     private static final long INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 3000;
     69     private static final long MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY = 12000;
     70 
     71     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
     72     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
     73 
     74     private final Handler mHandler = new Handler() {
     75         @Override
     76         public void handleMessage(Message msg) {
     77             String address = null;
     78             switch (msg.what) {
     79             case EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY:
     80                 address = (String)msg.obj;
     81                 if (address != null) {
     82                     mBluetoothService.createBond(address);
     83                     return;
     84                 }
     85                 break;
     86             case EVENT_RESTART_BLUETOOTH:
     87                 mBluetoothService.restart();
     88                 break;
     89             case EVENT_PAIRING_CONSENT_DELAYED_ACCEPT:
     90                 address = (String)msg.obj;
     91                 if (address != null) {
     92                     mBluetoothService.setPairingConfirmation(address, true);
     93                 }
     94                 break;
     95             case EVENT_AGENT_CANCEL:
     96                 // Set the Bond State to BOND_NONE.
     97                 // We always have only 1 device in BONDING state.
     98                 String[] devices =
     99                     mBluetoothService.getBondState().listInState(BluetoothDevice.BOND_BONDING);
    100                 if (devices.length == 0) {
    101                     break;
    102                 } else if (devices.length > 1) {
    103                     Log.e(TAG, " There is more than one device in the Bonding State");
    104                     break;
    105                 }
    106                 address = devices[0];
    107                 mBluetoothService.getBondState().setBondState(address,
    108                         BluetoothDevice.BOND_NONE,
    109                         BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED);
    110                 break;
    111             }
    112         }
    113     };
    114 
    115     static { classInitNative(); }
    116     private static native void classInitNative();
    117 
    118     /* pacakge */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
    119             BluetoothService bluetoothService) {
    120         mBluetoothService = bluetoothService;
    121         mContext = context;
    122         mPasskeyAgentRequestData = new HashMap();
    123         mAdapter = adapter;
    124         initializeNativeDataNative();
    125     }
    126 
    127     protected void finalize() throws Throwable {
    128         try {
    129             cleanupNativeDataNative();
    130         } finally {
    131             super.finalize();
    132         }
    133     }
    134 
    135     /* package */ HashMap<String, Integer> getPasskeyAgentRequestData() {
    136         return mPasskeyAgentRequestData;
    137     }
    138 
    139     /* package */ void start() {
    140 
    141         if (!isEventLoopRunningNative()) {
    142             if (DBG) log("Starting Event Loop thread");
    143             startEventLoopNative();
    144         }
    145     }
    146 
    147     public void stop() {
    148         if (isEventLoopRunningNative()) {
    149             if (DBG) log("Stopping Event Loop thread");
    150             stopEventLoopNative();
    151         }
    152     }
    153 
    154     public boolean isEventLoopRunning() {
    155         return isEventLoopRunningNative();
    156     }
    157 
    158     private void addDevice(String address, String[] properties) {
    159         mBluetoothService.addRemoteDeviceProperties(address, properties);
    160         String rssi = mBluetoothService.getRemoteDeviceProperty(address, "RSSI");
    161         String classValue = mBluetoothService.getRemoteDeviceProperty(address, "Class");
    162         String name = mBluetoothService.getRemoteDeviceProperty(address, "Name");
    163         short rssiValue;
    164         // For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE.
    165         // If we accept the pairing, we will automatically show it at the top of the list.
    166         if (rssi != null) {
    167             rssiValue = (short)Integer.valueOf(rssi).intValue();
    168         } else {
    169             rssiValue = Short.MIN_VALUE;
    170         }
    171         if (classValue != null) {
    172             Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
    173             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    174             intent.putExtra(BluetoothDevice.EXTRA_CLASS,
    175                     new BluetoothClass(Integer.valueOf(classValue)));
    176             intent.putExtra(BluetoothDevice.EXTRA_RSSI, rssiValue);
    177             intent.putExtra(BluetoothDevice.EXTRA_NAME, name);
    178 
    179             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    180         } else {
    181             log ("ClassValue: " + classValue + " for remote device: " + address + " is null");
    182         }
    183     }
    184 
    185     private void onDeviceFound(String address, String[] properties) {
    186         if (properties == null) {
    187             Log.e(TAG, "ERROR: Remote device properties are null");
    188             return;
    189         }
    190         addDevice(address, properties);
    191     }
    192 
    193     private void onDeviceDisappeared(String address) {
    194         Intent intent = new Intent(BluetoothDevice.ACTION_DISAPPEARED);
    195         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    196         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    197     }
    198 
    199     private void onDeviceDisconnectRequested(String deviceObjectPath) {
    200         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    201         if (address == null) {
    202             Log.e(TAG, "onDeviceDisconnectRequested: Address of the remote device in null");
    203             return;
    204         }
    205         Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
    206         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    207         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    208     }
    209 
    210     private void onCreatePairedDeviceResult(String address, int result) {
    211         address = address.toUpperCase();
    212         if (result == BluetoothDevice.BOND_SUCCESS) {
    213             mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
    214             if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
    215                 mBluetoothService.getBondState().clearPinAttempts(address);
    216             }
    217         } else if (result == BluetoothDevice.UNBOND_REASON_AUTH_FAILED &&
    218                 mBluetoothService.getBondState().getAttempt(address) == 1) {
    219             mBluetoothService.getBondState().addAutoPairingFailure(address);
    220             pairingAttempt(address, result);
    221         } else if (result == BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN &&
    222                 mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
    223             pairingAttempt(address, result);
    224         } else {
    225             mBluetoothService.getBondState().setBondState(address,
    226                                                           BluetoothDevice.BOND_NONE, result);
    227             if (mBluetoothService.getBondState().isAutoPairingAttemptsInProgress(address)) {
    228                 mBluetoothService.getBondState().clearPinAttempts(address);
    229             }
    230         }
    231     }
    232 
    233     private void pairingAttempt(String address, int result) {
    234         // This happens when our initial guess of "0000" as the pass key
    235         // fails. Try to create the bond again and display the pin dialog
    236         // to the user. Use back-off while posting the delayed
    237         // message. The initial value is
    238         // INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY and the max value is
    239         // MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY. If the max value is
    240         // reached, display an error to the user.
    241         int attempt = mBluetoothService.getBondState().getAttempt(address);
    242         if (attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY >
    243                     MAX_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY) {
    244             mBluetoothService.getBondState().clearPinAttempts(address);
    245             mBluetoothService.getBondState().setBondState(address,
    246                     BluetoothDevice.BOND_NONE, result);
    247             return;
    248         }
    249 
    250         Message message = mHandler.obtainMessage(EVENT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
    251         message.obj = address;
    252         boolean postResult =  mHandler.sendMessageDelayed(message,
    253                                         attempt * INIT_AUTO_PAIRING_FAILURE_ATTEMPT_DELAY);
    254         if (!postResult) {
    255             mBluetoothService.getBondState().clearPinAttempts(address);
    256             mBluetoothService.getBondState().setBondState(address,
    257                     BluetoothDevice.BOND_NONE, result);
    258             return;
    259         }
    260         mBluetoothService.getBondState().attempt(address);
    261     }
    262 
    263     private void onDeviceCreated(String deviceObjectPath) {
    264         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    265         if (!mBluetoothService.isRemoteDeviceInCache(address)) {
    266             // Incoming connection, we haven't seen this device, add to cache.
    267             String[] properties = mBluetoothService.getRemoteDeviceProperties(address);
    268             if (properties != null) {
    269                 addDevice(address, properties);
    270             }
    271         }
    272         return;
    273     }
    274 
    275     private void onDeviceRemoved(String deviceObjectPath) {
    276         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    277         if (address != null) {
    278             mBluetoothService.getBondState().setBondState(address.toUpperCase(),
    279                     BluetoothDevice.BOND_NONE, BluetoothDevice.UNBOND_REASON_REMOVED);
    280             mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null);
    281         }
    282     }
    283 
    284     /*package*/ void onPropertyChanged(String[] propValues) {
    285         if (mBluetoothService.isAdapterPropertiesEmpty()) {
    286             // We have got a property change before
    287             // we filled up our cache.
    288             mBluetoothService.getAllProperties();
    289         }
    290         String name = propValues[0];
    291         if (name.equals("Name")) {
    292             Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
    293             intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]);
    294             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    295             mBluetoothService.setProperty(name, propValues[1]);
    296         } else if (name.equals("Pairable") || name.equals("Discoverable")) {
    297             String pairable = name.equals("Pairable") ? propValues[1] :
    298                 mBluetoothService.getPropertyInternal("Pairable");
    299             String discoverable = name.equals("Discoverable") ? propValues[1] :
    300                 mBluetoothService.getPropertyInternal("Discoverable");
    301 
    302             // This shouldn't happen, unless Adapter Properties are null.
    303             if (pairable == null || discoverable == null)
    304                 return;
    305 
    306             int mode = BluetoothService.bluezStringToScanMode(
    307                     pairable.equals("true"),
    308                     discoverable.equals("true"));
    309             if (mode >= 0) {
    310                 Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
    311                 intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mode);
    312                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    313                 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    314             }
    315             mBluetoothService.setProperty(name, propValues[1]);
    316         } else if (name.equals("Discovering")) {
    317             Intent intent;
    318             if (propValues[1].equals("true")) {
    319                 mBluetoothService.setIsDiscovering(true);
    320                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
    321             } else {
    322                 // Stop the discovery.
    323                 mBluetoothService.cancelDiscovery();
    324                 mBluetoothService.setIsDiscovering(false);
    325                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    326             }
    327             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    328             mBluetoothService.setProperty(name, propValues[1]);
    329         } else if (name.equals("Devices")) {
    330             String value = null;
    331             int len = Integer.valueOf(propValues[1]);
    332             if (len > 0) {
    333                 StringBuilder str = new StringBuilder();
    334                 for (int i = 2; i < propValues.length; i++) {
    335                     str.append(propValues[i]);
    336                     str.append(",");
    337                 }
    338                 value = str.toString();
    339             }
    340             mBluetoothService.setProperty(name, value);
    341         } else if (name.equals("Powered")) {
    342             // bluetoothd has restarted, re-read all our properties.
    343             // Note: bluez only sends this property change when it restarts.
    344             if (propValues[1].equals("true"))
    345                 onRestartRequired();
    346         }
    347     }
    348 
    349     private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
    350         String name = propValues[0];
    351         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    352         if (address == null) {
    353             Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null");
    354             return;
    355         }
    356         if (DBG) {
    357             log("Device property changed:" + address + "property:" + name);
    358         }
    359         BluetoothDevice device = mAdapter.getRemoteDevice(address);
    360         if (name.equals("Name")) {
    361             Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);
    362             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    363             intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]);
    364             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    365             mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
    366         } else if (name.equals("Class")) {
    367             Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);
    368             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    369             intent.putExtra(BluetoothDevice.EXTRA_CLASS,
    370                     new BluetoothClass(Integer.valueOf(propValues[1])));
    371             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    372             mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
    373         } else if (name.equals("Connected")) {
    374             Intent intent = null;
    375             if (propValues[1].equals("true")) {
    376                 intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
    377                 // Set the link timeout to 8000 slots (5 sec timeout)
    378                 // for bluetooth docks.
    379                 if (mBluetoothService.isBluetoothDock(address)) {
    380                     mBluetoothService.setLinkTimeout(address, 8000);
    381                 }
    382             } else {
    383                 intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
    384             }
    385             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    386             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    387             mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
    388         } else if (name.equals("UUIDs")) {
    389             String uuid = null;
    390             int len = Integer.valueOf(propValues[1]);
    391             if (len > 0) {
    392                 StringBuilder str = new StringBuilder();
    393                 for (int i = 2; i < propValues.length; i++) {
    394                     str.append(propValues[i]);
    395                     str.append(",");
    396                 }
    397                 uuid = str.toString();
    398             }
    399             mBluetoothService.setRemoteDeviceProperty(address, name, uuid);
    400 
    401             // UUIDs have changed, query remote service channel and update cache.
    402             mBluetoothService.updateDeviceServiceChannelCache(address);
    403 
    404             mBluetoothService.sendUuidIntent(address);
    405         } else if (name.equals("Paired")) {
    406             if (propValues[1].equals("true")) {
    407                 mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDED);
    408             } else {
    409                 mBluetoothService.getBondState().setBondState(address,
    410                         BluetoothDevice.BOND_NONE);
    411                 mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false");
    412             }
    413         } else if (name.equals("Trusted")) {
    414             if (DBG)
    415                 log("set trust state succeded, value is  " + propValues[1]);
    416             mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
    417         }
    418     }
    419 
    420     private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) {
    421         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
    422         if (address == null) {
    423             Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " +
    424                   "returning null");
    425             return null;
    426         }
    427         address = address.toUpperCase();
    428         mPasskeyAgentRequestData.put(address, new Integer(nativeData));
    429 
    430         if (mBluetoothService.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) {
    431             // shutdown path
    432             mBluetoothService.cancelPairingUserInput(address);
    433             return null;
    434         }
    435         // Set state to BONDING. For incoming connections it will be set here.
    436         // For outgoing connections, it gets set when we call createBond.
    437         // Also set it only when the state is not already Bonded, we can sometimes
    438         // get an authorization request from the remote end if it doesn't have the link key
    439         // while we still have it.
    440         if (mBluetoothService.getBondState().getBondState(address) != BluetoothDevice.BOND_BONDED)
    441             mBluetoothService.getBondState().setBondState(address, BluetoothDevice.BOND_BONDING);
    442         return address;
    443     }
    444 
    445     private void onRequestPairingConsent(String objectPath, int nativeData) {
    446         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
    447         if (address == null) return;
    448 
    449         /* The link key will not be stored if the incoming request has MITM
    450          * protection switched on. Unfortunately, some devices have MITM
    451          * switched on even though their capabilities are NoInputNoOutput,
    452          * so we may get this request many times. Also if we respond immediately,
    453          * the other end is unable to handle it. Delay sending the message.
    454          */
    455         if (mBluetoothService.getBondState().getBondState(address) == BluetoothDevice.BOND_BONDED) {
    456             Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT);
    457             message.obj = address;
    458             mHandler.sendMessageDelayed(message, 1500);
    459             return;
    460         }
    461 
    462         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    463         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    464         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
    465                         BluetoothDevice.PAIRING_VARIANT_CONSENT);
    466         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    467         return;
    468     }
    469 
    470     private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) {
    471         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
    472         if (address == null) return;
    473 
    474         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    475         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    476         intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey);
    477         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
    478                 BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION);
    479         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    480         return;
    481     }
    482 
    483     private void onRequestPasskey(String objectPath, int nativeData) {
    484         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
    485         if (address == null) return;
    486 
    487         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    488         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    489         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
    490                 BluetoothDevice.PAIRING_VARIANT_PASSKEY);
    491         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    492         return;
    493     }
    494 
    495     private void onRequestPinCode(String objectPath, int nativeData) {
    496         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
    497         if (address == null) return;
    498 
    499         String pendingOutgoingAddress =
    500                 mBluetoothService.getBondState().getPendingOutgoingBonding();
    501         if (address.equals(pendingOutgoingAddress)) {
    502             // we initiated the bonding
    503 
    504             // Check if its a dock
    505             if (mBluetoothService.isBluetoothDock(address)) {
    506                 String pin = mBluetoothService.getDockPin();
    507                 mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes(pin));
    508                 return;
    509             }
    510 
    511             BluetoothClass btClass = new BluetoothClass(mBluetoothService.getRemoteClass(address));
    512 
    513             // try 0000 once if the device looks dumb
    514             switch (btClass.getDeviceClass()) {
    515             case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
    516             case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
    517             case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
    518             case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
    519             case BluetoothClass.Device.AUDIO_VIDEO_CAR_AUDIO:
    520             case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
    521                 if (!mBluetoothService.getBondState().hasAutoPairingFailed(address) &&
    522                     !mBluetoothService.getBondState().isAutoPairingBlacklisted(address)) {
    523                     mBluetoothService.getBondState().attempt(address);
    524                     mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
    525                     return;
    526                 }
    527            }
    528         }
    529         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    530         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    531         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN);
    532         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    533         return;
    534     }
    535 
    536     private void onDisplayPasskey(String objectPath, int passkey, int nativeData) {
    537         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
    538         if (address == null) return;
    539 
    540         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    541         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    542         intent.putExtra(BluetoothDevice.EXTRA_PASSKEY, passkey);
    543         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
    544                         BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY);
    545         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    546     }
    547 
    548     private boolean onAgentAuthorize(String objectPath, String deviceUuid) {
    549         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
    550         if (address == null) {
    551             Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize");
    552             return false;
    553         }
    554 
    555         boolean authorized = false;
    556         ParcelUuid uuid = ParcelUuid.fromString(deviceUuid);
    557         BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
    558 
    559         // Bluez sends the UUID of the local service being accessed, _not_ the
    560         // remote service
    561         if (mBluetoothService.isEnabled() &&
    562                 (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid)
    563                         || BluetoothUuid.isAdvAudioDist(uuid)) &&
    564                         !isOtherSinkInNonDisconnectingState(address)) {
    565             BluetoothDevice device = mAdapter.getRemoteDevice(address);
    566             authorized = a2dp.getSinkPriority(device) > BluetoothA2dp.PRIORITY_OFF;
    567             if (authorized) {
    568                 Log.i(TAG, "Allowing incoming A2DP / AVRCP connection from " + address);
    569             } else {
    570                 Log.i(TAG, "Rejecting incoming A2DP / AVRCP connection from " + address);
    571             }
    572         } else {
    573             Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
    574         }
    575         log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized);
    576         return authorized;
    577     }
    578 
    579     boolean isOtherSinkInNonDisconnectingState(String address) {
    580         BluetoothA2dp a2dp = new BluetoothA2dp(mContext);
    581         Set<BluetoothDevice> devices = a2dp.getNonDisconnectedSinks();
    582         if (devices.size() == 0) return false;
    583         for(BluetoothDevice dev: devices) {
    584             if (!dev.getAddress().equals(address)) return true;
    585         }
    586         return false;
    587     }
    588 
    589     private void onAgentCancel() {
    590         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL);
    591         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    592 
    593         mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_AGENT_CANCEL),
    594                    1500);
    595 
    596         return;
    597     }
    598 
    599     private void onDiscoverServicesResult(String deviceObjectPath, boolean result) {
    600         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    601         // We don't parse the xml here, instead just query Bluez for the properties.
    602         if (result) {
    603             mBluetoothService.updateRemoteDevicePropertiesCache(address);
    604         }
    605         mBluetoothService.sendUuidIntent(address);
    606         mBluetoothService.makeServiceChannelCallbacks(address);
    607     }
    608 
    609     private void onCreateDeviceResult(String address, int result) {
    610         if (DBG) log("Result of onCreateDeviceResult:" + result);
    611 
    612         switch (result) {
    613         case CREATE_DEVICE_ALREADY_EXISTS:
    614             String path = mBluetoothService.getObjectPathFromAddress(address);
    615             if (path != null) {
    616                 mBluetoothService.discoverServicesNative(path, "");
    617                 break;
    618             }
    619             Log.w(TAG, "Device exists, but we dont have the bluez path, failing");
    620             // fall-through
    621         case CREATE_DEVICE_FAILED:
    622             mBluetoothService.sendUuidIntent(address);
    623             mBluetoothService.makeServiceChannelCallbacks(address);
    624             break;
    625         case CREATE_DEVICE_SUCCESS:
    626             // nothing to do, UUID intent's will be sent via property changed
    627         }
    628     }
    629 
    630     private void onRestartRequired() {
    631         if (mBluetoothService.isEnabled()) {
    632             Log.e(TAG, "*** A serious error occured (did bluetoothd crash?) - " +
    633                        "restarting Bluetooth ***");
    634             mHandler.sendEmptyMessage(EVENT_RESTART_BLUETOOTH);
    635         }
    636     }
    637 
    638     private static void log(String msg) {
    639         Log.d(TAG, msg);
    640     }
    641 
    642     private native void initializeNativeDataNative();
    643     private native void startEventLoopNative();
    644     private native void stopEventLoopNative();
    645     private native boolean isEventLoopRunningNative();
    646     private native void cleanupNativeDataNative();
    647 }
    648