diff options
author | Ganesh Ganapathi Batta <ganeshg@broadcom.com> | 2013-02-05 15:38:27 -0800 |
---|---|---|
committer | Matthew Xie <mattx@google.com> | 2013-02-27 18:22:30 -0800 |
commit | 03b8386de26ba6500af2d66687bff9b01f2cbbd7 (patch) | |
tree | 340d93172e589ec6b504927914f19ceaad75cdd8 /src | |
parent | 8eb70f8bdf4e8c970810b3400aba8d08d14ce222 (diff) | |
download | android_packages_apps_Bluetooth-03b8386de26ba6500af2d66687bff9b01f2cbbd7.tar.gz android_packages_apps_Bluetooth-03b8386de26ba6500af2d66687bff9b01f2cbbd7.tar.bz2 android_packages_apps_Bluetooth-03b8386de26ba6500af2d66687bff9b01f2cbbd7.zip |
Initial version of BLE support for Bluedroid
Change-Id: I9579b3074bc4bc59dd45f71c0937e8879196555e
Diffstat (limited to 'src')
-rw-r--r--[-rwxr-xr-x] | src/com/android/bluetooth/btservice/AbstractionLayer.java | 2 | ||||
-rw-r--r--[-rwxr-xr-x] | src/com/android/bluetooth/btservice/BondStateMachine.java | 4 | ||||
-rw-r--r-- | src/com/android/bluetooth/btservice/Config.java | 7 | ||||
-rw-r--r-- | src/com/android/bluetooth/gatt/ContextMap.java | 251 | ||||
-rw-r--r-- | src/com/android/bluetooth/gatt/GattDebugUtils.java | 210 | ||||
-rw-r--r-- | src/com/android/bluetooth/gatt/GattService.java | 1676 | ||||
-rw-r--r-- | src/com/android/bluetooth/gatt/GattServiceConfig.java | 26 | ||||
-rw-r--r-- | src/com/android/bluetooth/gatt/HandleMap.java | 213 | ||||
-rw-r--r-- | src/com/android/bluetooth/gatt/ScanClient.java | 41 | ||||
-rw-r--r-- | src/com/android/bluetooth/gatt/SearchQueue.java | 92 | ||||
-rw-r--r-- | src/com/android/bluetooth/gatt/ServiceDeclaration.java | 108 |
11 files changed, 2628 insertions, 2 deletions
diff --git a/src/com/android/bluetooth/btservice/AbstractionLayer.java b/src/com/android/bluetooth/btservice/AbstractionLayer.java index 24bcb5dbd..4e0ebd0ec 100755..100644 --- a/src/com/android/bluetooth/btservice/AbstractionLayer.java +++ b/src/com/android/bluetooth/btservice/AbstractionLayer.java @@ -76,4 +76,6 @@ final public class AbstractionLayer { public static final int BT_STATUS_UNHANDLED = 8; public static final int BT_STATUS_AUTH_FAILURE = 9; public static final int BT_STATUS_RMT_DEV_DOWN = 10; + public static final int BT_STATUS_AUTH_REJECTED =11; + public static final int BT_STATUS_AUTH_TIMEOUT = 12; } diff --git a/src/com/android/bluetooth/btservice/BondStateMachine.java b/src/com/android/bluetooth/btservice/BondStateMachine.java index d80ad7cc5..0b6478b72 100755..100644 --- a/src/com/android/bluetooth/btservice/BondStateMachine.java +++ b/src/com/android/bluetooth/btservice/BondStateMachine.java @@ -347,6 +347,10 @@ final class BondStateMachine extends StateMachine { return BluetoothDevice.UNBOND_REASON_REMOTE_DEVICE_DOWN; else if (reason == AbstractionLayer.BT_STATUS_AUTH_FAILURE) return BluetoothDevice.UNBOND_REASON_AUTH_FAILED; + else if (reason == AbstractionLayer.BT_STATUS_AUTH_REJECTED) + return BluetoothDevice.UNBOND_REASON_AUTH_REJECTED; + else if (reason == AbstractionLayer.BT_STATUS_AUTH_TIMEOUT) + return BluetoothDevice.UNBOND_REASON_AUTH_TIMEOUT; /* default */ return BluetoothDevice.UNBOND_REASON_REMOVED; diff --git a/src/com/android/bluetooth/btservice/Config.java b/src/com/android/bluetooth/btservice/Config.java index d8d11948b..a2b2ff393 100644 --- a/src/com/android/bluetooth/btservice/Config.java +++ b/src/com/android/bluetooth/btservice/Config.java @@ -28,6 +28,7 @@ import com.android.bluetooth.hdp.HealthService; import com.android.bluetooth.hfp.HeadsetService; import com.android.bluetooth.hid.HidService; import com.android.bluetooth.pan.PanService; +import com.android.bluetooth.gatt.GattService; public class Config { private static final String TAG = "AdapterServiceConfig"; @@ -42,7 +43,8 @@ public class Config { A2dpService.class, HidService.class, HealthService.class, - PanService.class + PanService.class, + GattService.class }; /** * Resource flag to indicate whether profile is supported or not. @@ -52,7 +54,8 @@ public class Config { R.bool.profile_supported_a2dp, R.bool.profile_supported_hid, R.bool.profile_supported_hdp, - R.bool.profile_supported_pan + R.bool.profile_supported_pan, + R.bool.profile_supported_gatt }; private static Class[] SUPPORTED_PROFILES = new Class[0]; diff --git a/src/com/android/bluetooth/gatt/ContextMap.java b/src/com/android/bluetooth/gatt/ContextMap.java new file mode 100644 index 000000000..ca265b116 --- /dev/null +++ b/src/com/android/bluetooth/gatt/ContextMap.java @@ -0,0 +1,251 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + package com.android.bluetooth.gatt; + + +import android.util.Log; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; + +/** + * Helper class that keeps track of registered GATT applications. + * This class manages application callbacks and keeps track of GATT connections. + * @hide + */ +/*package*/ class ContextMap<T> { + private static final String TAG = GattServiceConfig.TAG_PREFIX + "ContextMap"; + + /** + * Connection class helps map connection IDs to device addresses. + */ + class Connection { + int connId; + String address; + int appId; + + Connection(int connId, String address,int appId) { + this.connId = connId; + this.address = address; + this.appId = appId; + } + } + + /** + * Application entry mapping UUIDs to appIDs and callbacks. + */ + class App { + /** The UUID of the application */ + UUID uuid; + + /** The id of the application */ + int id; + + /** Application callbacks */ + T callback; + + /** + * Creates a new app context. + */ + App(UUID uuid, T callback) { + this.uuid = uuid; + this.callback = callback; + } + } + + /** Our internal application list */ + List<App> mApps = new ArrayList<App>(); + + /** Internal list of connected devices **/ + Set<Connection> mConnections = new HashSet<Connection>(); + + /** + * Add an entry to the application context list. + */ + void add(UUID uuid, T callback) { + mApps.add(new App(uuid, callback)); + } + + /** + * Remove the context for a given application ID. + */ + void remove(int id) { + Iterator<App> i = mApps.iterator(); + while(i.hasNext()) { + App entry = i.next(); + if (entry.id == id) { + i.remove(); + break; + } + } + } + + /** + * Add a new connection for a given application ID. + */ + void addConnection(int id, int connId, String address) { + App entry = getById(id); + if (entry != null){ + mConnections.add(new Connection(connId, address, id)); + } + } + + /** + * Remove a connection with the given ID. + */ + void removeConnection(int id, int connId) { + Iterator<Connection> i = mConnections.iterator(); + while(i.hasNext()) { + Connection connection = i.next(); + if (connection.connId == connId) { + i.remove(); + break; + } + } + } + + /** + * Get an application context by ID. + */ + App getById(int id) { + Iterator<App> i = mApps.iterator(); + while(i.hasNext()) { + App entry = i.next(); + if (entry.id == id) return entry; + } + Log.e(TAG, "Context not found for ID " + id); + return null; + } + + /** + * Get an application context by UUID. + */ + App getByUuid(UUID uuid) { + Iterator<App> i = mApps.iterator(); + while(i.hasNext()) { + App entry = i.next(); + if (entry.uuid.equals(uuid)) return entry; + } + Log.e(TAG, "Context not found for UUID " + uuid); + return null; + } + + /** + * Get the device addresses for all connected devices + */ + Set<String> getConnectedDevices() { + Set<String> addresses = new HashSet<String>(); + Iterator<Connection> i = mConnections.iterator(); + while(i.hasNext()) { + Connection connection = i.next(); + addresses.add(connection.address); + } + return addresses; + } + + /** + * Get an application context by a connection ID. + */ + App getByConnId(int connId) { + Iterator<Connection> ii = mConnections.iterator(); + while(ii.hasNext()) { + Connection connection = ii.next(); + if (connection.connId == connId){ + return getById(connection.appId); + } + } + return null; + } + + /** + * Returns a connection ID for a given device address. + */ + Integer connIdByAddress(int id, String address) { + App entry = getById(id); + if (entry == null) return null; + + Iterator<Connection> i = mConnections.iterator(); + while(i.hasNext()) { + Connection connection = i.next(); + if (connection.address.equals(address) && connection.appId == id) + return connection.connId; + } + return null; + } + + /** + * Returns the device address for a given connection ID. + */ + String addressByConnId(int connId) { + Iterator<Connection> i = mConnections.iterator(); + while(i.hasNext()) { + Connection connection = i.next(); + if (connection.connId == connId) return connection.address; + } + return null; + } + + List<Connection> getConnectionByApp(int appId) { + List<Connection> currentConnections = new ArrayList<Connection>(); + Iterator<Connection> i = mConnections.iterator(); + while(i.hasNext()) { + Connection connection = i.next(); + if (connection.appId == appId) + currentConnections.add(connection); + } + return currentConnections; + } + + /** + * Erases all application context entries. + */ + void clear() { + mApps.clear(); + mConnections.clear(); + } + + /** + * Logs debug information. + */ + void dump() { + StringBuilder b = new StringBuilder(); + b.append( "-------------- GATT Context Map ----------------"); + b.append("\nEntries: " + mApps.size()); + + Iterator<App> i = mApps.iterator(); + while(i.hasNext()) { + App entry = i.next(); + List<Connection> connections = getConnectionByApp(entry.id); + + b.append("\n\nApplication Id: " + entry.id); + b.append("\nUUID: " + entry.uuid); + b.append("\nConnections: " + connections.size()); + + Iterator<Connection> ii = connections.iterator(); + while(ii.hasNext()) { + Connection connection = ii.next(); + b.append("\n " + connection.connId + ": " + connection.address); + } + } + + b.append("\n------------------------------------------------"); + Log.d(TAG, b.toString()); + } +} diff --git a/src/com/android/bluetooth/gatt/GattDebugUtils.java b/src/com/android/bluetooth/gatt/GattDebugUtils.java new file mode 100644 index 000000000..3911e6cda --- /dev/null +++ b/src/com/android/bluetooth/gatt/GattDebugUtils.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.gatt; + +import android.content.Intent; +import android.os.Bundle; +import android.util.Log; +import java.util.UUID; + +/** + * Helper class containing useful tools for GATT service debugging. + */ +/*package*/ class GattDebugUtils { + private static final String TAG = GattServiceConfig.TAG_PREFIX + "DebugUtils"; + private static final boolean DEBUG_ADMIN = GattServiceConfig.DEBUG_ADMIN; + + private static final String ACTION_DEBUG_DUMP_CLIENTMAP = + "android.bluetooth.action.DEBUG_DUMP_CLIENTMAP"; + private static final String ACTION_DEBUG_DUMP_SERVERMAP = + "android.bluetooth.action.DEBUG_DUMP_SERVERMAP"; + private static final String ACTION_DEBUG_DUMP_HANDLEMAP = + "android.bluetooth.action.DEBUG_DUMP_HANDLEMAP"; + + private static final String ACTION_GATT_PAIRING_CONFIG = + "android.bluetooth.action.GATT_PAIRING_CONFIG"; + + private static final String ACTION_GATT_TEST_USAGE = + "android.bluetooth.action.GATT_TEST_USAGE"; + private static final String ACTION_GATT_TEST_ENABLE = + "android.bluetooth.action.GATT_TEST_ENABLE"; + private static final String ACTION_GATT_TEST_CONNECT = + "android.bluetooth.action.GATT_TEST_CONNECT"; + private static final String ACTION_GATT_TEST_DISCONNECT = + "android.bluetooth.action.GATT_TEST_DISCONNECT"; + private static final String ACTION_GATT_TEST_DISCOVER = + "android.bluetooth.action.GATT_TEST_DISCOVER"; + + private static final String EXTRA_ENABLE = "enable"; + private static final String EXTRA_ADDRESS = "address"; + private static final String EXTRA_UUID = "uuid"; + private static final String EXTRA_TYPE = "type"; + private static final String EXTRA_SHANDLE = "start"; + private static final String EXTRA_EHANDLE = "end"; + private static final String EXTRA_AUTH_REQ = "auth_req"; + private static final String EXTRA_IO_CAP = "io_cap"; + private static final String EXTRA_INIT_KEY = "init_key"; + private static final String EXTRA_RESP_KEY = "resp_key"; + private static final String EXTRA_MAX_KEY = "max_key"; + + /** + * Handles intents passed in via GattService.onStartCommand(). + * This allows sending debug actions to the running service. + * To trigger a debug action, invoke the following shell command: + * + * adb shell am startservice -a <action> <component> + * + * Where <action> represents one of the ACTION_* constants defines above + * and <component> identifies the GATT service. + * + * For example: + * import com.android.bluetooth.gatt.GattService; + */ + static boolean handleDebugAction(GattService svc, Intent intent) { + if (!DEBUG_ADMIN) return false; + + String action = intent.getAction(); + Log.d(TAG, "handleDebugAction() action=" + action); + + /* + * Debug log functinos + */ + + if (ACTION_DEBUG_DUMP_CLIENTMAP.equals(action)) { + svc.mClientMap.dump(); + + } else if (ACTION_DEBUG_DUMP_SERVERMAP.equals(action)) { + svc.mServerMap.dump(); + + } else if (ACTION_DEBUG_DUMP_HANDLEMAP.equals(action)) { + svc.mHandleMap.dump(); + + /* + * PTS test commands + */ + + } else if (ACTION_GATT_TEST_USAGE.equals(action)) { + logUsageInfo(); + + } else if (ACTION_GATT_TEST_ENABLE.equals(action)) { + boolean bEnable = intent.getBooleanExtra(EXTRA_ENABLE, true); + svc.gattTestCommand( 0x01, null,null, bEnable ? 1 : 0, 0,0,0,0); + + } else if (ACTION_GATT_TEST_CONNECT.equals(action)) { + String address = intent.getStringExtra(EXTRA_ADDRESS); + int type = intent.getIntExtra(EXTRA_TYPE, 2 /* LE device */); + svc.gattTestCommand( 0x02, null, address, type, 0,0,0,0); + + } else if (ACTION_GATT_TEST_DISCONNECT.equals(action)) { + svc.gattTestCommand( 0x03, null, null, 0,0,0,0,0); + + } else if (ACTION_GATT_TEST_DISCOVER.equals(action)) { + UUID uuid = getUuidExtra(intent); + int type = intent.getIntExtra(EXTRA_TYPE, 1 /* All services */); + int shdl = getHandleExtra(intent, EXTRA_SHANDLE, 1); + int ehdl = getHandleExtra(intent, EXTRA_EHANDLE, 0xFFFF); + svc.gattTestCommand( 0x04, uuid, null, type, shdl, ehdl, 0,0); + + } else if (ACTION_GATT_PAIRING_CONFIG.equals(action)) { + int auth_req = intent.getIntExtra(EXTRA_AUTH_REQ, 5); + int io_cap = intent.getIntExtra(EXTRA_IO_CAP, 4); + int init_key = intent.getIntExtra(EXTRA_INIT_KEY, 7); + int resp_key = intent.getIntExtra(EXTRA_RESP_KEY, 7); + int max_key = intent.getIntExtra(EXTRA_MAX_KEY, 16); + svc.gattTestCommand( 0xF0, null, null, auth_req, io_cap, init_key, + resp_key, max_key); + + } else { + return false; + } + + return true; + } + + /** + * Attempts to retrieve an extra from an intent first as hex value, + * then as an ineger. + * @hide + */ + static private int getHandleExtra(Intent intent, String extra, int default_value) { + Bundle extras = intent.getExtras(); + Object uuid = extras != null ? extras.get(extra) : null; + if (uuid != null && uuid.getClass().getName().equals("java.lang.String")) { + try + { + return Integer.parseInt(extras.getString(extra), 16); + } catch (NumberFormatException e) { + return default_value; + } + } + + return intent.getIntExtra(extra, default_value); + } + + /** + * Retrieves the EXTRA_UUID parameter. + * If a string of length 4 is detected, a 16-bit hex UUID is assumed and + * the default Bluetooth UUID is appended. + * @hide + */ + static private UUID getUuidExtra(Intent intent) { + String uuidStr = intent.getStringExtra(EXTRA_UUID); + if (uuidStr != null && uuidStr.length() == 4) { + uuidStr = String.format("0000%s-0000-1000-8000-00805f9b34fb", uuidStr); + } + return (uuidStr != null) ? UUID.fromString(uuidStr) : null; + } + + /** + * Log usage information. + * @hide + */ + static private void logUsageInfo() { + StringBuilder b = new StringBuilder(); + b.append( "------------ GATT TEST ACTIONS ----------------"); + b.append("\nGATT_TEST_ENABLE"); + b.append("\n [--ez enable <bool>] Enable or disable,"); + b.append("\n defaults to true (enable).\n"); + b.append("\nGATT_TEST_CONNECT"); + b.append("\n --es address <bda>"); + b.append("\n [--ei type <type>] Default is 2 (LE Only)\n"); + b.append("\nGATT_TEST_DISCONNECT\n"); + b.append("\nGATT_TEST_DISCOVER"); + b.append("\n [--ei type <type>] Possible values:"); + b.append("\n 1 = Discover all services (default)"); + b.append("\n 2 = Discover services by UUID"); + b.append("\n 3 = Discover included services"); + b.append("\n 4 = Discover characteristics"); + b.append("\n 5 = Discover descriptors\n"); + b.append("\n [--es uuid <uuid>] Optional; Can be either full 128-bit"); + b.append("\n UUID hex string, or 4 hex characters"); + b.append("\n for 16-bit UUIDs.\n"); + b.append("\n [--ei start <hdl>] Start of handle range (default 1)"); + b.append("\n [--ei end <hdl>] End of handle range (default 65355)"); + b.append("\n or"); + b.append("\n [--es start <hdl>] Start of handle range (hex format)"); + b.append("\n [--es end <hdl>] End of handle range (hex format)\n"); + b.append("\nGATT_PAIRING_CONFIG"); + b.append("\n [--ei auth_req] Authentication flag (default 5)"); + b.append("\n [--ei io_cap] IO capabilities (default 4)"); + b.append("\n [--ei init_key] Initial key size (default 7)"); + b.append("\n [--ei resp_key] Response key size (default 7)"); + b.append("\n [--ei max_key] Maximum key size (default 16)"); + b.append("\n------------------------------------------------"); + Log.i(TAG, b.toString()); + } +} diff --git a/src/com/android/bluetooth/gatt/GattService.java b/src/com/android/bluetooth/gatt/GattService.java new file mode 100644 index 000000000..56db06dae --- /dev/null +++ b/src/com/android/bluetooth/gatt/GattService.java @@ -0,0 +1,1676 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.gatt; + +import android.app.Service; +import android.bluetooth.BluetoothAdapter; +import android.bluetooth.BluetoothDevice; +import android.bluetooth.BluetoothProfile; +import android.content.Intent; +import android.os.ParcelUuid; +import android.os.RemoteException; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; + +import com.android.bluetooth.btservice.ProfileService; +import com.android.bluetooth.btservice.ProfileService.IProfileServiceBinder; +import android.bluetooth.IBluetoothGatt; +import android.bluetooth.IBluetoothGattCallback; +import android.bluetooth.IBluetoothGattServerCallback; + +/** + * Provides Bluetooth Gatt profile, as a service in + * the Bluetooth application. + * @hide + */ +public class GattService extends ProfileService { + private static final boolean DBG = GattServiceConfig.DBG; + private static final String TAG = GattServiceConfig.TAG_PREFIX + "GattService"; + BluetoothAdapter mAdapter = BluetoothAdapter.getDefaultAdapter(); + + /** + * Search queue to serialize remote onbject inspection. + */ + SearchQueue mSearchQueue = new SearchQueue(); + + /** + * List of our registered clients. + */ + + class ClientMap extends ContextMap<IBluetoothGattCallback> {} + ClientMap mClientMap = new ClientMap(); + + /** + * List of our registered server apps. + */ + class ServerMap extends ContextMap<IBluetoothGattServerCallback> {} + ServerMap mServerMap = new ServerMap(); + + /** + * Server handle map. + */ + HandleMap mHandleMap = new HandleMap(); + + /** + * Pending service declaration queue + */ + private List<ServiceDeclaration> mServiceDeclarations = new ArrayList<ServiceDeclaration>(); + + private ServiceDeclaration addDeclaration() { + synchronized (mServiceDeclarations) { + mServiceDeclarations.add(new ServiceDeclaration()); + } + return getActiveDeclaration(); + } + + private ServiceDeclaration getActiveDeclaration() { + synchronized (mServiceDeclarations) { + if (mServiceDeclarations.size() > 0) + return mServiceDeclarations.get(mServiceDeclarations.size() - 1); + } + return null; + } + + private ServiceDeclaration getPendingDeclaration() { + synchronized (mServiceDeclarations) { + if (mServiceDeclarations.size() > 0) + return mServiceDeclarations.get(0); + } + return null; + } + + private void removePendingDeclaration() { + synchronized (mServiceDeclarations) { + if (mServiceDeclarations.size() > 0) + mServiceDeclarations.remove(0); + } + } + + /** + * List of clients intereste in scan results. + */ + private List<ScanClient> mScanQueue = new ArrayList<ScanClient>(); + + private ScanClient getScanClient(int appIf, boolean isServer) { + for(ScanClient client : mScanQueue) { + if (client.appIf == appIf && client.isServer == isServer) { + return client; + } + } + return null; + } + + private void removeScanClient(int appIf, boolean isServer) { + for(ScanClient client : mScanQueue) { + if (client.appIf == appIf && client.isServer == isServer) { + mScanQueue.remove(client); + break; + } + } + } + + /** + * Reliable write queue + */ + private Set<String> mReliableQueue = new HashSet<String>(); + + static { + classInitNative(); + } + + protected String getName() { + return TAG; + } + + protected IProfileServiceBinder initBinder() { + return new BluetoothGattBinder(this); + } + + protected boolean start() { + if (DBG) Log.d(TAG, "start()"); + initializeNative(); + return true; + } + + protected boolean stop() { + if (DBG) Log.d(TAG, "stop()"); + mClientMap.clear(); + mServerMap.clear(); + mSearchQueue.clear(); + mScanQueue.clear(); + mHandleMap.clear(); + mServiceDeclarations.clear(); + mReliableQueue.clear(); + return true; + } + + protected boolean cleanup() { + if (DBG) Log.d(TAG, "cleanup()"); + cleanupNative(); + return true; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + if (GattDebugUtils.handleDebugAction(this, intent)) { + return Service.START_NOT_STICKY; + } + return super.onStartCommand(intent, flags, startId); + } + + /** + * Handlers for incoming service calls + */ + private static class BluetoothGattBinder extends IBluetoothGatt.Stub implements IProfileServiceBinder { + private GattService mService; + + public BluetoothGattBinder(GattService svc) { + mService = svc; + } + + public boolean cleanup() { + mService = null; + return true; + } + + private GattService getService() { + if (mService != null && mService.isAvailable()) return mService; + Log.e(TAG, "getService() - Service requested, but not available!"); + return null; + } + + public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + GattService service = getService(); + if (service == null) return new ArrayList<BluetoothDevice>(); + return service.getDevicesMatchingConnectionStates(states); + } + + public void registerClient(ParcelUuid uuid, IBluetoothGattCallback callback) { + GattService service = getService(); + if (service == null) return; + service.registerClient(uuid.getUuid(), callback); + } + + public void unregisterClient(int clientIf) { + GattService service = getService(); + if (service == null) return; + service.unregisterClient(clientIf); + } + + public void startScan(int appIf, boolean isServer) { + GattService service = getService(); + if (service == null) return; + service.startScan(appIf, isServer); + } + + public void startScanWithUuids(int appIf, boolean isServer, ParcelUuid[] ids) { + GattService service = getService(); + if (service == null) return; + UUID[] uuids = new UUID[ids.length]; + for(int i = 0; i != ids.length; ++i) { + uuids[i] = ids[i].getUuid(); + } + service.startScanWithUuids(appIf, isServer, uuids); + } + + public void stopScan(int appIf, boolean isServer) { + GattService service = getService(); + if (service == null) return; + service.stopScan(appIf, isServer); + } + + public void clientConnect(int clientIf, String address, boolean isDirect) { + GattService service = getService(); + if (service == null) return; + service.clientConnect(clientIf, address, isDirect); + } + + public void clientDisconnect(int clientIf, String address) { + GattService service = getService(); + if (service == null) return; + service.clientDisconnect(clientIf, address); + } + + public void refreshDevice(int clientIf, String address) { + GattService service = getService(); + if (service == null) return; + service.refreshDevice(clientIf, address); + } + + public void discoverServices(int clientIf, String address) { + GattService service = getService(); + if (service == null) return; + service.discoverServices(clientIf, address); + } + + public void readCharacteristic(int clientIf, String address, int srvcType, + int srvcInstanceId, ParcelUuid srvcId, + int charInstanceId, ParcelUuid charId, + int authReq) { + GattService service = getService(); + if (service == null) return; + service.readCharacteristic(clientIf, address, srvcType, srvcInstanceId, + srvcId.getUuid(), charInstanceId, + charId.getUuid(), authReq); + } + + public void writeCharacteristic(int clientIf, String address, int srvcType, + int srvcInstanceId, ParcelUuid srvcId, + int charInstanceId, ParcelUuid charId, + int writeType, int authReq, byte[] value) { + GattService service = getService(); + if (service == null) return; + service.writeCharacteristic(clientIf, address, srvcType, srvcInstanceId, + srvcId.getUuid(), charInstanceId, + charId.getUuid(), writeType, authReq, + value); + } + + public void readDescriptor(int clientIf, String address, int srvcType, + int srvcInstanceId, ParcelUuid srvcId, + int charInstanceId, ParcelUuid charId, + ParcelUuid descrId, int authReq) { + GattService service = getService(); + if (service == null) return; + service.readDescriptor(clientIf, address, srvcType, srvcInstanceId, + srvcId.getUuid(), charInstanceId, + charId.getUuid(), descrId.getUuid(), + authReq); + } + + public void writeDescriptor(int clientIf, String address, int srvcType, + int srvcInstanceId, ParcelUuid srvcId, + int charInstanceId, ParcelUuid charId, + ParcelUuid descrId, int writeType, + int authReq, byte[] value) { + GattService service = getService(); + if (service == null) return; + service.writeDescriptor(clientIf, address, srvcType, srvcInstanceId, + srvcId.getUuid(), charInstanceId, + charId.getUuid(), descrId.getUuid(), + writeType, authReq, value); + } + + public void beginReliableWrite(int clientIf, String address) { + GattService service = getService(); + if (service == null) return; + service.beginReliableWrite(clientIf, address); + } + + public void endReliableWrite(int clientIf, String address, boolean execute) { + GattService service = getService(); + if (service == null) return; + service.endReliableWrite(clientIf, address, execute); + } + + public void registerForNotification(int clientIf, String address, int srvcType, + int srvcInstanceId, ParcelUuid srvcId, + int charInstanceId, ParcelUuid charId, + boolean enable) { + GattService service = getService(); + if (service == null) return; + service.registerForNotification(clientIf, address, srvcType, srvcInstanceId, + srvcId.getUuid(), charInstanceId, + charId.getUuid(), enable); + } + + public void readRemoteRssi(int clientIf, String address) { + GattService service = getService(); + if (service == null) return; + service.readRemoteRssi(clientIf, address); + } + + public void registerServer(ParcelUuid uuid, IBluetoothGattServerCallback callback) { + GattService service = getService(); + if (service == null) return; + service.registerServer(uuid.getUuid(), callback); + } + + public void unregisterServer(int serverIf) { + GattService service = getService(); + if (service == null) return; + service.unregisterServer(serverIf); + } + + public void serverConnect(int serverIf, String address, boolean isDirect) { + GattService service = getService(); + if (service == null) return; + service.serverConnect(serverIf, address, isDirect); + } + + public void serverDisconnect(int serverIf, String address) { + GattService service = getService(); + if (service == null) return; + service.serverDisconnect(serverIf, address); + } + + public void beginServiceDeclaration(int serverIf, int srvcType, + int srvcInstanceId, int minHandles, + ParcelUuid srvcId) { + GattService service = getService(); + if (service == null) return; + service.beginServiceDeclaration(serverIf, srvcType, srvcInstanceId, + minHandles, srvcId.getUuid()); + } + + public void addIncludedService(int serverIf, int srvcType, + int srvcInstanceId, ParcelUuid srvcId) { + GattService service = getService(); + if (service == null) return; + service.addIncludedService(serverIf, srvcType, srvcInstanceId, + srvcId.getUuid()); + } + + public void addCharacteristic(int serverIf, ParcelUuid charId, + int properties, int permissions) { + GattService service = getService(); + if (service == null) return; + service.addCharacteristic(serverIf, charId.getUuid(), properties, + permissions); + } + + public void addDescriptor(int serverIf, ParcelUuid descId, + int permissions) { + GattService service = getService(); + if (service == null) return; + service.addDescriptor(serverIf, descId.getUuid(), permissions); + } + + public void endServiceDeclaration(int serverIf) { + GattService service = getService(); + if (service == null) return; + service.endServiceDeclaration(serverIf); + } + + public void removeService(int serverIf, int srvcType, + int srvcInstanceId, ParcelUuid srvcId) { + GattService service = getService(); + if (service == null) return; + service.removeService(serverIf, srvcType, srvcInstanceId, + srvcId.getUuid()); + } + + public void clearServices(int serverIf) { + GattService service = getService(); + if (service == null) return; + service.clearServices(serverIf); + } + + public void sendResponse(int serverIf, String address, int requestId, + int status, int offset, byte[] value) { + GattService service = getService(); + if (service == null) return; + service.sendResponse(serverIf, address, requestId, status, offset, value); + } + + public void sendNotification(int serverIf, String address, int srvcType, + int srvcInstanceId, ParcelUuid srvcId, + int charInstanceId, ParcelUuid charId, + boolean confirm, byte[] value) { + GattService service = getService(); + if (service == null) return; + service.sendNotification(serverIf, address, srvcType, srvcInstanceId, + srvcId.getUuid(), charInstanceId, charId.getUuid(), confirm, value); + } + + }; + + /************************************************************************** + * Callback functions - CLIENT + *************************************************************************/ + + void onScanResult(String address, int rssi, byte[] adv_data) { + if (DBG) Log.d(TAG, "onScanResult() - address=" + address + + ", rssi=" + rssi); + + List<UUID> remoteUuids = parseUuids(adv_data); + for (ScanClient client : mScanQueue) { + if (client.uuids.length > 0) { + int matches = 0; + for (UUID search : client.uuids) { + for (UUID remote: remoteUuids) { + if (remote.equals(search)) { + ++matches; + break; // Only count 1st match in case of duplicates + } + } + } + + if (matches < client.uuids.length) continue; + } + + if (!client.isServer) { + ClientMap.App app = mClientMap.getById(client.appIf); + if (app != null) { + try { + app.callback.onScanResult(address, rssi, adv_data); + } catch (RemoteException e) { + Log.e(TAG, "Exception: " + e); + mClientMap.remove(client.appIf); + mScanQueue.remove(client); + } + } + } else { + ServerMap.App app = mServerMap.getById(client.appIf); + if (app != null) { + try { + app.callback.onScanResult(address, rssi, adv_data); + } catch (RemoteException e) { + Log.e(TAG, "Exception: " + e); + mServerMap.remove(client.appIf); + mScanQueue.remove(client); + } + } + } + } + } + + void onClientRegistered(int status, int clientIf, long uuidLsb, long uuidMsb) + throws RemoteException { + UUID uuid = new UUID(uuidMsb, uuidLsb); + if (DBG) Log.d(TAG, "onClientRegistered() - UUID=" + uuid + ", clientIf=" + clientIf); + ClientMap.App app = mClientMap.getByUuid(uuid); + if (app != null) { + app.id = clientIf; + app.callback.onClientRegistered(status, clientIf); + } + } + + void onConnected(int clientIf, int connId, int status, String address) + throws RemoteException { + if (DBG) Log.d(TAG, "onConnected() - clientIf=" + clientIf + + ", connId=" + connId + ", address=" + address); + + if (status == 0) mClientMap.addConnection(clientIf, connId, address); + ClientMap.App app = mClientMap.getById(clientIf); + if (app != null) { + app.callback.onClientConnectionState(status, clientIf, true, address); + } + } + + void onDisconnected(int clientIf, int connId, int status, String address) + throws RemoteException { + if (DBG) Log.d(TAG, "onDisconnected() - clientIf=" + clientIf + + ", connId=" + connId + ", address=" + address); + + mClientMap.removeConnection(clientIf, connId); + mSearchQueue.removeConnId(connId); + ClientMap.App app = mClientMap.getById(clientIf); + if (app != null) { + app.callback.onClientConnectionState(status, clientIf, false, address); + } + } + + void onSearchCompleted(int connId, int status) throws RemoteException { + if (DBG) Log.d(TAG, "onSearchCompleted() - connId=" + connId+ ", status=" + status); + // We got all services, now let's explore characteristics... + continueSearch(connId, status); + } + + void onSearchResult(int connId, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb) + throws RemoteException { + UUID uuid = new UUID(srvcUuidMsb, srvcUuidLsb); + String address = mClientMap.addressByConnId(connId); + + if (DBG) Log.d(TAG, "onSearchResult() - address=" + address + ", uuid=" + uuid); + + mSearchQueue.add(connId, srvcType, srvcInstId, srvcUuidLsb, srvcUuidMsb); + + ClientMap.App app = mClientMap.getByConnId(connId); + if (app != null) { + app.callback.onGetService(address, srvcType, srvcInstId, + new ParcelUuid(uuid)); + } + } + + void onGetCharacteristic(int connId, int status, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, + int charInstId, long charUuidLsb, long charUuidMsb, + int charProp) throws RemoteException { + + UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb); + UUID charUuid = new UUID(charUuidMsb, charUuidLsb); + String address = mClientMap.addressByConnId(connId); + + if (DBG) Log.d(TAG, "onGetCharacteristic() - address=" + address + + ", status=" + status + ", charUuid=" + charUuid + ", prop=" + charProp); + + if (status == 0) { + mSearchQueue.add(connId, srvcType, + srvcInstId, srvcUuidLsb, srvcUuidMsb, + charInstId, charUuidLsb, charUuidMsb); + + ClientMap.App app = mClientMap.getByConnId(connId); + if (app != null) { + app.callback.onGetCharacteristic(address, srvcType, + srvcInstId, new ParcelUuid(srvcUuid), + charInstId, new ParcelUuid(charUuid), charProp); + } + + // Get next characteristic in the current service + gattClientGetCharacteristicNative(connId, srvcType, + srvcInstId, srvcUuidLsb, srvcUuidMsb, + charInstId, charUuidLsb, charUuidMsb); + } else { + // Check for included services next + gattClientGetIncludedServiceNative(connId, + srvcType, srvcInstId, srvcUuidLsb, srvcUuidMsb, + 0,0,0,0); + } + } + + void onGetDescriptor(int connId, int status, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, + int charInstId, long charUuidLsb, long charUuidMsb, + long descrUuidLsb, long descrUuidMsb) throws RemoteException { + + UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb); + UUID charUuid = new UUID(charUuidMsb, charUuidLsb); + UUID descUuid = new UUID(descrUuidMsb, descrUuidLsb); + String address = mClientMap.addressByConnId(connId); + + if (DBG) Log.d(TAG, "onGetDescriptor() - address=" + address + + ", status=" + status + ", descUuid=" + descUuid); + + if (status == 0) { + ClientMap.App app = mClientMap.getByConnId(connId); + if (app != null) { + app.callback.onGetDescriptor(address, srvcType, + srvcInstId, new ParcelUuid(srvcUuid), + charInstId, new ParcelUuid(charUuid), + new ParcelUuid(descUuid)); + } + + // Get next descriptor for the current characteristic + gattClientGetDescriptorNative(connId, srvcType, + srvcInstId, srvcUuidLsb, srvcUuidMsb, + charInstId, charUuidLsb, charUuidMsb, + descrUuidLsb, descrUuidMsb); + } else { + // Explore the next service + continueSearch(connId, 0); + } + } + + void onGetIncludedService(int connId, int status, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, int inclSrvcType, + int inclSrvcInstId, long inclSrvcUuidLsb, long inclSrvcUuidMsb) + throws RemoteException { + UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb); + UUID inclSrvcUuid = new UUID(inclSrvcUuidMsb, inclSrvcUuidLsb); + String address = mClientMap.addressByConnId(connId); + + if (DBG) Log.d(TAG, "onGetIncludedService() - address=" + address + + ", status=" + status + ", uuid=" + srvcUuid + + ", inclUuid=" + inclSrvcUuid); + + if (status == 0) { + ClientMap.App app = mClientMap.getByConnId(connId); + if (app != null) { + app.callback.onGetIncludedService(address, + srvcType, srvcInstId, new ParcelUuid(srvcUuid), + inclSrvcType, inclSrvcInstId, new ParcelUuid(inclSrvcUuid)); + } + + // Find additional included services + gattClientGetIncludedServiceNative(connId, + srvcType, srvcInstId, srvcUuidLsb, srvcUuidMsb, + inclSrvcType, inclSrvcInstId, inclSrvcUuidLsb, inclSrvcUuidMsb); + } else { + // Discover descriptors now + continueSearch(connId, 0); + } + } + + void onRegisterForNotifications(int connId, int status, int registered, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, + int charInstId, long charUuidLsb, long charUuidMsb) { + UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb); + UUID charUuid = new UUID(charUuidMsb, charUuidLsb); + String address = mClientMap.addressByConnId(connId); + + if (DBG) Log.d(TAG, "onRegisterForNotifications() - address=" + address + + ", status=" + status + ", registered=" + registered + + ", charUuid=" + charUuid); + } + + void onNotify(int connId, String address, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, + int charInstId, long charUuidLsb, long charUuidMsb, + boolean isNotify, byte[] data) throws RemoteException { + UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb); + UUID charUuid = new UUID(charUuidMsb, charUuidLsb); + + if (DBG) Log.d(TAG, "onNotify() - address=" + address + + ", charUuid=" + charUuid + ", length=" + data.length); + + ClientMap.App app = mClientMap.getByConnId(connId); + if (app != null) { + app.callback.onNotify(address, srvcType, + srvcInstId, new ParcelUuid(srvcUuid), + charInstId, new ParcelUuid(charUuid), + data); + } + } + + void onReadCharacteristic(int connId, int status, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, + int charInstId, long charUuidLsb, long charUuidMsb, + int charType, byte[] data) throws RemoteException { + + UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb); + UUID charUuid = new UUID(charUuidMsb, charUuidLsb); + String address = mClientMap.addressByConnId(connId); + + if (DBG) Log.d(TAG, "onReadCharacteristic() - address=" + address + + ", status=" + status + ", length=" + data.length); + + ClientMap.App app = mClientMap.getByConnId(connId); + if (app != null) { + app.callback.onCharacteristicRead(address, status, srvcType, + srvcInstId, new ParcelUuid(srvcUuid), + charInstId, new ParcelUuid(charUuid), data); + } + } + + void onWriteCharacteristic(int connId, int status, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, + int charInstId, long charUuidLsb, long charUuidMsb) + throws RemoteException { + + UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb); + UUID charUuid = new UUID(charUuidMsb, charUuidLsb); + String address = mClientMap.addressByConnId(connId); + + if (DBG) Log.d(TAG, "onWriteCharacteristic() - address=" + address + + ", status=" + status); + + ClientMap.App app = mClientMap.getByConnId(connId); + if (app != null) { + app.callback.onCharacteristicWrite(address, status, srvcType, + srvcInstId, new ParcelUuid(srvcUuid), + charInstId, new ParcelUuid(charUuid)); + } + } + + void onExecuteCompleted(int connId, int status) throws RemoteException { + String address = mClientMap.addressByConnId(connId); + if (DBG) Log.d(TAG, "onExecuteCompleted() - address=" + address + + ", status=" + status); + + ClientMap.App app = mClientMap.getByConnId(connId); + if (app != null) { + app.callback.onExecuteWrite(address, status); + } + } + + void onReadDescriptor(int connId, int status, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, + int charInstId, long charUuidLsb, long charUuidMsb, + long descrUuidLsb, long descrUuidMsb, + int charType, byte[] data) throws RemoteException { + + UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb); + UUID charUuid = new UUID(charUuidMsb, charUuidLsb); + UUID descrUuid = new UUID(descrUuidMsb, descrUuidLsb); + String address = mClientMap.addressByConnId(connId); + + if (DBG) Log.d(TAG, "onReadDescriptor() - address=" + address + + ", status=" + status + ", length=" + data.length); + + ClientMap.App app = mClientMap.getByConnId(connId); + if (app != null) { + app.callback.onDescriptorRead(address, status, srvcType, + srvcInstId, new ParcelUuid(srvcUuid), + charInstId, new ParcelUuid(charUuid), + new ParcelUuid(descrUuid), data); + } + } + + void onWriteDescriptor(int connId, int status, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, + int charInstId, long charUuidLsb, long charUuidMsb, + long descrUuidLsb, long descrUuidMsb) throws RemoteException { + + UUID srvcUuid = new UUID(srvcUuidMsb, srvcUuidLsb); + UUID charUuid = new UUID(charUuidMsb, charUuidLsb); + UUID descrUuid = new UUID(descrUuidMsb, descrUuidLsb); + String address = mClientMap.addressByConnId(connId); + + if (DBG) Log.d(TAG, "onWriteDescriptor() - address=" + address + + ", status=" + status); + + ClientMap.App app = mClientMap.getByConnId(connId); + if (app != null) { + app.callback.onDescriptorWrite(address, status, srvcType, + srvcInstId, new ParcelUuid(srvcUuid), + charInstId, new ParcelUuid(charUuid), + new ParcelUuid(descrUuid)); + } + } + + void onReadRemoteRssi(int clientIf, String address, + int rssi, int status) throws RemoteException{ + if (DBG) Log.d(TAG, "onReadRemoteRssi() - clientIf=" + clientIf + " address=" + + address + ", rssi=" + rssi + ", status=" + status); + + ClientMap.App app = mClientMap.getById(clientIf); + if (app != null) { + app.callback.onReadRemoteRssi(address, rssi, status); + } + } + + /************************************************************************** + * GATT Service functions - Shared CLIENT/SERVER + *************************************************************************/ + + List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) { + final int DEVICE_TYPE_BREDR = 0x1; + + Map<BluetoothDevice, Integer> deviceStates = new HashMap<BluetoothDevice, + Integer>(); + + // Add paired LE devices + + Set<BluetoothDevice> bondedDevices = mAdapter.getBondedDevices(); + for (BluetoothDevice device : bondedDevices) { + if (getDeviceType(device) != DEVICE_TYPE_BREDR) { + deviceStates.put(device, BluetoothProfile.STATE_DISCONNECTED); + } + } + + // Add connected deviceStates + + Set<String> connectedDevices = new HashSet<String>(); + connectedDevices.addAll(mClientMap.getConnectedDevices()); + connectedDevices.addAll(mServerMap.getConnectedDevices()); + + for (String address : connectedDevices ) { + BluetoothDevice device = mAdapter.getRemoteDevice(address); + if (device != null) { + deviceStates.put(device, BluetoothProfile.STATE_CONNECTED); + } + } + + // Create matching device sub-set + + List<BluetoothDevice> deviceList = new ArrayList<BluetoothDevice>(); + + for (Map.Entry<BluetoothDevice, Integer> entry : deviceStates.entrySet()) { + for(int state : states) { + if (entry.getValue() == state) { + deviceList.add(entry.getKey()); + } + } + } + + return deviceList; + } + + void startScan(int appIf, boolean isServer) { + if (DBG) Log.d(TAG, "startScan() - queue=" + mScanQueue.size()); + + if (getScanClient(appIf, isServer) == null) { + if (DBG) Log.d(TAG, "startScan() - adding client=" + appIf); + mScanQueue.add(new ScanClient(appIf, isServer)); + } + + gattClientScanNative(appIf, true); + } + + void startScanWithUuids(int appIf, boolean isServer, UUID[] uuids) { + if (DBG) Log.d(TAG, "startScanWithUuids() - queue=" + mScanQueue.size()); + + if (getScanClient(appIf, isServer) == null) { + if (DBG) Log.d(TAG, "startScanWithUuids() - adding client=" + appIf); + mScanQueue.add(new ScanClient(appIf, isServer, uuids)); + } + + gattClientScanNative(appIf, true); + } + + void stopScan(int appIf, boolean isServer) { + if (DBG) Log.d(TAG, "stopScan() - queue=" + mScanQueue.size()); + + removeScanClient(appIf, isServer); + + if (mScanQueue.isEmpty()) { + if (DBG) Log.d(TAG, "stopScan() - queue empty; stopping scan"); + gattClientScanNative(appIf, false); + } + } + + /************************************************************************** + * GATT Service functions - CLIENT + *************************************************************************/ + + void registerClient(UUID uuid, IBluetoothGattCallback callback) { + if (DBG) Log.d(TAG, "registerClient() - UUID=" + uuid); + mClientMap.add(uuid, callback); + gattClientRegisterAppNative(uuid.getLeastSignificantBits(), + uuid.getMostSignificantBits()); + } + + void unregisterClient(int clientIf) { + if (DBG) Log.d(TAG, "unregisterClient() - clientIf=" + clientIf); + removeScanClient(clientIf, false); + mClientMap.remove(clientIf); + gattClientUnregisterAppNative(clientIf); + } + + void clientConnect(int clientIf, String address, boolean isDirect) { + if (DBG) Log.d(TAG, "clientConnect() - address=" + address); + gattClientConnectNative(clientIf, address, isDirect); + } + + void clientDisconnect(int clientIf, String address) { + Integer connId = mClientMap.connIdByAddress(clientIf, address); + if (DBG) Log.d(TAG, "clientDisconnect() - address=" + address + ", connId=" + connId); + + gattClientDisconnectNative(clientIf, address, connId != null ? connId : 0); + } + + List<String> getConnectedDevices() { + Set<String> connectedDevAddress = new HashSet<String>(); + connectedDevAddress.addAll(mClientMap.getConnectedDevices()); + connectedDevAddress.addAll(mServerMap.getConnectedDevices()); + List<String> connectedDeviceList = new ArrayList<String>(connectedDevAddress); + return connectedDeviceList; + } + + void refreshDevice(int clientIf, String address) { + if (DBG) Log.d(TAG, "refreshDevice() - address=" + address); + gattClientRefreshNative(clientIf, address); + } + + void discoverServices(int clientIf, String address) { + Integer connId = mClientMap.connIdByAddress(clientIf, address); + if (DBG) Log.d(TAG, "discoverServices() - address=" + address + ", connId=" + connId); + + if (connId != null) + gattClientSearchServiceNative(connId, true, 0, 0); + else + Log.e(TAG, "discoverServices() - No connection for " + address + "..."); + } + + void readCharacteristic(int clientIf, String address, int srvcType, + int srvcInstanceId, UUID srvcUuid, + int charInstanceId, UUID charUuid, int authReq) { + if (DBG) Log.d(TAG, "readCharacteristic() - address=" + address); + + Integer connId = mClientMap.connIdByAddress(clientIf, address); + if (connId != null) + gattClientReadCharacteristicNative(connId, srvcType, + srvcInstanceId, srvcUuid.getLeastSignificantBits(), + srvcUuid.getMostSignificantBits(), charInstanceId, + charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(), + authReq); + else + Log.e(TAG, "readCharacteristic() - No connection for " + address + "..."); + } + + void writeCharacteristic(int clientIf, String address, int srvcType, + int srvcInstanceId, UUID srvcUuid, + int charInstanceId, UUID charUuid, int writeType, + int authReq, byte[] value) { + if (DBG) Log.d(TAG, "writeCharacteristic() - address=" + address); + + if (mReliableQueue.contains(address)) writeType = 3; // Prepared write + + Integer connId = mClientMap.connIdByAddress(clientIf, address); + if (connId != null) + gattClientWriteCharacteristicNative(connId, srvcType, + srvcInstanceId, srvcUuid.getLeastSignificantBits(), + srvcUuid.getMostSignificantBits(), charInstanceId, + charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(), + writeType, authReq, value); + else + Log.e(TAG, "writeCharacteristic() - No connection for " + address + "..."); + } + + void readDescriptor(int clientIf, String address, int srvcType, + int srvcInstanceId, UUID srvcUuid, + int charInstanceId, UUID charUuid, + UUID descrUuid, int authReq) { + if (DBG) Log.d(TAG, "readDescriptor() - address=" + address); + + Integer connId = mClientMap.connIdByAddress(clientIf, address); + if (connId != null) + gattClientReadDescriptorNative(connId, srvcType, + srvcInstanceId, srvcUuid.getLeastSignificantBits(), + srvcUuid.getMostSignificantBits(), charInstanceId, + charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(), + descrUuid.getLeastSignificantBits(), descrUuid.getMostSignificantBits(), + authReq); + else + Log.e(TAG, "readDescriptor() - No connection for " + address + "..."); + }; + + void writeDescriptor(int clientIf, String address, int srvcType, + int srvcInstanceId, UUID srvcUuid, + int charInstanceId, UUID charUuid, + UUID descrUuid, int writeType, + int authReq, byte[] value) { + if (DBG) Log.d(TAG, "writeDescriptor() - address=" + address); + + Integer connId = mClientMap.connIdByAddress(clientIf, address); + if (connId != null) + gattClientWriteDescriptorNative(connId, srvcType, + srvcInstanceId, srvcUuid.getLeastSignificantBits(), + srvcUuid.getMostSignificantBits(), charInstanceId, + charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(), + descrUuid.getLeastSignificantBits(), descrUuid.getMostSignificantBits(), + writeType, authReq, value); + else + Log.e(TAG, "writeDescriptor() - No connection for " + address + "..."); + } + + void beginReliableWrite(int clientIf, String address) { + if (DBG) Log.d(TAG, "beginReliableWrite() - address=" + address); + mReliableQueue.add(address); + } + + void endReliableWrite(int clientIf, String address, boolean execute) { + if (DBG) Log.d(TAG, "endReliableWrite() - address=" + address + + " execute: " + execute); + mReliableQueue.remove(address); + + Integer connId = mClientMap.connIdByAddress(clientIf, address); + if (connId != null) gattClientExecuteWriteNative(connId, execute); + } + + void registerForNotification(int clientIf, String address, int srvcType, + int srvcInstanceId, UUID srvcUuid, + int charInstanceId, UUID charUuid, + boolean enable) { + if (DBG) Log.d(TAG, "registerForNotification() - address=" + address + " enable: " + enable); + + Integer connId = mClientMap.connIdByAddress(clientIf, address); + if (connId != null) { + gattClientRegisterForNotificationsNative(clientIf, address, + srvcType, srvcInstanceId, srvcUuid.getLeastSignificantBits(), + srvcUuid.getMostSignificantBits(), charInstanceId, + charUuid.getLeastSignificantBits(), charUuid.getMostSignificantBits(), + enable); + } else { + Log.e(TAG, "registerForNotification() - No connection for " + address + "..."); + } + } + + void readRemoteRssi(int clientIf, String address) { + if (DBG) Log.d(TAG, "readRemoteRssi() - address=" + address); + gattClientReadRemoteRssiNative(clientIf, address); + } + + /************************************************************************** + * Callback functions - SERVER + *************************************************************************/ + + void onServerRegistered(int status, int serverIf, long uuidLsb, long uuidMsb) + throws RemoteException { + + UUID uuid = new UUID(uuidMsb, uuidLsb); + if (DBG) Log.d(TAG, "onServerRegistered() - UUID=" + uuid + ", serverIf=" + serverIf); + ServerMap.App app = mServerMap.getByUuid(uuid); + if (app != null) { + app.id = serverIf; + app.callback.onServerRegistered(status, serverIf); + } + } + + void onServiceAdded(int status, int serverIf, int srvcType, int srvcInstId, + long srvcUuidLsb, long srvcUuidMsb, int srvcHandle) + throws RemoteException { + UUID uuid = new UUID(srvcUuidMsb, srvcUuidLsb); + if (DBG) Log.d(TAG, "onServiceAdded() UUID=" + uuid + ", status=" + status + + ", handle=" + srvcHandle); + if (status == 0) + mHandleMap.addService(serverIf, srvcHandle, uuid, srvcType, srvcInstId); + continueServiceDeclaration(serverIf, status, srvcHandle); + } + + void onIncludedServiceAdded(int status, int serverIf, int srvcHandle, + int includedSrvcHandle) throws RemoteException { + if (DBG) Log.d(TAG, "onIncludedServiceAdded() status=" + status + + ", service=" + srvcHandle + ", included=" + includedSrvcHandle); + continueServiceDeclaration(serverIf, status, srvcHandle); + } + + void onCharacteristicAdded(int status, int serverIf, + long charUuidLsb, long charUuidMsb, + int srvcHandle, int charHandle) + throws RemoteException { + UUID uuid = new UUID(charUuidMsb, charUuidLsb); + if (DBG) Log.d(TAG, "onCharacteristicAdded() UUID=" + uuid + ", status=" + status + + ", srvcHandle=" + srvcHandle + ", charHandle=" + charHandle); + if (status == 0) + mHandleMap.addCharacteristic(serverIf, charHandle, uuid, srvcHandle); + continueServiceDeclaration(serverIf, status, srvcHandle); + } + + void onDescriptorAdded(int status, int serverIf, + long descrUuidLsb, long descrUuidMsb, + int srvcHandle, int descrHandle) + throws RemoteException { + UUID uuid = new UUID(descrUuidMsb, descrUuidLsb); + if (DBG) Log.d(TAG, "onDescriptorAdded() UUID=" + uuid + ", status=" + status + + ", srvcHandle=" + srvcHandle + ", descrHandle=" + descrHandle); + if (status == 0) + mHandleMap.addDescriptor(serverIf, descrHandle, uuid, srvcHandle); + continueServiceDeclaration(serverIf, status, srvcHandle); + } + + void onServiceStarted(int status, int serverIf, int srvcHandle) + throws RemoteException { + if (DBG) Log.d(TAG, "onServiceStarted() srvcHandle=" + srvcHandle + + ", status=" + status); + if (status == 0) + mHandleMap.setStarted(serverIf, srvcHandle, true); + } + + void onServiceStopped(int status, int serverIf, int srvcHandle) + throws RemoteException { + if (DBG) Log.d(TAG, "onServiceStopped() srvcHandle=" + srvcHandle + + ", status=" + status); + if (status == 0) + mHandleMap.setStarted(serverIf, srvcHandle, false); + stopNextService(serverIf, status); + } + + void onServiceDeleted(int status, int serverIf, int srvcHandle) { + if (DBG) Log.d(TAG, "onServiceDeleted() srvcHandle=" + srvcHandle + + ", status=" + status); + mHandleMap.deleteService(serverIf, srvcHandle); + } + + void onClientConnected(String address, boolean connected, int connId) + throws RemoteException { + + if (DBG) Log.d(TAG, "onConnected() connId=" + connId + + ", address=" + address + ", connected=" + connected); + + Iterator<ServerMap.App> i = mServerMap.mApps.iterator(); + while(i.hasNext()) { + ServerMap.App entry = i.next(); + if (connected) { + mServerMap.addConnection(entry.id, connId, address); + } else { + mServerMap.removeConnection(entry.id, connId); + } + entry.callback.onServerConnectionState((byte)0, entry.id, connected, address); + } + } + + void onAttributeRead(String address, int connId, int transId, + int attrHandle, int offset, boolean isLong) + throws RemoteException { + if (DBG) Log.d(TAG, "onAttributeRead() connId=" + connId + + ", address=" + address + ", handle=" + attrHandle + + ", requestId=" + transId + ", offset=" + offset); + + HandleMap.Entry entry = mHandleMap.getByHandle(attrHandle); + if (entry == null) return; + + if (DBG) Log.d(TAG, "onAttributeRead() UUID=" + entry.uuid + + ", serverIf=" + entry.serverIf + ", type=" + entry.type); + + mHandleMap.addRequest(transId, attrHandle); + + ServerMap.App app = mServerMap.getById(entry.serverIf); + if (app == null) return; + + switch(entry.type) { + case HandleMap.TYPE_CHARACTERISTIC: + { + HandleMap.Entry serviceEntry = mHandleMap.getByHandle(entry.serviceHandle); + app.callback.onCharacteristicReadRequest(address, transId, offset, isLong, + serviceEntry.serviceType, serviceEntry.instance, + new ParcelUuid(serviceEntry.uuid), entry.instance, + new ParcelUuid(entry.uuid)); + break; + } + + case HandleMap.TYPE_DESCRIPTOR: + { + HandleMap.Entry serviceEntry = mHandleMap.getByHandle(entry.serviceHandle); + HandleMap.Entry charEntry = mHandleMap.getByHandle(entry.charHandle); + app.callback.onDescriptorReadRequest(address, transId, offset, isLong, + serviceEntry.serviceType, serviceEntry.instance, + new ParcelUuid(serviceEntry.uuid), charEntry.instance, + new ParcelUuid(charEntry.uuid), + new ParcelUuid(entry.uuid)); + break; + } + + default: + Log.e(TAG, "onAttributeRead() - Requested unknown attribute type."); + break; + } + } + + void onAttributeWrite(String address, int connId, int transId, + int attrHandle, int offset, int length, + boolean needRsp, boolean isPrep, + byte[] data) + throws RemoteException { + if (DBG) Log.d(TAG, "onAttributeWrite() connId=" + connId + + ", address=" + address + ", handle=" + attrHandle + + ", requestId=" + transId + ", isPrep=" + isPrep + + ", offset=" + offset); + + HandleMap.Entry entry = mHandleMap.getByHandle(attrHandle); + if (entry == null) return; + + if (DBG) Log.d(TAG, "onAttributeWrite() UUID=" + entry.uuid + + ", serverIf=" + entry.serverIf + ", type=" + entry.type); + + mHandleMap.addRequest(transId, attrHandle); + + ServerMap.App app = mServerMap.getById(entry.serverIf); + if (app == null) return; + + switch(entry.type) { + case HandleMap.TYPE_CHARACTERISTIC: + { + HandleMap.Entry serviceEntry = mHandleMap.getByHandle(entry.serviceHandle); + app.callback.onCharacteristicWriteRequest(address, transId, + offset, length, isPrep, needRsp, + serviceEntry.serviceType, serviceEntry.instance, + new ParcelUuid(serviceEntry.uuid), entry.instance, + new ParcelUuid(entry.uuid), data); + break; + } + + case HandleMap.TYPE_DESCRIPTOR: + { + HandleMap.Entry serviceEntry = mHandleMap.getByHandle(entry.serviceHandle); + HandleMap.Entry charEntry = mHandleMap.getByHandle(entry.charHandle); + app.callback.onDescriptorWriteRequest(address, transId, + offset, length, isPrep, needRsp, + serviceEntry.serviceType, serviceEntry.instance, + new ParcelUuid(serviceEntry.uuid), charEntry.instance, + new ParcelUuid(charEntry.uuid), + new ParcelUuid(entry.uuid), data); + break; + } + + default: + Log.e(TAG, "onAttributeWrite() - Requested unknown attribute type."); + break; + } + } + + void onExecuteWrite(String address, int connId, int transId, int execWrite) + throws RemoteException { + if (DBG) Log.d(TAG, "onExecuteWrite() connId=" + connId + + ", address=" + address + ", transId=" + transId); + + ServerMap.App app = mServerMap.getByConnId(connId); + if (app == null) return; + + app.callback.onExecuteWrite(address, transId, execWrite == 1); + } + + void onResponseSendCompleted(int status, int attrHandle) { + if (DBG) Log.d(TAG, "onResponseSendCompleted() handle=" + attrHandle); + } + + /************************************************************************** + * GATT Service functions - SERVER + *************************************************************************/ + + void registerServer(UUID uuid, IBluetoothGattServerCallback callback) { + if (DBG) Log.d(TAG, "registerServer() - UUID=" + uuid); + mServerMap.add(uuid, callback); + gattServerRegisterAppNative(uuid.getLeastSignificantBits(), + uuid.getMostSignificantBits()); + } + + void unregisterServer(int serverIf) { + if (DBG) Log.d(TAG, "unregisterServer() - serverIf=" + serverIf); + + deleteServices(serverIf); + + mServerMap.remove(serverIf); + gattServerUnregisterAppNative(serverIf); + } + + void serverConnect(int serverIf, String address, boolean isDirect) { + if (DBG) Log.d(TAG, "serverConnect() - address=" + address); + gattServerConnectNative(serverIf, address, isDirect); + } + + void serverDisconnect(int serverIf, String address) { + Integer connId = mServerMap.connIdByAddress(serverIf, address); + if (DBG) Log.d(TAG, "serverDisconnect() - address=" + address + ", connId=" + connId); + + gattServerDisconnectNative(serverIf, address, connId != null ? connId : 0); + } + + void beginServiceDeclaration(int serverIf, int srvcType, int srvcInstanceId, + int minHandles, UUID srvcUuid) { + if (DBG) Log.d(TAG, "beginServiceDeclaration() - uuid=" + srvcUuid); + ServiceDeclaration serviceDeclaration = addDeclaration(); + serviceDeclaration.addService(srvcUuid, srvcType, srvcInstanceId, minHandles); + } + + void addIncludedService(int serverIf, int srvcType, int srvcInstanceId, + UUID srvcUuid) { + if (DBG) Log.d(TAG, "addIncludedService() - uuid=" + srvcUuid); + getActiveDeclaration().addIncludedService(srvcUuid, srvcType, srvcInstanceId); + } + + void addCharacteristic(int serverIf, UUID charUuid, int properties, + int permissions) { + if (DBG) Log.d(TAG, "addCharacteristic() - uuid=" + charUuid); + getActiveDeclaration().addCharacteristic(charUuid, properties, permissions); + } + + void addDescriptor(int serverIf, UUID descUuid, int permissions) { + if (DBG) Log.d(TAG, "addDescriptor() - uuid=" + descUuid); + getActiveDeclaration().addDescriptor(descUuid, permissions); + } + + void endServiceDeclaration(int serverIf) { + if (DBG) Log.d(TAG, "endServiceDeclaration()"); + + if (getActiveDeclaration() == getPendingDeclaration()) { + try { + continueServiceDeclaration(serverIf, (byte)0, 0); + } catch (RemoteException e) { + Log.e(TAG,""+e); + } + } + } + + void removeService(int serverIf, int srvcType, + int srvcInstanceId, UUID srvcUuid) { + if (DBG) Log.d(TAG, "removeService() - uuid=" + srvcUuid); + + int srvcHandle = mHandleMap.getServiceHandle(srvcUuid, srvcType, srvcInstanceId); + if (srvcHandle == 0) return; + gattServerDeleteServiceNative(serverIf, srvcHandle); + } + + void clearServices(int serverIf) { + if (DBG) Log.d(TAG, "clearServices()"); + deleteServices(serverIf); + } + + void sendResponse(int serverIf, String address, int requestId, + int status, int offset, byte[] value) { + if (DBG) Log.d(TAG, "sendResponse() - address=" + address); + + int handle = 0; + HandleMap.Entry entry = mHandleMap.getByRequestId(requestId); + if (entry != null) handle = entry.handle; + + int connId = mServerMap.connIdByAddress(serverIf, address); + gattServerSendResponseNative(serverIf, connId, requestId, (byte)status, + handle, offset, value, (byte)0); + mHandleMap.deleteRequest(requestId); + } + + void sendNotification(int serverIf, String address, int srvcType, + int srvcInstanceId, UUID srvcUuid, + int charInstanceId, UUID charUuid, + boolean confirm, byte[] value) { + if (DBG) Log.d(TAG, "sendNotification() - address=" + address); + + int srvcHandle = mHandleMap.getServiceHandle(srvcUuid, srvcType, srvcInstanceId); + if (srvcHandle == 0) return; + + int charHandle = mHandleMap.getCharacteristicHandle(srvcHandle, charUuid, charInstanceId); + if (charHandle == 0) return; + + int connId = mServerMap.connIdByAddress(serverIf, address); + if (connId == 0) return; + + if (confirm) { + gattServerSendIndicationNative(serverIf, charHandle, connId, value); + } else { + gattServerSendNotificationNative(serverIf, charHandle, connId, value); + } + } + + /************************************************************************** + * Private functions + *************************************************************************/ + + private int getDeviceType(BluetoothDevice device) { + int type = gattClientGetDeviceTypeNative(device.getAddress()); + if (DBG) Log.d(TAG, "getDeviceType() - device=" + device + + ", type=" + type); + return type; + } + + private void continueSearch(int connId, int status) throws RemoteException { + if (status == 0 && !mSearchQueue.isEmpty()) { + SearchQueue.Entry svc = mSearchQueue.pop(); + + if (svc.charUuidLsb == 0) { + // Characteristic is up next + gattClientGetCharacteristicNative(svc.connId, svc.srvcType, + svc.srvcInstId, svc.srvcUuidLsb, svc.srvcUuidMsb, 0, 0, 0); + } else { + // Descriptor is up next + gattClientGetDescriptorNative(svc.connId, svc.srvcType, + svc.srvcInstId, svc.srvcUuidLsb, svc.srvcUuidMsb, + svc.charInstId, svc.charUuidLsb, svc.charUuidMsb, 0,0); + } + } else { + ClientMap.App app = mClientMap.getByConnId(connId); + if (app != null) { + app.callback.onSearchComplete(mClientMap.addressByConnId(connId), status); + } + } + } + + private void continueServiceDeclaration(int serverIf, int status, int srvcHandle) throws RemoteException { + if (mServiceDeclarations.size() == 0) return; + if (DBG) Log.d(TAG, "continueServiceDeclaration() - srvcHandle=" + srvcHandle); + + boolean finished = false; + + ServiceDeclaration.Entry entry = null; + if (status == 0) + entry = getPendingDeclaration().getNext(); + + if (entry != null) { + if (DBG) Log.d(TAG, "continueServiceDeclaration() - next entry type=" + + entry.type); + switch(entry.type) { + case ServiceDeclaration.TYPE_SERVICE: + gattServerAddServiceNative(serverIf, entry.serviceType, + entry.instance, + entry.uuid.getLeastSignificantBits(), + entry.uuid.getMostSignificantBits(), + getPendingDeclaration().getNumHandles()); + break; + + case ServiceDeclaration.TYPE_CHARACTERISTIC: + gattServerAddCharacteristicNative(serverIf, srvcHandle, + entry.uuid.getLeastSignificantBits(), + entry.uuid.getMostSignificantBits(), + entry.properties, entry.permissions); + break; + + case ServiceDeclaration.TYPE_DESCRIPTOR: + gattServerAddDescriptorNative(serverIf, srvcHandle, + entry.uuid.getLeastSignificantBits(), + entry.uuid.getMostSignificantBits(), + entry.permissions); + break; + + case ServiceDeclaration.TYPE_INCLUDED_SERVICE: + { + int inclSrvc = mHandleMap.getServiceHandle(entry.uuid, + entry.serviceType, entry.instance); + if (inclSrvc != 0) { + gattServerAddIncludedServiceNative(serverIf, srvcHandle, + inclSrvc); + } else { + finished = true; + } + break; + } + } + } else { + gattServerStartServiceNative(serverIf, srvcHandle, (byte)2 /*BREDR/LE*/); + finished = true; + } + + if (finished) { + if (DBG) Log.d(TAG, "continueServiceDeclaration() - completed."); + ServerMap.App app = mServerMap.getById(serverIf); + if (app != null) { + HandleMap.Entry serviceEntry = mHandleMap.getByHandle(srvcHandle); + if (serviceEntry != null) { + app.callback.onServiceAdded(status, serviceEntry.serviceType, + serviceEntry.instance, new ParcelUuid(serviceEntry.uuid)); + } else { + app.callback.onServiceAdded(status, 0, 0, null); + } + } + removePendingDeclaration(); + + if (getPendingDeclaration() != null) { + continueServiceDeclaration(serverIf, (byte)0, 0); + } + } + } + + private void stopNextService(int serverIf, int status) throws RemoteException { + if (DBG) Log.d(TAG, "stopNextService() - serverIf=" + serverIf + + ", status=" + status); + + if (status == 0) { + List<HandleMap.Entry> entries = mHandleMap.getEntries(); + for(HandleMap.Entry entry : entries) { + if (entry.type != HandleMap.TYPE_SERVICE || + entry.serverIf != serverIf || + entry.started == false) + continue; + + gattServerStopServiceNative(serverIf, entry.handle); + return; + } + } + } + + private void deleteServices(int serverIf) { + if (DBG) Log.d(TAG, "deleteServices() - serverIf=" + serverIf); + + /* + * Figure out which handles to delete. + * The handles are copied into a new list to avoid race conditions. + */ + List<Integer> handleList = new ArrayList<Integer>(); + List<HandleMap.Entry> entries = mHandleMap.getEntries(); + for(HandleMap.Entry entry : entries) { + if (entry.type != HandleMap.TYPE_SERVICE || + entry.serverIf != serverIf) + continue; + handleList.add(entry.handle); + } + + /* Now actually delete the services.... */ + for(Integer handle : handleList) { + gattServerDeleteServiceNative(serverIf, handle); + } + } + + private List<UUID> parseUuids(byte[] adv_data) { + List<UUID> uuids = new ArrayList<UUID>(); + + int offset = 0; + while(offset < (adv_data.length-2)) { + int len = adv_data[offset++]; + if (len == 0) break; + + int type = adv_data[offset++]; + switch (type) { + case 0x02: // Partial list of 16-bit UUIDs + case 0x03: // Complete list of 16-bit UUIDs + while (len > 1) { + int uuid16 = adv_data[offset++]; + uuid16 += (adv_data[offset++] << 8); + len -= 2; + uuids.add(UUID.fromString(String.format( + "%08x-0000-1000-8000-00805f9b34fb", uuid16))); + } + break; + + default: + offset += (len - 1); + break; + } + } + + return uuids; + } + + /************************************************************************** + * GATT Test functions + *************************************************************************/ + + void gattTestCommand(int command, UUID uuid1, String bda1, + int p1, int p2, int p3, int p4, int p5) { + if (bda1 == null) bda1 = "00:00:00:00:00:00"; + if (uuid1 != null) + gattTestNative(command, uuid1.getLeastSignificantBits(), + uuid1.getMostSignificantBits(), bda1, p1, p2, p3, p4, p5); + else + gattTestNative(command, 0,0, bda1, p1, p2, p3, p4, p5); + } + + private native void gattTestNative(int command, + long uuid1_lsb, long uuid1_msb, String bda1, + int p1, int p2, int p3, int p4, int p5); + + /************************************************************************** + * Native functions prototypes + *************************************************************************/ + + private native static void classInitNative(); + private native void initializeNative(); + private native void cleanupNative(); + + private native int gattClientGetDeviceTypeNative(String address); + + private native void gattClientRegisterAppNative(long app_uuid_lsb, + long app_uuid_msb); + + private native void gattClientUnregisterAppNative(int clientIf); + + private native void gattClientScanNative(int clientIf, boolean start); + + private native void gattClientConnectNative(int clientIf, String address, + boolean isDirect); + + private native void gattClientDisconnectNative(int clientIf, String address, + int conn_id); + + private native void gattClientRefreshNative(int clientIf, String address); + + private native void gattClientSearchServiceNative(int conn_id, + boolean search_all, long service_uuid_lsb, long service_uuid_msb); + + private native void gattClientGetCharacteristicNative(int conn_id, + int service_type, int service_id_inst_id, long service_id_uuid_lsb, + long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb, + long char_id_uuid_msb); + + private native void gattClientGetDescriptorNative(int conn_id, + int service_type, int service_id_inst_id, long service_id_uuid_lsb, + long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb, + long char_id_uuid_msb, long descr_id_uuid_lsb, long descr_id_uuid_msb); + + private native void gattClientGetIncludedServiceNative(int conn_id, + int service_type, int service_id_inst_id, + long service_id_uuid_lsb, long service_id_uuid_msb, + int incl_service_id_inst_id, int incl_service_type, + long incl_service_id_uuid_lsb, long incl_service_id_uuid_msb); + + private native void gattClientReadCharacteristicNative(int conn_id, + int service_type, int service_id_inst_id, long service_id_uuid_lsb, + long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb, + long char_id_uuid_msb, int authReq); + + private native void gattClientReadDescriptorNative(int conn_id, + int service_type, int service_id_inst_id, long service_id_uuid_lsb, + long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb, + long char_id_uuid_msb, long descr_id_uuid_lsb, long descr_id_uuid_msb, + int authReq); + + private native void gattClientWriteCharacteristicNative(int conn_id, + int service_type, int service_id_inst_id, long service_id_uuid_lsb, + long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb, + long char_id_uuid_msb, int write_type, int auth_req, byte[] value); + + private native void gattClientWriteDescriptorNative(int conn_id, + int service_type, int service_id_inst_id, long service_id_uuid_lsb, + long service_id_uuid_msb, int char_id_inst_id, long char_id_uuid_lsb, + long char_id_uuid_msb, long descr_id_uuid_lsb, long descr_id_uuid_msb, + int write_type, int auth_req, byte[] value); + + private native void gattClientExecuteWriteNative(int conn_id, boolean execute); + + private native void gattClientRegisterForNotificationsNative(int clientIf, + String address, int service_type, int service_id_inst_id, + long service_id_uuid_lsb, long service_id_uuid_msb, + int char_id_inst_id, long char_id_uuid_lsb, long char_id_uuid_msb, + boolean enable); + + private native void gattClientReadRemoteRssiNative(int clientIf, + String address); + + private native void gattServerRegisterAppNative(long app_uuid_lsb, + long app_uuid_msb); + + private native void gattServerUnregisterAppNative(int serverIf); + + private native void gattServerConnectNative(int server_if, String address, + boolean is_direct); + + private native void gattServerDisconnectNative(int serverIf, String address, + int conn_id); + + private native void gattServerAddServiceNative (int server_if, + int service_type, int service_id_inst_id, + long service_id_uuid_lsb, long service_id_uuid_msb, + int num_handles); + + private native void gattServerAddIncludedServiceNative (int server_if, + int svc_handle, int included_svc_handle); + + private native void gattServerAddCharacteristicNative (int server_if, + int svc_handle, long char_uuid_lsb, long char_uuid_msb, + int properties, int permissions); + + private native void gattServerAddDescriptorNative (int server_if, + int svc_handle, long desc_uuid_lsb, long desc_uuid_msb, + int permissions); + + private native void gattServerStartServiceNative (int server_if, + int svc_handle, int transport ); + + private native void gattServerStopServiceNative (int server_if, + int svc_handle); + + private native void gattServerDeleteServiceNative (int server_if, + int svc_handle); + + private native void gattServerSendIndicationNative (int server_if, + int attr_handle, int conn_id, byte[] val); + + private native void gattServerSendNotificationNative (int server_if, + int attr_handle, int conn_id, byte[] val); + + private native void gattServerSendResponseNative (int server_if, + int conn_id, int trans_id, int status, int handle, int offset, + byte[] val, int auth_req); +} diff --git a/src/com/android/bluetooth/gatt/GattServiceConfig.java b/src/com/android/bluetooth/gatt/GattServiceConfig.java new file mode 100644 index 000000000..9b5a8abca --- /dev/null +++ b/src/com/android/bluetooth/gatt/GattServiceConfig.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.gatt; + +/** + * GattService configuration. + */ +/*package*/ class GattServiceConfig { + public static final boolean DBG = true; + public static final String TAG_PREFIX = "BtGatt."; + public static final boolean DEBUG_ADMIN = true; +} diff --git a/src/com/android/bluetooth/gatt/HandleMap.java b/src/com/android/bluetooth/gatt/HandleMap.java new file mode 100644 index 000000000..5d45654b9 --- /dev/null +++ b/src/com/android/bluetooth/gatt/HandleMap.java @@ -0,0 +1,213 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.bluetooth.gatt; + +import android.util.Log; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +class HandleMap { + private static final boolean DBG = GattServiceConfig.DBG; + private static final String TAG = GattServiceConfig.TAG_PREFIX + "HandleMap"; + + public static final int TYPE_UNDEFINED = 0; + public static final int TYPE_SERVICE = 1; + public static final int TYPE_CHARACTERISTIC = 2; + public static final int TYPE_DESCRIPTOR = 3; + + class Entry { + int serverIf = 0; + int type = TYPE_UNDEFINED; + int handle = 0; + UUID uuid = null; + int instance = 0; + int serviceType = 0; + int serviceHandle = 0; + int charHandle = 0; + boolean started = false; + + Entry(int serverIf, int handle, UUID uuid, int serviceType, int instance) { + this.serverIf = serverIf; + this.type = TYPE_SERVICE; + this.handle = handle; + this.uuid = uuid; + this.instance = instance; + this.serviceType = serviceType; + } + + Entry(int serverIf, int type, int handle, UUID uuid, int serviceHandle) { + this.serverIf = serverIf; + this.type = type; + this.handle = handle; + this.uuid = uuid; + this.instance = instance; + this.serviceHandle = serviceHandle; + } + + Entry(int serverIf, int type, int handle, UUID uuid, int serviceHandle, int charHandle) { + this.serverIf = serverIf; + this.type = type; + this.handle = handle; + this.uuid = uuid; + this.instance = instance; + this.serviceHandle = serviceHandle; + this.charHandle = charHandle; + } + } + + List<Entry> mEntries = null; + Map<Integer, Integer> mRequestMap = null; + int mLastCharacteristic = 0; + + HandleMap() { + mEntries = new ArrayList<Entry>(); + mRequestMap = new HashMap<Integer, Integer>(); + } + + void clear() { + mEntries.clear(); + mRequestMap.clear(); + } + + void addService(int serverIf, int handle, UUID uuid, int serviceType, int instance) { + mEntries.add(new Entry(serverIf, handle, uuid, serviceType, instance)); + } + + void addCharacteristic(int serverIf, int handle, UUID uuid, int serviceHandle) { + mLastCharacteristic = handle; + mEntries.add(new Entry(serverIf, TYPE_CHARACTERISTIC, handle, uuid, serviceHandle)); + } + + void addDescriptor(int serverIf, int handle, UUID uuid, int serviceHandle) { + mEntries.add(new Entry(serverIf, TYPE_DESCRIPTOR, handle, uuid, serviceHandle, mLastCharacteristic)); + } + + void setStarted(int serverIf, int handle, boolean started) { + for(Entry entry : mEntries) { + if (entry.type != TYPE_SERVICE || + entry.serverIf != serverIf || + entry.handle != handle) + continue; + + entry.started = started; + return; + } + } + + Entry getByHandle(int handle) { + for(Entry entry : mEntries) { + if (entry.handle == handle) + return entry; + } + Log.e(TAG, "getByHandle() - Handle " + handle + " not found!"); + return null; + } + + int getServiceHandle(UUID uuid, int serviceType, int instance) { + for(Entry entry : mEntries) { + if (entry.type == TYPE_SERVICE && + entry.serviceType == serviceType && + entry.instance == instance && + entry.uuid.equals(uuid)) { + return entry.handle; + } + } + Log.e(TAG, "getServiceHandle() - UUID " + uuid + " not found!"); + return 0; + } + + int getCharacteristicHandle(int serviceHandle, UUID uuid, int instance) { + for(Entry entry : mEntries) { + if (entry.type == TYPE_CHARACTERISTIC && + entry.serviceHandle == serviceHandle && + entry.instance == instance && + entry.uuid.equals(uuid)) { + return entry.handle; + } + } + Log.e(TAG, "getCharacteristicHandle() - Service " + serviceHandle + + ", UUID " + uuid + " not found!"); + return 0; + } + + void deleteService(int serverIf, int serviceHandle) { + for(Iterator <Entry> it = mEntries.iterator(); it.hasNext();) { + Entry entry = it.next(); + if (entry.serverIf != serverIf) continue; + + if (entry.handle == serviceHandle || + entry.serviceHandle == serviceHandle) + it.remove(); + } + } + + List<Entry> getEntries() { + return mEntries; + } + + void addRequest(int requestId, int handle) { + mRequestMap.put(requestId, handle); + } + + void deleteRequest(int requestId) { + mRequestMap.remove(requestId); + } + + Entry getByRequestId(int requestId) { + Integer handle = mRequestMap.get(requestId); + if (handle == null) { + Log.e(TAG, "getByRequestId() - Request ID " + requestId + " not found!"); + return null; + } + return getByHandle(handle); + } + + + /** + * Logs debug information. + */ + void dump() { + StringBuilder b = new StringBuilder(); + b.append( "-------------- GATT Handle Map -----------------"); + b.append("\nEntries: " + mEntries.size()); + b.append("\nRequests: " + mRequestMap.size()); + + for (Entry entry : mEntries) { + b.append("\n" + entry.serverIf + ": [" + entry.handle + "] "); + switch(entry.type) { + case TYPE_SERVICE: + b.append("Service " + entry.uuid); + b.append(", started " + entry.started); + break; + + case TYPE_CHARACTERISTIC: + b.append(" Characteristic " + entry.uuid); + break; + + case TYPE_DESCRIPTOR: + b.append(" Descriptor " + entry.uuid); + break; + } + } + + b.append("\n------------------------------------------------"); + Log.d(TAG, b.toString()); + } +} diff --git a/src/com/android/bluetooth/gatt/ScanClient.java b/src/com/android/bluetooth/gatt/ScanClient.java new file mode 100644 index 000000000..3b1e4216c --- /dev/null +++ b/src/com/android/bluetooth/gatt/ScanClient.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.gatt; + +import java.util.UUID; + +/** + * Helper class identifying a client that has requested LE scan results. + * @hide + */ +/*package*/ class ScanClient { + int appIf; + boolean isServer; + UUID[] uuids; + + ScanClient(int appIf, boolean isServer) { + this.appIf = appIf; + this.isServer = isServer; + this.uuids = new UUID[0]; + } + + ScanClient(int appIf, boolean isServer, UUID[] uuids) { + this.appIf = appIf; + this.isServer = isServer; + this.uuids = uuids; + } +} diff --git a/src/com/android/bluetooth/gatt/SearchQueue.java b/src/com/android/bluetooth/gatt/SearchQueue.java new file mode 100644 index 000000000..3064a51e3 --- /dev/null +++ b/src/com/android/bluetooth/gatt/SearchQueue.java @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.bluetooth.gatt; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +/** + * Helper class to store characteristics and descriptors that will be + * queued up for future exploration. + * @hide + */ +/*package*/ class SearchQueue { + class Entry { + public int connId; + public int srvcType; + public int srvcInstId; + public long srvcUuidLsb; + public long srvcUuidMsb; + public int charInstId; + public long charUuidLsb; + public long charUuidMsb; + } + + private List<Entry> mEntries = new ArrayList<Entry>(); + + void add(int connId, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb) { + Entry entry = new Entry(); + entry.connId = connId; + entry.srvcType = srvcType; + entry.srvcInstId = srvcInstId; + entry.srvcUuidLsb = srvcUuidLsb; + entry.srvcUuidMsb = srvcUuidMsb; + entry.charUuidLsb = 0; + mEntries.add(entry); + } + + void add(int connId, int srvcType, + int srvcInstId, long srvcUuidLsb, long srvcUuidMsb, + int charInstId, long charUuidLsb, long charUuidMsb) + { + Entry entry = new Entry(); + entry.connId = connId; + entry.srvcType = srvcType; + entry.srvcInstId = srvcInstId; + entry.srvcUuidLsb = srvcUuidLsb; + entry.srvcUuidMsb = srvcUuidMsb; + entry.charInstId = charInstId; + entry.charUuidLsb = charUuidLsb; + entry.charUuidMsb = charUuidMsb; + mEntries.add(entry); + } + + Entry pop() { + Entry entry = mEntries.get(0); + mEntries.remove(0); + return entry; + } + + void removeConnId(int connId) { + for (Iterator<Entry> it = mEntries.iterator(); it.hasNext();) { + Entry entry = it.next(); + if (entry.connId == connId) { + it.remove(); + } + } + } + + boolean isEmpty() { + return mEntries.isEmpty(); + } + + void clear() { + mEntries.clear(); + } +} diff --git a/src/com/android/bluetooth/gatt/ServiceDeclaration.java b/src/com/android/bluetooth/gatt/ServiceDeclaration.java new file mode 100644 index 000000000..0c9a51b2f --- /dev/null +++ b/src/com/android/bluetooth/gatt/ServiceDeclaration.java @@ -0,0 +1,108 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.bluetooth.gatt; + +import android.util.Log; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.UUID; + +class ServiceDeclaration { + private static final boolean DBG = GattServiceConfig.DBG; + private static final String TAG = GattServiceConfig.TAG_PREFIX + "ServiceDeclaration"; + + public static final byte TYPE_UNDEFINED = 0; + public static final byte TYPE_SERVICE = 1; + public static final byte TYPE_CHARACTERISTIC = 2; + public static final byte TYPE_DESCRIPTOR = 3; + public static final byte TYPE_INCLUDED_SERVICE = 4; + + class Entry { + byte type = TYPE_UNDEFINED; + UUID uuid = null; + int instance = 0; + int permissions = 0; + int properties = 0; + int serviceType = 0; + int serviceHandle = 0; + + Entry(UUID uuid, int serviceType, int instance) { + this.type = TYPE_SERVICE; + this.uuid = uuid; + this.instance = instance; + this.serviceType = serviceType; + } + + Entry(UUID uuid, int properties, int permissions, int instance) { + this.type = TYPE_CHARACTERISTIC; + this.uuid = uuid; + this.instance = instance; + this.permissions = permissions; + this.properties = properties; + } + + Entry(UUID uuid, int permissions) { + this.type = TYPE_DESCRIPTOR; + this.uuid = uuid; + this.permissions = permissions; + } + } + + List<Entry> mEntries = null; + int mNumHandles = 0; + + ServiceDeclaration() { + mEntries = new ArrayList<Entry>(); + } + + void addService(UUID uuid, int serviceType, int instance, int minHandles) { + mEntries.add(new Entry(uuid, serviceType, instance)); + if (minHandles == 0) { + ++mNumHandles; + } else { + mNumHandles = minHandles; + } + } + + void addIncludedService(UUID uuid, int serviceType, int instance) { + Entry entry = new Entry(uuid, serviceType, instance); + entry.type = TYPE_INCLUDED_SERVICE; + mEntries.add(entry); + ++mNumHandles; + } + + void addCharacteristic(UUID uuid, int properties, int permissions) { + mEntries.add(new Entry(uuid, properties, permissions, 0 /*instance*/)); + mNumHandles += 2; + } + + void addDescriptor(UUID uuid, int permissions) { + mEntries.add(new Entry(uuid, permissions)); + ++mNumHandles; + } + + Entry getNext() { + if (mEntries.isEmpty()) return null; + Entry entry = mEntries.get(0); + mEntries.remove(0); + return entry; + } + + int getNumHandles() { + return mNumHandles; + } +} |