Add BluetoothGatt, BluetoothDevice, BluetoothGattCallback.

Note that "BluetoothGatt.refresh" is invoked by reflection.

Test: Skip tests for wrapper class. Will be tested by other tests.

Bug: 200231384
Change-Id: I36b2056d8bc8fe788402d3a9fb58d5bfead1b4f1
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java
new file mode 100644
index 0000000..c767af6
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothDevice.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth;
+
+import android.annotation.TargetApi;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothSocket;
+import android.content.Context;
+import android.os.ParcelUuid;
+
+import java.io.IOException;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+/**
+ * Mockable wrapper of {@link android.bluetooth.BluetoothDevice}.
+ */
+@TargetApi(18)
+public class BluetoothDevice {
+    /** See {@link android.bluetooth.BluetoothDevice#BOND_BONDED}. */
+    public static final int BOND_BONDED = android.bluetooth.BluetoothDevice.BOND_BONDED;
+
+    /** See {@link android.bluetooth.BluetoothDevice#BOND_BONDING}. */
+    public static final int BOND_BONDING = android.bluetooth.BluetoothDevice.BOND_BONDING;
+
+    /** See {@link android.bluetooth.BluetoothDevice#BOND_NONE}. */
+    public static final int BOND_NONE = android.bluetooth.BluetoothDevice.BOND_NONE;
+
+    /** See {@link android.bluetooth.BluetoothDevice#ACTION_ACL_CONNECTED}. */
+    public static final String ACTION_ACL_CONNECTED =
+            android.bluetooth.BluetoothDevice.ACTION_ACL_CONNECTED;
+
+    /** See {@link android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECT_REQUESTED}. */
+    public static final String ACTION_ACL_DISCONNECT_REQUESTED =
+            android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECT_REQUESTED;
+
+    /** See {@link android.bluetooth.BluetoothDevice#ACTION_ACL_DISCONNECTED}. */
+    public static final String ACTION_ACL_DISCONNECTED =
+            android.bluetooth.BluetoothDevice.ACTION_ACL_DISCONNECTED;
+
+    /** See {@link android.bluetooth.BluetoothDevice#ACTION_BOND_STATE_CHANGED}. */
+    public static final String ACTION_BOND_STATE_CHANGED =
+            android.bluetooth.BluetoothDevice.ACTION_BOND_STATE_CHANGED;
+
+    /** See {@link android.bluetooth.BluetoothDevice#ACTION_CLASS_CHANGED}. */
+    public static final String ACTION_CLASS_CHANGED =
+            android.bluetooth.BluetoothDevice.ACTION_CLASS_CHANGED;
+
+    /** See {@link android.bluetooth.BluetoothDevice#ACTION_FOUND}. */
+    public static final String ACTION_FOUND = android.bluetooth.BluetoothDevice.ACTION_FOUND;
+
+    /** See {@link android.bluetooth.BluetoothDevice#ACTION_NAME_CHANGED}. */
+    public static final String ACTION_NAME_CHANGED =
+            android.bluetooth.BluetoothDevice.ACTION_NAME_CHANGED;
+
+    /** See {@link android.bluetooth.BluetoothDevice#ACTION_PAIRING_REQUEST}. */
+    // API 19 only
+    public static final String ACTION_PAIRING_REQUEST =
+            "android.bluetooth.device.action.PAIRING_REQUEST";
+
+    /** See {@link android.bluetooth.BluetoothDevice#ACTION_UUID}. */
+    public static final String ACTION_UUID = android.bluetooth.BluetoothDevice.ACTION_UUID;
+
+    /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_CLASSIC}. */
+    public static final int DEVICE_TYPE_CLASSIC =
+            android.bluetooth.BluetoothDevice.DEVICE_TYPE_CLASSIC;
+
+    /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_DUAL}. */
+    public static final int DEVICE_TYPE_DUAL = android.bluetooth.BluetoothDevice.DEVICE_TYPE_DUAL;
+
+    /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_LE}. */
+    public static final int DEVICE_TYPE_LE = android.bluetooth.BluetoothDevice.DEVICE_TYPE_LE;
+
+    /** See {@link android.bluetooth.BluetoothDevice#DEVICE_TYPE_UNKNOWN}. */
+    public static final int DEVICE_TYPE_UNKNOWN =
+            android.bluetooth.BluetoothDevice.DEVICE_TYPE_UNKNOWN;
+
+    /** See {@link android.bluetooth.BluetoothDevice#ERROR}. */
+    public static final int ERROR = android.bluetooth.BluetoothDevice.ERROR;
+
+    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_BOND_STATE}. */
+    public static final String EXTRA_BOND_STATE =
+            android.bluetooth.BluetoothDevice.EXTRA_BOND_STATE;
+
+    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_CLASS}. */
+    public static final String EXTRA_CLASS = android.bluetooth.BluetoothDevice.EXTRA_CLASS;
+
+    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_DEVICE}. */
+    public static final String EXTRA_DEVICE = android.bluetooth.BluetoothDevice.EXTRA_DEVICE;
+
+    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_NAME}. */
+    public static final String EXTRA_NAME = android.bluetooth.BluetoothDevice.EXTRA_NAME;
+
+    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_PAIRING_KEY}. */
+    // API 19 only
+    public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY";
+
+    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_PAIRING_VARIANT}. */
+    // API 19 only
+    public static final String EXTRA_PAIRING_VARIANT =
+            "android.bluetooth.device.extra.PAIRING_VARIANT";
+
+    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_PREVIOUS_BOND_STATE}. */
+    public static final String EXTRA_PREVIOUS_BOND_STATE =
+            android.bluetooth.BluetoothDevice.EXTRA_PREVIOUS_BOND_STATE;
+
+    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_RSSI}. */
+    public static final String EXTRA_RSSI = android.bluetooth.BluetoothDevice.EXTRA_RSSI;
+
+    /** See {@link android.bluetooth.BluetoothDevice#EXTRA_UUID}. */
+    public static final String EXTRA_UUID = android.bluetooth.BluetoothDevice.EXTRA_UUID;
+
+    /** See {@link android.bluetooth.BluetoothDevice#PAIRING_VARIANT_PASSKEY_CONFIRMATION}. */
+    // API 19 only
+    public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2;
+
+    /** See {@link android.bluetooth.BluetoothDevice#PAIRING_VARIANT_PIN}. */
+    // API 19 only
+    public static final int PAIRING_VARIANT_PIN = 0;
+
+    private final android.bluetooth.BluetoothDevice mWrappedBluetoothDevice;
+
+    private BluetoothDevice(android.bluetooth.BluetoothDevice bluetoothDevice) {
+        mWrappedBluetoothDevice = bluetoothDevice;
+    }
+
+    /**
+     * See {@link android.bluetooth.BluetoothDevice#connectGatt(Context, boolean,
+     * android.bluetooth.BluetoothGattCallback)}.
+     */
+    @Nullable(/* when bt service is not available */)
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+            BluetoothGattCallback callback) {
+        android.bluetooth.BluetoothGatt gatt =
+                mWrappedBluetoothDevice.connectGatt(context, autoConnect, callback.unwrap());
+        if (gatt == null) {
+            return null;
+        }
+        return BluetoothGatt.wrap(gatt);
+    }
+
+    /**
+     * See {@link android.bluetooth.BluetoothDevice#connectGatt(Context, boolean,
+     * android.bluetooth.BluetoothGattCallback, int)}.
+     */
+    @TargetApi(23)
+    @Nullable(/* when bt service is not available */)
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+            BluetoothGattCallback callback, int transport) {
+        android.bluetooth.BluetoothGatt gatt =
+                mWrappedBluetoothDevice.connectGatt(
+                        context, autoConnect, callback.unwrap(), transport);
+        if (gatt == null) {
+            return null;
+        }
+        return BluetoothGatt.wrap(gatt);
+    }
+
+
+    /**
+     * See {@link android.bluetooth.BluetoothDevice#createRfcommSocketToServiceRecord(UUID)}.
+     */
+    public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {
+        return mWrappedBluetoothDevice.createRfcommSocketToServiceRecord(uuid);
+    }
+
+    /**
+     * See
+     * {@link android.bluetooth.BluetoothDevice#createInsecureRfcommSocketToServiceRecord(UUID)}.
+     */
+    public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException {
+        return mWrappedBluetoothDevice.createInsecureRfcommSocketToServiceRecord(uuid);
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#setPin(byte[])}. */
+    @TargetApi(19)
+    public boolean setPairingConfirmation(byte[] pin) {
+        return mWrappedBluetoothDevice.setPin(pin);
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#setPairingConfirmation(boolean)}. */
+    public boolean setPairingConfirmation(boolean confirm) {
+        return mWrappedBluetoothDevice.setPairingConfirmation(confirm);
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#fetchUuidsWithSdp()}. */
+    public boolean fetchUuidsWithSdp() {
+        return mWrappedBluetoothDevice.fetchUuidsWithSdp();
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#createBond()}. */
+    public boolean createBond() {
+        return mWrappedBluetoothDevice.createBond();
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#getUuids()}. */
+    @Nullable(/* on error */)
+    public ParcelUuid[] getUuids() {
+        return mWrappedBluetoothDevice.getUuids();
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#getBondState()}. */
+    public int getBondState() {
+        return mWrappedBluetoothDevice.getBondState();
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#getAddress()}. */
+    public String getAddress() {
+        return mWrappedBluetoothDevice.getAddress();
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#getBluetoothClass()}. */
+    @Nullable(/* on error */)
+    public BluetoothClass getBluetoothClass() {
+        return mWrappedBluetoothDevice.getBluetoothClass();
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#getType()}. */
+    public int getType() {
+        return mWrappedBluetoothDevice.getType();
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#getName()}. */
+    @Nullable(/* on error */)
+    public String getName() {
+        return mWrappedBluetoothDevice.getName();
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#toString()}. */
+    @Override
+    public String toString() {
+        return mWrappedBluetoothDevice.toString();
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#hashCode()}. */
+    @Override
+    public int hashCode() {
+        return mWrappedBluetoothDevice.hashCode();
+    }
+
+    /** See {@link android.bluetooth.BluetoothDevice#equals(Object)}. */
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o ==  this) {
+            return true;
+        }
+        if (!(o instanceof BluetoothDevice)) {
+            return false;
+        }
+        return mWrappedBluetoothDevice.equals(((BluetoothDevice) o).unwrap());
+    }
+
+    /** Unwraps a Bluetooth device. */
+    public android.bluetooth.BluetoothDevice unwrap() {
+        return mWrappedBluetoothDevice;
+    }
+
+    /** Wraps a Bluetooth device. */
+    public static BluetoothDevice wrap(android.bluetooth.BluetoothDevice bluetoothDevice) {
+        return new BluetoothDevice(bluetoothDevice);
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGatt.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGatt.java
new file mode 100644
index 0000000..bec762a
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGatt.java
@@ -0,0 +1,190 @@
+/*
+ * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth;
+
+import android.annotation.TargetApi;
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+import android.bluetooth.BluetoothGattService;
+import android.os.Build;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+import java.util.UUID;
+
+import javax.annotation.Nullable;
+
+/** Mockable wrapper of {@link android.bluetooth.BluetoothGatt}. */
+@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+public class BluetoothGatt {
+
+    /** See {@link android.bluetooth.BluetoothGatt#STATE_CONNECTED}. */
+    public static final int STATE_CONNECTED = android.bluetooth.BluetoothGatt.STATE_CONNECTED;
+
+    /** See {@link android.bluetooth.BluetoothGatt#STATE_DISCONNECTED}. */
+    public static final int STATE_DISCONNECTED = android.bluetooth.BluetoothGatt.STATE_DISCONNECTED;
+
+    /** See {@link android.bluetooth.BluetoothGatt#GATT_REQUEST_NOT_SUPPORTED}. */
+    public static final int GATT_REQUEST_NOT_SUPPORTED =
+            android.bluetooth.BluetoothGatt.GATT_REQUEST_NOT_SUPPORTED;
+
+    /** See {@link android.bluetooth.BluetoothGatt#GATT_SUCCESS}. */
+    public static final int GATT_SUCCESS = android.bluetooth.BluetoothGatt.GATT_SUCCESS;
+
+    /** See {@link android.bluetooth.BluetoothGatt#GATT_FAILURE}. */
+    public static final int GATT_FAILURE = android.bluetooth.BluetoothGatt.GATT_FAILURE;
+
+    /** See {@link android.bluetooth.BluetoothGatt#CONNECTION_PRIORITY_BALANCED}. */
+    public static final int CONNECTION_PRIORITY_BALANCED =
+            android.bluetooth.BluetoothGatt.CONNECTION_PRIORITY_BALANCED;
+
+    /** See {@link android.bluetooth.BluetoothGatt#CONNECTION_PRIORITY_HIGH}. */
+    public static final int CONNECTION_PRIORITY_HIGH =
+            android.bluetooth.BluetoothGatt.CONNECTION_PRIORITY_HIGH;
+
+    /** See {@link android.bluetooth.BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}. */
+    public static final int CONNECTION_PRIORITY_LOW_POWER =
+            android.bluetooth.BluetoothGatt.CONNECTION_PRIORITY_LOW_POWER;
+
+    private final android.bluetooth.BluetoothGatt mWrappedBluetoothGatt;
+
+    private BluetoothGatt(android.bluetooth.BluetoothGatt bluetoothGatt) {
+        mWrappedBluetoothGatt = bluetoothGatt;
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#getDevice()}. */
+    public BluetoothDevice getDevice() {
+        return BluetoothDevice.wrap(mWrappedBluetoothGatt.getDevice());
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#getServices()}. */
+    public List<BluetoothGattService> getServices() {
+        return mWrappedBluetoothGatt.getServices();
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#getService(UUID)}. */
+    @Nullable(/* null if service is not found */)
+    public BluetoothGattService getService(UUID uuid) {
+        return mWrappedBluetoothGatt.getService(uuid);
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#discoverServices()}. */
+    public boolean discoverServices() {
+        return mWrappedBluetoothGatt.discoverServices();
+    }
+
+    /**
+     * Hidden method. Clears the internal cache and forces a refresh of the services from the remote
+     * device.
+     */
+    // TODO(b/201300471): remove refresh call using reflection.
+    public boolean refresh() {
+        try {
+            Method refreshMethod = android.bluetooth.BluetoothGatt.class.getMethod("refresh");
+            return (Boolean) refreshMethod.invoke(mWrappedBluetoothGatt);
+        } catch (NoSuchMethodException
+            | IllegalAccessException
+            | IllegalArgumentException
+            | InvocationTargetException e) {
+            return false;
+        }
+    }
+
+    /**
+     * See {@link android.bluetooth.BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)}.
+     */
+    public boolean readCharacteristic(BluetoothGattCharacteristic characteristic) {
+        return mWrappedBluetoothGatt.readCharacteristic(characteristic);
+    }
+
+    /**
+     * See {@link android.bluetooth.BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic)}.
+     */
+    public boolean writeCharacteristic(BluetoothGattCharacteristic characteristic) {
+        return mWrappedBluetoothGatt.writeCharacteristic(characteristic);
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#readDescriptor(BluetoothGattDescriptor)}. */
+    public boolean readDescriptor(BluetoothGattDescriptor descriptor) {
+        return mWrappedBluetoothGatt.readDescriptor(descriptor);
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#writeDescriptor(BluetoothGattDescriptor)}. */
+    public boolean writeDescriptor(BluetoothGattDescriptor descriptor) {
+        return mWrappedBluetoothGatt.writeDescriptor(descriptor);
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#readRemoteRssi()}. */
+    public boolean readRemoteRssi() {
+        return mWrappedBluetoothGatt.readRemoteRssi();
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#requestConnectionPriority(int)}. */
+    public boolean requestConnectionPriority(int connectionPriority) {
+        return mWrappedBluetoothGatt.requestConnectionPriority(connectionPriority);
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#requestMtu(int)}. */
+    public boolean requestMtu(int mtu) {
+        return mWrappedBluetoothGatt.requestMtu(mtu);
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#setCharacteristicNotification}. */
+    public boolean setCharacteristicNotification(
+            BluetoothGattCharacteristic characteristic, boolean enable) {
+        return mWrappedBluetoothGatt.setCharacteristicNotification(characteristic, enable);
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#disconnect()}. */
+    public void disconnect() {
+        mWrappedBluetoothGatt.disconnect();
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#close()}. */
+    public void close() {
+        mWrappedBluetoothGatt.close();
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#hashCode()}. */
+    @Override
+    public int hashCode() {
+        return mWrappedBluetoothGatt.hashCode();
+    }
+
+    /** See {@link android.bluetooth.BluetoothGatt#equals(Object)}. */
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (!(o instanceof BluetoothGatt)) {
+            return false;
+        }
+        return mWrappedBluetoothGatt.equals(((BluetoothGatt) o).unwrap());
+    }
+
+    /** Unwraps a Bluetooth Gatt instance. */
+    public android.bluetooth.BluetoothGatt unwrap() {
+        return mWrappedBluetoothGatt;
+    }
+
+    /** Wraps a Bluetooth Gatt instance. */
+    public static BluetoothGatt wrap(android.bluetooth.BluetoothGatt gatt) {
+        return new BluetoothGatt(gatt);
+    }
+}
diff --git a/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallback.java b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallback.java
new file mode 100644
index 0000000..33d68cd
--- /dev/null
+++ b/nearby/service/java/com/android/server/nearby/common/bluetooth/testability/android/bluetooth/BluetoothGattCallback.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2021 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.server.nearby.common.bluetooth.testability.android.bluetooth;
+
+import android.bluetooth.BluetoothGattCharacteristic;
+import android.bluetooth.BluetoothGattDescriptor;
+
+/**
+ * Wrapper of {@link android.bluetooth.BluetoothGattCallback} that uses mockable objects.
+ */
+public abstract class BluetoothGattCallback {
+
+    private final android.bluetooth.BluetoothGattCallback mWrappedBluetoothGattCallback =
+            new InternalBluetoothGattCallback();
+
+    /**
+     * See {@link android.bluetooth.BluetoothGattCallback#onConnectionStateChange(
+     * android.bluetooth.BluetoothGatt, int, int)}
+     */
+    public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {}
+
+    /**
+     * See {@link android.bluetooth.BluetoothGattCallback#onServicesDiscovered(
+     * android.bluetooth.BluetoothGatt,int)}
+     */
+    public void onServicesDiscovered(BluetoothGatt gatt, int status) {}
+
+    /**
+     * See {@link android.bluetooth.BluetoothGattCallback#onCharacteristicRead(
+     * android.bluetooth.BluetoothGatt, BluetoothGattCharacteristic, int)}
+     */
+    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
+            int status) {}
+
+    /**
+     * See {@link android.bluetooth.BluetoothGattCallback#onCharacteristicWrite(
+     * android.bluetooth.BluetoothGatt, BluetoothGattCharacteristic, int)}
+     */
+    public void onCharacteristicWrite(BluetoothGatt gatt,
+            BluetoothGattCharacteristic characteristic, int status) {}
+
+    /**
+     * See {@link android.bluetooth.BluetoothGattCallback#onDescriptorRead(
+     * android.bluetooth.BluetoothGatt, BluetoothGattDescriptor, int)}
+     */
+    public void onDescriptorRead(
+            BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {}
+
+    /**
+     * See {@link android.bluetooth.BluetoothGattCallback#onDescriptorWrite(
+     * android.bluetooth.BluetoothGatt, BluetoothGattDescriptor, int)}
+     */
+    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+            int status) {}
+
+    /**
+     * See {@link android.bluetooth.BluetoothGattCallback#onReadRemoteRssi(
+     * android.bluetooth.BluetoothGatt, int, int)}
+     */
+    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {}
+
+    /**
+     * See {@link android.bluetooth.BluetoothGattCallback#onReliableWriteCompleted(
+     * android.bluetooth.BluetoothGatt, int)}
+     */
+    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {}
+
+    /**
+     * See
+     * {@link android.bluetooth.BluetoothGattCallback#onMtuChanged(android.bluetooth.BluetoothGatt,
+     * int, int)}
+     */
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {}
+
+    /**
+     * See
+     * {@link android.bluetooth.BluetoothGattCallback#onCharacteristicChanged(
+     * android.bluetooth.BluetoothGatt, BluetoothGattCharacteristic)}
+     */
+    public void onCharacteristicChanged(BluetoothGatt gatt,
+            BluetoothGattCharacteristic characteristic) {}
+
+    /** Unwraps a Bluetooth Gatt callback. */
+    public android.bluetooth.BluetoothGattCallback unwrap() {
+        return mWrappedBluetoothGattCallback;
+    }
+
+    /** Forward callback to testable instance. */
+    private class InternalBluetoothGattCallback extends android.bluetooth.BluetoothGattCallback {
+        @Override
+        public void onConnectionStateChange(android.bluetooth.BluetoothGatt gatt, int status,
+                int newState) {
+            BluetoothGattCallback.this.onConnectionStateChange(BluetoothGatt.wrap(gatt), status,
+                    newState);
+        }
+
+        @Override
+        public void onServicesDiscovered(android.bluetooth.BluetoothGatt gatt, int status) {
+            BluetoothGattCallback.this.onServicesDiscovered(BluetoothGatt.wrap(gatt), status);
+        }
+
+        @Override
+        public void onCharacteristicRead(android.bluetooth.BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic, int status) {
+            BluetoothGattCallback.this.onCharacteristicRead(
+                    BluetoothGatt.wrap(gatt), characteristic, status);
+        }
+
+        @Override
+        public void onCharacteristicWrite(android.bluetooth.BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic, int status) {
+            BluetoothGattCallback.this.onCharacteristicWrite(
+                    BluetoothGatt.wrap(gatt), characteristic, status);
+        }
+
+        @Override
+        public void onDescriptorRead(android.bluetooth.BluetoothGatt gatt,
+                BluetoothGattDescriptor descriptor, int status) {
+            BluetoothGattCallback.this.onDescriptorRead(
+                    BluetoothGatt.wrap(gatt), descriptor, status);
+        }
+
+        @Override
+        public void onDescriptorWrite(android.bluetooth.BluetoothGatt gatt,
+                BluetoothGattDescriptor descriptor, int status) {
+            BluetoothGattCallback.this.onDescriptorWrite(
+                    BluetoothGatt.wrap(gatt), descriptor, status);
+        }
+
+        @Override
+        public void onReadRemoteRssi(android.bluetooth.BluetoothGatt gatt, int rssi, int status) {
+            BluetoothGattCallback.this.onReadRemoteRssi(BluetoothGatt.wrap(gatt), rssi, status);
+        }
+
+        @Override
+        public void onReliableWriteCompleted(android.bluetooth.BluetoothGatt gatt, int status) {
+            BluetoothGattCallback.this.onReliableWriteCompleted(BluetoothGatt.wrap(gatt), status);
+        }
+
+        @Override
+        public void onMtuChanged(android.bluetooth.BluetoothGatt gatt, int mtu, int status) {
+            BluetoothGattCallback.this.onMtuChanged(BluetoothGatt.wrap(gatt), mtu, status);
+        }
+
+        @Override
+        public void onCharacteristicChanged(android.bluetooth.BluetoothGatt gatt,
+                BluetoothGattCharacteristic characteristic) {
+            BluetoothGattCallback.this.onCharacteristicChanged(
+                    BluetoothGatt.wrap(gatt), characteristic);
+        }
+    }
+}