• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
classInitNative()103     static { classInitNative(); }
classInitNative()104     private static native void classInitNative();
105 
BluetoothEventLoop(Context context, BluetoothAdapter adapter, BluetoothService bluetoothService, BluetoothAdapterStateMachine bluetoothState)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 
getProfileProxy()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 
finalize()143     protected void finalize() throws Throwable {
144         try {
145             cleanupNativeDataNative();
146         } finally {
147             super.finalize();
148         }
149     }
150 
getPasskeyAgentRequestData()151     /* package */ HashMap<String, Integer> getPasskeyAgentRequestData() {
152         return mPasskeyAgentRequestData;
153     }
154 
getAuthorizationAgentRequestData()155     /* package */ HashMap<String, Integer> getAuthorizationAgentRequestData() {
156         return mAuthorizationAgentRequestData;
157     }
158 
start()159     /* package */ void start() {
160 
161         if (!isEventLoopRunningNative()) {
162             if (DBG) log("Starting Event Loop thread");
163             startEventLoopNative();
164         }
165     }
166 
stop()167     public void stop() {
168         if (isEventLoopRunningNative()) {
169             if (DBG) log("Stopping Event Loop thread");
170             stopEventLoopNative();
171         }
172     }
173 
isEventLoopRunning()174     public boolean isEventLoopRunning() {
175         return isEventLoopRunningNative();
176     }
177 
addDevice(String address, String[] properties)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      */
onDeviceFound(String address, String[] properties)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      */
onDeviceDisappeared(String address)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      */
onDeviceDisconnectRequested(String deviceObjectPath)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      */
onCreatePairedDeviceResult(String address, int result)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      */
onDeviceCreated(String deviceObjectPath)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      */
onDeviceRemoved(String deviceObjectPath)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      */
onPropertyChanged(String[] propValues)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      */
onDevicePropertyChanged(String deviceObjectPath, String[] propValues)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      */
onInputDevicePropertyChanged(String path, String[] propValues)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      */
onPanDevicePropertyChanged(String deviceObjectPath, String[] propValues)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 
checkPairingRequestAndGetAddress(String objectPath, int nativeData)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      */
onRequestPairingConsent(String objectPath, int nativeData)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      */
onRequestPasskeyConfirmation(String objectPath, int passkey, int nativeData)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      */
onRequestPasskey(String objectPath, int nativeData)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      */
onRequestPinCode(String objectPath, int nativeData)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      */
onDisplayPasskey(String objectPath, int passkey, int nativeData)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 
sendDisplayPinIntent(String address, int pin)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      */
onRequestOobData(String objectPath, int nativeData)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      */
onAgentAuthorize(String objectPath, String deviceUuid, int nativeData)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 
onAgentOutOfBandDataAvailable(String objectPath)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 
isOtherSinkInNonDisconnectedState(String address)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      */
onAgentCancel()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      */
onDiscoverServicesResult(String deviceObjectPath, boolean result)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      */
onCreateDeviceResult(String address, int result)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      */
onInputDeviceConnectionResult(String path, int result)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      */
onPanDeviceConnectionResult(String path, int result)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      */
onHealthDeviceConnectionResult(int chanCode, int result)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      */
onNetworkDeviceDisconnected(String address)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      */
onNetworkDeviceConnected(String address, String iface, int destUuid)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      */
onHealthDevicePropertyChanged(String devicePath, String[] propValues)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      */
onHealthDeviceChannelChanged(String devicePath, String channelPath, boolean exists)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 
log(String msg)1058     private static void log(String msg) {
1059         Log.d(TAG, msg);
1060     }
1061 
initializeNativeDataNative()1062     private native void initializeNativeDataNative();
startEventLoopNative()1063     private native void startEventLoopNative();
stopEventLoopNative()1064     private native void stopEventLoopNative();
isEventLoopRunningNative()1065     private native boolean isEventLoopRunningNative();
cleanupNativeDataNative()1066     private native void cleanupNativeDataNative();
1067 }
1068