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.BluetoothHealth;
     24 import android.bluetooth.BluetoothInputDevice;
     25 import android.bluetooth.BluetoothPan;
     26 import android.bluetooth.BluetoothProfile;
     27 import android.bluetooth.BluetoothUuid;
     28 import android.content.Context;
     29 import android.content.Intent;
     30 import android.os.Handler;
     31 import android.os.Message;
     32 import android.os.ParcelUuid;
     33 import android.os.PowerManager;
     34 import android.util.Log;
     35 
     36 import java.util.HashMap;
     37 import java.util.List;
     38 
     39 
     40 /**
     41  * @hide
     42  */
     43 class BluetoothEventLoop {
     44     private static final String TAG = "BluetoothEventLoop";
     45     private static final boolean DBG = false;
     46 
     47     private int mNativeData;
     48     private Thread mThread;
     49     private boolean mStarted;
     50     private boolean mInterrupted;
     51 
     52     private final HashMap<String, Integer> mPasskeyAgentRequestData;
     53     private final HashMap<String, Integer> mAuthorizationAgentRequestData;
     54     private final BluetoothService mBluetoothService;
     55     private final BluetoothAdapter mAdapter;
     56     private final BluetoothAdapterStateMachine mBluetoothState;
     57     private BluetoothA2dp mA2dp;
     58     private final Context mContext;
     59     // The WakeLock is used for bringing up the LCD during a pairing request
     60     // from remote device when Android is in Suspend state.
     61     private PowerManager.WakeLock mWakeLock;
     62 
     63     private static final int EVENT_PAIRING_CONSENT_DELAYED_ACCEPT = 1;
     64     private static final int EVENT_AGENT_CANCEL = 2;
     65 
     66     private static final int CREATE_DEVICE_ALREADY_EXISTS = 1;
     67     private static final int CREATE_DEVICE_SUCCESS = 0;
     68     private static final int CREATE_DEVICE_FAILED = -1;
     69 
     70     private static final String BLUETOOTH_ADMIN_PERM = android.Manifest.permission.BLUETOOTH_ADMIN;
     71     private static final String BLUETOOTH_PERM = android.Manifest.permission.BLUETOOTH;
     72 
     73     private final Handler mHandler = new Handler() {
     74         @Override
     75         public void handleMessage(Message msg) {
     76             String address = null;
     77             switch (msg.what) {
     78             case EVENT_PAIRING_CONSENT_DELAYED_ACCEPT:
     79                 address = (String)msg.obj;
     80                 if (address != null) {
     81                     mBluetoothService.setPairingConfirmation(address, true);
     82                 }
     83                 break;
     84             case EVENT_AGENT_CANCEL:
     85                 // Set the Bond State to BOND_NONE.
     86                 // We always have only 1 device in BONDING state.
     87                 String[] devices = mBluetoothService.listInState(BluetoothDevice.BOND_BONDING);
     88                 if (devices.length == 0) {
     89                     break;
     90                 } else if (devices.length > 1) {
     91                     Log.e(TAG, " There is more than one device in the Bonding State");
     92                     break;
     93                 }
     94                 address = devices[0];
     95                 mBluetoothService.setBondState(address,
     96                         BluetoothDevice.BOND_NONE,
     97                         BluetoothDevice.UNBOND_REASON_REMOTE_AUTH_CANCELED);
     98                 break;
     99             }
    100         }
    101     };
    102 
    103     static { classInitNative(); }
    104     private static native void classInitNative();
    105 
    106     /* package */ BluetoothEventLoop(Context context, BluetoothAdapter adapter,
    107                                      BluetoothService bluetoothService,
    108                                      BluetoothAdapterStateMachine bluetoothState) {
    109         mBluetoothService = bluetoothService;
    110         mContext = context;
    111         mBluetoothState = bluetoothState;
    112         mPasskeyAgentRequestData = new HashMap<String, Integer>();
    113         mAuthorizationAgentRequestData = new HashMap<String, Integer>();
    114         mAdapter = adapter;
    115         //WakeLock instantiation in BluetoothEventLoop class
    116         PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
    117         mWakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
    118                 | PowerManager.ON_AFTER_RELEASE, TAG);
    119         mWakeLock.setReferenceCounted(false);
    120         initializeNativeDataNative();
    121     }
    122 
    123     /*package*/ void getProfileProxy() {
    124         mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.A2DP);
    125         mAdapter.getProfileProxy(mContext, mProfileServiceListener, BluetoothProfile.INPUT_DEVICE);
    126     }
    127 
    128     private BluetoothProfile.ServiceListener mProfileServiceListener =
    129         new BluetoothProfile.ServiceListener() {
    130         public void onServiceConnected(int profile, BluetoothProfile proxy) {
    131             if (profile == BluetoothProfile.A2DP) {
    132                 mA2dp = (BluetoothA2dp) proxy;
    133             }
    134         }
    135         public void onServiceDisconnected(int profile) {
    136             if (profile == BluetoothProfile.A2DP) {
    137                 mA2dp = null;
    138             }
    139         }
    140     };
    141 
    142 
    143     protected void finalize() throws Throwable {
    144         try {
    145             cleanupNativeDataNative();
    146         } finally {
    147             super.finalize();
    148         }
    149     }
    150 
    151     /* package */ HashMap<String, Integer> getPasskeyAgentRequestData() {
    152         return mPasskeyAgentRequestData;
    153     }
    154 
    155     /* package */ HashMap<String, Integer> getAuthorizationAgentRequestData() {
    156         return mAuthorizationAgentRequestData;
    157     }
    158 
    159     /* package */ void start() {
    160 
    161         if (!isEventLoopRunningNative()) {
    162             if (DBG) log("Starting Event Loop thread");
    163             startEventLoopNative();
    164         }
    165     }
    166 
    167     public void stop() {
    168         if (isEventLoopRunningNative()) {
    169             if (DBG) log("Stopping Event Loop thread");
    170             stopEventLoopNative();
    171         }
    172     }
    173 
    174     public boolean isEventLoopRunning() {
    175         return isEventLoopRunningNative();
    176     }
    177 
    178     private void addDevice(String address, String[] properties) {
    179         BluetoothDeviceProperties deviceProperties =
    180                 mBluetoothService.getDeviceProperties();
    181         deviceProperties.addProperties(address, properties);
    182         String rssi = deviceProperties.getProperty(address, "RSSI");
    183         String classValue = deviceProperties.getProperty(address, "Class");
    184         String name = deviceProperties.getProperty(address, "Name");
    185         short rssiValue;
    186         // For incoming connections, we don't get the RSSI value. Use a default of MIN_VALUE.
    187         // If we accept the pairing, we will automatically show it at the top of the list.
    188         if (rssi != null) {
    189             rssiValue = (short)Integer.valueOf(rssi).intValue();
    190         } else {
    191             rssiValue = Short.MIN_VALUE;
    192         }
    193         if (classValue != null) {
    194             Intent intent = new Intent(BluetoothDevice.ACTION_FOUND);
    195             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    196             intent.putExtra(BluetoothDevice.EXTRA_CLASS,
    197                     new BluetoothClass(Integer.valueOf(classValue)));
    198             intent.putExtra(BluetoothDevice.EXTRA_RSSI, rssiValue);
    199             intent.putExtra(BluetoothDevice.EXTRA_NAME, name);
    200 
    201             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    202         } else {
    203             log ("ClassValue: " + classValue + " for remote device: " + address + " is null");
    204         }
    205     }
    206 
    207     /**
    208      * Called by native code on a DeviceFound signal from org.bluez.Adapter.
    209      *
    210      * @param address the MAC address of the new device
    211      * @param properties an array of property keys and value strings
    212      *
    213      * @see BluetoothDeviceProperties#addProperties(String, String[])
    214      */
    215     private void onDeviceFound(String address, String[] properties) {
    216         if (properties == null) {
    217             Log.e(TAG, "ERROR: Remote device properties are null");
    218             return;
    219         }
    220         addDevice(address, properties);
    221     }
    222 
    223     /**
    224      * Called by native code on a DeviceDisappeared signal from
    225      * org.bluez.Adapter.
    226      *
    227      * @param address the MAC address of the disappeared device
    228      */
    229     private void onDeviceDisappeared(String address) {
    230         Intent intent = new Intent(BluetoothDevice.ACTION_DISAPPEARED);
    231         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    232         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    233     }
    234 
    235     /**
    236      * Called by native code on a DisconnectRequested signal from
    237      * org.bluez.Device.
    238      *
    239      * @param deviceObjectPath the object path for the disconnecting device
    240      */
    241     private void onDeviceDisconnectRequested(String deviceObjectPath) {
    242         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    243         if (address == null) {
    244             Log.e(TAG, "onDeviceDisconnectRequested: Address of the remote device in null");
    245             return;
    246         }
    247         Intent intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED);
    248         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    249         mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    250     }
    251 
    252     /**
    253      * Called by native code for the async response to a CreatePairedDevice
    254      * method call to org.bluez.Adapter.
    255      *
    256      * @param address the MAC address of the device to pair
    257      * @param result success or error result for the pairing operation
    258      */
    259     private void onCreatePairedDeviceResult(String address, int result) {
    260         address = address.toUpperCase();
    261         mBluetoothService.onCreatePairedDeviceResult(address, result);
    262     }
    263 
    264     /**
    265      * Called by native code on a DeviceCreated signal from org.bluez.Adapter.
    266      *
    267      * @param deviceObjectPath the object path for the created device
    268      */
    269     private void onDeviceCreated(String deviceObjectPath) {
    270         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    271         if (address == null) {
    272             Log.e(TAG, "onDeviceCreated: device address null!" + " deviceObjectPath: " +
    273                   deviceObjectPath);
    274             return;
    275         }
    276         if (!mBluetoothService.isRemoteDeviceInCache(address)) {
    277             // Incoming connection, we haven't seen this device, add to cache.
    278             String[] properties = mBluetoothService.getRemoteDeviceProperties(address);
    279             if (properties != null) {
    280                 addDevice(address, properties);
    281             }
    282         }
    283     }
    284 
    285     /**
    286      * Called by native code on a DeviceRemoved signal from org.bluez.Adapter.
    287      *
    288      * @param deviceObjectPath the object path for the removed device
    289      */
    290     private void onDeviceRemoved(String deviceObjectPath) {
    291         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    292         if (address != null) {
    293             mBluetoothService.setBondState(address.toUpperCase(), BluetoothDevice.BOND_NONE,
    294                 BluetoothDevice.UNBOND_REASON_REMOVED);
    295             mBluetoothService.setRemoteDeviceProperty(address, "UUIDs", null);
    296         }
    297     }
    298 
    299     /**
    300      * Called by native code on a PropertyChanged signal from
    301      * org.bluez.Adapter. This method is also called from
    302      * {@link BluetoothAdapterStateMachine} to set the "Pairable"
    303      * property when Bluetooth is enabled.
    304      *
    305      * @param propValues a string array containing the key and one or more
    306      *  values.
    307      */
    308     /*package*/ void onPropertyChanged(String[] propValues) {
    309         BluetoothAdapterProperties adapterProperties =
    310                 mBluetoothService.getAdapterProperties();
    311 
    312         if (adapterProperties.isEmpty()) {
    313             // We have got a property change before
    314             // we filled up our cache.
    315             adapterProperties.getAllProperties();
    316         }
    317         log("Property Changed: " + propValues[0] + " : " + propValues[1]);
    318         String name = propValues[0];
    319         if (name.equals("Name")) {
    320             adapterProperties.setProperty(name, propValues[1]);
    321             Intent intent = new Intent(BluetoothAdapter.ACTION_LOCAL_NAME_CHANGED);
    322             intent.putExtra(BluetoothAdapter.EXTRA_LOCAL_NAME, propValues[1]);
    323             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    324             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    325         } else if (name.equals("Pairable") || name.equals("Discoverable")) {
    326             adapterProperties.setProperty(name, propValues[1]);
    327 
    328             if (name.equals("Discoverable")) {
    329                 mBluetoothState.sendMessage(BluetoothAdapterStateMachine.SCAN_MODE_CHANGED);
    330             }
    331 
    332             String pairable = name.equals("Pairable") ? propValues[1] :
    333                 adapterProperties.getProperty("Pairable");
    334             String discoverable = name.equals("Discoverable") ? propValues[1] :
    335                 adapterProperties.getProperty("Discoverable");
    336 
    337             // This shouldn't happen, unless Adapter Properties are null.
    338             if (pairable == null || discoverable == null)
    339                 return;
    340 
    341             int mode = BluetoothService.bluezStringToScanMode(
    342                     pairable.equals("true"),
    343                     discoverable.equals("true"));
    344             if (mode >= 0) {
    345                 Intent intent = new Intent(BluetoothAdapter.ACTION_SCAN_MODE_CHANGED);
    346                 intent.putExtra(BluetoothAdapter.EXTRA_SCAN_MODE, mode);
    347                 intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    348                 mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    349             }
    350         } else if (name.equals("Discovering")) {
    351             Intent intent;
    352             adapterProperties.setProperty(name, propValues[1]);
    353             if (propValues[1].equals("true")) {
    354                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_STARTED);
    355             } else {
    356                 // Stop the discovery.
    357                 mBluetoothService.cancelDiscovery();
    358                 intent = new Intent(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
    359             }
    360             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    361         } else if (name.equals("Devices") || name.equals("UUIDs")) {
    362             String value = null;
    363             int len = Integer.valueOf(propValues[1]);
    364             if (len > 0) {
    365                 StringBuilder str = new StringBuilder();
    366                 for (int i = 2; i < propValues.length; i++) {
    367                     str.append(propValues[i]);
    368                     str.append(",");
    369                 }
    370                 value = str.toString();
    371             }
    372             adapterProperties.setProperty(name, value);
    373             if (name.equals("UUIDs")) {
    374                 mBluetoothService.updateBluetoothState(value);
    375             }
    376         } else if (name.equals("Powered")) {
    377             mBluetoothState.sendMessage(BluetoothAdapterStateMachine.POWER_STATE_CHANGED,
    378                 propValues[1].equals("true") ? new Boolean(true) : new Boolean(false));
    379         } else if (name.equals("DiscoverableTimeout")) {
    380             adapterProperties.setProperty(name, propValues[1]);
    381         }
    382     }
    383 
    384     /**
    385      * Called by native code on a PropertyChanged signal from
    386      * org.bluez.Device.
    387      *
    388      * @param deviceObjectPath the object path for the changed device
    389      * @param propValues a string array containing the key and one or more
    390      *  values.
    391      */
    392     private void onDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
    393         String name = propValues[0];
    394         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    395         if (address == null) {
    396             Log.e(TAG, "onDevicePropertyChanged: Address of the remote device in null");
    397             return;
    398         }
    399         log("Device property changed: " + address + " property: "
    400             + name + " value: " + propValues[1]);
    401 
    402         BluetoothDevice device = mAdapter.getRemoteDevice(address);
    403         if (name.equals("Name")) {
    404             mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
    405             Intent intent = new Intent(BluetoothDevice.ACTION_NAME_CHANGED);
    406             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    407             intent.putExtra(BluetoothDevice.EXTRA_NAME, propValues[1]);
    408             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    409             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    410         } else if (name.equals("Alias")) {
    411             mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
    412             Intent intent = new Intent(BluetoothDevice.ACTION_ALIAS_CHANGED);
    413             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    414             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    415             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    416         } else if (name.equals("Class")) {
    417             mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
    418             Intent intent = new Intent(BluetoothDevice.ACTION_CLASS_CHANGED);
    419             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    420             intent.putExtra(BluetoothDevice.EXTRA_CLASS,
    421                     new BluetoothClass(Integer.valueOf(propValues[1])));
    422             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    423             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    424         } else if (name.equals("Connected")) {
    425             mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
    426             Intent intent = null;
    427             if (propValues[1].equals("true")) {
    428                 intent = new Intent(BluetoothDevice.ACTION_ACL_CONNECTED);
    429                 // Set the link timeout to 8000 slots (5 sec timeout)
    430                 // for bluetooth docks.
    431                 if (mBluetoothService.isBluetoothDock(address)) {
    432                     mBluetoothService.setLinkTimeout(address, 8000);
    433                 }
    434             } else {
    435                 intent = new Intent(BluetoothDevice.ACTION_ACL_DISCONNECTED);
    436             }
    437             intent.putExtra(BluetoothDevice.EXTRA_DEVICE, device);
    438             intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
    439             mContext.sendBroadcast(intent, BLUETOOTH_PERM);
    440         } else if (name.equals("UUIDs")) {
    441             String uuid = null;
    442             int len = Integer.valueOf(propValues[1]);
    443             if (len > 0) {
    444                 StringBuilder str = new StringBuilder();
    445                 for (int i = 2; i < propValues.length; i++) {
    446                     str.append(propValues[i]);
    447                     str.append(",");
    448                 }
    449                 uuid = str.toString();
    450             }
    451             mBluetoothService.setRemoteDeviceProperty(address, name, uuid);
    452 
    453             // UUIDs have changed, query remote service channel and update cache.
    454             mBluetoothService.updateDeviceServiceChannelCache(address);
    455 
    456             mBluetoothService.sendUuidIntent(address);
    457         } else if (name.equals("Paired")) {
    458             if (propValues[1].equals("true")) {
    459                 // If locally initiated pairing, we will
    460                 // not go to BOND_BONDED state until we have received a
    461                 // successful return value in onCreatePairedDeviceResult
    462                 if (null == mBluetoothService.getPendingOutgoingBonding()) {
    463                     mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDED);
    464                 }
    465             } else {
    466                 mBluetoothService.setBondState(address, BluetoothDevice.BOND_NONE);
    467                 mBluetoothService.setRemoteDeviceProperty(address, "Trusted", "false");
    468             }
    469         } else if (name.equals("Trusted")) {
    470             if (DBG)
    471                 log("set trust state succeeded, value is: " + propValues[1]);
    472             mBluetoothService.setRemoteDeviceProperty(address, name, propValues[1]);
    473         }
    474     }
    475 
    476     /**
    477      * Called by native code on a PropertyChanged signal from
    478      * org.bluez.Input.
    479      *
    480      * @param path the object path for the changed input device
    481      * @param propValues a string array containing the key and one or more
    482      *  values.
    483      */
    484     private void onInputDevicePropertyChanged(String path, String[] propValues) {
    485         String address = mBluetoothService.getAddressFromObjectPath(path);
    486         if (address == null) {
    487             Log.e(TAG, "onInputDevicePropertyChanged: Address of the remote device is null");
    488             return;
    489         }
    490         log("Input Device : Name of Property is: " + propValues[0]);
    491         boolean state = false;
    492         if (propValues[1].equals("true")) {
    493             state = true;
    494         }
    495         mBluetoothService.handleInputDevicePropertyChange(address, state);
    496     }
    497 
    498     /**
    499      * Called by native code on a PropertyChanged signal from
    500      * org.bluez.Network.
    501      *
    502      * @param deviceObjectPath the object path for the changed PAN device
    503      * @param propValues a string array containing the key and one or more
    504      *  values.
    505      */
    506     private void onPanDevicePropertyChanged(String deviceObjectPath, String[] propValues) {
    507         String name = propValues[0];
    508         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    509         if (address == null) {
    510             Log.e(TAG, "onPanDevicePropertyChanged: Address of the remote device in null");
    511             return;
    512         }
    513         if (DBG) {
    514             log("Pan Device property changed: " + address + "  property: "
    515                     + name + " value: "+ propValues[1]);
    516         }
    517         BluetoothDevice device = mAdapter.getRemoteDevice(address);
    518         if (name.equals("Connected")) {
    519             if (propValues[1].equals("false")) {
    520                 mBluetoothService.handlePanDeviceStateChange(device,
    521                                           BluetoothPan.STATE_DISCONNECTED,
    522                                           BluetoothPan.LOCAL_PANU_ROLE);
    523             }
    524         } else if (name.equals("Interface")) {
    525             String iface = propValues[1];
    526             if (!iface.equals("")) {
    527                 mBluetoothService.handlePanDeviceStateChange(device, iface,
    528                                               BluetoothPan.STATE_CONNECTED,
    529                                               BluetoothPan.LOCAL_PANU_ROLE);
    530             }
    531         }
    532     }
    533 
    534     private String checkPairingRequestAndGetAddress(String objectPath, int nativeData) {
    535         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
    536         if (address == null) {
    537             Log.e(TAG, "Unable to get device address in checkPairingRequestAndGetAddress, " +
    538                   "returning null");
    539             return null;
    540         }
    541         address = address.toUpperCase();
    542         mPasskeyAgentRequestData.put(address, new Integer(nativeData));
    543 
    544         if (mBluetoothService.getBluetoothState() == BluetoothAdapter.STATE_TURNING_OFF) {
    545             // shutdown path
    546             mBluetoothService.cancelPairingUserInput(address);
    547             return null;
    548         }
    549         // Set state to BONDING. For incoming connections it will be set here.
    550         // For outgoing connections, it gets set when we call createBond.
    551         // Also set it only when the state is not already Bonded, we can sometimes
    552         // get an authorization request from the remote end if it doesn't have the link key
    553         // while we still have it.
    554         if (mBluetoothService.getBondState(address) != BluetoothDevice.BOND_BONDED)
    555             mBluetoothService.setBondState(address, BluetoothDevice.BOND_BONDING);
    556         return address;
    557     }
    558 
    559     /**
    560      * Called by native code on a RequestPairingConsent method call to
    561      * org.bluez.Agent.
    562      *
    563      * @param objectPath the path of the device to request pairing consent for
    564      * @param nativeData a native pointer to the original D-Bus message
    565      */
    566     private void onRequestPairingConsent(String objectPath, int nativeData) {
    567         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
    568         if (address == null) return;
    569 
    570         /* The link key will not be stored if the incoming request has MITM
    571          * protection switched on. Unfortunately, some devices have MITM
    572          * switched on even though their capabilities are NoInputNoOutput,
    573          * so we may get this request many times. Also if we respond immediately,
    574          * the other end is unable to handle it. Delay sending the message.
    575          */
    576         if (mBluetoothService.getBondState(address) == BluetoothDevice.BOND_BONDED) {
    577             Message message = mHandler.obtainMessage(EVENT_PAIRING_CONSENT_DELAYED_ACCEPT);
    578             message.obj = address;
    579             mHandler.sendMessageDelayed(message, 1500);
    580             return;
    581         }
    582         // Acquire wakelock during PIN code request to bring up LCD display
    583         mWakeLock.acquire();
    584         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    585         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    586         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
    587                         BluetoothDevice.PAIRING_VARIANT_CONSENT);
    588         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    589         // Release wakelock to allow the LCD to go off after the PIN popup notification.
    590         mWakeLock.release();
    591         return;
    592     }
    593 
    594     /**
    595      * Called by native code on a RequestConfirmation method call to
    596      * org.bluez.Agent.
    597      *
    598      * @param objectPath the path of the device to confirm the passkey for
    599      * @param passkey an integer containing the 6-digit passkey to confirm
    600      * @param nativeData a native pointer to the original D-Bus message
    601      */
    602     private void onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData) {
    603         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
    604         if (address == null) return;
    605         // Acquire wakelock during PIN code request to bring up LCD display
    606         mWakeLock.acquire();
    607         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    608         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    609         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey);
    610         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
    611                 BluetoothDevice.PAIRING_VARIANT_PASSKEY_CONFIRMATION);
    612         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    613         // Release wakelock to allow the LCD to go off after the PIN popup notification.
    614         mWakeLock.release();
    615         return;
    616     }
    617 
    618     /**
    619      * Called by native code on a RequestPasskey method call to
    620      * org.bluez.Agent.
    621      *
    622      * @param objectPath the path of the device requesting a passkey
    623      * @param nativeData a native pointer to the original D-Bus message
    624      */
    625     private void onRequestPasskey(String objectPath, int nativeData) {
    626         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
    627         if (address == null) return;
    628         // Acquire wakelock during PIN code request to bring up LCD display
    629         mWakeLock.acquire();
    630         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    631         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    632         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
    633                 BluetoothDevice.PAIRING_VARIANT_PASSKEY);
    634         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    635         // Release wakelock to allow the LCD to go off after the PIN popup notification.
    636         mWakeLock.release();
    637         return;
    638     }
    639 
    640     /**
    641      * Called by native code on a RequestPinCode method call to
    642      * org.bluez.Agent.
    643      *
    644      * @param objectPath the path of the device requesting a PIN code
    645      * @param nativeData a native pointer to the original D-Bus message
    646      */
    647     private void onRequestPinCode(String objectPath, int nativeData) {
    648         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
    649         if (address == null) return;
    650 
    651         String pendingOutgoingAddress =
    652                 mBluetoothService.getPendingOutgoingBonding();
    653         BluetoothClass btClass = new BluetoothClass(mBluetoothService.getRemoteClass(address));
    654         int btDeviceClass = btClass.getDeviceClass();
    655 
    656         if (address.equals(pendingOutgoingAddress)) {
    657             // we initiated the bonding
    658 
    659             // Check if its a dock
    660             if (mBluetoothService.isBluetoothDock(address)) {
    661                 String pin = mBluetoothService.getDockPin();
    662                 mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes(pin));
    663                 return;
    664             }
    665 
    666             // try 0000 once if the device looks dumb
    667             switch (btDeviceClass) {
    668             case BluetoothClass.Device.AUDIO_VIDEO_WEARABLE_HEADSET:
    669             case BluetoothClass.Device.AUDIO_VIDEO_HANDSFREE:
    670             case BluetoothClass.Device.AUDIO_VIDEO_HEADPHONES:
    671             case BluetoothClass.Device.AUDIO_VIDEO_PORTABLE_AUDIO:
    672             case BluetoothClass.Device.AUDIO_VIDEO_HIFI_AUDIO:
    673                 if (mBluetoothService.attemptAutoPair(address)) return;
    674            }
    675         }
    676 
    677         if (btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD ||
    678             btDeviceClass == BluetoothClass.Device.PERIPHERAL_KEYBOARD_POINTING) {
    679             // Its a keyboard. Follow the HID spec recommendation of creating the
    680             // passkey and displaying it to the user. If the keyboard doesn't follow
    681             // the spec recommendation, check if the keyboard has a fixed PIN zero
    682             // and pair.
    683             if (mBluetoothService.isFixedPinZerosAutoPairKeyboard(address)) {
    684                 mBluetoothService.setPin(address, BluetoothDevice.convertPinToBytes("0000"));
    685                 return;
    686             }
    687 
    688             // Generate a variable PIN. This is not truly random but good enough.
    689             int pin = (int) Math.floor(Math.random() * 10000);
    690             sendDisplayPinIntent(address, pin);
    691             return;
    692         }
    693         // Acquire wakelock during PIN code request to bring up LCD display
    694         mWakeLock.acquire();
    695         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    696         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    697         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT, BluetoothDevice.PAIRING_VARIANT_PIN);
    698         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    699         // Release wakelock to allow the LCD to go off after the PIN popup notification.
    700         mWakeLock.release();
    701         return;
    702     }
    703 
    704     /**
    705      * Called by native code on a DisplayPasskey method call to
    706      * org.bluez.Agent.
    707      *
    708      * @param objectPath the path of the device to display the passkey for
    709      * @param passkey an integer containing the 6-digit passkey
    710      * @param nativeData a native pointer to the original D-Bus message
    711      */
    712     private void onDisplayPasskey(String objectPath, int passkey, int nativeData) {
    713         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
    714         if (address == null) return;
    715 
    716         // Acquire wakelock during PIN code request to bring up LCD display
    717         mWakeLock.acquire();
    718         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    719         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    720         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, passkey);
    721         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
    722                         BluetoothDevice.PAIRING_VARIANT_DISPLAY_PASSKEY);
    723         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    724         //Release wakelock to allow the LCD to go off after the PIN popup notification.
    725         mWakeLock.release();
    726     }
    727 
    728     private void sendDisplayPinIntent(String address, int pin) {
    729         // Acquire wakelock during PIN code request to bring up LCD display
    730         mWakeLock.acquire();
    731         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    732         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    733         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_KEY, pin);
    734         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
    735                         BluetoothDevice.PAIRING_VARIANT_DISPLAY_PIN);
    736         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    737         //Release wakelock to allow the LCD to go off after the PIN popup notifcation.
    738         mWakeLock.release();
    739     }
    740 
    741     /**
    742      * Called by native code on a RequestOobData method call to
    743      * org.bluez.Agent.
    744      *
    745      * @param objectPath the path of the device requesting OOB data
    746      * @param nativeData a native pointer to the original D-Bus message
    747      */
    748     private void onRequestOobData(String objectPath, int nativeData) {
    749         String address = checkPairingRequestAndGetAddress(objectPath, nativeData);
    750         if (address == null) return;
    751 
    752         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_REQUEST);
    753         intent.putExtra(BluetoothDevice.EXTRA_DEVICE, mAdapter.getRemoteDevice(address));
    754         intent.putExtra(BluetoothDevice.EXTRA_PAIRING_VARIANT,
    755                 BluetoothDevice.PAIRING_VARIANT_OOB_CONSENT);
    756         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    757     }
    758 
    759     /**
    760      * Called by native code on an Authorize method call to org.bluez.Agent.
    761      *
    762      * @param objectPath the path of the device requesting to be authorized
    763      * @param deviceUuid the UUID of the requesting device
    764      * @param nativeData reference for native data
    765      */
    766     private void  onAgentAuthorize(String objectPath, String deviceUuid, int nativeData) {
    767         if (!mBluetoothService.isEnabled()) return;
    768 
    769         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
    770         if (address == null) {
    771             Log.e(TAG, "Unable to get device address in onAuthAgentAuthorize");
    772             return;
    773         }
    774 
    775         boolean authorized = false;
    776         ParcelUuid uuid = ParcelUuid.fromString(deviceUuid);
    777 
    778         BluetoothDevice device = mAdapter.getRemoteDevice(address);
    779         mAuthorizationAgentRequestData.put(address, new Integer(nativeData));
    780 
    781         // Bluez sends the UUID of the local service being accessed, _not_ the
    782         // remote service
    783         if (mA2dp != null &&
    784             (BluetoothUuid.isAudioSource(uuid) || BluetoothUuid.isAvrcpTarget(uuid)
    785               || BluetoothUuid.isAdvAudioDist(uuid)) &&
    786               !isOtherSinkInNonDisconnectedState(address)) {
    787             authorized = mA2dp.getPriority(device) > BluetoothProfile.PRIORITY_OFF;
    788             if (authorized && !BluetoothUuid.isAvrcpTarget(uuid)) {
    789                 Log.i(TAG, "First check pass for incoming A2DP / AVRCP connection from " + address);
    790                 // Some headsets try to connect AVCTP before AVDTP - against the recommendation
    791                 // If AVCTP connection fails, we get stuck in IncomingA2DP state in the state
    792                 // machine.  We don't handle AVCTP signals currently. We only send
    793                 // intents for AVDTP state changes. We need to handle both of them in
    794                 // some cases. For now, just don't move to incoming state in this case.
    795                 mBluetoothService.notifyIncomingA2dpConnection(address, false);
    796             } else {
    797                 Log.i(TAG, "" + authorized +
    798                       "Incoming A2DP / AVRCP connection from " + address);
    799                 mA2dp.allowIncomingConnect(device, authorized);
    800                 mBluetoothService.notifyIncomingA2dpConnection(address, true);
    801             }
    802         } else if (BluetoothUuid.isInputDevice(uuid)) {
    803             // We can have more than 1 input device connected.
    804             authorized = mBluetoothService.getInputDevicePriority(device) >
    805                     BluetoothInputDevice.PRIORITY_OFF;
    806             if (authorized) {
    807                 Log.i(TAG, "First check pass for incoming HID connection from " + address);
    808                 // notify profile state change
    809                 mBluetoothService.notifyIncomingHidConnection(address);
    810             } else {
    811                 Log.i(TAG, "Rejecting incoming HID connection from " + address);
    812                 mBluetoothService.allowIncomingProfileConnect(device, authorized);
    813             }
    814         } else if (BluetoothUuid.isBnep(uuid)) {
    815             // PAN doesn't go to the state machine, accept or reject from here
    816             authorized = mBluetoothService.allowIncomingTethering();
    817             mBluetoothService.allowIncomingProfileConnect(device, authorized);
    818         } else {
    819             Log.i(TAG, "Rejecting incoming " + deviceUuid + " connection from " + address);
    820             mBluetoothService.allowIncomingProfileConnect(device, authorized);
    821         }
    822         log("onAgentAuthorize(" + objectPath + ", " + deviceUuid + ") = " + authorized);
    823     }
    824 
    825     private boolean onAgentOutOfBandDataAvailable(String objectPath) {
    826         if (!mBluetoothService.isEnabled()) return false;
    827 
    828         String address = mBluetoothService.getAddressFromObjectPath(objectPath);
    829         if (address == null) return false;
    830 
    831         if (mBluetoothService.getDeviceOutOfBandData(
    832             mAdapter.getRemoteDevice(address)) != null) {
    833             return true;
    834         }
    835         return false;
    836     }
    837 
    838     private boolean isOtherSinkInNonDisconnectedState(String address) {
    839         List<BluetoothDevice> devices =
    840             mA2dp.getDevicesMatchingConnectionStates(new int[] {BluetoothA2dp.STATE_CONNECTED,
    841                                                      BluetoothA2dp.STATE_CONNECTING,
    842                                                      BluetoothA2dp.STATE_DISCONNECTING});
    843 
    844         if (devices.size() == 0) return false;
    845         for (BluetoothDevice dev: devices) {
    846             if (!dev.getAddress().equals(address)) return true;
    847         }
    848         return false;
    849     }
    850 
    851     /**
    852      * Called by native code on a Cancel method call to org.bluez.Agent.
    853      */
    854     private void onAgentCancel() {
    855         Intent intent = new Intent(BluetoothDevice.ACTION_PAIRING_CANCEL);
    856         mContext.sendBroadcast(intent, BLUETOOTH_ADMIN_PERM);
    857 
    858         mHandler.sendMessageDelayed(mHandler.obtainMessage(EVENT_AGENT_CANCEL),
    859                    1500);
    860 
    861         return;
    862     }
    863 
    864     /**
    865      * Called by native code for the async response to a DiscoverServices
    866      * method call to org.bluez.Adapter.
    867      *
    868      * @param deviceObjectPath the path for the specified device
    869      * @param result true for success; false on error
    870      */
    871     private void onDiscoverServicesResult(String deviceObjectPath, boolean result) {
    872         String address = mBluetoothService.getAddressFromObjectPath(deviceObjectPath);
    873         if (address == null) return;
    874 
    875         // We don't parse the xml here, instead just query Bluez for the properties.
    876         if (result) {
    877             mBluetoothService.updateRemoteDevicePropertiesCache(address);
    878         }
    879         mBluetoothService.sendUuidIntent(address);
    880         mBluetoothService.makeServiceChannelCallbacks(address);
    881     }
    882 
    883     /**
    884      * Called by native code for the async response to a CreateDevice
    885      * method call to org.bluez.Adapter.
    886      *
    887      * @param address the MAC address of the device to create
    888      * @param result {@link #CREATE_DEVICE_SUCCESS},
    889      *  {@link #CREATE_DEVICE_ALREADY_EXISTS} or {@link #CREATE_DEVICE_FAILED}}
    890      */
    891     private void onCreateDeviceResult(String address, int result) {
    892         if (DBG) log("Result of onCreateDeviceResult:" + result);
    893 
    894         switch (result) {
    895         case CREATE_DEVICE_ALREADY_EXISTS:
    896             String path = mBluetoothService.getObjectPathFromAddress(address);
    897             if (path != null) {
    898                 mBluetoothService.discoverServicesNative(path, "");
    899                 break;
    900             }
    901             Log.w(TAG, "Device exists, but we don't have the bluez path, failing");
    902             // fall-through
    903         case CREATE_DEVICE_FAILED:
    904             mBluetoothService.sendUuidIntent(address);
    905             mBluetoothService.makeServiceChannelCallbacks(address);
    906             break;
    907         case CREATE_DEVICE_SUCCESS:
    908             // nothing to do, UUID intent's will be sent via property changed
    909         }
    910     }
    911 
    912     /**
    913      * Called by native code for the async response to a Connect
    914      * method call to org.bluez.Input.
    915      *
    916      * @param path the path of the specified input device
    917      * @param result Result code of the operation.
    918      */
    919     private void onInputDeviceConnectionResult(String path, int result) {
    920         // Success case gets handled by Property Change signal
    921         if (result != BluetoothInputDevice.INPUT_OPERATION_SUCCESS) {
    922             String address = mBluetoothService.getAddressFromObjectPath(path);
    923             if (address == null) return;
    924 
    925             boolean connected = false;
    926             BluetoothDevice device = mAdapter.getRemoteDevice(address);
    927             int state = mBluetoothService.getInputDeviceConnectionState(device);
    928             if (state == BluetoothInputDevice.STATE_CONNECTING) {
    929                 if (result == BluetoothInputDevice.INPUT_CONNECT_FAILED_ALREADY_CONNECTED) {
    930                     connected = true;
    931                 } else {
    932                     connected = false;
    933                 }
    934             } else if (state == BluetoothInputDevice.STATE_DISCONNECTING) {
    935                 if (result == BluetoothInputDevice.INPUT_DISCONNECT_FAILED_NOT_CONNECTED) {
    936                     connected = false;
    937                 } else {
    938                     // There is no better way to handle this, this shouldn't happen
    939                     connected = true;
    940                 }
    941             } else {
    942                 Log.e(TAG, "Error onInputDeviceConnectionResult. State is:" + state);
    943             }
    944             mBluetoothService.handleInputDevicePropertyChange(address, connected);
    945         }
    946     }
    947 
    948     /**
    949      * Called by native code for the async response to a Connect
    950      * method call to org.bluez.Network.
    951      *
    952      * @param path the path of the specified PAN device
    953      * @param result Result code of the operation.
    954      */
    955     private void onPanDeviceConnectionResult(String path, int result) {
    956         log ("onPanDeviceConnectionResult " + path + " " + result);
    957         // Success case gets handled by Property Change signal
    958         if (result != BluetoothPan.PAN_OPERATION_SUCCESS) {
    959             String address = mBluetoothService.getAddressFromObjectPath(path);
    960             if (address == null) return;
    961 
    962             boolean connected = false;
    963             BluetoothDevice device = mAdapter.getRemoteDevice(address);
    964             int state = mBluetoothService.getPanDeviceConnectionState(device);
    965             if (state == BluetoothPan.STATE_CONNECTING) {
    966                 if (result == BluetoothPan.PAN_CONNECT_FAILED_ALREADY_CONNECTED) {
    967                     connected = true;
    968                 } else {
    969                     connected = false;
    970                 }
    971             } else if (state == BluetoothPan.STATE_DISCONNECTING) {
    972                 if (result == BluetoothPan.PAN_DISCONNECT_FAILED_NOT_CONNECTED) {
    973                     connected = false;
    974                 } else {
    975                     // There is no better way to handle this, this shouldn't happen
    976                     connected = true;
    977                 }
    978             } else {
    979                 Log.e(TAG, "Error onPanDeviceConnectionResult. State is: "
    980                         + state + " result: "+ result);
    981             }
    982             int newState = connected? BluetoothPan.STATE_CONNECTED :
    983                 BluetoothPan.STATE_DISCONNECTED;
    984             mBluetoothService.handlePanDeviceStateChange(device, newState,
    985                                                   BluetoothPan.LOCAL_PANU_ROLE);
    986         }
    987     }
    988 
    989     /**
    990      * Called by native code for the async response to a Connect
    991      * method call to org.bluez.Health
    992      *
    993      * @param chanCode The internal id of the channel
    994      * @param result Result code of the operation.
    995      */
    996     private void onHealthDeviceConnectionResult(int chanCode, int result) {
    997         log ("onHealthDeviceConnectionResult " + chanCode + " " + result);
    998         // Success case gets handled by Property Change signal
    999         if (result != BluetoothHealth.HEALTH_OPERATION_SUCCESS) {
   1000             mBluetoothService.onHealthDeviceChannelConnectionError(chanCode,
   1001                                                  BluetoothHealth.STATE_CHANNEL_DISCONNECTED);
   1002         }
   1003     }
   1004 
   1005     /**
   1006      * Called by native code on a DeviceDisconnected signal from
   1007      * org.bluez.NetworkServer.
   1008      *
   1009      * @param address the MAC address of the disconnected device
   1010      */
   1011     private void onNetworkDeviceDisconnected(String address) {
   1012         BluetoothDevice device = mAdapter.getRemoteDevice(address);
   1013         mBluetoothService.handlePanDeviceStateChange(device, BluetoothPan.STATE_DISCONNECTED,
   1014                                                       BluetoothPan.LOCAL_NAP_ROLE);
   1015     }
   1016 
   1017     /**
   1018      * Called by native code on a DeviceConnected signal from
   1019      * org.bluez.NetworkServer.
   1020      *
   1021      * @param address the MAC address of the connected device
   1022      * @param iface interface of remote network
   1023      * @param destUuid unused UUID parameter
   1024      */
   1025     private void onNetworkDeviceConnected(String address, String iface, int destUuid) {
   1026         BluetoothDevice device = mAdapter.getRemoteDevice(address);
   1027         mBluetoothService.handlePanDeviceStateChange(device, iface, BluetoothPan.STATE_CONNECTED,
   1028                                                       BluetoothPan.LOCAL_NAP_ROLE);
   1029     }
   1030 
   1031     /**
   1032      * Called by native code on a PropertyChanged signal from
   1033      * org.bluez.HealthDevice.
   1034      *
   1035      * @param devicePath the object path of the remote device
   1036      * @param propValues Properties (Name-Value) of the Health Device.
   1037      */
   1038     private void onHealthDevicePropertyChanged(String devicePath, String[] propValues) {
   1039         log("Health Device : Name of Property is: " + propValues[0] + " Value:" + propValues[1]);
   1040         mBluetoothService.onHealthDevicePropertyChanged(devicePath, propValues[1]);
   1041     }
   1042 
   1043     /**
   1044      * Called by native code on a ChannelCreated/Deleted signal from
   1045      * org.bluez.HealthDevice.
   1046      *
   1047      * @param devicePath the object path of the remote device
   1048      * @param channelPath the path of the health channel.
   1049      * @param exists Boolean to indicate if the channel was created or deleted.
   1050      */
   1051     private void onHealthDeviceChannelChanged(String devicePath, String channelPath,
   1052             boolean exists) {
   1053         log("Health Device : devicePath: " + devicePath + ":channelPath:" + channelPath +
   1054                 ":exists" + exists);
   1055         mBluetoothService.onHealthDeviceChannelChanged(devicePath, channelPath, exists);
   1056     }
   1057 
   1058     private static void log(String msg) {
   1059         Log.d(TAG, msg);
   1060     }
   1061 
   1062     private native void initializeNativeDataNative();
   1063     private native void startEventLoopNative();
   1064     private native void stopEventLoopNative();
   1065     private native boolean isEventLoopRunningNative();
   1066     private native void cleanupNativeDataNative();
   1067 }
   1068