Initial version of BLE support for Bluedroid

The API classes are hidden for now. Will unhide after API console
approval.
Change-Id: I8283dd562fd6189fdd15c866ef2efb8bbdbc4109
diff --git a/core/java/android/bluetooth/BluetoothGatt.java b/core/java/android/bluetooth/BluetoothGatt.java
new file mode 100644
index 0000000..1e12025
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothGatt.java
@@ -0,0 +1,1309 @@
+/*
+ * 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 android.bluetooth;
+
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.BluetoothProfile.ServiceListener;
+import android.bluetooth.IBluetoothManager;
+import android.bluetooth.IBluetoothStateChangeCallback;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Public API for the Bluetooth Gatt Profile.
+ *
+ * <p>This class provides Bluetooth Gatt functionality to enable communication
+ * with Bluetooth Smart or Smart Ready devices.
+ *
+ * <p>BluetoothGatt is a proxy object for controlling the Bluetooth Service
+ * via IPC.  Use {@link BluetoothAdapter#getProfileProxy} to get the
+ * BluetoothGatt proxy object.
+ *
+ * <p>To connect to a remote peripheral device, create a {@link BluetoothGattCallback}
+ * and call {@link #registerApp} to register your application. Gatt capable
+ * devices can be discovered using the {@link #startScan} function or the
+ * regular Bluetooth device discovery process.
+ * @hide
+ */
+public final class BluetoothGatt implements BluetoothProfile {
+    private static final String TAG = "BluetoothGatt";
+    private static final boolean DBG = true;
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
+    private BluetoothAdapter mAdapter;
+    private IBluetoothGatt mService;
+    private BluetoothGattCallback mCallback;
+    private int mClientIf;
+    private boolean mAuthRetry = false;
+
+    private List<BluetoothGattService> mServices;
+
+    /** A Gatt operation completed successfully */
+    public static final int GATT_SUCCESS = 0;
+
+    /** Gatt read operation is not permitted */
+    public static final int GATT_READ_NOT_PERMITTED = 0x2;
+
+    /** Gatt write operation is not permitted */
+    public static final int GATT_WRITE_NOT_PERMITTED = 0x3;
+
+    /** Insufficient authentication for a given operation */
+    public static final int GATT_INSUFFICIENT_AUTHENTICATION = 0x5;
+
+    /** The given request is not supported */
+    public static final int GATT_REQUEST_NOT_SUPPORTED = 0x6;
+
+    /** Insufficient encryption for a given operation */
+    public static final int GATT_INSUFFICIENT_ENCRYPTION = 0xf;
+
+    /** A read or write operation was requested with an invalid offset */
+    public static final int GATT_INVALID_OFFSET = 0x7;
+
+    /** A write operation exceeds the maximum length of the attribute */
+    public static final int GATT_INVALID_ATTRIBUTE_LENGTH = 0xd;
+
+    /**
+     * No authentication required.
+     * @hide
+     */
+    /*package*/ static final int AUTHENTICATION_NONE = 0;
+
+    /**
+     * Authentication requested; no man-in-the-middle protection required.
+     * @hide
+     */
+    /*package*/ static final int AUTHENTICATION_NO_MITM = 1;
+
+    /**
+     * Authentication with man-in-the-middle protection requested.
+     * @hide
+     */
+    /*package*/ static final int AUTHENTICATION_MITM = 2;
+
+    /**
+     * Bluetooth state change handlers
+     */
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+        new IBluetoothStateChangeCallback.Stub() {
+            public void onBluetoothStateChange(boolean up) {
+                if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                if (!up) {
+                    if (DBG) Log.d(TAG,"Unbinding service...");
+                    synchronized (mConnection) {
+                        mService = null;
+                        mContext.unbindService(mConnection);
+                    }
+                } else {
+                    synchronized (mConnection) {
+                        if (mService == null) {
+                            if (DBG) Log.d(TAG,"Binding service...");
+                            if (!mContext.bindService(new Intent(IBluetoothGatt.class.getName()),
+                                                      mConnection, 0)) {
+                                Log.e(TAG, "Could not bind to Bluetooth GATT Service");
+                            }
+                        }
+                    }
+                }
+            }
+        };
+
+    /**
+     * Service binder handling
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+            public void onServiceConnected(ComponentName className, IBinder service) {
+                if (DBG) Log.d(TAG, "Proxy object connected");
+                mService = IBluetoothGatt.Stub.asInterface(service);
+                ServiceListener serviceListener = mServiceListener;
+                if (serviceListener != null) {
+                    serviceListener.onServiceConnected(BluetoothProfile.GATT, BluetoothGatt.this);
+                }
+            }
+            public void onServiceDisconnected(ComponentName className) {
+                if (DBG) Log.d(TAG, "Proxy object disconnected");
+                mService = null;
+                ServiceListener serviceListener = mServiceListener;
+                if (serviceListener != null) {
+                    serviceListener.onServiceDisconnected(BluetoothProfile.GATT);
+                }
+            }
+        };
+
+    /**
+     * Bluetooth GATT interface callbacks
+     */
+    private final IBluetoothGattCallback mBluetoothGattCallback =
+        new IBluetoothGattCallback.Stub() {
+            /**
+             * Application interface registered - app is ready to go
+             * @hide
+             */
+            public void onClientRegistered(int status, int clientIf) {
+                if (DBG) Log.d(TAG, "onClientRegistered() - status=" + status
+                    + " clientIf=" + clientIf);
+                mClientIf = clientIf;
+                try {
+                    mCallback.onAppRegistered(status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Client connection state changed
+             * @hide
+             */
+            public void onClientConnectionState(int status, int clientIf,
+                                                boolean connected, String address) {
+                if (DBG) Log.d(TAG, "onClientConnectionState() - status=" + status
+                                 + " clientIf=" + clientIf + " device=" + address);
+                try {
+                    mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status,
+                                                      connected ? BluetoothProfile.STATE_CONNECTED
+                                                      : BluetoothProfile.STATE_DISCONNECTED);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Callback reporting an LE scan result.
+             * @hide
+             */
+            public void onScanResult(String address, int rssi, byte[] advData) {
+                if (DBG) Log.d(TAG, "onScanResult() - Device=" + address + " RSSI=" +rssi);
+
+                try {
+                    mCallback.onScanResult(mAdapter.getRemoteDevice(address), rssi, advData);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * A new GATT service has been discovered.
+             * The service is added to the internal list and the search
+             * continues.
+             * @hide
+             */
+            public void onGetService(String address, int srvcType,
+                                     int srvcInstId, ParcelUuid srvcUuid) {
+                if (DBG) Log.d(TAG, "onGetService() - Device=" + address + " UUID=" + srvcUuid);
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                mServices.add(new BluetoothGattService(device, srvcUuid.getUuid(),
+                                                       srvcInstId, srvcType));
+            }
+
+            /**
+             * An included service has been found durig GATT discovery.
+             * The included service is added to the respective parent.
+             * @hide
+             */
+            public void onGetIncludedService(String address, int srvcType,
+                                             int srvcInstId, ParcelUuid srvcUuid,
+                                             int inclSrvcType, int inclSrvcInstId,
+                                             ParcelUuid inclSrvcUuid) {
+                if (DBG) Log.d(TAG, "onGetIncludedService() - Device=" + address
+                    + " UUID=" + srvcUuid + " Included=" + inclSrvcUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device,
+                        srvcUuid.getUuid(), srvcInstId, srvcType);
+                BluetoothGattService includedService = getService(device,
+                        inclSrvcUuid.getUuid(), inclSrvcInstId, inclSrvcType);
+
+                if (service != null && includedService != null) {
+                    service.addIncludedService(includedService);
+                }
+            }
+
+            /**
+             * A new GATT characteristic has been discovered.
+             * Add the new characteristic to the relevant service and continue
+             * the remote device inspection.
+             * @hide
+             */
+            public void onGetCharacteristic(String address, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid,
+                             int charProps) {
+                if (DBG) Log.d(TAG, "onGetCharacteristic() - Device=" + address + " UUID=" +
+                               charUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service != null) {
+                    service.addCharacteristic(new BluetoothGattCharacteristic(
+                           service, charUuid.getUuid(), charInstId, charProps, 0));
+                }
+            }
+
+            /**
+             * A new GATT descriptor has been discovered.
+             * Finally, add the descriptor to the related characteristic.
+             * This should conclude the remote device update.
+             * @hide
+             */
+            public void onGetDescriptor(String address, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid,
+                             ParcelUuid descUuid) {
+                if (DBG) Log.d(TAG, "onGetDescriptor() - Device=" + address + " UUID=" + descUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                    charUuid.getUuid());
+                if (characteristic == null) return;
+
+                characteristic.addDescriptor(new BluetoothGattDescriptor(
+                    characteristic, descUuid.getUuid(), 0));
+            }
+
+            /**
+             * Remote search has been completed.
+             * The internal object structure should now reflect the state
+             * of the remote device database. Let the application know that
+             * we are done at this point.
+             * @hide
+             */
+            public void onSearchComplete(String address, int status) {
+                if (DBG) Log.d(TAG, "onSearchComplete() = Device=" + address + " Status=" + status);
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                try {
+                    mCallback.onServicesDiscovered(device, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Remote characteristic has been read.
+             * Updates the internal value.
+             * @hide
+             */
+            public void onCharacteristicRead(String address, int status, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid, byte[] value) {
+                if (DBG) Log.d(TAG, "onCharacteristicRead() - Device=" + address
+                            + " UUID=" + charUuid + " Status=" + status);
+
+                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                  || status == GATT_INSUFFICIENT_ENCRYPTION)
+                  && mAuthRetry == false) {
+                    try {
+                        mAuthRetry = true;
+                        mService.readCharacteristic(mClientIf, address,
+                            srvcType, srvcInstId, srvcUuid,
+                            charInstId, charUuid, AUTHENTICATION_MITM);
+                        return;
+                    } catch (RemoteException e) {
+                        Log.e(TAG,"",e);
+                    }
+                }
+
+                mAuthRetry = false;
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                        charUuid.getUuid(), charInstId);
+                if (characteristic == null) return;
+
+                if (status == 0) characteristic.setValue(value);
+
+                try {
+                    mCallback.onCharacteristicRead(characteristic, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Characteristic has been written to the remote device.
+             * Let the app know how we did...
+             * @hide
+             */
+            public void onCharacteristicWrite(String address, int status, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid) {
+                if (DBG) Log.d(TAG, "onCharacteristicWrite() - Device=" + address
+                            + " UUID=" + charUuid + " Status=" + status);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                        charUuid.getUuid(), charInstId);
+                if (characteristic == null) return;
+
+                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                  || status == GATT_INSUFFICIENT_ENCRYPTION)
+                  && mAuthRetry == false) {
+                    try {
+                        mAuthRetry = true;
+                        mService.writeCharacteristic(mClientIf, address,
+                            srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+                            characteristic.getWriteType(), AUTHENTICATION_MITM,
+                            characteristic.getValue());
+                        return;
+                    } catch (RemoteException e) {
+                        Log.e(TAG,"",e);
+                    }
+                }
+
+                mAuthRetry = false;
+
+                try {
+                    mCallback.onCharacteristicWrite(characteristic, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Remote characteristic has been updated.
+             * Updates the internal value.
+             * @hide
+             */
+            public void onNotify(String address, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid,
+                             byte[] value) {
+                if (DBG) Log.d(TAG, "onNotify() - Device=" + address + " UUID=" + charUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                        charUuid.getUuid(), charInstId);
+                if (characteristic == null) return;
+
+                characteristic.setValue(value);
+
+                try {
+                    mCallback.onCharacteristicChanged(characteristic);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Descriptor has been read.
+             * @hide
+             */
+            public void onDescriptorRead(String address, int status, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid,
+                             ParcelUuid descrUuid, byte[] value) {
+                if (DBG) Log.d(TAG, "onDescriptorRead() - Device=" + address + " UUID=" + charUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                        charUuid.getUuid(), charInstId);
+                if (characteristic == null) return;
+
+                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+                        descrUuid.getUuid());
+                if (descriptor == null) return;
+
+                if (status == 0) descriptor.setValue(value);
+
+                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                  || status == GATT_INSUFFICIENT_ENCRYPTION)
+                  && mAuthRetry == false) {
+                    try {
+                        mAuthRetry = true;
+                        mService.readDescriptor(mClientIf, address,
+                            srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+                            descrUuid, AUTHENTICATION_MITM);
+                    } catch (RemoteException e) {
+                        Log.e(TAG,"",e);
+                    }
+                }
+
+                mAuthRetry = true;
+
+                try {
+                    mCallback.onDescriptorRead(descriptor, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Descriptor write operation complete.
+             * @hide
+             */
+            public void onDescriptorWrite(String address, int status, int srvcType,
+                             int srvcInstId, ParcelUuid srvcUuid,
+                             int charInstId, ParcelUuid charUuid,
+                             ParcelUuid descrUuid) {
+                if (DBG) Log.d(TAG, "onDescriptorWrite() - Device=" + address + " UUID=" + charUuid);
+
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                BluetoothGattService service = getService(device, srvcUuid.getUuid(),
+                                                          srvcInstId, srvcType);
+                if (service == null) return;
+
+                BluetoothGattCharacteristic characteristic = service.getCharacteristic(
+                        charUuid.getUuid(), charInstId);
+                if (characteristic == null) return;
+
+                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(
+                        descrUuid.getUuid());
+                if (descriptor == null) return;
+
+                if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                  || status == GATT_INSUFFICIENT_ENCRYPTION)
+                  && mAuthRetry == false) {
+                    try {
+                        mAuthRetry = true;
+                        mService.writeDescriptor(mClientIf, address,
+                            srvcType, srvcInstId, srvcUuid, charInstId, charUuid,
+                            descrUuid, characteristic.getWriteType(),
+                            AUTHENTICATION_MITM, descriptor.getValue());
+                    } catch (RemoteException e) {
+                        Log.e(TAG,"",e);
+                    }
+                }
+
+                mAuthRetry = false;
+
+                try {
+                    mCallback.onDescriptorWrite(descriptor, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Prepared write transaction completed (or aborted)
+             * @hide
+             */
+            public void onExecuteWrite(String address, int status) {
+                if (DBG) Log.d(TAG, "onExecuteWrite() - Device=" + address
+                    + " status=" + status);
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                try {
+                    mCallback.onReliableWriteCompleted(device, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+
+            /**
+             * Remote device RSSI has been read
+             * @hide
+             */
+            public void onReadRemoteRssi(String address, int rssi, int status) {
+                if (DBG) Log.d(TAG, "onReadRemoteRssi() - Device=" + address +
+                            " rssi=" + rssi + " status=" + status);
+                BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                try {
+                    mCallback.onReadRemoteRssi(device, rssi, status);
+                } catch (Exception ex) {
+                    Log.w(TAG, "Unhandled exception: " + ex);
+                }
+            }
+        };
+
+    /**
+     * Create a BluetoothGatt proxy object.
+     */
+    /*package*/ BluetoothGatt(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mServices = new ArrayList<BluetoothGattService>();
+
+        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+        if (b != null) {
+            IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b);
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "Unable to register BluetoothStateChangeCallback", re);
+            }
+        } else {
+            Log.e(TAG, "Unable to get BluetoothManager interface.");
+            throw new RuntimeException("BluetoothManager inactive");
+        }
+
+        //Bind to the service only if the Bluetooth is ON
+        if(mAdapter.isEnabled()){
+            if (!context.bindService(new Intent(IBluetoothGatt.class.getName()), mConnection, 0)) {
+                Log.e(TAG, "Could not bind to Bluetooth Gatt Service");
+            }
+        }
+    }
+
+    /**
+     * Close the connection to the gatt service.
+     */
+    /*package*/ void close() {
+        if (DBG) Log.d(TAG, "close()");
+
+        unregisterApp();
+        mServiceListener = null;
+
+        IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+        if (b != null) {
+            IBluetoothManager mgr = IBluetoothManager.Stub.asInterface(b);
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "Unable to unregister BluetoothStateChangeCallback", re);
+            }
+        }
+
+        synchronized (mConnection) {
+            if (mService != null) {
+                mService = null;
+                mContext.unbindService(mConnection);
+            }
+        }
+    }
+
+    /**
+     * Returns a service by UUID, instance and type.
+     * @hide
+     */
+    /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
+                                                int instanceId, int type) {
+        for(BluetoothGattService svc : mServices) {
+            if (svc.getDevice().equals(device) &&
+                svc.getType() == type &&
+                svc.getInstanceId() == instanceId &&
+                svc.getUuid().equals(uuid)) {
+                return svc;
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * Register an application callback to start using Gatt.
+     *
+     * <p>This is an asynchronous call. The callback is used to notify
+     * success or failure if the function returns true.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param callback Gatt callback handler that will receive asynchronous
+     *          callbacks.
+     * @return true, if application was successfully registered.
+     */
+    public boolean registerApp(BluetoothGattCallback callback) {
+        if (DBG) Log.d(TAG, "registerApp()");
+        if (mService == null) return false;
+
+        mCallback = callback;
+        UUID uuid = UUID.randomUUID();
+        if (DBG) Log.d(TAG, "registerApp() - UUID=" + uuid);
+
+        try {
+            mService.registerClient(new ParcelUuid(uuid), mBluetoothGattCallback);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Unregister the current application and callbacks.
+     */
+    public void unregisterApp() {
+        if (DBG) Log.d(TAG, "unregisterApp() - mClientIf=" + mClientIf);
+        if (mService == null || mClientIf == 0) return;
+
+        try {
+            mCallback = null;
+            mService.unregisterClient(mClientIf);
+            mClientIf = 0;
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Starts a scan for Bluetooth LE devices.
+     *
+     * <p>Results of the scan are reported using the
+     * {@link BluetoothGattCallback#onScanResult} callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return true, if the scan was started successfully
+     */
+    public boolean startScan() {
+        if (DBG) Log.d(TAG, "startScan()");
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.startScan(mClientIf, false);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Starts a scan for Bluetooth LE devices, looking for devices that
+     * advertise given services.
+     *
+     * <p>Devices which advertise all specified services are reported using the
+     * {@link BluetoothGattCallback#onScanResult} callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param serviceUuids Array of services to look for
+     * @return true, if the scan was started successfully
+     */
+    public boolean startScan(UUID[] serviceUuids) {
+        if (DBG) Log.d(TAG, "startScan() - with UUIDs");
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            ParcelUuid[] uuids = new ParcelUuid[serviceUuids.length];
+            for(int i = 0; i != uuids.length; ++i) {
+                uuids[i] = new ParcelUuid(serviceUuids[i]);
+            }
+            mService.startScanWithUuids(mClientIf, false, uuids);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Stops an ongoing Bluetooth LE device scan.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     */
+    public void stopScan() {
+        if (DBG) Log.d(TAG, "stopScan()");
+        if (mService == null || mClientIf == 0) return;
+
+        try {
+            mService.stopScan(mClientIf, false);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Initiate a connection to a Bluetooth Gatt capable device.
+     *
+     * <p>The connection may not be established right away, but will be
+     * completed when the remote device is available. A
+     * {@link BluetoothGattCallback#onConnectionStateChange} callback will be
+     * invoked when the connection state changes as a result of this function.
+     *
+     * <p>The autoConnect paramter determines whether to actively connect to
+     * the remote device, or rather passively scan and finalize the connection
+     * when the remote device is in range/available. Generally, the first ever
+     * connection to a device should be direct (autoConnect set to false) and
+     * subsequent connections to known devices should be invoked with the
+     * autoConnect parameter set to false.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device to connect to
+     * @param autoConnect Whether to directly connect to the remote device (false)
+     *                    or to automatically connect as soon as the remote
+     *                    device becomes available (true).
+     * @return true, if the connection attempt was initiated successfully
+     */
+    public boolean connect(BluetoothDevice device, boolean autoConnect) {
+        if (DBG) Log.d(TAG, "connect() - device: " + device.getAddress() + ", auto: " + autoConnect);
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.clientConnect(mClientIf, device.getAddress(),
+                                   autoConnect ? false : true); // autoConnect is inverse of "isDirect"
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Disconnects an established connection, or cancels a connection attempt
+     * currently in progress.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     */
+    public void cancelConnection(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "cancelOpen() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return;
+
+        try {
+            mService.clientDisconnect(mClientIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Discovers services offered by a remote device as well as their
+     * characteristics and descriptors.
+     *
+     * <p>This is an asynchronous operation. Once service discovery is completed,
+     * the {@link BluetoothGattCallback#onServicesDiscovered} callback is
+     * triggered. If the discovery was successful, the remote services can be
+     * retrieved using the {@link #getServices} function.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device to explore
+     * @return true, if the remote service discovery has been started
+     */
+    public boolean discoverServices(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "discoverServices() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        mServices.clear();
+
+        try {
+            mService.discoverServices(mClientIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Returns a list of GATT services offered by the remote device.
+     *
+     * <p>This function requires that service discovery has been completed
+     * for the given device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     * @return List of services on the remote device. Returns an empty list
+     *         if service discovery has not yet been performed.
+     */
+    public List<BluetoothGattService> getServices(BluetoothDevice device) {
+        List<BluetoothGattService> result =
+                new ArrayList<BluetoothGattService>();
+
+        for (BluetoothGattService service : mServices) {
+            if (service.getDevice().equals(device)) {
+                result.add(service);
+            }
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns a {@link BluetoothGattService}, if the requested UUID is
+     * supported by the remote device.
+     *
+     * <p>This function requires that service discovery has been completed
+     * for the given device.
+     *
+     * <p>If multiple instances of the same service (as identified by UUID)
+     * exist, the first instance of the service is returned.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     * @param uuid UUID of the requested service
+     * @return BluetoothGattService if supported, or null if the requested
+     *         service is not offered by the remote device.
+     */
+    public BluetoothGattService getService(BluetoothDevice device, UUID uuid) {
+        for (BluetoothGattService service : mServices) {
+            if (service.getDevice().equals(device) &&
+                service.getUuid().equals(uuid)) {
+                return service;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Reads the requested characteristic from the associated remote device.
+     *
+     * <p>This is an asynchronous operation. The result of the read operation
+     * is reported by the {@link BluetoothGattCallback#onCharacteristicRead}
+     * callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param characteristic Characteristic to read from the remote device
+     * @return true, if the read operation was initiated successfully
+     */
+    public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
+        if ((characteristic.getProperties() &
+                BluetoothGattCharacteristic.PROPERTY_READ) == 0) return false;
+
+        if (DBG) Log.d(TAG, "readCharacteristic() - uuid: " + characteristic.getUuid());
+        if (mService == null || mClientIf == 0) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        BluetoothDevice device = service.getDevice();
+        if (device == null) return false;
+
+        try {
+            mService.readCharacteristic(mClientIf, device.getAddress(),
+                service.getType(), service.getInstanceId(),
+                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+                new ParcelUuid(characteristic.getUuid()), AUTHENTICATION_NONE);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Writes a given characteristic and it's values to the associated remote
+     * device.
+     *
+     * <p>Once the write operation has been completed, the
+     * {@link BluetoothGattCallback#onCharacteristicWrite} callback is invoked,
+     * reporting the result of the operation.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param characteristic Characteristic to write on the remote device
+     * @return true, if the write operation was initiated successfully
+     */
+    public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
+        if ((characteristic.getProperties() & BluetoothGattCharacteristic.PROPERTY_WRITE) == 0
+            && (characteristic.getProperties() &
+                BluetoothGattCharacteristic.PROPERTY_WRITE_NO_RESPONSE) == 0) return false;
+
+        if (DBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
+        if (mService == null || mClientIf == 0) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        BluetoothDevice device = service.getDevice();
+        if (device == null) return false;
+
+        try {
+            mService.writeCharacteristic(mClientIf, device.getAddress(),
+                service.getType(), service.getInstanceId(),
+                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+                new ParcelUuid(characteristic.getUuid()),
+                characteristic.getWriteType(), AUTHENTICATION_NONE,
+                characteristic.getValue());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Reads the value for a given descriptor from the associated remote device.
+     *
+     * <p>Once the read operation has been completed, the
+     * {@link BluetoothGattCallback#onDescriptorRead} callback is
+     * triggered, signaling the result of the operation.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param descriptor Descriptor value to read from the remote device
+     * @return true, if the read operation was initiated successfully
+     */
+    public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
+        if (DBG) Log.d(TAG, "readDescriptor() - uuid: " + descriptor.getUuid());
+        if (mService == null || mClientIf == 0) return false;
+
+        BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
+        if (characteristic == null) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        BluetoothDevice device = service.getDevice();
+        if (device == null) return false;
+
+        try {
+            mService.readDescriptor(mClientIf, device.getAddress(),
+                service.getType(), service.getInstanceId(),
+                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+                new ParcelUuid(characteristic.getUuid()),
+                new ParcelUuid(descriptor.getUuid()), AUTHENTICATION_NONE);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Write the value of a given descriptor to the associated remote device.
+     *
+     * <p>A {@link BluetoothGattCallback#onDescriptorWrite} callback is
+     * triggered to report the result of the write operation.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param descriptor Descriptor to write to the associated remote device
+     * @return true, if the write operation was initiated successfully
+     */
+    public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
+        if (DBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
+        if (mService == null || mClientIf == 0) return false;
+
+        BluetoothGattCharacteristic characteristic = descriptor.getCharacteristic();
+        if (characteristic == null) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        BluetoothDevice device = service.getDevice();
+        if (device == null) return false;
+
+        try {
+            mService.writeDescriptor(mClientIf, device.getAddress(),
+                service.getType(), service.getInstanceId(),
+                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+                new ParcelUuid(characteristic.getUuid()),
+                new ParcelUuid(descriptor.getUuid()),
+                characteristic.getWriteType(), AUTHENTICATION_NONE,
+                descriptor.getValue());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Initiates a reliable write transaction for a given remote device.
+     *
+     * <p>Once a reliable write transaction has been initiated, all calls
+     * to {@link #writeCharacteristic} are sent to the remote device for
+     * verification and queued up for atomic execution. The application will
+     * receive an {@link BluetoothGattCallback#onCharacteristicWrite} callback
+     * in response to every {@link #writeCharacteristic} call and is responsible
+     * for verifying if the value has been transmitted accurately.
+     *
+     * <p>After all characteristics have been queued up and verified,
+     * {@link #executeReliableWrite} will execute all writes. If a characteristic
+     * was not written correctly, calling {@link #abortReliableWrite} will
+     * cancel the current transaction without commiting any values on the
+     * remote device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     * @return true, if the reliable write transaction has been initiated
+     */
+    public boolean beginReliableWrite(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "beginReliableWrite() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.beginReliableWrite(mClientIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Executes a reliable write transaction for a given remote device.
+     *
+     * <p>This function will commit all queued up characteristic write
+     * operations for a given remote device.
+     *
+     * <p>A {@link BluetoothGattCallback#onReliableWriteCompleted} callback is
+     * invoked to indicate whether the transaction has been executed correctly.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     * @return true, if the request to execute the transaction has been sent
+     */
+    public boolean executeReliableWrite(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "executeReliableWrite() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.endReliableWrite(mClientIf, device.getAddress(), true);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Cancels a reliable write transaction for a given device.
+     *
+     * <p>Calling this function will discard all queued characteristic write
+     * operations for a given remote device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     */
+    public void abortReliableWrite(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "abortReliableWrite() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return;
+
+        try {
+            mService.endReliableWrite(mClientIf, device.getAddress(), false);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+    }
+
+    /**
+     * Enable or disable notifications/indications for a given characteristic.
+     *
+     * <p>Once notifications are enabled for a characteristic, a
+     * {@link BluetoothGattCallback#onCharacteristicChanged} callback will be
+     * triggered if the remote device indicates that the given characteristic
+     * has changed.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param characteristic The characteristic for which to enable notifications
+     * @param enable Set to true to enable notifications/indications
+     * @return true, if the requested notification status was set successfully
+     */
+    public boolean setCharacteristicNotification(BluetoothGattCharacteristic characteristic,
+                                              boolean enable) {
+        if (DBG) Log.d(TAG, "setCharacteristicNotification() - uuid: " + characteristic.getUuid()
+                         + " enable: " + enable);
+        if (mService == null || mClientIf == 0) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        BluetoothDevice device = service.getDevice();
+        if (device == null) return false;
+
+        try {
+            mService.registerForNotification(mClientIf, device.getAddress(),
+                service.getType(), service.getInstanceId(),
+                new ParcelUuid(service.getUuid()), characteristic.getInstanceId(),
+                new ParcelUuid(characteristic.getUuid()),
+                enable);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Clears the internal cache and forces a refresh of the services from the
+     * remote device.
+     * @hide
+     */
+    public boolean refresh(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "refresh() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.refreshDevice(mClientIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Read the RSSI for a connected remote device.
+     *
+     * <p>The {@link BluetoothGattCallback#onReadRemoteRssi} callback will be
+     * invoked when the RSSI value has been read.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote device
+     * @return true, if the RSSI value has been requested successfully
+     */
+    public boolean readRemoteRssi(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "readRssi() - device: " + device.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.readRemoteRssi(mClientIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Get the current connection state of the profile.
+     *
+     * <p>This is not specific to any application configuration but represents
+     * the connection state of the local Bluetooth adapter for this profile.
+     * This can be used by applications like status bar which would just like
+     * to know the state of the local adapter.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote bluetooth device.
+     * @return State of the profile connection. One of
+     *               {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+     *               {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING}
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (DBG) Log.d(TAG,"getConnectionState()");
+        if (mService == null) return STATE_DISCONNECTED;
+
+        List<BluetoothDevice> connectedDevices = getConnectedDevices();
+        for(BluetoothDevice connectedDevice : connectedDevices) {
+            if (device.equals(connectedDevice)) {
+                return STATE_CONNECTED;
+            }
+        }
+
+        return STATE_DISCONNECTED;
+    }
+
+    /**
+     * Get connected devices for the Gatt profile.
+     *
+     * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
+     *
+     * <p>This is not specific to any application configuration but represents
+     * the connection state of the local Bluetooth adapter for this profile.
+     * This can be used by applications like status bar which would just like
+     * to know the state of the local adapter.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return List of devices. The list will be empty on error.
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (DBG) Log.d(TAG,"getConnectedDevices");
+
+        List<BluetoothDevice> connectedDevices = new ArrayList<BluetoothDevice>();
+        if (mService == null) return connectedDevices;
+
+        try {
+            connectedDevices = mService.getDevicesMatchingConnectionStates(
+                new int[] { BluetoothProfile.STATE_CONNECTED });
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+
+        return connectedDevices;
+    }
+
+    /**
+     * Get a list of devices that match any of the given connection
+     * states.
+     *
+     * <p> If none of the devices match any of the given states,
+     * an empty list will be returned.
+     *
+     * <p>This is not specific to any application configuration but represents
+     * the connection state of the local Bluetooth adapter for this profile.
+     * This can be used by applications like status bar which would just like
+     * to know the state of the local adapter.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param states Array of states. States can be one of
+     *              {@link #STATE_CONNECTED}, {@link #STATE_CONNECTING},
+     *              {@link #STATE_DISCONNECTED}, {@link #STATE_DISCONNECTING},
+     * @return List of devices. The list will be empty on error.
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) Log.d(TAG,"getDevicesMatchingConnectionStates");
+
+        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+        if (mService == null) return devices;
+
+        try {
+            devices = mService.getDevicesMatchingConnectionStates(states);
+        } catch (RemoteException e) {
+            Log.e(TAG,"",e);
+        }
+
+        return devices;
+    }
+}