Bluetooth: Add support for HID Device Role

This patch adds the HID Device Role support in Bluetooth framework.
Also AIDL and callback related files for HID Device role are added
to provide interface for third party applications to communicate with
HID Device Service.

Change-Id: Id03a362b7bcfa2e76056fa0197eaac12ce49b5a2
diff --git a/Android.mk b/Android.mk
index bac3802..3ef94ab 100644
--- a/Android.mk
+++ b/Android.mk
@@ -125,6 +125,8 @@
 	core/java/android/bluetooth/IBluetoothSap.aidl \
 	core/java/android/bluetooth/IBluetoothStateChangeCallback.aidl \
 	core/java/android/bluetooth/IBluetoothHeadsetClient.aidl \
+	core/java/android/bluetooth/IBluetoothHidDevice.aidl \
+	core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl \
 	core/java/android/bluetooth/IBluetoothGatt.aidl \
 	core/java/android/bluetooth/IBluetoothGattCallback.aidl \
 	core/java/android/bluetooth/IBluetoothGattServerCallback.aidl \
diff --git a/core/java/android/bluetooth/BluetoothAdapter.java b/core/java/android/bluetooth/BluetoothAdapter.java
index 4a97b07..2b35e2b 100644
--- a/core/java/android/bluetooth/BluetoothAdapter.java
+++ b/core/java/android/bluetooth/BluetoothAdapter.java
@@ -1940,6 +1940,9 @@
         } else if (profile == BluetoothProfile.MAP_CLIENT) {
             BluetoothMapClient mapClient = new BluetoothMapClient(context, listener);
             return true;
+        } else if (profile == BluetoothProfile.HID_DEVICE) {
+            BluetoothHidDevice hidd = new BluetoothHidDevice(context, listener);
+            return true;
         } else {
             return false;
         }
@@ -2016,6 +2019,10 @@
                 BluetoothMapClient mapClient = (BluetoothMapClient)proxy;
                 mapClient.close();
                 break;
+            case BluetoothProfile.HID_DEVICE:
+                BluetoothHidDevice hidd = (BluetoothHidDevice) proxy;
+                hidd.close();
+                break;
         }
     }
 
diff --git a/core/java/android/bluetooth/BluetoothHidDevice.java b/core/java/android/bluetooth/BluetoothHidDevice.java
new file mode 100644
index 0000000..3e6a078
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDevice.java
@@ -0,0 +1,500 @@
+/*
+ * Copyright (C) 2016 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.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @hide
+ */
+public final class BluetoothHidDevice implements BluetoothProfile {
+
+    private static final String TAG = BluetoothHidDevice.class.getSimpleName();
+
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+        "android.bluetooth.hid.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Constants representing device subclass.
+     *
+     * @see #registerApp(String, String, String, byte, byte[],
+     *      BluetoothHidDeviceCallback)
+     */
+    public static final byte SUBCLASS1_NONE = (byte) 0x00;
+    public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
+    public static final byte SUBCLASS1_MOUSE = (byte) 0x80;
+    public static final byte SUBCLASS1_COMBO = (byte) 0xC0;
+
+    public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00;
+    public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01;
+    public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02;
+    public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03;
+    public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04;
+    public static final byte SUBCLASS2_DIGITIZER_TABLED = (byte) 0x05;
+    public static final byte SUBCLASS2_CARD_READER = (byte) 0x06;
+
+    /**
+     * Constants representing report types.
+     *
+     * @see BluetoothHidDeviceCallback#onGetReport(byte, byte, int)
+     * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
+     * @see BluetoothHidDeviceCallback#onIntrData(byte, byte[])
+     */
+    public static final byte REPORT_TYPE_INPUT = (byte) 1;
+    public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
+    public static final byte REPORT_TYPE_FEATURE = (byte) 3;
+
+    /**
+     * Constants representing error response for Set Report.
+     *
+     * @see BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])
+     */
+    public static final byte ERROR_RSP_SUCCESS = (byte) 0;
+    public static final byte ERROR_RSP_NOT_READY = (byte) 1;
+    public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2;
+    public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3;
+    public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4;
+    public static final byte ERROR_RSP_UNKNOWN = (byte) 14;
+
+    /**
+     * Constants representing protocol mode used set by host. Default is always
+     * {@link #PROTOCOL_REPORT_MODE} unless notified otherwise.
+     *
+     * @see BluetoothHidDeviceCallback#onSetProtocol(byte)
+     */
+    public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
+    public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
+
+    private Context mContext;
+
+    private ServiceListener mServiceListener;
+
+    private IBluetoothHidDevice mService;
+
+    private BluetoothAdapter mAdapter;
+
+    private static class BluetoothHidDeviceCallbackWrapper extends IBluetoothHidDeviceCallback.Stub {
+
+        private BluetoothHidDeviceCallback mCallback;
+
+        public BluetoothHidDeviceCallbackWrapper(BluetoothHidDeviceCallback callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onAppStatusChanged(BluetoothDevice pluggedDevice,
+                BluetoothHidDeviceAppConfiguration config, boolean registered) {
+            mCallback.onAppStatusChanged(pluggedDevice, config, registered);
+        }
+
+        @Override
+        public void onConnectionStateChanged(BluetoothDevice device, int state) {
+            mCallback.onConnectionStateChanged(device, state);
+        }
+
+        @Override
+        public void onGetReport(byte type, byte id, int bufferSize) {
+            mCallback.onGetReport(type, id, bufferSize);
+        }
+
+        @Override
+        public void onSetReport(byte type, byte id, byte[] data) {
+            mCallback.onSetReport(type, id, data);
+        }
+
+        @Override
+        public void onSetProtocol(byte protocol) {
+            mCallback.onSetProtocol(protocol);
+        }
+
+        @Override
+        public void onIntrData(byte reportId, byte[] data) {
+            mCallback.onIntrData(reportId, data);
+        }
+
+        @Override
+        public void onVirtualCableUnplug() {
+            mCallback.onVirtualCableUnplug();
+        }
+    }
+
+    final private IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+        new IBluetoothStateChangeCallback.Stub() {
+
+        public void onBluetoothStateChange(boolean up) {
+            Log.d(TAG, "onBluetoothStateChange: up=" + up);
+            synchronized (mConnection) {
+                if (!up) {
+                    Log.d(TAG,"Unbinding service...");
+                    if (mService != null) {
+                        mService = null;
+                        try {
+                            mContext.unbindService(mConnection);
+                        } catch (IllegalArgumentException e) {
+                            Log.e(TAG,"onBluetoothStateChange: could not unbind service:", e);
+                        }
+                    }
+                } else {
+                    try {
+                        if (mService == null) {
+                            Log.d(TAG,"Binding HID Device service...");
+                            doBind();
+                        }
+                    } catch (IllegalStateException e) {
+                        Log.e(TAG,"onBluetoothStateChange: could not bind to HID Dev service: ", e);
+                    } catch (SecurityException e) {
+                        Log.e(TAG,"onBluetoothStateChange: could not bind to HID Dev service: ", e);
+                    }
+                }
+            }
+        }
+    };
+
+    private ServiceConnection mConnection = new ServiceConnection() {
+
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            Log.d(TAG, "onServiceConnected()");
+
+            mService = IBluetoothHidDevice.Stub.asInterface(service);
+
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(BluetoothProfile.HID_DEVICE,
+                    BluetoothHidDevice.this);
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            Log.d(TAG, "onServiceDisconnected()");
+
+            mService = null;
+
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected(BluetoothProfile.HID_DEVICE);
+            }
+        }
+    };
+
+    BluetoothHidDevice(Context context, ServiceListener listener) {
+        Log.v(TAG, "BluetoothHidDevice");
+
+        mContext = context;
+        mServiceListener = listener;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+
+        doBind();
+    }
+
+    boolean doBind() {
+        Intent intent = new Intent(IBluetoothHidDevice.class.getName());
+        ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0);
+        intent.setComponent(comp);
+        if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+                android.os.Process.myUserHandle())) {
+            Log.e(TAG, "Could not bind to Bluetooth HID Device Service with " + intent);
+            return false;
+        }
+        Log.d(TAG, "Bound to HID Device Service");
+        return true;
+    }
+
+    void close() {
+        Log.v(TAG, "close()");
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+        }
+
+        synchronized (mConnection) {
+            if (mService != null) {
+                mService = null;
+                try {
+                    mContext.unbindService(mConnection);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG,"close: could not unbind HID Dev service: ", e);
+                }
+           }
+        }
+
+        mServiceListener = null;
+    }
+
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        Log.v(TAG, "getConnectedDevices()");
+        return null;
+    }
+
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        Log.v(TAG, "getDevicesMatchingConnectionStates(): states=" + Arrays.toString(states));
+        return null;
+    }
+
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        Log.v(TAG, "getConnectionState(): device=" + device.getAddress());
+
+        return STATE_DISCONNECTED;
+    }
+
+    /**
+     * Registers application to be used for HID device. Connections to HID
+     * Device are only possible when application is registered. Only one
+     * application can be registered at time. When no longer used, application
+     * should be unregistered using
+     * {@link #unregisterApp(BluetoothHidDeviceAppConfiguration)}.
+     *
+     * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of
+     *             HID Device SDP record.
+     * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of
+     *             Incoming QoS Settings.
+     * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of
+     *             Outgoing QoS Settings.
+     * @param callback {@link BluetoothHidDeviceCallback} object to which
+     *            callback messages will be sent.
+     * @return
+     */
+    public boolean registerApp(BluetoothHidDeviceAppSdpSettings sdp,
+            BluetoothHidDeviceAppQosSettings inQos, BluetoothHidDeviceAppQosSettings outQos,
+            BluetoothHidDeviceCallback callback) {
+        Log.v(TAG, "registerApp(): sdp=" + sdp + " inQos=" + inQos + " outQos=" + outQos
+                + " callback=" + callback);
+
+        boolean result = false;
+
+        if (sdp == null || callback == null) {
+            return false;
+        }
+
+        if (mService != null) {
+            try {
+                BluetoothHidDeviceAppConfiguration config =
+                    new BluetoothHidDeviceAppConfiguration();
+                BluetoothHidDeviceCallbackWrapper cbw =
+                    new BluetoothHidDeviceCallbackWrapper(callback);
+                result = mService.registerApp(config, sdp, inQos, outQos, cbw);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Unregisters application. Active connection will be disconnected and no
+     * new connections will be allowed until registered again using
+     * {@link #registerApp(String, String, String, byte, byte[], BluetoothHidDeviceCallback)}
+     *
+     * @param config {@link BluetoothHidDeviceAppConfiguration} object as
+     *            obtained from
+     *            {@link BluetoothHidDeviceCallback#onAppStatusChanged(BluetoothDevice,
+     *            BluetoothHidDeviceAppConfiguration, boolean)}
+     *
+     * @return
+     */
+    public boolean unregisterApp(BluetoothHidDeviceAppConfiguration config) {
+        Log.v(TAG, "unregisterApp()");
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.unregisterApp(config);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Sends report to remote host using interrupt channel.
+     *
+     * @param id Report Id, as defined in descriptor. Can be 0 in case Report Id
+     *            are not defined in descriptor.
+     * @param data Report data, not including Report Id.
+     * @return
+     */
+    public boolean sendReport(int id, byte[] data) {
+        Log.v(TAG, "sendReport(): id=" + id);
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.sendReport(id, data);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Sends report to remote host as reply for GET_REPORT request from
+     * {@link BluetoothHidDeviceCallback#onGetReport(byte, byte, int)}.
+     *
+     * @param type Report Type, as in request.
+     * @param id Report Id, as in request.
+     * @param data Report data, not including Report Id.
+     * @return
+     */
+    public boolean replyReport(byte type, byte id, byte[] data) {
+        Log.v(TAG, "replyReport(): type=" + type + " id=" + id);
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.replyReport(type, id, data);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Sends error handshake message as reply for invalid SET_REPORT request
+     * from {@link BluetoothHidDeviceCallback#onSetReport(byte, byte, byte[])}.
+     *
+     * @param error Error to be sent for SET_REPORT via HANDSHAKE.
+     * @return
+     */
+    public boolean reportError(byte error) {
+        Log.v(TAG, "reportError(): error = " + error);
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.reportError(error);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Sends Virtual Cable Unplug to currently connected host.
+     *
+     * @return
+     */
+    public boolean unplug() {
+        Log.v(TAG, "unplug()");
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.unplug();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Initiates connection to host which currently has Virtual Cable
+     * established with device.
+     *
+     * @return
+     */
+    public boolean connect() {
+        Log.v(TAG, "connect()");
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.connect();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Disconnects from currently connected host.
+     *
+     * @return
+     */
+    public boolean disconnect() {
+        Log.v(TAG, "disconnect()");
+
+        boolean result = false;
+
+        if (mService != null) {
+            try {
+                result = mService.disconnect();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.aidl b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.aidl
new file mode 100644
index 0000000..283a717
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2016, 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;
+
+parcelable BluetoothHidDeviceAppConfiguration;
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
new file mode 100644
index 0000000..05ba64e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppConfiguration.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Random;
+
+/** @hide */
+public final class BluetoothHidDeviceAppConfiguration implements Parcelable {
+    private final long mHash;
+
+    BluetoothHidDeviceAppConfiguration() {
+        Random rnd = new Random();
+        mHash = rnd.nextLong();
+    }
+
+    BluetoothHidDeviceAppConfiguration(long hash) {
+        mHash = hash;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothHidDeviceAppConfiguration) {
+            BluetoothHidDeviceAppConfiguration config = (BluetoothHidDeviceAppConfiguration) o;
+            return mHash == config.mHash;
+        }
+        return false;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<BluetoothHidDeviceAppConfiguration> CREATOR =
+        new Parcelable.Creator<BluetoothHidDeviceAppConfiguration>() {
+
+        @Override
+        public BluetoothHidDeviceAppConfiguration createFromParcel(Parcel in) {
+            long hash = in.readLong();
+            return new BluetoothHidDeviceAppConfiguration(hash);
+        }
+
+        @Override
+        public BluetoothHidDeviceAppConfiguration[] newArray(int size) {
+            return new BluetoothHidDeviceAppConfiguration[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mHash);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.aidl b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.aidl
new file mode 100644
index 0000000..14f9114
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2016, 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;
+
+parcelable BluetoothHidDeviceAppQosSettings;
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
new file mode 100644
index 0000000..0d6530c
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2016 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Random;
+
+/** @hide */
+public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
+
+    final public int serviceType;
+    final public int tokenRate;
+    final public int tokenBucketSize;
+    final public int peakBandwidth;
+    final public int latency;
+    final public int delayVariation;
+
+    final static public int SERVICE_NO_TRAFFIC = 0x00;
+    final static public int SERVICE_BEST_EFFORT = 0x01;
+    final static public int SERVICE_GUARANTEED = 0x02;
+
+    final static public int MAX = (int) 0xffffffff;
+
+    public BluetoothHidDeviceAppQosSettings(int serviceType, int tokenRate, int tokenBucketSize,
+            int peakBandwidth,
+            int latency, int delayVariation) {
+        this.serviceType = serviceType;
+        this.tokenRate = tokenRate;
+        this.tokenBucketSize = tokenBucketSize;
+        this.peakBandwidth = peakBandwidth;
+        this.latency = latency;
+        this.delayVariation = delayVariation;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothHidDeviceAppQosSettings) {
+            BluetoothHidDeviceAppQosSettings qos = (BluetoothHidDeviceAppQosSettings) o;
+            return false;
+        }
+        return false;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<BluetoothHidDeviceAppQosSettings> CREATOR =
+        new Parcelable.Creator<BluetoothHidDeviceAppQosSettings>() {
+
+        @Override
+        public BluetoothHidDeviceAppQosSettings createFromParcel(Parcel in) {
+
+            return new BluetoothHidDeviceAppQosSettings(in.readInt(), in.readInt(), in.readInt(),
+                    in.readInt(),
+                    in.readInt(), in.readInt());
+        }
+
+        @Override
+        public BluetoothHidDeviceAppQosSettings[] newArray(int size) {
+            return new BluetoothHidDeviceAppQosSettings[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(serviceType);
+        out.writeInt(tokenRate);
+        out.writeInt(tokenBucketSize);
+        out.writeInt(peakBandwidth);
+        out.writeInt(latency);
+        out.writeInt(delayVariation);
+    }
+
+    public int[] toArray() {
+        return new int[] {
+                serviceType, tokenRate, tokenBucketSize, peakBandwidth, latency, delayVariation
+        };
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.aidl b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.aidl
new file mode 100644
index 0000000..87dd10e
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.aidl
@@ -0,0 +1,19 @@
+/*
+** Copyright 2016, 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;
+
+parcelable BluetoothHidDeviceAppSdpSettings;
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
new file mode 100644
index 0000000..f9a2245
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 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.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Random;
+
+/** @hide */
+public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
+
+    final public String name;
+    final public String description;
+    final public String provider;
+    final public byte subclass;
+    final public byte[] descriptors;
+
+    public BluetoothHidDeviceAppSdpSettings(String name, String description, String provider,
+            byte subclass, byte[] descriptors) {
+        this.name = name;
+        this.description = description;
+        this.provider = provider;
+        this.subclass = subclass;
+        this.descriptors = descriptors.clone();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothHidDeviceAppSdpSettings) {
+            BluetoothHidDeviceAppSdpSettings sdp = (BluetoothHidDeviceAppSdpSettings) o;
+            return false;
+        }
+        return false;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final Parcelable.Creator<BluetoothHidDeviceAppSdpSettings> CREATOR =
+        new Parcelable.Creator<BluetoothHidDeviceAppSdpSettings>() {
+
+        @Override
+        public BluetoothHidDeviceAppSdpSettings createFromParcel(Parcel in) {
+
+            return new BluetoothHidDeviceAppSdpSettings(in.readString(), in.readString(),
+                    in.readString(), in.readByte(), in.createByteArray());
+        }
+
+        @Override
+        public BluetoothHidDeviceAppSdpSettings[] newArray(int size) {
+            return new BluetoothHidDeviceAppSdpSettings[size];
+        }
+    };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(name);
+        out.writeString(description);
+        out.writeString(provider);
+        out.writeByte(subclass);
+        out.writeByteArray(descriptors);
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothHidDeviceCallback.java b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
new file mode 100644
index 0000000..0f0e050
--- /dev/null
+++ b/core/java/android/bluetooth/BluetoothHidDeviceCallback.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 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.util.Log;
+
+/** @hide */
+public abstract class BluetoothHidDeviceCallback {
+
+    private static final String TAG = BluetoothHidDeviceCallback.class.getSimpleName();
+
+    /**
+     * Callback called when application registration state changes. Usually it's
+     * called due to either
+     * {@link BluetoothHidDevice#registerApp(String, String, String, byte, byte[],
+     * BluetoothHidDeviceCallback)}
+     * or
+     * {@link BluetoothHidDevice#unregisterApp(BluetoothHidDeviceAppConfiguration)}
+     * , but can be also unsolicited in case e.g. Bluetooth was turned off in
+     * which case application is unregistered automatically.
+     *
+     * @param pluggedDevice {@link BluetoothDevice} object which represents host
+     *            that currently has Virtual Cable established with device. Only
+     *            valid when application is registered, can be <code>null</code>
+     *            .
+     * @param config {@link BluetoothHidDeviceAppConfiguration} object which
+     *            represents token required to unregister application using
+     *            {@link BluetoothHidDevice#unregisterApp(BluetoothHidDeviceAppConfiguration)}
+     *            .
+     * @param registered <code>true</code> if application is registered,
+     *            <code>false</code> otherwise.
+     */
+    public void onAppStatusChanged(BluetoothDevice pluggedDevice,
+                                    BluetoothHidDeviceAppConfiguration config, boolean registered) {
+        Log.d(TAG, "onAppStatusChanged: pluggedDevice=" + (pluggedDevice == null ?
+            null : pluggedDevice.toString()) + " registered=" + registered);
+    }
+
+    /**
+     * Callback called when connection state with remote host was changed.
+     * Application can assume than Virtual Cable is established when called with
+     * {@link BluetoothProfile#STATE_CONNECTED} <code>state</code>.
+     *
+     * @param device {@link BluetoothDevice} object representing host device
+     *            which connection state was changed.
+     * @param state Connection state as defined in {@link BluetoothProfile}.
+     */
+    public void onConnectionStateChanged(BluetoothDevice device, int state) {
+        Log.d(TAG, "onConnectionStateChanged: device=" + device.toString() + " state=" + state);
+    }
+
+    /**
+     * Callback called when GET_REPORT is received from remote host. Should be
+     * replied by application using
+     * {@link BluetoothHidDevice#replyReport(byte, byte, byte[])}.
+     *
+     * @param type Requested Report Type.
+     * @param id Requested Report Id, can be 0 if no Report Id are defined in
+     *            descriptor.
+     * @param bufferSize Requested buffer size, application shall respond with
+     *            at least given number of bytes.
+     */
+    public void onGetReport(byte type, byte id, int bufferSize) {
+        Log.d(TAG, "onGetReport: type=" + type + " id=" + id + " bufferSize=" + bufferSize);
+    }
+
+    /**
+     * Callback called when SET_REPORT is received from remote host. In case
+     * received data are invalid, application shall respond with
+     * {@link BluetoothHidDevice#reportError()}.
+     *
+     * @param type Report Type.
+     * @param id Report Id.
+     * @param data Report data.
+     */
+    public void onSetReport(byte type, byte id, byte[] data) {
+        Log.d(TAG, "onSetReport: type=" + type + " id=" + id);
+    }
+
+    /**
+     * Callback called when SET_PROTOCOL is received from remote host.
+     * Application shall use this information to send only reports valid for
+     * given protocol mode. By default,
+     * {@link BluetoothHidDevice#PROTOCOL_REPORT_MODE} shall be assumed.
+     *
+     * @param protocol Protocol Mode.
+     */
+    public void onSetProtocol(byte protocol) {
+        Log.d(TAG, "onSetProtocol: protocol=" + protocol);
+    }
+
+    /**
+     * Callback called when report data is received over interrupt channel.
+     * Report Type is assumed to be
+     * {@link BluetoothHidDevice#REPORT_TYPE_OUTPUT}.
+     *
+     * @param reportId Report Id.
+     * @param data Report data.
+     */
+    public void onIntrData(byte reportId, byte[] data) {
+        Log.d(TAG, "onIntrData: reportId=" + reportId);
+    }
+
+    /**
+     * Callback called when Virtual Cable is removed. This can be either due to
+     * {@link BluetoothHidDevice#unplug()} or request from remote side. After
+     * this callback is received connection will be disconnected automatically.
+     */
+    public void onVirtualCableUnplug() {
+        Log.d(TAG, "onVirtualCableUnplug");
+    }
+}
diff --git a/core/java/android/bluetooth/BluetoothProfile.java b/core/java/android/bluetooth/BluetoothProfile.java
index f363607..6a009a9 100644
--- a/core/java/android/bluetooth/BluetoothProfile.java
+++ b/core/java/android/bluetooth/BluetoothProfile.java
@@ -143,11 +143,17 @@
     public static final int MAP_CLIENT = 18;
 
     /**
+     * HID device
+     * @hide
+     */
+    static public final int HID_DEVICE = 19;
+
+    /**
      * Max profile ID. This value should be updated whenever a new profile is added to match
      * the largest value assigned to a profile.
      * @hide
      */
-    public static final int MAX_PROFILE_ID = 17;
+    public static final int MAX_PROFILE_ID = 19;
 
     /**
      * Default priority for devices that we try to auto-connect to and
diff --git a/core/java/android/bluetooth/IBluetoothHidDevice.aidl b/core/java/android/bluetooth/IBluetoothHidDevice.aidl
new file mode 100644
index 0000000..15f7313
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHidDevice.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 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.BluetoothDevice;
+import android.bluetooth.BluetoothHidDeviceAppConfiguration;
+import android.bluetooth.IBluetoothHidDeviceCallback;
+import android.bluetooth.BluetoothHidDeviceAppSdpSettings;
+import android.bluetooth.BluetoothHidDeviceAppQosSettings;
+
+/** @hide */
+interface IBluetoothHidDevice {
+    boolean registerApp(in BluetoothHidDeviceAppConfiguration config,
+            in BluetoothHidDeviceAppSdpSettings sdp, in BluetoothHidDeviceAppQosSettings inQos,
+            in BluetoothHidDeviceAppQosSettings outQos, in IBluetoothHidDeviceCallback callback);
+    boolean unregisterApp(in BluetoothHidDeviceAppConfiguration config);
+    boolean sendReport(in int id, in byte[] data);
+    boolean replyReport(in byte type, in byte id, in byte[] data);
+    boolean reportError(byte error);
+    boolean unplug();
+    boolean connect();
+    boolean disconnect();
+}
diff --git a/core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl b/core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl
new file mode 100644
index 0000000..1252876
--- /dev/null
+++ b/core/java/android/bluetooth/IBluetoothHidDeviceCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016, 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.BluetoothDevice;
+import android.bluetooth.BluetoothHidDeviceAppConfiguration;
+
+/** @hide */
+interface IBluetoothHidDeviceCallback {
+   void onAppStatusChanged(in BluetoothDevice device, in BluetoothHidDeviceAppConfiguration config, boolean registered);
+   void onConnectionStateChanged(in BluetoothDevice device, in int state);
+   void onGetReport(in byte type, in byte id, in int bufferSize);
+   void onSetReport(in byte type, in byte id, in byte[] data);
+   void onSetProtocol(in byte protocol);
+   void onIntrData(in byte reportId, in byte[] data);
+   void onVirtualCableUnplug();
+}