Automatic sources dropoff on 2020-06-10 18:32:38.095721

The change is generated with prebuilt drop tool.

Change-Id: I24cbf6ba6db262a1ae1445db1427a08fee35b3b4
diff --git a/android/bluetooth/BluetoothA2dp.java b/android/bluetooth/BluetoothA2dp.java
new file mode 100644
index 0000000..5374d6d
--- /dev/null
+++ b/android/bluetooth/BluetoothA2dp.java
@@ -0,0 +1,896 @@
+/*
+ * Copyright (C) 2008 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.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Build;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * This class provides the public APIs to control the Bluetooth A2DP
+ * profile.
+ *
+ * <p>BluetoothA2dp is a proxy object for controlling the Bluetooth A2DP
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothA2dp proxy object.
+ *
+ * <p> Android only supports one connected Bluetooth A2dp device at a time.
+ * Each method is protected with its appropriate permission.
+ */
+public final class BluetoothA2dp implements BluetoothProfile {
+    private static final String TAG = "BluetoothA2dp";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the A2DP
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.a2dp.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Intent used to broadcast the change in the Playing state of the A2DP
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_PLAYING}, {@link #STATE_NOT_PLAYING},
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PLAYING_STATE_CHANGED =
+            "android.bluetooth.a2dp.profile.action.PLAYING_STATE_CHANGED";
+
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_AVRCP_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.a2dp.profile.action.AVRCP_CONNECTION_STATE_CHANGED";
+
+    /**
+     * Intent used to broadcast the selection of a connected device as active.
+     *
+     * <p>This intent will have one extra:
+     * <ul>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
+     * be null if no device is active. </li>
+     * </ul>
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @UnsupportedAppUsage
+    public static final String ACTION_ACTIVE_DEVICE_CHANGED =
+            "android.bluetooth.a2dp.profile.action.ACTIVE_DEVICE_CHANGED";
+
+    /**
+     * Intent used to broadcast the change in the Audio Codec state of the
+     * A2DP Source profile.
+     *
+     * <p>This intent will have 2 extras:
+     * <ul>
+     * <li> {@link BluetoothCodecStatus#EXTRA_CODEC_STATUS} - The codec status. </li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device if the device is currently
+     * connected, otherwise it is not included.</li>
+     * </ul>
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @UnsupportedAppUsage
+    public static final String ACTION_CODEC_CONFIG_CHANGED =
+            "android.bluetooth.a2dp.profile.action.CODEC_CONFIG_CHANGED";
+
+    /**
+     * A2DP sink device is streaming music. This state can be one of
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+     * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+     */
+    public static final int STATE_PLAYING = 10;
+
+    /**
+     * A2DP sink device is NOT streaming music. This state can be one of
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+     * {@link #ACTION_PLAYING_STATE_CHANGED} intent.
+     */
+    public static final int STATE_NOT_PLAYING = 11;
+
+    /** @hide */
+    @IntDef(prefix = "OPTIONAL_CODECS_", value = {
+            OPTIONAL_CODECS_SUPPORT_UNKNOWN,
+            OPTIONAL_CODECS_NOT_SUPPORTED,
+            OPTIONAL_CODECS_SUPPORTED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OptionalCodecsSupportStatus {}
+
+    /**
+     * We don't have a stored preference for whether or not the given A2DP sink device supports
+     * optional codecs.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int OPTIONAL_CODECS_SUPPORT_UNKNOWN = -1;
+
+    /**
+     * The given A2DP sink device does not support optional codecs.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int OPTIONAL_CODECS_NOT_SUPPORTED = 0;
+
+    /**
+     * The given A2DP sink device does support optional codecs.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int OPTIONAL_CODECS_SUPPORTED = 1;
+
+    /** @hide */
+    @IntDef(prefix = "OPTIONAL_CODECS_PREF_", value = {
+            OPTIONAL_CODECS_PREF_UNKNOWN,
+            OPTIONAL_CODECS_PREF_DISABLED,
+            OPTIONAL_CODECS_PREF_ENABLED
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface OptionalCodecsPreferenceStatus {}
+
+    /**
+     * We don't have a stored preference for whether optional codecs should be enabled or
+     * disabled for the given A2DP device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int OPTIONAL_CODECS_PREF_UNKNOWN = -1;
+
+    /**
+     * Optional codecs should be disabled for the given A2DP device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int OPTIONAL_CODECS_PREF_DISABLED = 0;
+
+    /**
+     * Optional codecs should be enabled for the given A2DP device.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int OPTIONAL_CODECS_PREF_ENABLED = 1;
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothA2dp> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.A2DP, "BluetoothA2dp",
+                    IBluetoothA2dp.class.getName()) {
+                @Override
+                public IBluetoothA2dp getServiceInterface(IBinder service) {
+                    return IBluetoothA2dp.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
+    /**
+     * Create a BluetoothA2dp proxy object for interacting with the local
+     * Bluetooth A2DP service.
+     */
+    /*package*/ BluetoothA2dp(Context context, ServiceListener listener) {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+    }
+
+    @UnsupportedAppUsage
+    /*package*/ void close() {
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothA2dp getService() {
+        return mProfileConnector.getService();
+    }
+
+    @Override
+    public void finalize() {
+        // The empty finalize needs to be kept or the
+        // cts signature tests would fail.
+    }
+
+    /**
+     * Initiate connection to a profile of the remote Bluetooth device.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is already connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that
+     * connection state intent for the profile will be broadcasted with
+     * the state. Users can get the connection state of the profile
+     * from this intent.
+     *
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @UnsupportedAppUsage
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.connect(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * Initiate disconnection from a profile
+     *
+     * <p> This API will return false in scenarios like the profile on the
+     * Bluetooth device is not in connected state etc. When this API returns,
+     * true, it is guaranteed that the connection state change
+     * intent will be broadcasted with the state. Users can get the
+     * disconnection state of the profile from this intent.
+     *
+     * <p> If the disconnection is initiated by a remote device, the state
+     * will transition from {@link #STATE_CONNECTED} to
+     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
+     * host (local) device the state will transition from
+     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
+     * state {@link #STATE_DISCONNECTED}. The transition to
+     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
+     * two scenarios.
+     *
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @UnsupportedAppUsage
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.disconnect(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                return service.getConnectedDevices();
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return new ArrayList<BluetoothDevice>();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return new ArrayList<BluetoothDevice>();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                return service.getDevicesMatchingConnectionStates(states);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return new ArrayList<BluetoothDevice>();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return new ArrayList<BluetoothDevice>();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public @BtProfileState int getConnectionState(BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                return service.getConnectionState(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return BluetoothProfile.STATE_DISCONNECTED;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+    }
+
+    /**
+     * Select a connected device as active.
+     *
+     * The active device selection is per profile. An active device's
+     * purpose is profile-specific. For example, A2DP audio streaming
+     * is to the active A2DP Sink device. If a remote device is not
+     * connected, it cannot be selected as active.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is not connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that the
+     * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
+     * with the active device.
+     *
+     * @param device the remote Bluetooth device. Could be null to clear
+     * the active device and stop streaming audio to a Bluetooth device.
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @UnsupportedAppUsage
+    public boolean setActiveDevice(@Nullable BluetoothDevice device) {
+        if (DBG) log("setActiveDevice(" + device + ")");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
+                    && ((device == null) || isValidDevice(device))) {
+                return service.setActiveDevice(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * Get the connected device that is active.
+     *
+     * @return the connected device that is active or null if no device
+     * is active
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @Nullable
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public BluetoothDevice getActiveDevice() {
+        if (VDBG) log("getActiveDevice()");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                return service.getActiveDevice();
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return null;
+        }
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                return BluetoothAdapter.connectionPolicyToPriority(service.getPriority(device));
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return BluetoothProfile.PRIORITY_OFF;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return BluetoothProfile.PRIORITY_OFF;
+        }
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
+        if (VDBG) log("getConnectionPolicy(" + device + ")");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                return service.getConnectionPolicy(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        }
+    }
+
+    /**
+     * Checks if Avrcp device supports the absolute volume feature.
+     *
+     * @return true if device supports absolute volume
+     * @hide
+     */
+    public boolean isAvrcpAbsoluteVolumeSupported() {
+        if (DBG) Log.d(TAG, "isAvrcpAbsoluteVolumeSupported");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                return service.isAvrcpAbsoluteVolumeSupported();
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to BT service in isAvrcpAbsoluteVolumeSupported()", e);
+            return false;
+        }
+    }
+
+    /**
+     * Tells remote device to set an absolute volume. Only if absolute volume is supported
+     *
+     * @param volume Absolute volume to be set on AVRCP side
+     * @hide
+     */
+    public void setAvrcpAbsoluteVolume(int volume) {
+        if (DBG) Log.d(TAG, "setAvrcpAbsoluteVolume");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                service.setAvrcpAbsoluteVolume(volume);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to BT service in setAvrcpAbsoluteVolume()", e);
+        }
+    }
+
+    /**
+     * Check if A2DP profile is streaming music.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device BluetoothDevice device
+     */
+    public boolean isA2dpPlaying(BluetoothDevice device) {
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                return service.isA2dpPlaying(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * This function checks if the remote device is an AVCRP
+     * target and thus whether we should send volume keys
+     * changes or not.
+     *
+     * @hide
+     */
+    public boolean shouldSendVolumeKeys(BluetoothDevice device) {
+        if (isEnabled() && isValidDevice(device)) {
+            ParcelUuid[] uuids = device.getUuids();
+            if (uuids == null) return false;
+
+            for (ParcelUuid uuid : uuids) {
+                if (uuid.equals(BluetoothUuid.AVRCP_TARGET)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Gets the current codec status (configuration and capability).
+     *
+     * @param device the remote Bluetooth device. If null, use the current
+     * active A2DP Bluetooth device.
+     * @return the current codec status
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @Nullable
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public BluetoothCodecStatus getCodecStatus(@NonNull BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "getCodecStatus(" + device + ")");
+        verifyDeviceNotNull(device, "getCodecStatus");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                return service.getCodecStatus(device);
+            }
+            if (service == null) {
+                Log.w(TAG, "Proxy not attached to service");
+            }
+            return null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to BT service in getCodecStatus()", e);
+            return null;
+        }
+    }
+
+    /**
+     * Sets the codec configuration preference.
+     *
+     * @param device the remote Bluetooth device. If null, use the current
+     * active A2DP Bluetooth device.
+     * @param codecConfig the codec configuration preference
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public void setCodecConfigPreference(@NonNull BluetoothDevice device,
+                                         @NonNull BluetoothCodecConfig codecConfig) {
+        if (DBG) Log.d(TAG, "setCodecConfigPreference(" + device + ")");
+        verifyDeviceNotNull(device, "setCodecConfigPreference");
+        if (codecConfig == null) {
+            Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
+            throw new IllegalArgumentException("codecConfig cannot be null");
+        }
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                service.setCodecConfigPreference(device, codecConfig);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to BT service in setCodecConfigPreference()", e);
+            return;
+        }
+    }
+
+    /**
+     * Enables the optional codecs.
+     *
+     * @param device the remote Bluetooth device. If null, use the currect
+     * active A2DP Bluetooth device.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public void enableOptionalCodecs(@NonNull BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "enableOptionalCodecs(" + device + ")");
+        verifyDeviceNotNull(device, "enableOptionalCodecs");
+        enableDisableOptionalCodecs(device, true);
+    }
+
+    /**
+     * Disables the optional codecs.
+     *
+     * @param device the remote Bluetooth device. If null, use the currect
+     * active A2DP Bluetooth device.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public void disableOptionalCodecs(@NonNull BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "disableOptionalCodecs(" + device + ")");
+        verifyDeviceNotNull(device, "disableOptionalCodecs");
+        enableDisableOptionalCodecs(device, false);
+    }
+
+    /**
+     * Enables or disables the optional codecs.
+     *
+     * @param device the remote Bluetooth device. If null, use the currect
+     * active A2DP Bluetooth device.
+     * @param enable if true, enable the optional codecs, other disable them
+     */
+    private void enableDisableOptionalCodecs(BluetoothDevice device, boolean enable) {
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()) {
+                if (enable) {
+                    service.enableOptionalCodecs(device);
+                } else {
+                    service.disableOptionalCodecs(device);
+                }
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to BT service in enableDisableOptionalCodecs()", e);
+            return;
+        }
+    }
+
+    /**
+     * Returns whether this device supports optional codecs.
+     *
+     * @param device The device to check
+     * @return one of OPTIONAL_CODECS_SUPPORT_UNKNOWN, OPTIONAL_CODECS_NOT_SUPPORTED, or
+     * OPTIONAL_CODECS_SUPPORTED.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @OptionalCodecsSupportStatus
+    public int isOptionalCodecsSupported(@NonNull BluetoothDevice device) {
+        verifyDeviceNotNull(device, "isOptionalCodecsSupported");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.supportsOptionalCodecs(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to BT service in supportsOptionalCodecs()", e);
+            return OPTIONAL_CODECS_SUPPORT_UNKNOWN;
+        }
+    }
+
+    /**
+     * Returns whether this device should have optional codecs enabled.
+     *
+     * @param device The device in question.
+     * @return one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or
+     * OPTIONAL_CODECS_PREF_DISABLED.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @OptionalCodecsPreferenceStatus
+    public int isOptionalCodecsEnabled(@NonNull BluetoothDevice device) {
+        verifyDeviceNotNull(device, "isOptionalCodecsEnabled");
+        try {
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.getOptionalCodecsEnabled(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return OPTIONAL_CODECS_PREF_UNKNOWN;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Error talking to BT service in getOptionalCodecsEnabled()", e);
+            return OPTIONAL_CODECS_PREF_UNKNOWN;
+        }
+    }
+
+    /**
+     * Sets a persistent preference for whether a given device should have optional codecs enabled.
+     *
+     * @param device The device to set this preference for.
+     * @param value Whether the optional codecs should be enabled for this device.  This should be
+     * one of OPTIONAL_CODECS_PREF_UNKNOWN, OPTIONAL_CODECS_PREF_ENABLED, or
+     * OPTIONAL_CODECS_PREF_DISABLED.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public void setOptionalCodecsEnabled(@NonNull BluetoothDevice device,
+            @OptionalCodecsPreferenceStatus int value) {
+        verifyDeviceNotNull(device, "setOptionalCodecsEnabled");
+        try {
+            if (value != BluetoothA2dp.OPTIONAL_CODECS_PREF_UNKNOWN
+                    && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_DISABLED
+                    && value != BluetoothA2dp.OPTIONAL_CODECS_PREF_ENABLED) {
+                Log.e(TAG, "Invalid value passed to setOptionalCodecsEnabled: " + value);
+                return;
+            }
+            final IBluetoothA2dp service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                service.setOptionalCodecsEnabled(device, value);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return;
+        }
+    }
+
+    /**
+     * Helper for converting a state to a string.
+     *
+     * For debug use only - strings are not internationalized.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public static String stateToString(int state) {
+        switch (state) {
+            case STATE_DISCONNECTED:
+                return "disconnected";
+            case STATE_CONNECTING:
+                return "connecting";
+            case STATE_CONNECTED:
+                return "connected";
+            case STATE_DISCONNECTING:
+                return "disconnecting";
+            case STATE_PLAYING:
+                return "playing";
+            case STATE_NOT_PLAYING:
+                return "not playing";
+            default:
+                return "<unknown state " + state + ">";
+        }
+    }
+
+    private boolean isEnabled() {
+        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+        return false;
+    }
+
+    private void verifyDeviceNotNull(BluetoothDevice device, String methodName) {
+        if (device == null) {
+            Log.e(TAG, methodName + ": device param is null");
+            throw new IllegalArgumentException("Device cannot be null");
+        }
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+        if (device == null) return false;
+
+        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+        return false;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/android/bluetooth/BluetoothA2dpSink.java b/android/bluetooth/BluetoothA2dpSink.java
new file mode 100644
index 0000000..53f87e6
--- /dev/null
+++ b/android/bluetooth/BluetoothA2dpSink.java
@@ -0,0 +1,425 @@
+/*
+ * Copyright (C) 2014 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the public APIs to control the Bluetooth A2DP Sink
+ * profile.
+ *
+ * <p>BluetoothA2dpSink is a proxy object for controlling the Bluetooth A2DP Sink
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothA2dpSink proxy object.
+ *
+ * @hide
+ */
+@SystemApi
+public final class BluetoothA2dpSink implements BluetoothProfile {
+    private static final String TAG = "BluetoothA2dpSink";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the A2DP Sink
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SuppressLint("ActionValue")
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothA2dpSink> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.A2DP_SINK,
+                    "BluetoothA2dpSink", IBluetoothA2dpSink.class.getName()) {
+                @Override
+                public IBluetoothA2dpSink getServiceInterface(IBinder service) {
+                    return IBluetoothA2dpSink.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
+    /**
+     * Create a BluetoothA2dp proxy object for interacting with the local
+     * Bluetooth A2DP service.
+     */
+    /*package*/ BluetoothA2dpSink(Context context, ServiceListener listener) {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+    }
+
+    /*package*/ void close() {
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothA2dpSink getService() {
+        return mProfileConnector.getService();
+    }
+
+    @Override
+    public void finalize() {
+        close();
+    }
+
+    /**
+     * Initiate connection to a profile of the remote bluetooth device.
+     *
+     * <p> Currently, the system supports only 1 connection to the
+     * A2DP profile. The API will automatically disconnect connected
+     * devices before connecting.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is already connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that
+     * connection state intent for the profile will be broadcasted with
+     * the state. Users can get the connection state of the profile
+     * from this intent.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        final IBluetoothA2dpSink service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Initiate disconnection from a profile
+     *
+     * <p> This API will return false in scenarios like the profile on the
+     * Bluetooth device is not in connected state etc. When this API returns,
+     * true, it is guaranteed that the connection state change
+     * intent will be broadcasted with the state. Users can get the
+     * disconnection state of the profile from this intent.
+     *
+     * <p> If the disconnection is initiated by a remote device, the state
+     * will transition from {@link #STATE_CONNECTED} to
+     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
+     * host (local) device the state will transition from
+     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
+     * state {@link #STATE_DISCONNECTED}. The transition to
+     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
+     * two scenarios.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        final IBluetoothA2dpSink service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        final IBluetoothA2dpSink service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        final IBluetoothA2dpSink service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
+        final IBluetoothA2dpSink service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Get the current audio configuration for the A2DP source device,
+     * or null if the device has no audio configuration
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Remote bluetooth device.
+     * @return audio configuration for the device, or null
+     *
+     * {@see BluetoothAudioConfig}
+     *
+     * @hide
+     */
+    public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
+        if (VDBG) log("getAudioConfig(" + device + ")");
+        final IBluetoothA2dpSink service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getAudioConfig(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return null;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return null;
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        final IBluetoothA2dpSink service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                return false;
+            }
+            try {
+                return service.setConnectionPolicy(device, connectionPolicy);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
+        if (VDBG) log("getConnectionPolicy(" + device + ")");
+        final IBluetoothA2dpSink service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionPolicy(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    /**
+     * Check if audio is playing on the bluetooth device (A2DP profile is streaming music).
+     *
+     * @param device BluetoothDevice device
+     * @return true if audio is playing (A2dp is streaming music), false otherwise
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean isAudioPlaying(@NonNull BluetoothDevice device) {
+        final IBluetoothA2dpSink service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.isA2dpPlaying(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Helper for converting a state to a string.
+     *
+     * For debug use only - strings are not internationalized.
+     *
+     * @hide
+     */
+    public static String stateToString(int state) {
+        switch (state) {
+            case STATE_DISCONNECTED:
+                return "disconnected";
+            case STATE_CONNECTING:
+                return "connecting";
+            case STATE_CONNECTED:
+                return "connected";
+            case STATE_DISCONNECTING:
+                return "disconnecting";
+            case BluetoothA2dp.STATE_PLAYING:
+                return "playing";
+            case BluetoothA2dp.STATE_NOT_PLAYING:
+                return "not playing";
+            default:
+                return "<unknown state " + state + ">";
+        }
+    }
+
+    private boolean isEnabled() {
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
+    }
+
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/android/bluetooth/BluetoothActivityEnergyInfo.java b/android/bluetooth/BluetoothActivityEnergyInfo.java
new file mode 100644
index 0000000..df065bf
--- /dev/null
+++ b/android/bluetooth/BluetoothActivityEnergyInfo.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2014 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.Arrays;
+
+/**
+ * Record of energy and activity information from controller and
+ * underlying bt stack state.Timestamp the record with system
+ * time
+ *
+ * @hide
+ */
+public final class BluetoothActivityEnergyInfo implements Parcelable {
+    private final long mTimestamp;
+    private int mBluetoothStackState;
+    private long mControllerTxTimeMs;
+    private long mControllerRxTimeMs;
+    private long mControllerIdleTimeMs;
+    private long mControllerEnergyUsed;
+    private UidTraffic[] mUidTraffic;
+
+    public static final int BT_STACK_STATE_INVALID = 0;
+    public static final int BT_STACK_STATE_STATE_ACTIVE = 1;
+    public static final int BT_STACK_STATE_STATE_SCANNING = 2;
+    public static final int BT_STACK_STATE_STATE_IDLE = 3;
+
+    public BluetoothActivityEnergyInfo(long timestamp, int stackState,
+            long txTime, long rxTime, long idleTime, long energyUsed) {
+        mTimestamp = timestamp;
+        mBluetoothStackState = stackState;
+        mControllerTxTimeMs = txTime;
+        mControllerRxTimeMs = rxTime;
+        mControllerIdleTimeMs = idleTime;
+        mControllerEnergyUsed = energyUsed;
+    }
+
+    @SuppressWarnings("unchecked")
+    BluetoothActivityEnergyInfo(Parcel in) {
+        mTimestamp = in.readLong();
+        mBluetoothStackState = in.readInt();
+        mControllerTxTimeMs = in.readLong();
+        mControllerRxTimeMs = in.readLong();
+        mControllerIdleTimeMs = in.readLong();
+        mControllerEnergyUsed = in.readLong();
+        mUidTraffic = in.createTypedArray(UidTraffic.CREATOR);
+    }
+
+    @Override
+    public String toString() {
+        return "BluetoothActivityEnergyInfo{"
+                + " mTimestamp=" + mTimestamp
+                + " mBluetoothStackState=" + mBluetoothStackState
+                + " mControllerTxTimeMs=" + mControllerTxTimeMs
+                + " mControllerRxTimeMs=" + mControllerRxTimeMs
+                + " mControllerIdleTimeMs=" + mControllerIdleTimeMs
+                + " mControllerEnergyUsed=" + mControllerEnergyUsed
+                + " mUidTraffic=" + Arrays.toString(mUidTraffic)
+                + " }";
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothActivityEnergyInfo> CREATOR =
+            new Parcelable.Creator<BluetoothActivityEnergyInfo>() {
+                public BluetoothActivityEnergyInfo createFromParcel(Parcel in) {
+                    return new BluetoothActivityEnergyInfo(in);
+                }
+
+                public BluetoothActivityEnergyInfo[] newArray(int size) {
+                    return new BluetoothActivityEnergyInfo[size];
+                }
+            };
+
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeLong(mTimestamp);
+        out.writeInt(mBluetoothStackState);
+        out.writeLong(mControllerTxTimeMs);
+        out.writeLong(mControllerRxTimeMs);
+        out.writeLong(mControllerIdleTimeMs);
+        out.writeLong(mControllerEnergyUsed);
+        out.writeTypedArray(mUidTraffic, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @return bt stack reported state
+     */
+    public int getBluetoothStackState() {
+        return mBluetoothStackState;
+    }
+
+    /**
+     * @return tx time in ms
+     */
+    public long getControllerTxTimeMillis() {
+        return mControllerTxTimeMs;
+    }
+
+    /**
+     * @return rx time in ms
+     */
+    public long getControllerRxTimeMillis() {
+        return mControllerRxTimeMs;
+    }
+
+    /**
+     * @return idle time in ms
+     */
+    public long getControllerIdleTimeMillis() {
+        return mControllerIdleTimeMs;
+    }
+
+    /**
+     * product of current(mA), voltage(V) and time(ms)
+     *
+     * @return energy used
+     */
+    public long getControllerEnergyUsed() {
+        return mControllerEnergyUsed;
+    }
+
+    /**
+     * @return timestamp(real time elapsed in milliseconds since boot) of record creation.
+     */
+    public long getTimeStamp() {
+        return mTimestamp;
+    }
+
+    public UidTraffic[] getUidTraffic() {
+        return mUidTraffic;
+    }
+
+    public void setUidTraffic(UidTraffic[] traffic) {
+        mUidTraffic = traffic;
+    }
+
+    /**
+     * @return if the record is valid
+     */
+    public boolean isValid() {
+        return ((mControllerTxTimeMs >= 0) && (mControllerRxTimeMs >= 0)
+                && (mControllerIdleTimeMs >= 0));
+    }
+}
diff --git a/android/bluetooth/BluetoothAdapter.java b/android/bluetooth/BluetoothAdapter.java
new file mode 100644
index 0000000..29a98fa
--- /dev/null
+++ b/android/bluetooth/BluetoothAdapter.java
@@ -0,0 +1,3626 @@
+/*
+ * Copyright 2009-2016 The Android Open Source Project
+ * Copyright 2015 Samsung LSI
+ *
+ * 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.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.app.ActivityThread;
+import android.app.PropertyInvalidatedCache;
+import android.bluetooth.BluetoothProfile.ConnectionPolicy;
+import android.bluetooth.le.BluetoothLeAdvertiser;
+import android.bluetooth.le.BluetoothLeScanner;
+import android.bluetooth.le.PeriodicAdvertisingManager;
+import android.bluetooth.le.ScanCallback;
+import android.bluetooth.le.ScanFilter;
+import android.bluetooth.le.ScanRecord;
+import android.bluetooth.le.ScanResult;
+import android.bluetooth.le.ScanSettings;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.os.SynchronousResultReceiver;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.util.Pair;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Executor;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Represents the local device Bluetooth adapter. The {@link BluetoothAdapter}
+ * lets you perform fundamental Bluetooth tasks, such as initiate
+ * device discovery, query a list of bonded (paired) devices,
+ * instantiate a {@link BluetoothDevice} using a known MAC address, and create
+ * a {@link BluetoothServerSocket} to listen for connection requests from other
+ * devices, and start a scan for Bluetooth LE devices.
+ *
+ * <p>To get a {@link BluetoothAdapter} representing the local Bluetooth
+ * adapter, call the {@link BluetoothManager#getAdapter} function on {@link BluetoothManager}.
+ * On JELLY_BEAN_MR1 and below you will need to use the static {@link #getDefaultAdapter}
+ * method instead.
+ * </p><p>
+ * Fundamentally, this is your starting point for all
+ * Bluetooth actions. Once you have the local adapter, you can get a set of
+ * {@link BluetoothDevice} objects representing all paired devices with
+ * {@link #getBondedDevices()}; start device discovery with
+ * {@link #startDiscovery()}; or create a {@link BluetoothServerSocket} to
+ * listen for incoming RFComm connection requests with {@link
+ * #listenUsingRfcommWithServiceRecord(String, UUID)}; listen for incoming L2CAP Connection-oriented
+ * Channels (CoC) connection requests with {@link #listenUsingL2capChannel()}; or start a scan for
+ * Bluetooth LE devices with {@link #startLeScan(LeScanCallback callback)}.
+ * </p>
+ * <p>This class is thread safe.</p>
+ * <p class="note"><strong>Note:</strong>
+ * Most methods require the {@link android.Manifest.permission#BLUETOOTH}
+ * permission and some also require the
+ * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ * </p>
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>
+ * For more information about using Bluetooth, read the <a href=
+ * "{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer
+ * guide.
+ * </p>
+ * </div>
+ *
+ * {@see BluetoothDevice}
+ * {@see BluetoothServerSocket}
+ */
+public final class BluetoothAdapter {
+    private static final String TAG = "BluetoothAdapter";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Default MAC address reported to a client that does not have the
+     * android.permission.LOCAL_MAC_ADDRESS permission.
+     *
+     * @hide
+     */
+    public static final String DEFAULT_MAC_ADDRESS = "02:00:00:00:00:00";
+
+    /**
+     * Sentinel error value for this class. Guaranteed to not equal any other
+     * integer constant in this class. Provided as a convenience for functions
+     * that require a sentinel error value, for example:
+     * <p><code>Intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
+     * BluetoothAdapter.ERROR)</code>
+     */
+    public static final int ERROR = Integer.MIN_VALUE;
+
+    /**
+     * Broadcast Action: The state of the local Bluetooth adapter has been
+     * changed.
+     * <p>For example, Bluetooth has been turned on or off.
+     * <p>Always contains the extra fields {@link #EXTRA_STATE} and {@link
+     * #EXTRA_PREVIOUS_STATE} containing the new and old states
+     * respectively.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
+            ACTION_STATE_CHANGED = "android.bluetooth.adapter.action.STATE_CHANGED";
+
+    /**
+     * Used as an int extra field in {@link #ACTION_STATE_CHANGED}
+     * intents to request the current power state. Possible values are:
+     * {@link #STATE_OFF},
+     * {@link #STATE_TURNING_ON},
+     * {@link #STATE_ON},
+     * {@link #STATE_TURNING_OFF},
+     */
+    public static final String EXTRA_STATE = "android.bluetooth.adapter.extra.STATE";
+    /**
+     * Used as an int extra field in {@link #ACTION_STATE_CHANGED}
+     * intents to request the previous power state. Possible values are:
+     * {@link #STATE_OFF},
+     * {@link #STATE_TURNING_ON},
+     * {@link #STATE_ON},
+     * {@link #STATE_TURNING_OFF}
+     */
+    public static final String EXTRA_PREVIOUS_STATE =
+            "android.bluetooth.adapter.extra.PREVIOUS_STATE";
+
+    /** @hide */
+    @IntDef(prefix = { "STATE_" }, value = {
+            STATE_OFF,
+            STATE_TURNING_ON,
+            STATE_ON,
+            STATE_TURNING_OFF,
+            STATE_BLE_TURNING_ON,
+            STATE_BLE_ON,
+            STATE_BLE_TURNING_OFF
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AdapterState {}
+
+    /**
+     * Indicates the local Bluetooth adapter is off.
+     */
+    public static final int STATE_OFF = 10;
+    /**
+     * Indicates the local Bluetooth adapter is turning on. However local
+     * clients should wait for {@link #STATE_ON} before attempting to
+     * use the adapter.
+     */
+    public static final int STATE_TURNING_ON = 11;
+    /**
+     * Indicates the local Bluetooth adapter is on, and ready for use.
+     */
+    public static final int STATE_ON = 12;
+    /**
+     * Indicates the local Bluetooth adapter is turning off. Local clients
+     * should immediately attempt graceful disconnection of any remote links.
+     */
+    public static final int STATE_TURNING_OFF = 13;
+
+    /**
+     * Indicates the local Bluetooth adapter is turning Bluetooth LE mode on.
+     *
+     * @hide
+     */
+    public static final int STATE_BLE_TURNING_ON = 14;
+
+    /**
+     * Indicates the local Bluetooth adapter is in LE only mode.
+     *
+     * @hide
+     */
+    public static final int STATE_BLE_ON = 15;
+
+    /**
+     * Indicates the local Bluetooth adapter is turning off LE only mode.
+     *
+     * @hide
+     */
+    public static final int STATE_BLE_TURNING_OFF = 16;
+
+    /**
+     * UUID of the GATT Read Characteristics for LE_PSM value.
+     *
+     * @hide
+     */
+    public static final UUID LE_PSM_CHARACTERISTIC_UUID =
+            UUID.fromString("2d410339-82b6-42aa-b34e-e2e01df8cc1a");
+
+    /**
+     * Human-readable string helper for AdapterState
+     *
+     * @hide
+     */
+    public static String nameForState(@AdapterState int state) {
+        switch (state) {
+            case STATE_OFF:
+                return "OFF";
+            case STATE_TURNING_ON:
+                return "TURNING_ON";
+            case STATE_ON:
+                return "ON";
+            case STATE_TURNING_OFF:
+                return "TURNING_OFF";
+            case STATE_BLE_TURNING_ON:
+                return "BLE_TURNING_ON";
+            case STATE_BLE_ON:
+                return "BLE_ON";
+            case STATE_BLE_TURNING_OFF:
+                return "BLE_TURNING_OFF";
+            default:
+                return "?!?!? (" + state + ")";
+        }
+    }
+
+    /**
+     * Activity Action: Show a system activity that requests discoverable mode.
+     * This activity will also request the user to turn on Bluetooth if it
+     * is not currently enabled.
+     * <p>Discoverable mode is equivalent to {@link
+     * #SCAN_MODE_CONNECTABLE_DISCOVERABLE}. It allows remote devices to see
+     * this Bluetooth adapter when they perform a discovery.
+     * <p>For privacy, Android is not discoverable by default.
+     * <p>The sender of this Intent can optionally use extra field {@link
+     * #EXTRA_DISCOVERABLE_DURATION} to request the duration of
+     * discoverability. Currently the default duration is 120 seconds, and
+     * maximum duration is capped at 300 seconds for each request.
+     * <p>Notification of the result of this activity is posted using the
+     * {@link android.app.Activity#onActivityResult} callback. The
+     * <code>resultCode</code>
+     * will be the duration (in seconds) of discoverability or
+     * {@link android.app.Activity#RESULT_CANCELED} if the user rejected
+     * discoverability or an error has occurred.
+     * <p>Applications can also listen for {@link #ACTION_SCAN_MODE_CHANGED}
+     * for global notification whenever the scan mode changes. For example, an
+     * application can be notified when the device has ended discoverability.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String
+            ACTION_REQUEST_DISCOVERABLE = "android.bluetooth.adapter.action.REQUEST_DISCOVERABLE";
+
+    /**
+     * Used as an optional int extra field in {@link
+     * #ACTION_REQUEST_DISCOVERABLE} intents to request a specific duration
+     * for discoverability in seconds. The current default is 120 seconds, and
+     * requests over 300 seconds will be capped. These values could change.
+     */
+    public static final String EXTRA_DISCOVERABLE_DURATION =
+            "android.bluetooth.adapter.extra.DISCOVERABLE_DURATION";
+
+    /**
+     * Activity Action: Show a system activity that allows the user to turn on
+     * Bluetooth.
+     * <p>This system activity will return once Bluetooth has completed turning
+     * on, or the user has decided not to turn Bluetooth on.
+     * <p>Notification of the result of this activity is posted using the
+     * {@link android.app.Activity#onActivityResult} callback. The
+     * <code>resultCode</code>
+     * will be {@link android.app.Activity#RESULT_OK} if Bluetooth has been
+     * turned on or {@link android.app.Activity#RESULT_CANCELED} if the user
+     * has rejected the request or an error has occurred.
+     * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED}
+     * for global notification whenever Bluetooth is turned on or off.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String
+            ACTION_REQUEST_ENABLE = "android.bluetooth.adapter.action.REQUEST_ENABLE";
+
+    /**
+     * Activity Action: Show a system activity that allows the user to turn off
+     * Bluetooth. This is used only if permission review is enabled which is for
+     * apps targeting API less than 23 require a permission review before any of
+     * the app's components can run.
+     * <p>This system activity will return once Bluetooth has completed turning
+     * off, or the user has decided not to turn Bluetooth off.
+     * <p>Notification of the result of this activity is posted using the
+     * {@link android.app.Activity#onActivityResult} callback. The
+     * <code>resultCode</code>
+     * will be {@link android.app.Activity#RESULT_OK} if Bluetooth has been
+     * turned off or {@link android.app.Activity#RESULT_CANCELED} if the user
+     * has rejected the request or an error has occurred.
+     * <p>Applications can also listen for {@link #ACTION_STATE_CHANGED}
+     * for global notification whenever Bluetooth is turned on or off.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) public static final String
+            ACTION_REQUEST_DISABLE = "android.bluetooth.adapter.action.REQUEST_DISABLE";
+
+    /**
+     * Activity Action: Show a system activity that allows user to enable BLE scans even when
+     * Bluetooth is turned off.<p>
+     *
+     * Notification of result of this activity is posted using
+     * {@link android.app.Activity#onActivityResult}. The <code>resultCode</code> will be
+     * {@link android.app.Activity#RESULT_OK} if BLE scan always available setting is turned on or
+     * {@link android.app.Activity#RESULT_CANCELED} if the user has rejected the request or an
+     * error occurred.
+     *
+     * @hide
+     */
+    @SystemApi
+    @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
+    public static final String ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE =
+            "android.bluetooth.adapter.action.REQUEST_BLE_SCAN_ALWAYS_AVAILABLE";
+
+    /**
+     * Broadcast Action: Indicates the Bluetooth scan mode of the local Adapter
+     * has changed.
+     * <p>Always contains the extra fields {@link #EXTRA_SCAN_MODE} and {@link
+     * #EXTRA_PREVIOUS_SCAN_MODE} containing the new and old scan modes
+     * respectively.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
+            ACTION_SCAN_MODE_CHANGED = "android.bluetooth.adapter.action.SCAN_MODE_CHANGED";
+
+    /**
+     * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED}
+     * intents to request the current scan mode. Possible values are:
+     * {@link #SCAN_MODE_NONE},
+     * {@link #SCAN_MODE_CONNECTABLE},
+     * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE},
+     */
+    public static final String EXTRA_SCAN_MODE = "android.bluetooth.adapter.extra.SCAN_MODE";
+    /**
+     * Used as an int extra field in {@link #ACTION_SCAN_MODE_CHANGED}
+     * intents to request the previous scan mode. Possible values are:
+     * {@link #SCAN_MODE_NONE},
+     * {@link #SCAN_MODE_CONNECTABLE},
+     * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE},
+     */
+    public static final String EXTRA_PREVIOUS_SCAN_MODE =
+            "android.bluetooth.adapter.extra.PREVIOUS_SCAN_MODE";
+
+    /** @hide */
+    @IntDef(prefix = { "SCAN_" }, value = {
+            SCAN_MODE_NONE,
+            SCAN_MODE_CONNECTABLE,
+            SCAN_MODE_CONNECTABLE_DISCOVERABLE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ScanMode {}
+
+    /**
+     * Indicates that both inquiry scan and page scan are disabled on the local
+     * Bluetooth adapter. Therefore this device is neither discoverable
+     * nor connectable from remote Bluetooth devices.
+     */
+    public static final int SCAN_MODE_NONE = 20;
+    /**
+     * Indicates that inquiry scan is disabled, but page scan is enabled on the
+     * local Bluetooth adapter. Therefore this device is not discoverable from
+     * remote Bluetooth devices, but is connectable from remote devices that
+     * have previously discovered this device.
+     */
+    public static final int SCAN_MODE_CONNECTABLE = 21;
+    /**
+     * Indicates that both inquiry scan and page scan are enabled on the local
+     * Bluetooth adapter. Therefore this device is both discoverable and
+     * connectable from remote Bluetooth devices.
+     */
+    public static final int SCAN_MODE_CONNECTABLE_DISCOVERABLE = 23;
+
+    /**
+     * Device only has a display.
+     *
+     * @hide
+     */
+    public static final int IO_CAPABILITY_OUT = 0;
+
+    /**
+     * Device has a display and the ability to input Yes/No.
+     *
+     * @hide
+     */
+    public static final int IO_CAPABILITY_IO = 1;
+
+    /**
+     * Device only has a keyboard for entry but no display.
+     *
+     * @hide
+     */
+    public static final int IO_CAPABILITY_IN = 2;
+
+    /**
+     * Device has no Input or Output capability.
+     *
+     * @hide
+     */
+    public static final int IO_CAPABILITY_NONE = 3;
+
+    /**
+     * Device has a display and a full keyboard.
+     *
+     * @hide
+     */
+    public static final int IO_CAPABILITY_KBDISP = 4;
+
+    /**
+     * Maximum range value for Input/Output capabilities.
+     *
+     * <p>This should be updated when adding a new Input/Output capability. Other code
+     * like validation depends on this being accurate.
+     *
+     * @hide
+     */
+    public static final int IO_CAPABILITY_MAX = 5;
+
+    /**
+     * The Input/Output capability of the device is unknown.
+     *
+     * @hide
+     */
+    public static final int IO_CAPABILITY_UNKNOWN = 255;
+
+    /** @hide */
+    @IntDef({IO_CAPABILITY_OUT, IO_CAPABILITY_IO, IO_CAPABILITY_IN, IO_CAPABILITY_NONE,
+            IO_CAPABILITY_KBDISP})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface IoCapability {}
+
+    /** @hide */
+    @IntDef(prefix = "ACTIVE_DEVICE_", value = {ACTIVE_DEVICE_AUDIO,
+            ACTIVE_DEVICE_PHONE_CALL, ACTIVE_DEVICE_ALL})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ActiveDeviceUse {}
+
+    /**
+     * Use the specified device for audio (a2dp and hearing aid profile)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ACTIVE_DEVICE_AUDIO = 0;
+
+    /**
+     * Use the specified device for phone calls (headset profile and hearing
+     * aid profile)
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ACTIVE_DEVICE_PHONE_CALL = 1;
+
+    /**
+     * Use the specified device for a2dp, hearing aid profile, and headset profile
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ACTIVE_DEVICE_ALL = 2;
+
+    /**
+     * Broadcast Action: The local Bluetooth adapter has started the remote
+     * device discovery process.
+     * <p>This usually involves an inquiry scan of about 12 seconds, followed
+     * by a page scan of each new device to retrieve its Bluetooth name.
+     * <p>Register for {@link BluetoothDevice#ACTION_FOUND} to be notified as
+     * remote Bluetooth devices are found.
+     * <p>Device discovery is a heavyweight procedure. New connections to
+     * remote Bluetooth devices should not be attempted while discovery is in
+     * progress, and existing connections will experience limited bandwidth
+     * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing
+     * discovery.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
+            ACTION_DISCOVERY_STARTED = "android.bluetooth.adapter.action.DISCOVERY_STARTED";
+    /**
+     * Broadcast Action: The local Bluetooth adapter has finished the device
+     * discovery process.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
+            ACTION_DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED";
+
+    /**
+     * Broadcast Action: The local Bluetooth adapter has changed its friendly
+     * Bluetooth name.
+     * <p>This name is visible to remote Bluetooth devices.
+     * <p>Always contains the extra field {@link #EXTRA_LOCAL_NAME} containing
+     * the name.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
+            ACTION_LOCAL_NAME_CHANGED = "android.bluetooth.adapter.action.LOCAL_NAME_CHANGED";
+    /**
+     * Used as a String extra field in {@link #ACTION_LOCAL_NAME_CHANGED}
+     * intents to request the local Bluetooth name.
+     */
+    public static final String EXTRA_LOCAL_NAME = "android.bluetooth.adapter.extra.LOCAL_NAME";
+
+    /**
+     * Intent used to broadcast the change in connection state of the local
+     * Bluetooth adapter to a profile of the remote device. When the adapter is
+     * not connected to any profiles of any remote devices and it attempts a
+     * connection to a profile this intent will be sent. Once connected, this intent
+     * will not be sent for any more connection attempts to any profiles of any
+     * remote device. When the adapter disconnects from the last profile its
+     * connected to of any remote device, this intent will be sent.
+     *
+     * <p> This intent is useful for applications that are only concerned about
+     * whether the local adapter is connected to any profile of any device and
+     * are not really concerned about which profile. For example, an application
+     * which displays an icon to display whether Bluetooth is connected or not
+     * can use this intent.
+     *
+     * <p>This intent will have 3 extras:
+     * {@link #EXTRA_CONNECTION_STATE} - The current connection state.
+     * {@link #EXTRA_PREVIOUS_CONNECTION_STATE}- The previous connection state.
+     * {@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+     *
+     * {@link #EXTRA_CONNECTION_STATE} or {@link #EXTRA_PREVIOUS_CONNECTION_STATE}
+     * can be any of {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String
+            ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.adapter.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED}
+     *
+     * This extra represents the current connection state.
+     */
+    public static final String EXTRA_CONNECTION_STATE =
+            "android.bluetooth.adapter.extra.CONNECTION_STATE";
+
+    /**
+     * Extra used by {@link #ACTION_CONNECTION_STATE_CHANGED}
+     *
+     * This extra represents the previous connection state.
+     */
+    public static final String EXTRA_PREVIOUS_CONNECTION_STATE =
+            "android.bluetooth.adapter.extra.PREVIOUS_CONNECTION_STATE";
+
+    /**
+     * Broadcast Action: The Bluetooth adapter state has changed in LE only mode.
+     *
+     * @hide
+     */
+    @SystemApi public static final String ACTION_BLE_STATE_CHANGED =
+            "android.bluetooth.adapter.action.BLE_STATE_CHANGED";
+
+    /**
+     * Intent used to broadcast the change in the Bluetooth address
+     * of the local Bluetooth adapter.
+     * <p>Always contains the extra field {@link
+     * #EXTRA_BLUETOOTH_ADDRESS} containing the Bluetooth address.
+     *
+     * Note: only system level processes are allowed to send this
+     * defined broadcast.
+     *
+     * @hide
+     */
+    public static final String ACTION_BLUETOOTH_ADDRESS_CHANGED =
+            "android.bluetooth.adapter.action.BLUETOOTH_ADDRESS_CHANGED";
+
+    /**
+     * Used as a String extra field in {@link
+     * #ACTION_BLUETOOTH_ADDRESS_CHANGED} intent to store the local
+     * Bluetooth address.
+     *
+     * @hide
+     */
+    public static final String EXTRA_BLUETOOTH_ADDRESS =
+            "android.bluetooth.adapter.extra.BLUETOOTH_ADDRESS";
+
+    /**
+     * Broadcast Action: The notifys Bluetooth ACL connected event. This will be
+     * by BLE Always on enabled application to know the ACL_CONNECTED event
+     * when Bluetooth state in STATE_BLE_ON. This denotes GATT connection
+     * as Bluetooth LE is the only feature available in STATE_BLE_ON
+     *
+     * This is counterpart of {@link BluetoothDevice#ACTION_ACL_CONNECTED} which
+     * works in Bluetooth state STATE_ON
+     *
+     * @hide
+     */
+    public static final String ACTION_BLE_ACL_CONNECTED =
+            "android.bluetooth.adapter.action.BLE_ACL_CONNECTED";
+
+    /**
+     * Broadcast Action: The notifys Bluetooth ACL connected event. This will be
+     * by BLE Always on enabled application to know the ACL_DISCONNECTED event
+     * when Bluetooth state in STATE_BLE_ON. This denotes GATT disconnection as Bluetooth
+     * LE is the only feature available in STATE_BLE_ON
+     *
+     * This is counterpart of {@link BluetoothDevice#ACTION_ACL_DISCONNECTED} which
+     * works in Bluetooth state STATE_ON
+     *
+     * @hide
+     */
+    public static final String ACTION_BLE_ACL_DISCONNECTED =
+            "android.bluetooth.adapter.action.BLE_ACL_DISCONNECTED";
+
+    /** The profile is in disconnected state */
+    public static final int STATE_DISCONNECTED = BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTED;
+    /** The profile is in connecting state */
+    public static final int STATE_CONNECTING = BluetoothProtoEnums.CONNECTION_STATE_CONNECTING;
+    /** The profile is in connected state */
+    public static final int STATE_CONNECTED = BluetoothProtoEnums.CONNECTION_STATE_CONNECTED;
+    /** The profile is in disconnecting state */
+    public static final int STATE_DISCONNECTING =
+            BluetoothProtoEnums.CONNECTION_STATE_DISCONNECTING;
+
+    /** @hide */
+    public static final String BLUETOOTH_MANAGER_SERVICE = "bluetooth_manager";
+    private final IBinder mToken;
+
+
+    /**
+     * When creating a ServerSocket using listenUsingRfcommOn() or
+     * listenUsingL2capOn() use SOCKET_CHANNEL_AUTO_STATIC to create
+     * a ServerSocket that auto assigns a channel number to the first
+     * bluetooth socket.
+     * The channel number assigned to this first Bluetooth Socket will
+     * be stored in the ServerSocket, and reused for subsequent Bluetooth
+     * sockets.
+     *
+     * @hide
+     */
+    public static final int SOCKET_CHANNEL_AUTO_STATIC_NO_SDP = -2;
+
+
+    private static final int ADDRESS_LENGTH = 17;
+
+    /**
+     * Lazily initialized singleton. Guaranteed final after first object
+     * constructed.
+     */
+    private static BluetoothAdapter sAdapter;
+
+    private static BluetoothLeScanner sBluetoothLeScanner;
+    private static BluetoothLeAdvertiser sBluetoothLeAdvertiser;
+    private static PeriodicAdvertisingManager sPeriodicAdvertisingManager;
+
+    private final IBluetoothManager mManagerService;
+    @UnsupportedAppUsage
+    private IBluetooth mService;
+    private Context mContext;
+    private final ReentrantReadWriteLock mServiceLock = new ReentrantReadWriteLock();
+
+    private final Object mLock = new Object();
+    private final Map<LeScanCallback, ScanCallback> mLeScanClients;
+    private static final Map<BluetoothDevice, List<Pair<OnMetadataChangedListener, Executor>>>
+                sMetadataListeners = new HashMap<>();
+
+    /**
+     * Bluetooth metadata listener. Overrides the default BluetoothMetadataListener
+     * implementation.
+     */
+    private static final IBluetoothMetadataListener sBluetoothMetadataListener =
+            new IBluetoothMetadataListener.Stub() {
+        @Override
+        public void onMetadataChanged(BluetoothDevice device, int key, byte[] value) {
+            synchronized (sMetadataListeners) {
+                if (sMetadataListeners.containsKey(device)) {
+                    List<Pair<OnMetadataChangedListener, Executor>> list =
+                            sMetadataListeners.get(device);
+                    for (Pair<OnMetadataChangedListener, Executor> pair : list) {
+                        OnMetadataChangedListener listener = pair.first;
+                        Executor executor = pair.second;
+                        executor.execute(() -> {
+                            listener.onMetadataChanged(device, key, value);
+                        });
+                    }
+                }
+            }
+            return;
+        }
+    };
+
+    /**
+     * Get a handle to the default local Bluetooth adapter.
+     * <p>Currently Android only supports one Bluetooth adapter, but the API
+     * could be extended to support more. This will always return the default
+     * adapter.
+     * </p>
+     *
+     * @return the default local adapter, or null if Bluetooth is not supported on this hardware
+     * platform
+     */
+    public static synchronized BluetoothAdapter getDefaultAdapter() {
+        if (sAdapter == null) {
+            IBinder b = ServiceManager.getService(BLUETOOTH_MANAGER_SERVICE);
+            if (b != null) {
+                IBluetoothManager managerService = IBluetoothManager.Stub.asInterface(b);
+                sAdapter = new BluetoothAdapter(managerService);
+            } else {
+                Log.e(TAG, "Bluetooth binder is null");
+            }
+        }
+        return sAdapter;
+    }
+
+    /**
+     * Use {@link #getDefaultAdapter} to get the BluetoothAdapter instance.
+     */
+    BluetoothAdapter(IBluetoothManager managerService) {
+
+        if (managerService == null) {
+            throw new IllegalArgumentException("bluetooth manager service is null");
+        }
+        try {
+            mServiceLock.writeLock().lock();
+            mService = managerService.registerAdapter(mManagerCallback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.writeLock().unlock();
+        }
+        mManagerService = managerService;
+        mLeScanClients = new HashMap<LeScanCallback, ScanCallback>();
+        mToken = new Binder();
+    }
+
+    /**
+     * Get a {@link BluetoothDevice} object for the given Bluetooth hardware
+     * address.
+     * <p>Valid Bluetooth hardware addresses must be upper case, in a format
+     * such as "00:11:22:33:AA:BB". The helper {@link #checkBluetoothAddress} is
+     * available to validate a Bluetooth address.
+     * <p>A {@link BluetoothDevice} will always be returned for a valid
+     * hardware address, even if this adapter has never seen that device.
+     *
+     * @param address valid Bluetooth MAC address
+     * @throws IllegalArgumentException if address is invalid
+     */
+    public BluetoothDevice getRemoteDevice(String address) {
+        return new BluetoothDevice(address);
+    }
+
+    /**
+     * Get a {@link BluetoothDevice} object for the given Bluetooth hardware
+     * address.
+     * <p>Valid Bluetooth hardware addresses must be 6 bytes. This method
+     * expects the address in network byte order (MSB first).
+     * <p>A {@link BluetoothDevice} will always be returned for a valid
+     * hardware address, even if this adapter has never seen that device.
+     *
+     * @param address Bluetooth MAC address (6 bytes)
+     * @throws IllegalArgumentException if address is invalid
+     */
+    public BluetoothDevice getRemoteDevice(byte[] address) {
+        if (address == null || address.length != 6) {
+            throw new IllegalArgumentException("Bluetooth address must have 6 bytes");
+        }
+        return new BluetoothDevice(
+                String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", address[0], address[1],
+                        address[2], address[3], address[4], address[5]));
+    }
+
+    /**
+     * Returns a {@link BluetoothLeAdvertiser} object for Bluetooth LE Advertising operations.
+     * Will return null if Bluetooth is turned off or if Bluetooth LE Advertising is not
+     * supported on this device.
+     * <p>
+     * Use {@link #isMultipleAdvertisementSupported()} to check whether LE Advertising is supported
+     * on this device before calling this method.
+     */
+    public BluetoothLeAdvertiser getBluetoothLeAdvertiser() {
+        if (!getLeAccess()) {
+            return null;
+        }
+        synchronized (mLock) {
+            if (sBluetoothLeAdvertiser == null) {
+                sBluetoothLeAdvertiser = new BluetoothLeAdvertiser(mManagerService);
+            }
+        }
+        return sBluetoothLeAdvertiser;
+    }
+
+    /**
+     * Returns a {@link PeriodicAdvertisingManager} object for Bluetooth LE Periodic Advertising
+     * operations. Will return null if Bluetooth is turned off or if Bluetooth LE Periodic
+     * Advertising is not supported on this device.
+     * <p>
+     * Use {@link #isLePeriodicAdvertisingSupported()} to check whether LE Periodic Advertising is
+     * supported on this device before calling this method.
+     *
+     * @hide
+     */
+    public PeriodicAdvertisingManager getPeriodicAdvertisingManager() {
+        if (!getLeAccess()) {
+            return null;
+        }
+
+        if (!isLePeriodicAdvertisingSupported()) {
+            return null;
+        }
+
+        synchronized (mLock) {
+            if (sPeriodicAdvertisingManager == null) {
+                sPeriodicAdvertisingManager = new PeriodicAdvertisingManager(mManagerService);
+            }
+        }
+        return sPeriodicAdvertisingManager;
+    }
+
+    /**
+     * Returns a {@link BluetoothLeScanner} object for Bluetooth LE scan operations.
+     */
+    public BluetoothLeScanner getBluetoothLeScanner() {
+        if (!getLeAccess()) {
+            return null;
+        }
+        synchronized (mLock) {
+            if (sBluetoothLeScanner == null) {
+                sBluetoothLeScanner = new BluetoothLeScanner(mManagerService, getOpPackageName(),
+                        getAttributionTag());
+            }
+        }
+        return sBluetoothLeScanner;
+    }
+
+    /**
+     * Return true if Bluetooth is currently enabled and ready for use.
+     * <p>Equivalent to:
+     * <code>getBluetoothState() == STATE_ON</code>
+     *
+     * @return true if the local adapter is turned on
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean isEnabled() {
+        return getState() == BluetoothAdapter.STATE_ON;
+    }
+
+    /**
+     * Return true if Bluetooth LE(Always BLE On feature) is currently
+     * enabled and ready for use
+     * <p>This returns true if current state is either STATE_ON or STATE_BLE_ON
+     *
+     * @return true if the local Bluetooth LE adapter is turned on
+     * @hide
+     */
+    @SystemApi
+    public boolean isLeEnabled() {
+        final int state = getLeState();
+        if (DBG) {
+            Log.d(TAG, "isLeEnabled(): " + BluetoothAdapter.nameForState(state));
+        }
+        return (state == BluetoothAdapter.STATE_ON
+                || state == BluetoothAdapter.STATE_BLE_ON
+                || state == BluetoothAdapter.STATE_TURNING_ON
+                || state == BluetoothAdapter.STATE_TURNING_OFF);
+    }
+
+    /**
+     * Turns off Bluetooth LE which was earlier turned on by calling enableBLE().
+     *
+     * <p> If the internal Adapter state is STATE_BLE_ON, this would trigger the transition
+     * to STATE_OFF and completely shut-down Bluetooth
+     *
+     * <p> If the Adapter state is STATE_ON, This would unregister the existance of
+     * special Bluetooth LE application and hence the further turning off of Bluetooth
+     * from UI would ensure the complete turn-off of Bluetooth rather than staying back
+     * BLE only state
+     *
+     * <p>This is an asynchronous call: it will return immediately, and
+     * clients should listen for {@link #ACTION_BLE_STATE_CHANGED}
+     * to be notified of subsequent adapter state changes If this call returns
+     * true, then the adapter state will immediately transition from {@link
+     * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time
+     * later transition to either {@link #STATE_BLE_ON} or {@link
+     * #STATE_OFF} based on the existance of the further Always BLE ON enabled applications
+     * If this call returns false then there was an
+     * immediate problem that will prevent the QAdapter from being turned off -
+     * such as the QAadapter already being turned off.
+     *
+     * @return true to indicate success, or false on immediate error
+     * @hide
+     */
+    @SystemApi
+    public boolean disableBLE() {
+        if (!isBleScanAlwaysAvailable()) {
+            return false;
+        }
+        String packageName = ActivityThread.currentPackageName();
+        try {
+            return mManagerService.disableBle(packageName, mToken);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Applications who want to only use Bluetooth Low Energy (BLE) can call enableBLE.
+     *
+     * enableBLE registers the existence of an app using only LE functions.
+     *
+     * enableBLE may enable Bluetooth to an LE only mode so that an app can use
+     * LE related features (BluetoothGatt or BluetoothGattServer classes)
+     *
+     * If the user disables Bluetooth while an app is registered to use LE only features,
+     * Bluetooth will remain on in LE only mode for the app.
+     *
+     * When Bluetooth is in LE only mode, it is not shown as ON to the UI.
+     *
+     * <p>This is an asynchronous call: it returns immediately, and
+     * clients should listen for {@link #ACTION_BLE_STATE_CHANGED}
+     * to be notified of adapter state changes.
+     *
+     * If this call returns * true, then the adapter state is either in a mode where
+     * LE is available, or will transition from {@link #STATE_OFF} to {@link #STATE_BLE_TURNING_ON},
+     * and some time later transition to either {@link #STATE_OFF} or {@link #STATE_BLE_ON}.
+     *
+     * If this call returns false then there was an immediate problem that prevents the
+     * adapter from being turned on - such as Airplane mode.
+     *
+     * {@link #ACTION_BLE_STATE_CHANGED} returns the Bluetooth Adapter's various
+     * states, It includes all the classic Bluetooth Adapter states along with
+     * internal BLE only states
+     *
+     * @return true to indicate Bluetooth LE will be available, or false on immediate error
+     * @hide
+     */
+    @SystemApi
+    public boolean enableBLE() {
+        if (!isBleScanAlwaysAvailable()) {
+            return false;
+        }
+        String packageName = ActivityThread.currentPackageName();
+        try {
+            return mManagerService.enableBle(packageName, mToken);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+
+        return false;
+    }
+
+    private static final String BLUETOOTH_GET_STATE_CACHE_PROPERTY = "cache_key.bluetooth.get_state";
+
+    private final PropertyInvalidatedCache<Void, Integer> mBluetoothGetStateCache =
+            new PropertyInvalidatedCache<Void, Integer>(
+                8, BLUETOOTH_GET_STATE_CACHE_PROPERTY) {
+                @Override
+                protected Integer recompute(Void query) {
+                    try {
+                        return mService.getState();
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                }
+            };
+
+    /** @hide */
+    public void disableBluetoothGetStateCache() {
+        mBluetoothGetStateCache.disableLocal();
+    }
+
+    /** @hide */
+    public static void invalidateBluetoothGetStateCache() {
+        PropertyInvalidatedCache.invalidateCache(BLUETOOTH_GET_STATE_CACHE_PROPERTY);
+    }
+
+    /**
+     * Fetch the current bluetooth state.  If the service is down, return
+     * OFF.
+     */
+    @AdapterState
+    private int getStateInternal() {
+        int state = BluetoothAdapter.STATE_OFF;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                state = mBluetoothGetStateCache.query(null);
+            }
+        } catch (RuntimeException e) {
+            if (e.getCause() instanceof RemoteException) {
+                Log.e(TAG, "", e.getCause());
+            } else {
+                throw e;
+            }
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return state;
+    }
+
+    /**
+     * Get the current state of the local Bluetooth adapter.
+     * <p>Possible return values are
+     * {@link #STATE_OFF},
+     * {@link #STATE_TURNING_ON},
+     * {@link #STATE_ON},
+     * {@link #STATE_TURNING_OFF}.
+     *
+     * @return current state of Bluetooth adapter
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @AdapterState
+    public int getState() {
+        int state = getStateInternal();
+
+        // Consider all internal states as OFF
+        if (state == BluetoothAdapter.STATE_BLE_ON || state == BluetoothAdapter.STATE_BLE_TURNING_ON
+                || state == BluetoothAdapter.STATE_BLE_TURNING_OFF) {
+            if (VDBG) {
+                Log.d(TAG, "Consider " + BluetoothAdapter.nameForState(state) + " state as OFF");
+            }
+            state = BluetoothAdapter.STATE_OFF;
+        }
+        if (VDBG) {
+            Log.d(TAG, "" + hashCode() + ": getState(). Returning " + BluetoothAdapter.nameForState(
+                    state));
+        }
+        return state;
+    }
+
+    /**
+     * Get the current state of the local Bluetooth adapter
+     * <p>This returns current internal state of Adapter including LE ON/OFF
+     *
+     * <p>Possible return values are
+     * {@link #STATE_OFF},
+     * {@link #STATE_BLE_TURNING_ON},
+     * {@link #STATE_BLE_ON},
+     * {@link #STATE_TURNING_ON},
+     * {@link #STATE_ON},
+     * {@link #STATE_TURNING_OFF},
+     * {@link #STATE_BLE_TURNING_OFF}.
+     *
+     * @return current state of Bluetooth adapter
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @AdapterState
+    @UnsupportedAppUsage(publicAlternatives = "Use {@link #getState()} instead to determine "
+            + "whether you can use BLE & BT classic.")
+    public int getLeState() {
+        int state = getStateInternal();
+
+        if (VDBG) {
+            Log.d(TAG, "getLeState() returning " + BluetoothAdapter.nameForState(state));
+        }
+        return state;
+    }
+
+    boolean getLeAccess() {
+        if (getLeState() == STATE_ON) {
+            return true;
+        } else if (getLeState() == STATE_BLE_ON) {
+            return true; // TODO: FILTER SYSTEM APPS HERE <--
+        }
+
+        return false;
+    }
+
+    /**
+     * Turn on the local Bluetooth adapter&mdash;do not use without explicit
+     * user action to turn on Bluetooth.
+     * <p>This powers on the underlying Bluetooth hardware, and starts all
+     * Bluetooth system services.
+     * <p class="caution"><strong>Bluetooth should never be enabled without
+     * direct user consent</strong>. If you want to turn on Bluetooth in order
+     * to create a wireless connection, you should use the {@link
+     * #ACTION_REQUEST_ENABLE} Intent, which will raise a dialog that requests
+     * user permission to turn on Bluetooth. The {@link #enable()} method is
+     * provided only for applications that include a user interface for changing
+     * system settings, such as a "power manager" app.</p>
+     * <p>This is an asynchronous call: it will return immediately, and
+     * clients should listen for {@link #ACTION_STATE_CHANGED}
+     * to be notified of subsequent adapter state changes. If this call returns
+     * true, then the adapter state will immediately transition from {@link
+     * #STATE_OFF} to {@link #STATE_TURNING_ON}, and some time
+     * later transition to either {@link #STATE_OFF} or {@link
+     * #STATE_ON}. If this call returns false then there was an
+     * immediate problem that will prevent the adapter from being turned on -
+     * such as Airplane mode, or the adapter is already turned on.
+     *
+     * @return true to indicate adapter startup has begun, or false on immediate error
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean enable() {
+        if (isEnabled()) {
+            if (DBG) {
+                Log.d(TAG, "enable(): BT already enabled!");
+            }
+            return true;
+        }
+        try {
+            return mManagerService.enable(ActivityThread.currentPackageName());
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Turn off the local Bluetooth adapter&mdash;do not use without explicit
+     * user action to turn off Bluetooth.
+     * <p>This gracefully shuts down all Bluetooth connections, stops Bluetooth
+     * system services, and powers down the underlying Bluetooth hardware.
+     * <p class="caution"><strong>Bluetooth should never be disabled without
+     * direct user consent</strong>. The {@link #disable()} method is
+     * provided only for applications that include a user interface for changing
+     * system settings, such as a "power manager" app.</p>
+     * <p>This is an asynchronous call: it will return immediately, and
+     * clients should listen for {@link #ACTION_STATE_CHANGED}
+     * to be notified of subsequent adapter state changes. If this call returns
+     * true, then the adapter state will immediately transition from {@link
+     * #STATE_ON} to {@link #STATE_TURNING_OFF}, and some time
+     * later transition to either {@link #STATE_OFF} or {@link
+     * #STATE_ON}. If this call returns false then there was an
+     * immediate problem that will prevent the adapter from being turned off -
+     * such as the adapter already being turned off.
+     *
+     * @return true to indicate adapter shutdown has begun, or false on immediate error
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean disable() {
+        try {
+            return mManagerService.disable(ActivityThread.currentPackageName(), true);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Turn off the local Bluetooth adapter and don't persist the setting.
+     *
+     * <p>Requires the {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission
+     *
+     * @return true to indicate adapter shutdown has begun, or false on immediate error
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean disable(boolean persist) {
+
+        try {
+            return mManagerService.disable(ActivityThread.currentPackageName(), persist);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Returns the hardware address of the local Bluetooth adapter.
+     * <p>For example, "00:11:22:AA:BB:CC".
+     *
+     * @return Bluetooth hardware address as string
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public String getAddress() {
+        try {
+            return mManagerService.getAddress();
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return null;
+    }
+
+    /**
+     * Get the friendly Bluetooth name of the local Bluetooth adapter.
+     * <p>This name is visible to remote Bluetooth devices.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @return the Bluetooth name, or null on error
+     */
+    public String getName() {
+        try {
+            return mManagerService.getName();
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return null;
+    }
+
+    /**
+     * Factory reset bluetooth settings.
+     *
+     * @return true to indicate that the config file was successfully cleared
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean factoryReset() {
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null && mService.factoryReset()
+                    && mManagerService != null && mManagerService.onFactoryReset()) {
+                return true;
+            }
+            Log.e(TAG, "factoryReset(): Setting persist.bluetooth.factoryreset to retry later");
+            SystemProperties.set("persist.bluetooth.factoryreset", "true");
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Get the UUIDs supported by the local Bluetooth adapter.
+     *
+     * @return the UUIDs supported by the local Bluetooth Adapter.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @Nullable ParcelUuid[] getUuids() {
+        if (getState() != STATE_ON) {
+            return null;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.getUuids();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return null;
+    }
+
+    /**
+     * Set the friendly Bluetooth name of the local Bluetooth adapter.
+     * <p>This name is visible to remote Bluetooth devices.
+     * <p>Valid Bluetooth names are a maximum of 248 bytes using UTF-8
+     * encoding, although many remote devices can only display the first
+     * 40 characters, and some may be limited to just 20.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return false. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
+     *
+     * @param name a valid Bluetooth name
+     * @return true if the name was set, false otherwise
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setName(String name) {
+        if (getState() != STATE_ON) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.setName(name);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Returns the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth
+     * adapter.
+     *
+     * @return {@link BluetoothClass} Bluetooth CoD of local Bluetooth device.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public BluetoothClass getBluetoothClass() {
+        if (getState() != STATE_ON) {
+            return null;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.getBluetoothClass();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return null;
+    }
+
+    /**
+     * Sets the {@link BluetoothClass} Bluetooth Class of Device (CoD) of the local Bluetooth
+     * adapter.
+     *
+     * <p>Note: This value persists across system reboot.
+     *
+     * @param bluetoothClass {@link BluetoothClass} to set the local Bluetooth adapter to.
+     * @return true if successful, false if unsuccessful.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setBluetoothClass(BluetoothClass bluetoothClass) {
+        if (getState() != STATE_ON) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.setBluetoothClass(bluetoothClass);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Returns the Input/Output capability of the device for classic Bluetooth.
+     *
+     * @return Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT},
+     *         {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN}, {@link #IO_CAPABILITY_NONE},
+     *         {@link #IO_CAPABILITY_KBDISP} or {@link #IO_CAPABILITY_UNKNOWN}.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @IoCapability
+    public int getIoCapability() {
+        if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.getIoCapability();
+        } catch (RemoteException e) {
+            Log.e(TAG, e.getMessage(), e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+    }
+
+    /**
+     * Sets the Input/Output capability of the device for classic Bluetooth.
+     *
+     * <p>Changing the Input/Output capability of a device only takes effect on restarting the
+     * Bluetooth stack. You would need to restart the stack using {@link BluetoothAdapter#disable()}
+     * and {@link BluetoothAdapter#enable()} to see the changes.
+     *
+     * @param capability Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT},
+     *                   {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN},
+     *                   {@link #IO_CAPABILITY_NONE} or {@link #IO_CAPABILITY_KBDISP}.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setIoCapability(@IoCapability int capability) {
+        if (getState() != STATE_ON) return false;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.setIoCapability(capability);
+        } catch (RemoteException e) {
+            Log.e(TAG, e.getMessage(), e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Returns the Input/Output capability of the device for BLE operations.
+     *
+     * @return Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT},
+     *         {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN}, {@link #IO_CAPABILITY_NONE},
+     *         {@link #IO_CAPABILITY_KBDISP} or {@link #IO_CAPABILITY_UNKNOWN}.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @IoCapability
+    public int getLeIoCapability() {
+        if (getState() != STATE_ON) return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.getLeIoCapability();
+        } catch (RemoteException e) {
+            Log.e(TAG, e.getMessage(), e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return BluetoothAdapter.IO_CAPABILITY_UNKNOWN;
+    }
+
+    /**
+     * Sets the Input/Output capability of the device for BLE operations.
+     *
+     * <p>Changing the Input/Output capability of a device only takes effect on restarting the
+     * Bluetooth stack. You would need to restart the stack using {@link BluetoothAdapter#disable()}
+     * and {@link BluetoothAdapter#enable()} to see the changes.
+     *
+     * @param capability Input/Output capability of the device. One of {@link #IO_CAPABILITY_OUT},
+     *                   {@link #IO_CAPABILITY_IO}, {@link #IO_CAPABILITY_IN},
+     *                   {@link #IO_CAPABILITY_NONE} or {@link #IO_CAPABILITY_KBDISP}.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setLeIoCapability(@IoCapability int capability) {
+        if (getState() != STATE_ON) return false;
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) return mService.setLeIoCapability(capability);
+        } catch (RemoteException e) {
+            Log.e(TAG, e.getMessage(), e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Get the current Bluetooth scan mode of the local Bluetooth adapter.
+     * <p>The Bluetooth scan mode determines if the local adapter is
+     * connectable and/or discoverable from remote Bluetooth devices.
+     * <p>Possible values are:
+     * {@link #SCAN_MODE_NONE},
+     * {@link #SCAN_MODE_CONNECTABLE},
+     * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return {@link #SCAN_MODE_NONE}. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
+     *
+     * @return scan mode
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @ScanMode
+    public int getScanMode() {
+        if (getState() != STATE_ON) {
+            return SCAN_MODE_NONE;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.getScanMode();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return SCAN_MODE_NONE;
+    }
+
+    /**
+     * Set the Bluetooth scan mode of the local Bluetooth adapter.
+     * <p>The Bluetooth scan mode determines if the local adapter is
+     * connectable and/or discoverable from remote Bluetooth devices.
+     * <p>For privacy reasons, discoverable mode is automatically turned off
+     * after <code>durationMillis</code> milliseconds. For example, 120000 milliseconds should be
+     * enough for a remote device to initiate and complete its discovery process.
+     * <p>Valid scan mode values are:
+     * {@link #SCAN_MODE_NONE},
+     * {@link #SCAN_MODE_CONNECTABLE},
+     * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return false. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
+     * <p>Applications cannot set the scan mode. They should use
+     * <code>startActivityForResult(
+     * BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE})
+     * </code>instead.
+     *
+     * @param mode valid scan mode
+     * @param durationMillis time in milliseconds to apply scan mode, only used for {@link
+     * #SCAN_MODE_CONNECTABLE_DISCOVERABLE}
+     * @return true if the scan mode was set, false otherwise
+     * @hide
+     */
+    @UnsupportedAppUsage(publicAlternatives = "Use {@link #ACTION_REQUEST_DISCOVERABLE}, which "
+            + "shows UI that confirms the user wants to go into discoverable mode.")
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean setScanMode(@ScanMode int mode, long durationMillis) {
+        if (getState() != STATE_ON) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                int durationSeconds = Math.toIntExact(durationMillis / 1000);
+                return mService.setScanMode(mode, durationSeconds);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } catch (ArithmeticException ex) {
+            Log.e(TAG, "setScanMode: Duration in seconds outside of the bounds of an int");
+            throw new IllegalArgumentException("Duration not in bounds. In seconds, the "
+                    + "durationMillis must be in the range of an int");
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Set the Bluetooth scan mode of the local Bluetooth adapter.
+     * <p>The Bluetooth scan mode determines if the local adapter is
+     * connectable and/or discoverable from remote Bluetooth devices.
+     * <p>For privacy reasons, discoverable mode is automatically turned off
+     * after <code>duration</code> seconds. For example, 120 seconds should be
+     * enough for a remote device to initiate and complete its discovery
+     * process.
+     * <p>Valid scan mode values are:
+     * {@link #SCAN_MODE_NONE},
+     * {@link #SCAN_MODE_CONNECTABLE},
+     * {@link #SCAN_MODE_CONNECTABLE_DISCOVERABLE}.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return false. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
+     * <p>Applications cannot set the scan mode. They should use
+     * <code>startActivityForResult(
+     * BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE})
+     * </code>instead.
+     *
+     * @param mode valid scan mode
+     * @return true if the scan mode was set, false otherwise
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean setScanMode(@ScanMode int mode) {
+        if (getState() != STATE_ON) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.setScanMode(mode, getDiscoverableTimeout());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public int getDiscoverableTimeout() {
+        if (getState() != STATE_ON) {
+            return -1;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.getDiscoverableTimeout();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return -1;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public void setDiscoverableTimeout(int timeout) {
+        if (getState() != STATE_ON) {
+            return;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                mService.setDiscoverableTimeout(timeout);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+    }
+
+    /**
+     * Get the end time of the latest remote device discovery process.
+     *
+     * @return the latest time that the bluetooth adapter was/will be in discovery mode, in
+     * milliseconds since the epoch. This time can be in the future if {@link #startDiscovery()} has
+     * been called recently.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public long getDiscoveryEndMillis() {
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.getDiscoveryEndMillis();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return -1;
+    }
+
+    /**
+     * Set the context for this BluetoothAdapter (only called from BluetoothManager)
+     * @hide
+     */
+    public void setContext(Context context) {
+        mContext = context;
+    }
+
+    private String getOpPackageName() {
+        // Workaround for legacy API for getting a BluetoothAdapter not
+        // passing a context
+        if (mContext != null) {
+            return mContext.getOpPackageName();
+        }
+        return ActivityThread.currentOpPackageName();
+    }
+
+    private String getAttributionTag() {
+        // Workaround for legacy API for getting a BluetoothAdapter not
+        // passing a context
+        if (mContext != null) {
+            return mContext.getAttributionTag();
+        }
+        return null;
+    }
+
+    /**
+     * Start the remote device discovery process.
+     * <p>The discovery process usually involves an inquiry scan of about 12
+     * seconds, followed by a page scan of each new device to retrieve its
+     * Bluetooth name.
+     * <p>This is an asynchronous call, it will return immediately. Register
+     * for {@link #ACTION_DISCOVERY_STARTED} and {@link
+     * #ACTION_DISCOVERY_FINISHED} intents to determine exactly when the
+     * discovery starts and completes. Register for {@link
+     * BluetoothDevice#ACTION_FOUND} to be notified as remote Bluetooth devices
+     * are found.
+     * <p>Device discovery is a heavyweight procedure. New connections to
+     * remote Bluetooth devices should not be attempted while discovery is in
+     * progress, and existing connections will experience limited bandwidth
+     * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing
+     * discovery. Discovery is not managed by the Activity,
+     * but is run as a system service, so an application should always call
+     * {@link BluetoothAdapter#cancelDiscovery()} even if it
+     * did not directly request a discovery, just to be sure.
+     * <p>Device discovery will only find remote devices that are currently
+     * <i>discoverable</i> (inquiry scan enabled). Many Bluetooth devices are
+     * not discoverable by default, and need to be entered into a special mode.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return false. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
+     *
+     * @return true on success, false on error
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean startDiscovery() {
+        if (getState() != STATE_ON) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.startDiscovery(getOpPackageName(), getAttributionTag());
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Cancel the current device discovery process.
+     * <p>Because discovery is a heavyweight procedure for the Bluetooth
+     * adapter, this method should always be called before attempting to connect
+     * to a remote device with {@link
+     * android.bluetooth.BluetoothSocket#connect()}. Discovery is not managed by
+     * the  Activity, but is run as a system service, so an application should
+     * always call cancel discovery even if it did not directly request a
+     * discovery, just to be sure.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return false. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
+     *
+     * @return true on success, false on error
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean cancelDiscovery() {
+        if (getState() != STATE_ON) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.cancelDiscovery();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Return true if the local Bluetooth adapter is currently in the device
+     * discovery process.
+     * <p>Device discovery is a heavyweight procedure. New connections to
+     * remote Bluetooth devices should not be attempted while discovery is in
+     * progress, and existing connections will experience limited bandwidth
+     * and high latency. Use {@link #cancelDiscovery()} to cancel an ongoing
+     * discovery.
+     * <p>Applications can also register for {@link #ACTION_DISCOVERY_STARTED}
+     * or {@link #ACTION_DISCOVERY_FINISHED} to be notified when discovery
+     * starts or completes.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return false. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
+     *
+     * @return true if discovering
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean isDiscovering() {
+        if (getState() != STATE_ON) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.isDiscovering();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Removes the active device for the grouping of @ActiveDeviceUse specified
+     *
+     * @param profiles represents the purpose for which we are setting this as the active device.
+     *                 Possible values are:
+     *                 {@link BluetoothAdapter#ACTIVE_DEVICE_AUDIO},
+     *                 {@link BluetoothAdapter#ACTIVE_DEVICE_PHONE_CALL},
+     *                 {@link BluetoothAdapter#ACTIVE_DEVICE_ALL}
+     * @return false on immediate error, true otherwise
+     * @throws IllegalArgumentException if device is null or profiles is not one of
+     * {@link ActiveDeviceUse}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean removeActiveDevice(@ActiveDeviceUse int profiles) {
+        if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL
+                && profiles != ACTIVE_DEVICE_ALL) {
+            Log.e(TAG, "Invalid profiles param value in removeActiveDevice");
+            throw new IllegalArgumentException("Profiles must be one of "
+                    + "BluetoothAdapter.ACTIVE_DEVICE_AUDIO, "
+                    + "BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL, or "
+                    + "BluetoothAdapter.ACTIVE_DEVICE_ALL");
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.removeActiveDevice(profiles);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+
+        return false;
+    }
+
+    /**
+     * Sets device as the active devices for the profiles passed into the function
+     *
+     * @param device is the remote bluetooth device
+     * @param profiles represents the purpose for which we are setting this as the active device.
+     *                 Possible values are:
+     *                 {@link BluetoothAdapter#ACTIVE_DEVICE_AUDIO},
+     *                 {@link BluetoothAdapter#ACTIVE_DEVICE_PHONE_CALL},
+     *                 {@link BluetoothAdapter#ACTIVE_DEVICE_ALL}
+     * @return false on immediate error, true otherwise
+     * @throws IllegalArgumentException if device is null or profiles is not one of
+     * {@link ActiveDeviceUse}
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setActiveDevice(@NonNull BluetoothDevice device,
+            @ActiveDeviceUse int profiles) {
+        if (device == null) {
+            Log.e(TAG, "setActiveDevice: Null device passed as parameter");
+            throw new IllegalArgumentException("device cannot be null");
+        }
+        if (profiles != ACTIVE_DEVICE_AUDIO && profiles != ACTIVE_DEVICE_PHONE_CALL
+                && profiles != ACTIVE_DEVICE_ALL) {
+            Log.e(TAG, "Invalid profiles param value in setActiveDevice");
+            throw new IllegalArgumentException("Profiles must be one of "
+                    + "BluetoothAdapter.ACTIVE_DEVICE_AUDIO, "
+                    + "BluetoothAdapter.ACTIVE_DEVICE_PHONE_CALL, or "
+                    + "BluetoothAdapter.ACTIVE_DEVICE_ALL");
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.setActiveDevice(device, profiles);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+
+        return false;
+    }
+
+    /**
+     * Connects all enabled and supported bluetooth profiles between the local and remote device.
+     * Connection is asynchronous and you should listen to each profile's broadcast intent
+     * ACTION_CONNECTION_STATE_CHANGED to verify whether connection was successful. For example,
+     * to verify a2dp is connected, you would listen for
+     * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED}
+     *
+     * @param device is the remote device with which to connect these profiles
+     * @return true if message sent to try to connect all profiles, false if an error occurred
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean connectAllEnabledProfiles(@NonNull BluetoothDevice device) {
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.connectAllEnabledProfiles(device);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+
+        return false;
+    }
+
+    /**
+     * Disconnects all enabled and supported bluetooth profiles between the local and remote device.
+     * Disconnection is asynchronous and you should listen to each profile's broadcast intent
+     * ACTION_CONNECTION_STATE_CHANGED to verify whether disconnection was successful. For example,
+     * to verify a2dp is disconnected, you would listen for
+     * {@link BluetoothA2dp#ACTION_CONNECTION_STATE_CHANGED}
+     *
+     * @param device is the remote device with which to disconnect these profiles
+     * @return true if message sent to try to disconnect all profiles, false if an error occurred
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean disconnectAllEnabledProfiles(@NonNull BluetoothDevice device) {
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.disconnectAllEnabledProfiles(device);
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+
+        return false;
+    }
+
+    /**
+     * Return true if the multi advertisement is supported by the chipset
+     *
+     * @return true if Multiple Advertisement feature is supported
+     */
+    public boolean isMultipleAdvertisementSupported() {
+        if (getState() != STATE_ON) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.isMultiAdvertisementSupported();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get isMultipleAdvertisementSupported, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Returns {@code true} if BLE scan is always available, {@code false} otherwise. <p>
+     *
+     * If this returns {@code true}, application can issue {@link BluetoothLeScanner#startScan} and
+     * fetch scan results even when Bluetooth is turned off.<p>
+     *
+     * To change this setting, use {@link #ACTION_REQUEST_BLE_SCAN_ALWAYS_AVAILABLE}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean isBleScanAlwaysAvailable() {
+        try {
+            return mManagerService.isBleScanAlwaysAvailable();
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote expection when calling isBleScanAlwaysAvailable", e);
+            return false;
+        }
+    }
+
+    private static final String BLUETOOTH_FILTERING_CACHE_PROPERTY =
+            "cache_key.bluetooth.is_offloaded_filtering_supported";
+    private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache =
+            new PropertyInvalidatedCache<Void, Boolean>(
+                8, BLUETOOTH_FILTERING_CACHE_PROPERTY) {
+                @Override
+                protected Boolean recompute(Void query) {
+                    try {
+                        mServiceLock.readLock().lock();
+                        if (mService != null) {
+                            return mService.isOffloadedFilteringSupported();
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "failed to get isOffloadedFilteringSupported, error: ", e);
+                    } finally {
+                        mServiceLock.readLock().unlock();
+                    }
+                    return false;
+
+                }
+            };
+
+    /** @hide */
+    public void disableIsOffloadedFilteringSupportedCache() {
+        mBluetoothFilteringCache.disableLocal();
+    }
+
+    /** @hide */
+    public static void invalidateIsOffloadedFilteringSupportedCache() {
+        PropertyInvalidatedCache.invalidateCache(BLUETOOTH_FILTERING_CACHE_PROPERTY);
+    }
+
+    /**
+     * Return true if offloaded filters are supported
+     *
+     * @return true if chipset supports on-chip filtering
+     */
+    public boolean isOffloadedFilteringSupported() {
+        if (!getLeAccess()) {
+            return false;
+        }
+        return mBluetoothFilteringCache.query(null);
+    }
+
+    /**
+     * Return true if offloaded scan batching is supported
+     *
+     * @return true if chipset supports on-chip scan batching
+     */
+    public boolean isOffloadedScanBatchingSupported() {
+        if (!getLeAccess()) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.isOffloadedScanBatchingSupported();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get isOffloadedScanBatchingSupported, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Return true if LE 2M PHY feature is supported.
+     *
+     * @return true if chipset supports LE 2M PHY feature
+     */
+    public boolean isLe2MPhySupported() {
+        if (!getLeAccess()) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.isLe2MPhySupported();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get isExtendedAdvertisingSupported, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Return true if LE Coded PHY feature is supported.
+     *
+     * @return true if chipset supports LE Coded PHY feature
+     */
+    public boolean isLeCodedPhySupported() {
+        if (!getLeAccess()) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.isLeCodedPhySupported();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get isLeCodedPhySupported, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Return true if LE Extended Advertising feature is supported.
+     *
+     * @return true if chipset supports LE Extended Advertising feature
+     */
+    public boolean isLeExtendedAdvertisingSupported() {
+        if (!getLeAccess()) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.isLeExtendedAdvertisingSupported();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get isLeExtendedAdvertisingSupported, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Return true if LE Periodic Advertising feature is supported.
+     *
+     * @return true if chipset supports LE Periodic Advertising feature
+     */
+    public boolean isLePeriodicAdvertisingSupported() {
+        if (!getLeAccess()) {
+            return false;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.isLePeriodicAdvertisingSupported();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get isLePeriodicAdvertisingSupported, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return false;
+    }
+
+    /**
+     * Return the maximum LE advertising data length in bytes,
+     * if LE Extended Advertising feature is supported, 0 otherwise.
+     *
+     * @return the maximum LE advertising data length.
+     */
+    public int getLeMaximumAdvertisingDataLength() {
+        if (!getLeAccess()) {
+            return 0;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.getLeMaximumAdvertisingDataLength();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get getLeMaximumAdvertisingDataLength, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return 0;
+    }
+
+    /**
+     * Return true if Hearing Aid Profile is supported.
+     *
+     * @return true if phone supports Hearing Aid Profile
+     */
+    private boolean isHearingAidProfileSupported() {
+        try {
+            return mManagerService.isHearingAidProfileSupported();
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote expection when calling isHearingAidProfileSupported", e);
+            return false;
+        }
+    }
+
+    /**
+     * Get the maximum number of connected audio devices.
+     *
+     * @return the maximum number of connected audio devices
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public int getMaxConnectedAudioDevices() {
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.getMaxConnectedAudioDevices();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to get getMaxConnectedAudioDevices, error: ", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return 1;
+    }
+
+    /**
+     * Return true if hardware has entries available for matching beacons
+     *
+     * @return true if there are hw entries available for matching beacons
+     * @hide
+     */
+    public boolean isHardwareTrackingFiltersAvailable() {
+        if (!getLeAccess()) {
+            return false;
+        }
+        try {
+            IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
+            if (iGatt == null) {
+                // BLE is not supported
+                return false;
+            }
+            return (iGatt.numHwTrackFiltersAvailable() != 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Return the record of {@link BluetoothActivityEnergyInfo} object that
+     * has the activity and energy info. This can be used to ascertain what
+     * the controller has been up to, since the last sample.
+     *
+     * @param updateType Type of info, cached vs refreshed.
+     * @return a record with {@link BluetoothActivityEnergyInfo} or null if report is unavailable or
+     * unsupported
+     * @hide
+     * @deprecated use the asynchronous {@link #requestControllerActivityEnergyInfo(ResultReceiver)}
+     * instead.
+     */
+    @Deprecated
+    public BluetoothActivityEnergyInfo getControllerActivityEnergyInfo(int updateType) {
+        SynchronousResultReceiver receiver = new SynchronousResultReceiver();
+        requestControllerActivityEnergyInfo(receiver);
+        try {
+            SynchronousResultReceiver.Result result = receiver.awaitResult(1000);
+            if (result.bundle != null) {
+                return result.bundle.getParcelable(BatteryStats.RESULT_RECEIVER_CONTROLLER_KEY);
+            }
+        } catch (TimeoutException e) {
+            Log.e(TAG, "getControllerActivityEnergyInfo timed out");
+        }
+        return null;
+    }
+
+    /**
+     * Request the record of {@link BluetoothActivityEnergyInfo} object that
+     * has the activity and energy info. This can be used to ascertain what
+     * the controller has been up to, since the last sample.
+     *
+     * A null value for the activity info object may be sent if the bluetooth service is
+     * unreachable or the device does not support reporting such information.
+     *
+     * @param result The callback to which to send the activity info.
+     * @hide
+     */
+    public void requestControllerActivityEnergyInfo(ResultReceiver result) {
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                mService.requestActivityInfo(result);
+                result = null;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "getControllerActivityEnergyInfoCallback: " + e);
+        } finally {
+            mServiceLock.readLock().unlock();
+            if (result != null) {
+                // Only send an immediate result if we failed.
+                result.send(0, null);
+            }
+        }
+    }
+
+    /**
+     * Fetches a list of the most recently connected bluetooth devices ordered by how recently they
+     * were connected with most recently first and least recently last
+     *
+     * @return {@link List} of bonded {@link BluetoothDevice} ordered by how recently they were
+     * connected
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public @NonNull List<BluetoothDevice> getMostRecentlyConnectedDevices() {
+        if (getState() != STATE_ON) {
+            return new ArrayList<>();
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.getMostRecentlyConnectedDevices();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return new ArrayList<>();
+    }
+
+    /**
+     * Return the set of {@link BluetoothDevice} objects that are bonded
+     * (paired) to the local adapter.
+     * <p>If Bluetooth state is not {@link #STATE_ON}, this API
+     * will return an empty set. After turning on Bluetooth,
+     * wait for {@link #ACTION_STATE_CHANGED} with {@link #STATE_ON}
+     * to get the updated value.
+     *
+     * @return unmodifiable set of {@link BluetoothDevice}, or null on error
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public Set<BluetoothDevice> getBondedDevices() {
+        if (getState() != STATE_ON) {
+            return toDeviceSet(new BluetoothDevice[0]);
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return toDeviceSet(mService.getBondedDevices());
+            }
+            return toDeviceSet(new BluetoothDevice[0]);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return null;
+    }
+
+    /**
+     * Gets the currently supported profiles by the adapter.
+     *
+     * <p> This can be used to check whether a profile is supported before attempting
+     * to connect to its respective proxy.
+     *
+     * @return a list of integers indicating the ids of supported profiles as defined in {@link
+     * BluetoothProfile}.
+     * @hide
+     */
+    public @NonNull List<Integer> getSupportedProfiles() {
+        final ArrayList<Integer> supportedProfiles = new ArrayList<Integer>();
+
+        try {
+            synchronized (mManagerCallback) {
+                if (mService != null) {
+                    final long supportedProfilesBitMask = mService.getSupportedProfiles();
+
+                    for (int i = 0; i <= BluetoothProfile.MAX_PROFILE_ID; i++) {
+                        if ((supportedProfilesBitMask & (1 << i)) != 0) {
+                            supportedProfiles.add(i);
+                        }
+                    }
+                } else {
+                    // Bluetooth is disabled. Just fill in known supported Profiles
+                    if (isHearingAidProfileSupported()) {
+                        supportedProfiles.add(BluetoothProfile.HEARING_AID);
+                    }
+                }
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "getSupportedProfiles:", e);
+        }
+        return supportedProfiles;
+    }
+
+    /**
+     * Get the current connection state of the local Bluetooth adapter.
+     * This can be used to check whether the local Bluetooth adapter is connected
+     * to any profile of any other remote Bluetooth Device.
+     *
+     * <p> Use this function along with {@link #ACTION_CONNECTION_STATE_CHANGED}
+     * intent to get the connection state of the adapter.
+     *
+     * @return One of {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTED}, {@link
+     * #STATE_CONNECTING} or {@link #STATE_DISCONNECTED}
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getConnectionState() {
+        if (getState() != STATE_ON) {
+            return BluetoothAdapter.STATE_DISCONNECTED;
+        }
+        try {
+            mServiceLock.readLock().lock();
+            if (mService != null) {
+                return mService.getAdapterConnectionState();
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, "getConnectionState:", e);
+        } finally {
+            mServiceLock.readLock().unlock();
+        }
+        return BluetoothAdapter.STATE_DISCONNECTED;
+    }
+
+    private static final String BLUETOOTH_PROFILE_CACHE_PROPERTY =
+            "cache_key.bluetooth.get_profile_connection_state";
+    private final PropertyInvalidatedCache<Integer, Integer>
+            mGetProfileConnectionStateCache =
+            new PropertyInvalidatedCache<Integer, Integer>(
+                8, BLUETOOTH_PROFILE_CACHE_PROPERTY) {
+                @Override
+                protected Integer recompute(Integer query) {
+                    try {
+                        mServiceLock.readLock().lock();
+                        if (mService != null) {
+                            return mService.getProfileConnectionState(query);
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "getProfileConnectionState:", e);
+                    } finally {
+                        mServiceLock.readLock().unlock();
+                    }
+                    return BluetoothProfile.STATE_DISCONNECTED;
+                }
+                @Override
+                public String queryToString(Integer query) {
+                    return String.format("getProfileConnectionState(profile=\"%d\")",
+                                         query);
+                }
+            };
+
+    /** @hide */
+    public void disableGetProfileConnectionStateCache() {
+        mGetProfileConnectionStateCache.disableLocal();
+    }
+
+    /** @hide */
+    public static void invalidateGetProfileConnectionStateCache() {
+        PropertyInvalidatedCache.invalidateCache(BLUETOOTH_PROFILE_CACHE_PROPERTY);
+    }
+
+    /**
+     * Get the current connection state of a profile.
+     * This function can be used to check whether the local Bluetooth adapter
+     * is connected to any remote device for a specific profile.
+     * Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP}.
+     *
+     * <p> Return value can be one of
+     * {@link BluetoothProfile#STATE_DISCONNECTED},
+     * {@link BluetoothProfile#STATE_CONNECTING},
+     * {@link BluetoothProfile#STATE_CONNECTED},
+     * {@link BluetoothProfile#STATE_DISCONNECTING}
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public int getProfileConnectionState(int profile) {
+        if (getState() != STATE_ON) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return mGetProfileConnectionStateCache.query(new Integer(profile));
+    }
+
+    /**
+     * Create a listening, secure RFCOMM Bluetooth socket.
+     * <p>A remote device connecting to this socket will be authenticated and
+     * communication on this socket will be encrypted.
+     * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
+     * connections from a listening {@link BluetoothServerSocket}.
+     * <p>Valid RFCOMM channels are in range 1 to 30.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     *
+     * @param channel RFCOMM channel to listen on
+     * @return a listening RFCOMM BluetoothServerSocket
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions, or channel in use.
+     * @hide
+     */
+    public BluetoothServerSocket listenUsingRfcommOn(int channel) throws IOException {
+        return listenUsingRfcommOn(channel, false, false);
+    }
+
+    /**
+     * Create a listening, secure RFCOMM Bluetooth socket.
+     * <p>A remote device connecting to this socket will be authenticated and
+     * communication on this socket will be encrypted.
+     * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
+     * connections from a listening {@link BluetoothServerSocket}.
+     * <p>Valid RFCOMM channels are in range 1 to 30.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * <p>To auto assign a channel without creating a SDP record use
+     * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as channel number.
+     *
+     * @param channel RFCOMM channel to listen on
+     * @param mitm enforce man-in-the-middle protection for authentication.
+     * @param min16DigitPin enforce a pin key length og minimum 16 digit for sec mode 2
+     * connections.
+     * @return a listening RFCOMM BluetoothServerSocket
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions, or channel in use.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public BluetoothServerSocket listenUsingRfcommOn(int channel, boolean mitm,
+            boolean min16DigitPin) throws IOException {
+        BluetoothServerSocket socket =
+                new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, true, true, channel, mitm,
+                        min16DigitPin);
+        int errno = socket.mSocket.bindListen();
+        if (channel == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+            socket.setChannel(socket.mSocket.getPort());
+        }
+        if (errno != 0) {
+            //TODO(BT): Throw the same exception error code
+            // that the previous code was using.
+            //socket.mSocket.throwErrnoNative(errno);
+            throw new IOException("Error: " + errno);
+        }
+        return socket;
+    }
+
+    /**
+     * Create a listening, secure RFCOMM Bluetooth socket with Service Record.
+     * <p>A remote device connecting to this socket will be authenticated and
+     * communication on this socket will be encrypted.
+     * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
+     * connections from a listening {@link BluetoothServerSocket}.
+     * <p>The system will assign an unused RFCOMM channel to listen on.
+     * <p>The system will also register a Service Discovery
+     * Protocol (SDP) record with the local SDP server containing the specified
+     * UUID, service name, and auto-assigned channel. Remote Bluetooth devices
+     * can use the same UUID to query our SDP server and discover which channel
+     * to connect to. This SDP record will be removed when this socket is
+     * closed, or if this application closes unexpectedly.
+     * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to
+     * connect to this socket from another device using the same {@link UUID}.
+     *
+     * @param name service name for SDP record
+     * @param uuid uuid for SDP record
+     * @return a listening RFCOMM BluetoothServerSocket
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions, or channel in use.
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public BluetoothServerSocket listenUsingRfcommWithServiceRecord(String name, UUID uuid)
+            throws IOException {
+        return createNewRfcommSocketAndRecord(name, uuid, true, true);
+    }
+
+    /**
+     * Create a listening, insecure RFCOMM Bluetooth socket with Service Record.
+     * <p>The link key is not required to be authenticated, i.e the communication may be
+     * vulnerable to Man In the Middle attacks. For Bluetooth 2.1 devices,
+     * the link will be encrypted, as encryption is mandartory.
+     * For legacy devices (pre Bluetooth 2.1 devices) the link will not
+     * be encrypted. Use {@link #listenUsingRfcommWithServiceRecord}, if an
+     * encrypted and authenticated communication channel is desired.
+     * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
+     * connections from a listening {@link BluetoothServerSocket}.
+     * <p>The system will assign an unused RFCOMM channel to listen on.
+     * <p>The system will also register a Service Discovery
+     * Protocol (SDP) record with the local SDP server containing the specified
+     * UUID, service name, and auto-assigned channel. Remote Bluetooth devices
+     * can use the same UUID to query our SDP server and discover which channel
+     * to connect to. This SDP record will be removed when this socket is
+     * closed, or if this application closes unexpectedly.
+     * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to
+     * connect to this socket from another device using the same {@link UUID}.
+     *
+     * @param name service name for SDP record
+     * @param uuid uuid for SDP record
+     * @return a listening RFCOMM BluetoothServerSocket
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions, or channel in use.
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public BluetoothServerSocket listenUsingInsecureRfcommWithServiceRecord(String name, UUID uuid)
+            throws IOException {
+        return createNewRfcommSocketAndRecord(name, uuid, false, false);
+    }
+
+    /**
+     * Create a listening, encrypted,
+     * RFCOMM Bluetooth socket with Service Record.
+     * <p>The link will be encrypted, but the link key is not required to be authenticated
+     * i.e the communication is vulnerable to Man In the Middle attacks. Use
+     * {@link #listenUsingRfcommWithServiceRecord}, to ensure an authenticated link key.
+     * <p> Use this socket if authentication of link key is not possible.
+     * For example, for Bluetooth 2.1 devices, if any of the devices does not have
+     * an input and output capability or just has the ability to display a numeric key,
+     * a secure socket connection is not possible and this socket can be used.
+     * Use {@link #listenUsingInsecureRfcommWithServiceRecord}, if encryption is not required.
+     * For Bluetooth 2.1 devices, the link will be encrypted, as encryption is mandartory.
+     * For more details, refer to the Security Model section 5.2 (vol 3) of
+     * Bluetooth Core Specification version 2.1 + EDR.
+     * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming
+     * connections from a listening {@link BluetoothServerSocket}.
+     * <p>The system will assign an unused RFCOMM channel to listen on.
+     * <p>The system will also register a Service Discovery
+     * Protocol (SDP) record with the local SDP server containing the specified
+     * UUID, service name, and auto-assigned channel. Remote Bluetooth devices
+     * can use the same UUID to query our SDP server and discover which channel
+     * to connect to. This SDP record will be removed when this socket is
+     * closed, or if this application closes unexpectedly.
+     * <p>Use {@link BluetoothDevice#createRfcommSocketToServiceRecord} to
+     * connect to this socket from another device using the same {@link UUID}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param name service name for SDP record
+     * @param uuid uuid for SDP record
+     * @return a listening RFCOMM BluetoothServerSocket
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions, or channel in use.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public BluetoothServerSocket listenUsingEncryptedRfcommWithServiceRecord(String name, UUID uuid)
+            throws IOException {
+        return createNewRfcommSocketAndRecord(name, uuid, false, true);
+    }
+
+
+    private BluetoothServerSocket createNewRfcommSocketAndRecord(String name, UUID uuid,
+            boolean auth, boolean encrypt) throws IOException {
+        BluetoothServerSocket socket;
+        socket = new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, auth, encrypt,
+                new ParcelUuid(uuid));
+        socket.setServiceName(name);
+        int errno = socket.mSocket.bindListen();
+        if (errno != 0) {
+            //TODO(BT): Throw the same exception error code
+            // that the previous code was using.
+            //socket.mSocket.throwErrnoNative(errno);
+            throw new IOException("Error: " + errno);
+        }
+        return socket;
+    }
+
+    /**
+     * Construct an unencrypted, unauthenticated, RFCOMM server socket.
+     * Call #accept to retrieve connections to this socket.
+     *
+     * @return An RFCOMM BluetoothServerSocket
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * permissions.
+     * @hide
+     */
+    public BluetoothServerSocket listenUsingInsecureRfcommOn(int port) throws IOException {
+        BluetoothServerSocket socket =
+                new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, false, false, port);
+        int errno = socket.mSocket.bindListen();
+        if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+            socket.setChannel(socket.mSocket.getPort());
+        }
+        if (errno != 0) {
+            //TODO(BT): Throw the same exception error code
+            // that the previous code was using.
+            //socket.mSocket.throwErrnoNative(errno);
+            throw new IOException("Error: " + errno);
+        }
+        return socket;
+    }
+
+    /**
+     * Construct an encrypted, RFCOMM server socket.
+     * Call #accept to retrieve connections to this socket.
+     *
+     * @return An RFCOMM BluetoothServerSocket
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * permissions.
+     * @hide
+     */
+    public BluetoothServerSocket listenUsingEncryptedRfcommOn(int port) throws IOException {
+        BluetoothServerSocket socket =
+                new BluetoothServerSocket(BluetoothSocket.TYPE_RFCOMM, false, true, port);
+        int errno = socket.mSocket.bindListen();
+        if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+            socket.setChannel(socket.mSocket.getPort());
+        }
+        if (errno < 0) {
+            //TODO(BT): Throw the same exception error code
+            // that the previous code was using.
+            //socket.mSocket.throwErrnoNative(errno);
+            throw new IOException("Error: " + errno);
+        }
+        return socket;
+    }
+
+    /**
+     * Construct a SCO server socket.
+     * Call #accept to retrieve connections to this socket.
+     *
+     * @return A SCO BluetoothServerSocket
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * permissions.
+     * @hide
+     */
+    public static BluetoothServerSocket listenUsingScoOn() throws IOException {
+        BluetoothServerSocket socket =
+                new BluetoothServerSocket(BluetoothSocket.TYPE_SCO, false, false, -1);
+        int errno = socket.mSocket.bindListen();
+        if (errno < 0) {
+            //TODO(BT): Throw the same exception error code
+            // that the previous code was using.
+            //socket.mSocket.throwErrnoNative(errno);
+        }
+        return socket;
+    }
+
+    /**
+     * Construct an encrypted, authenticated, L2CAP server socket.
+     * Call #accept to retrieve connections to this socket.
+     * <p>To auto assign a port without creating a SDP record use
+     * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number.
+     *
+     * @param port the PSM to listen on
+     * @param mitm enforce man-in-the-middle protection for authentication.
+     * @param min16DigitPin enforce a pin key length og minimum 16 digit for sec mode 2
+     * connections.
+     * @return An L2CAP BluetoothServerSocket
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * permissions.
+     * @hide
+     */
+    public BluetoothServerSocket listenUsingL2capOn(int port, boolean mitm, boolean min16DigitPin)
+            throws IOException {
+        BluetoothServerSocket socket =
+                new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP, true, true, port, mitm,
+                        min16DigitPin);
+        int errno = socket.mSocket.bindListen();
+        if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+            int assignedChannel = socket.mSocket.getPort();
+            if (DBG) Log.d(TAG, "listenUsingL2capOn: set assigned channel to " + assignedChannel);
+            socket.setChannel(assignedChannel);
+        }
+        if (errno != 0) {
+            //TODO(BT): Throw the same exception error code
+            // that the previous code was using.
+            //socket.mSocket.throwErrnoNative(errno);
+            throw new IOException("Error: " + errno);
+        }
+        return socket;
+    }
+
+    /**
+     * Construct an encrypted, authenticated, L2CAP server socket.
+     * Call #accept to retrieve connections to this socket.
+     * <p>To auto assign a port without creating a SDP record use
+     * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number.
+     *
+     * @param port the PSM to listen on
+     * @return An L2CAP BluetoothServerSocket
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * permissions.
+     * @hide
+     */
+    public BluetoothServerSocket listenUsingL2capOn(int port) throws IOException {
+        return listenUsingL2capOn(port, false, false);
+    }
+
+
+    /**
+     * Construct an insecure L2CAP server socket.
+     * Call #accept to retrieve connections to this socket.
+     * <p>To auto assign a port without creating a SDP record use
+     * {@link #SOCKET_CHANNEL_AUTO_STATIC_NO_SDP} as port number.
+     *
+     * @param port the PSM to listen on
+     * @return An L2CAP BluetoothServerSocket
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * permissions.
+     * @hide
+     */
+    public BluetoothServerSocket listenUsingInsecureL2capOn(int port) throws IOException {
+        Log.d(TAG, "listenUsingInsecureL2capOn: port=" + port);
+        BluetoothServerSocket socket =
+                new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP, false, false, port, false,
+                                          false);
+        int errno = socket.mSocket.bindListen();
+        if (port == SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+            int assignedChannel = socket.mSocket.getPort();
+            if (DBG) {
+                Log.d(TAG, "listenUsingInsecureL2capOn: set assigned channel to "
+                        + assignedChannel);
+            }
+            socket.setChannel(assignedChannel);
+        }
+        if (errno != 0) {
+            //TODO(BT): Throw the same exception error code
+            // that the previous code was using.
+            //socket.mSocket.throwErrnoNative(errno);
+            throw new IOException("Error: " + errno);
+        }
+        return socket;
+
+    }
+
+    /**
+     * Read the local Out of Band Pairing Data
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @return Pair<byte[], byte[]> of Hash and Randomizer
+     * @hide
+     */
+    public Pair<byte[], byte[]> readOutOfBandData() {
+        return null;
+    }
+
+    /**
+     * Get the profile proxy object associated with the profile.
+     *
+     * <p>Profile can be one of {@link BluetoothProfile#HEADSET}, {@link BluetoothProfile#A2DP},
+     * {@link BluetoothProfile#GATT}, {@link BluetoothProfile#HEARING_AID}, or {@link
+     * BluetoothProfile#GATT_SERVER}. Clients must implement {@link
+     * BluetoothProfile.ServiceListener} to get notified of the connection status and to get the
+     * proxy object.
+     *
+     * @param context Context of the application
+     * @param listener The service Listener for connection callbacks.
+     * @param profile The Bluetooth profile; either {@link BluetoothProfile#HEADSET},
+     * {@link BluetoothProfile#A2DP}, {@link BluetoothProfile#GATT}, {@link
+     * BluetoothProfile#HEARING_AID} or {@link BluetoothProfile#GATT_SERVER}.
+     * @return true on success, false on error
+     */
+    public boolean getProfileProxy(Context context, BluetoothProfile.ServiceListener listener,
+            int profile) {
+        if (context == null || listener == null) {
+            return false;
+        }
+
+        if (profile == BluetoothProfile.HEADSET) {
+            BluetoothHeadset headset = new BluetoothHeadset(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.A2DP) {
+            BluetoothA2dp a2dp = new BluetoothA2dp(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.A2DP_SINK) {
+            BluetoothA2dpSink a2dpSink = new BluetoothA2dpSink(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.AVRCP_CONTROLLER) {
+            BluetoothAvrcpController avrcp = new BluetoothAvrcpController(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.HID_HOST) {
+            BluetoothHidHost iDev = new BluetoothHidHost(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.PAN) {
+            BluetoothPan pan = new BluetoothPan(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.PBAP) {
+            BluetoothPbap pbap = new BluetoothPbap(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.HEALTH) {
+            Log.e(TAG, "getProfileProxy(): BluetoothHealth is deprecated");
+            return false;
+        } else if (profile == BluetoothProfile.MAP) {
+            BluetoothMap map = new BluetoothMap(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.HEADSET_CLIENT) {
+            BluetoothHeadsetClient headsetClient = new BluetoothHeadsetClient(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.SAP) {
+            BluetoothSap sap = new BluetoothSap(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.PBAP_CLIENT) {
+            BluetoothPbapClient pbapClient = new BluetoothPbapClient(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.MAP_CLIENT) {
+            BluetoothMapClient mapClient = new BluetoothMapClient(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.HID_DEVICE) {
+            BluetoothHidDevice hidDevice = new BluetoothHidDevice(context, listener);
+            return true;
+        } else if (profile == BluetoothProfile.HEARING_AID) {
+            if (isHearingAidProfileSupported()) {
+                BluetoothHearingAid hearingAid = new BluetoothHearingAid(context, listener);
+                return true;
+            }
+            return false;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Close the connection of the profile proxy to the Service.
+     *
+     * <p> Clients should call this when they are no longer using
+     * the proxy obtained from {@link #getProfileProxy}.
+     * Profile can be one of  {@link BluetoothProfile#HEADSET} or {@link BluetoothProfile#A2DP}
+     *
+     * @param profile
+     * @param proxy Profile proxy object
+     */
+    public void closeProfileProxy(int profile, BluetoothProfile proxy) {
+        if (proxy == null) {
+            return;
+        }
+
+        switch (profile) {
+            case BluetoothProfile.HEADSET:
+                BluetoothHeadset headset = (BluetoothHeadset) proxy;
+                headset.close();
+                break;
+            case BluetoothProfile.A2DP:
+                BluetoothA2dp a2dp = (BluetoothA2dp) proxy;
+                a2dp.close();
+                break;
+            case BluetoothProfile.A2DP_SINK:
+                BluetoothA2dpSink a2dpSink = (BluetoothA2dpSink) proxy;
+                a2dpSink.close();
+                break;
+            case BluetoothProfile.AVRCP_CONTROLLER:
+                BluetoothAvrcpController avrcp = (BluetoothAvrcpController) proxy;
+                avrcp.close();
+                break;
+            case BluetoothProfile.HID_HOST:
+                BluetoothHidHost iDev = (BluetoothHidHost) proxy;
+                iDev.close();
+                break;
+            case BluetoothProfile.PAN:
+                BluetoothPan pan = (BluetoothPan) proxy;
+                pan.close();
+                break;
+            case BluetoothProfile.PBAP:
+                BluetoothPbap pbap = (BluetoothPbap) proxy;
+                pbap.close();
+                break;
+            case BluetoothProfile.GATT:
+                BluetoothGatt gatt = (BluetoothGatt) proxy;
+                gatt.close();
+                break;
+            case BluetoothProfile.GATT_SERVER:
+                BluetoothGattServer gattServer = (BluetoothGattServer) proxy;
+                gattServer.close();
+                break;
+            case BluetoothProfile.MAP:
+                BluetoothMap map = (BluetoothMap) proxy;
+                map.close();
+                break;
+            case BluetoothProfile.HEADSET_CLIENT:
+                BluetoothHeadsetClient headsetClient = (BluetoothHeadsetClient) proxy;
+                headsetClient.close();
+                break;
+            case BluetoothProfile.SAP:
+                BluetoothSap sap = (BluetoothSap) proxy;
+                sap.close();
+                break;
+            case BluetoothProfile.PBAP_CLIENT:
+                BluetoothPbapClient pbapClient = (BluetoothPbapClient) proxy;
+                pbapClient.close();
+                break;
+            case BluetoothProfile.MAP_CLIENT:
+                BluetoothMapClient mapClient = (BluetoothMapClient) proxy;
+                mapClient.close();
+                break;
+            case BluetoothProfile.HID_DEVICE:
+                BluetoothHidDevice hidDevice = (BluetoothHidDevice) proxy;
+                hidDevice.close();
+                break;
+            case BluetoothProfile.HEARING_AID:
+                BluetoothHearingAid hearingAid = (BluetoothHearingAid) proxy;
+                hearingAid.close();
+        }
+    }
+
+    private final IBluetoothManagerCallback mManagerCallback =
+            new IBluetoothManagerCallback.Stub() {
+                public void onBluetoothServiceUp(IBluetooth bluetoothService) {
+                    if (DBG) {
+                        Log.d(TAG, "onBluetoothServiceUp: " + bluetoothService);
+                    }
+
+                    mServiceLock.writeLock().lock();
+                    mService = bluetoothService;
+                    mServiceLock.writeLock().unlock();
+
+                    synchronized (mProxyServiceStateCallbacks) {
+                        for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks) {
+                            try {
+                                if (cb != null) {
+                                    cb.onBluetoothServiceUp(bluetoothService);
+                                } else {
+                                    Log.d(TAG, "onBluetoothServiceUp: cb is null!");
+                                }
+                            } catch (Exception e) {
+                                Log.e(TAG, "", e);
+                            }
+                        }
+                    }
+                    synchronized (sMetadataListeners) {
+                        sMetadataListeners.forEach((device, pair) -> {
+                            try {
+                                mService.registerMetadataListener(sBluetoothMetadataListener,
+                                        device);
+                            } catch (RemoteException e) {
+                                Log.e(TAG, "Failed to register metadata listener", e);
+                            }
+                        });
+                    }
+                }
+
+                public void onBluetoothServiceDown() {
+                    if (DBG) {
+                        Log.d(TAG, "onBluetoothServiceDown: " + mService);
+                    }
+
+                    try {
+                        mServiceLock.writeLock().lock();
+                        mService = null;
+                        if (mLeScanClients != null) {
+                            mLeScanClients.clear();
+                        }
+                        if (sBluetoothLeAdvertiser != null) {
+                            sBluetoothLeAdvertiser.cleanup();
+                        }
+                        if (sBluetoothLeScanner != null) {
+                            sBluetoothLeScanner.cleanup();
+                        }
+                    } finally {
+                        mServiceLock.writeLock().unlock();
+                    }
+
+                    synchronized (mProxyServiceStateCallbacks) {
+                        for (IBluetoothManagerCallback cb : mProxyServiceStateCallbacks) {
+                            try {
+                                if (cb != null) {
+                                    cb.onBluetoothServiceDown();
+                                } else {
+                                    Log.d(TAG, "onBluetoothServiceDown: cb is null!");
+                                }
+                            } catch (Exception e) {
+                                Log.e(TAG, "", e);
+                            }
+                        }
+                    }
+                }
+
+                public void onBrEdrDown() {
+                    if (VDBG) {
+                        Log.i(TAG, "onBrEdrDown: " + mService);
+                    }
+                }
+            };
+
+    /**
+     * Enable the Bluetooth Adapter, but don't auto-connect devices
+     * and don't persist state. Only for use by system applications.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean enableNoAutoConnect() {
+        if (isEnabled()) {
+            if (DBG) {
+                Log.d(TAG, "enableNoAutoConnect(): BT already enabled!");
+            }
+            return true;
+        }
+        try {
+            return mManagerService.enableNoAutoConnect(ActivityThread.currentPackageName());
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Enable control of the Bluetooth Adapter for a single application.
+     *
+     * <p>Some applications need to use Bluetooth for short periods of time to
+     * transfer data but don't want all the associated implications like
+     * automatic connection to headsets etc.
+     *
+     * <p> Multiple applications can call this. This is reference counted and
+     * Bluetooth disabled only when no one else is using it. There will be no UI
+     * shown to the user while bluetooth is being enabled. Any user action will
+     * override this call. For example, if user wants Bluetooth on and the last
+     * user of this API wanted to disable Bluetooth, Bluetooth will not be
+     * turned off.
+     *
+     * <p> This API is only meant to be used by internal applications. Third
+     * party applications but use {@link #enable} and {@link #disable} APIs.
+     *
+     * <p> If this API returns true, it means the callback will be called.
+     * The callback will be called with the current state of Bluetooth.
+     * If the state is not what was requested, an internal error would be the
+     * reason. If Bluetooth is already on and if this function is called to turn
+     * it on, the api will return true and a callback will be called.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param on True for on, false for off.
+     * @param callback The callback to notify changes to the state.
+     * @hide
+     */
+    public boolean changeApplicationBluetoothState(boolean on,
+            BluetoothStateChangeCallback callback) {
+        return false;
+    }
+
+    /**
+     * @hide
+     */
+    public interface BluetoothStateChangeCallback {
+        /**
+         * @hide
+         */
+        void onBluetoothStateChange(boolean on);
+    }
+
+    /**
+     * @hide
+     */
+    public class StateChangeCallbackWrapper extends IBluetoothStateChangeCallback.Stub {
+        private BluetoothStateChangeCallback mCallback;
+
+        StateChangeCallbackWrapper(BluetoothStateChangeCallback callback) {
+            mCallback = callback;
+        }
+
+        @Override
+        public void onBluetoothStateChange(boolean on) {
+            mCallback.onBluetoothStateChange(on);
+        }
+    }
+
+    private Set<BluetoothDevice> toDeviceSet(BluetoothDevice[] devices) {
+        Set<BluetoothDevice> deviceSet = new HashSet<BluetoothDevice>(Arrays.asList(devices));
+        return Collections.unmodifiableSet(deviceSet);
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            mManagerService.unregisterAdapter(mManagerCallback);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        } finally {
+            super.finalize();
+        }
+    }
+
+
+    /**
+     * Validate a String Bluetooth address, such as "00:43:A8:23:10:F0"
+     * <p>Alphabetic characters must be uppercase to be valid.
+     *
+     * @param address Bluetooth address as string
+     * @return true if the address is valid, false otherwise
+     */
+    public static boolean checkBluetoothAddress(String address) {
+        if (address == null || address.length() != ADDRESS_LENGTH) {
+            return false;
+        }
+        for (int i = 0; i < ADDRESS_LENGTH; i++) {
+            char c = address.charAt(i);
+            switch (i % 3) {
+                case 0:
+                case 1:
+                    if ((c >= '0' && c <= '9') || (c >= 'A' && c <= 'F')) {
+                        // hex character, OK
+                        break;
+                    }
+                    return false;
+                case 2:
+                    if (c == ':') {
+                        break;  // OK
+                    }
+                    return false;
+            }
+        }
+        return true;
+    }
+
+    @UnsupportedAppUsage
+    /*package*/ IBluetoothManager getBluetoothManager() {
+        return mManagerService;
+    }
+
+    private final ArrayList<IBluetoothManagerCallback> mProxyServiceStateCallbacks =
+            new ArrayList<IBluetoothManagerCallback>();
+
+    @UnsupportedAppUsage
+    /*package*/ IBluetooth getBluetoothService(IBluetoothManagerCallback cb) {
+        synchronized (mProxyServiceStateCallbacks) {
+            if (cb == null) {
+                Log.w(TAG, "getBluetoothService() called with no BluetoothManagerCallback");
+            } else if (!mProxyServiceStateCallbacks.contains(cb)) {
+                mProxyServiceStateCallbacks.add(cb);
+            }
+        }
+        return mService;
+    }
+
+    /*package*/ void removeServiceStateCallback(IBluetoothManagerCallback cb) {
+        synchronized (mProxyServiceStateCallbacks) {
+            mProxyServiceStateCallbacks.remove(cb);
+        }
+    }
+
+    /**
+     * Callback interface used to deliver LE scan results.
+     *
+     * @see #startLeScan(LeScanCallback)
+     * @see #startLeScan(UUID[], LeScanCallback)
+     */
+    public interface LeScanCallback {
+        /**
+         * Callback reporting an LE device found during a device scan initiated
+         * by the {@link BluetoothAdapter#startLeScan} function.
+         *
+         * @param device Identifies the remote device
+         * @param rssi The RSSI value for the remote device as reported by the Bluetooth hardware. 0
+         * if no RSSI value is available.
+         * @param scanRecord The content of the advertisement record offered by the remote device.
+         */
+        void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord);
+    }
+
+    /**
+     * Starts a scan for Bluetooth LE devices.
+     *
+     * <p>Results of the scan are reported using the
+     * {@link LeScanCallback#onLeScan} callback.
+     *
+     * @param callback the callback LE scan results are delivered
+     * @return true, if the scan was started successfully
+     * @deprecated use {@link BluetoothLeScanner#startScan(List, ScanSettings, ScanCallback)}
+     * instead.
+     */
+    @Deprecated
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean startLeScan(LeScanCallback callback) {
+        return startLeScan(null, callback);
+    }
+
+    /**
+     * 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 LeScanCallback#onLeScan} callback.
+     *
+     * @param serviceUuids Array of services to look for
+     * @param callback the callback LE scan results are delivered
+     * @return true, if the scan was started successfully
+     * @deprecated use {@link BluetoothLeScanner#startScan(List, ScanSettings, ScanCallback)}
+     * instead.
+     */
+    @Deprecated
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean startLeScan(final UUID[] serviceUuids, final LeScanCallback callback) {
+        if (DBG) {
+            Log.d(TAG, "startLeScan(): " + Arrays.toString(serviceUuids));
+        }
+        if (callback == null) {
+            if (DBG) {
+                Log.e(TAG, "startLeScan: null callback");
+            }
+            return false;
+        }
+        BluetoothLeScanner scanner = getBluetoothLeScanner();
+        if (scanner == null) {
+            if (DBG) {
+                Log.e(TAG, "startLeScan: cannot get BluetoothLeScanner");
+            }
+            return false;
+        }
+
+        synchronized (mLeScanClients) {
+            if (mLeScanClients.containsKey(callback)) {
+                if (DBG) {
+                    Log.e(TAG, "LE Scan has already started");
+                }
+                return false;
+            }
+
+            try {
+                IBluetoothGatt iGatt = mManagerService.getBluetoothGatt();
+                if (iGatt == null) {
+                    // BLE is not supported
+                    return false;
+                }
+
+                ScanCallback scanCallback = new ScanCallback() {
+                    @Override
+                    public void onScanResult(int callbackType, ScanResult result) {
+                        if (callbackType != ScanSettings.CALLBACK_TYPE_ALL_MATCHES) {
+                            // Should not happen.
+                            Log.e(TAG, "LE Scan has already started");
+                            return;
+                        }
+                        ScanRecord scanRecord = result.getScanRecord();
+                        if (scanRecord == null) {
+                            return;
+                        }
+                        if (serviceUuids != null) {
+                            List<ParcelUuid> uuids = new ArrayList<ParcelUuid>();
+                            for (UUID uuid : serviceUuids) {
+                                uuids.add(new ParcelUuid(uuid));
+                            }
+                            List<ParcelUuid> scanServiceUuids = scanRecord.getServiceUuids();
+                            if (scanServiceUuids == null || !scanServiceUuids.containsAll(uuids)) {
+                                if (DBG) {
+                                    Log.d(TAG, "uuids does not match");
+                                }
+                                return;
+                            }
+                        }
+                        callback.onLeScan(result.getDevice(), result.getRssi(),
+                                scanRecord.getBytes());
+                    }
+                };
+                ScanSettings settings = new ScanSettings.Builder().setCallbackType(
+                        ScanSettings.CALLBACK_TYPE_ALL_MATCHES)
+                        .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY)
+                        .build();
+
+                List<ScanFilter> filters = new ArrayList<ScanFilter>();
+                if (serviceUuids != null && serviceUuids.length > 0) {
+                    // Note scan filter does not support matching an UUID array so we put one
+                    // UUID to hardware and match the whole array in callback.
+                    ScanFilter filter =
+                            new ScanFilter.Builder().setServiceUuid(new ParcelUuid(serviceUuids[0]))
+                                    .build();
+                    filters.add(filter);
+                }
+                scanner.startScan(filters, settings, scanCallback);
+
+                mLeScanClients.put(callback, scanCallback);
+                return true;
+
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Stops an ongoing Bluetooth LE device scan.
+     *
+     * @param callback used to identify which scan to stop must be the same handle used to start the
+     * scan
+     * @deprecated Use {@link BluetoothLeScanner#stopScan(ScanCallback)} instead.
+     */
+    @Deprecated
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public void stopLeScan(LeScanCallback callback) {
+        if (DBG) {
+            Log.d(TAG, "stopLeScan()");
+        }
+        BluetoothLeScanner scanner = getBluetoothLeScanner();
+        if (scanner == null) {
+            return;
+        }
+        synchronized (mLeScanClients) {
+            ScanCallback scanCallback = mLeScanClients.remove(callback);
+            if (scanCallback == null) {
+                if (DBG) {
+                    Log.d(TAG, "scan not started yet");
+                }
+                return;
+            }
+            scanner.stopScan(scanCallback);
+        }
+    }
+
+    /**
+     * Create a secure L2CAP Connection-oriented Channel (CoC) {@link BluetoothServerSocket} and
+     * assign a dynamic protocol/service multiplexer (PSM) value. This socket can be used to listen
+     * for incoming connections. The supported Bluetooth transport is LE only.
+     * <p>A remote device connecting to this socket will be authenticated and communication on this
+     * socket will be encrypted.
+     * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening
+     * {@link BluetoothServerSocket}.
+     * <p>The system will assign a dynamic PSM value. This PSM value can be read from the {@link
+     * BluetoothServerSocket#getPsm()} and this value will be released when this server socket is
+     * closed, Bluetooth is turned off, or the application exits unexpectedly.
+     * <p>The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is
+     * defined and performed by the application.
+     * <p>Use {@link BluetoothDevice#createL2capChannel(int)} to connect to this server
+     * socket from another Android device that is given the PSM value.
+     *
+     * @return an L2CAP CoC BluetoothServerSocket
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions, or unable to start this CoC
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @NonNull BluetoothServerSocket listenUsingL2capChannel()
+            throws IOException {
+        BluetoothServerSocket socket =
+                            new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP_LE, true, true,
+                                      SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false, false);
+        int errno = socket.mSocket.bindListen();
+        if (errno != 0) {
+            throw new IOException("Error: " + errno);
+        }
+
+        int assignedPsm = socket.mSocket.getPort();
+        if (assignedPsm == 0) {
+            throw new IOException("Error: Unable to assign PSM value");
+        }
+        if (DBG) {
+            Log.d(TAG, "listenUsingL2capChannel: set assigned PSM to "
+                    + assignedPsm);
+        }
+        socket.setChannel(assignedPsm);
+
+        return socket;
+    }
+
+    /**
+     * Create an insecure L2CAP Connection-oriented Channel (CoC) {@link BluetoothServerSocket} and
+     * assign a dynamic PSM value. This socket can be used to listen for incoming connections. The
+     * supported Bluetooth transport is LE only.
+     * <p>The link key is not required to be authenticated, i.e the communication may be vulnerable
+     * to man-in-the-middle attacks. Use {@link #listenUsingL2capChannel}, if an encrypted and
+     * authenticated communication channel is desired.
+     * <p>Use {@link BluetoothServerSocket#accept} to retrieve incoming connections from a listening
+     * {@link BluetoothServerSocket}.
+     * <p>The system will assign a dynamic protocol/service multiplexer (PSM) value. This PSM value
+     * can be read from the {@link BluetoothServerSocket#getPsm()} and this value will be released
+     * when this server socket is closed, Bluetooth is turned off, or the application exits
+     * unexpectedly.
+     * <p>The mechanism of disclosing the assigned dynamic PSM value to the initiating peer is
+     * defined and performed by the application.
+     * <p>Use {@link BluetoothDevice#createInsecureL2capChannel(int)} to connect to this server
+     * socket from another Android device that is given the PSM value.
+     *
+     * @return an L2CAP CoC BluetoothServerSocket
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions, or unable to start this CoC
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @NonNull BluetoothServerSocket listenUsingInsecureL2capChannel()
+            throws IOException {
+        BluetoothServerSocket socket =
+                            new BluetoothServerSocket(BluetoothSocket.TYPE_L2CAP_LE, false, false,
+                                      SOCKET_CHANNEL_AUTO_STATIC_NO_SDP, false, false);
+        int errno = socket.mSocket.bindListen();
+        if (errno != 0) {
+            throw new IOException("Error: " + errno);
+        }
+
+        int assignedPsm = socket.mSocket.getPort();
+        if (assignedPsm == 0) {
+            throw new IOException("Error: Unable to assign PSM value");
+        }
+        if (DBG) {
+            Log.d(TAG, "listenUsingInsecureL2capChannel: set assigned PSM to "
+                    + assignedPsm);
+        }
+        socket.setChannel(assignedPsm);
+
+        return socket;
+    }
+
+    /**
+     * Register a {@link #OnMetadataChangedListener} to receive update about metadata
+     * changes for this {@link BluetoothDevice}.
+     * Registration must be done when Bluetooth is ON and will last until
+     * {@link #removeOnMetadataChangedListener(BluetoothDevice)} is called, even when Bluetooth
+     * restarted in the middle.
+     * All input parameters should not be null or {@link NullPointerException} will be triggered.
+     * The same {@link BluetoothDevice} and {@link #OnMetadataChangedListener} pair can only be
+     * registered once, double registration would cause {@link IllegalArgumentException}.
+     *
+     * @param device {@link BluetoothDevice} that will be registered
+     * @param executor the executor for listener callback
+     * @param listener {@link #OnMetadataChangedListener} that will receive asynchronous callbacks
+     * @return true on success, false on error
+     * @throws NullPointerException If one of {@code listener}, {@code device} or {@code executor}
+     * is null.
+     * @throws IllegalArgumentException The same {@link #OnMetadataChangedListener} and
+     * {@link BluetoothDevice} are registered twice.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean addOnMetadataChangedListener(@NonNull BluetoothDevice device,
+            @NonNull Executor executor, @NonNull OnMetadataChangedListener listener) {
+        if (DBG) Log.d(TAG, "addOnMetadataChangedListener()");
+
+        final IBluetooth service = mService;
+        if (service == null) {
+            Log.e(TAG, "Bluetooth is not enabled. Cannot register metadata listener");
+            return false;
+        }
+        if (listener == null) {
+            throw new NullPointerException("listener is null");
+        }
+        if (device == null) {
+            throw new NullPointerException("device is null");
+        }
+        if (executor == null) {
+            throw new NullPointerException("executor is null");
+        }
+
+        synchronized (sMetadataListeners) {
+            List<Pair<OnMetadataChangedListener, Executor>> listenerList =
+                    sMetadataListeners.get(device);
+            if (listenerList == null) {
+                // Create new listener/executor list for registeration
+                listenerList = new ArrayList<>();
+                sMetadataListeners.put(device, listenerList);
+            } else {
+                // Check whether this device was already registed by the lisenter
+                if (listenerList.stream().anyMatch((pair) -> (pair.first.equals(listener)))) {
+                    throw new IllegalArgumentException("listener was already regestered"
+                            + " for the device");
+                }
+            }
+
+            Pair<OnMetadataChangedListener, Executor> listenerPair = new Pair(listener, executor);
+            listenerList.add(listenerPair);
+
+            boolean ret = false;
+            try {
+                ret = service.registerMetadataListener(sBluetoothMetadataListener, device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "registerMetadataListener fail", e);
+            } finally {
+                if (!ret) {
+                    // Remove listener registered earlier when fail.
+                    listenerList.remove(listenerPair);
+                    if (listenerList.isEmpty()) {
+                        // Remove the device if its listener list is empty
+                        sMetadataListeners.remove(device);
+                    }
+                }
+            }
+            return ret;
+        }
+    }
+
+    /**
+     * Unregister a {@link #OnMetadataChangedListener} from a registered {@link BluetoothDevice}.
+     * Unregistration can be done when Bluetooth is either ON or OFF.
+     * {@link #addOnMetadataChangedListener(OnMetadataChangedListener, BluetoothDevice, Executor)}
+     * must be called before unregisteration.
+     *
+     * @param device {@link BluetoothDevice} that will be unregistered. It
+     * should not be null or {@link NullPointerException} will be triggered.
+     * @param listener {@link OnMetadataChangedListener} that will be unregistered. It
+     * should not be null or {@link NullPointerException} will be triggered.
+     * @return true on success, false on error
+     * @throws NullPointerException If {@code listener} or {@code device} is null.
+     * @throws IllegalArgumentException If {@code device} has not been registered before.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean removeOnMetadataChangedListener(@NonNull BluetoothDevice device,
+            @NonNull OnMetadataChangedListener listener) {
+        if (DBG) Log.d(TAG, "removeOnMetadataChangedListener()");
+        if (device == null) {
+            throw new NullPointerException("device is null");
+        }
+        if (listener == null) {
+            throw new NullPointerException("listener is null");
+        }
+
+        synchronized (sMetadataListeners) {
+            if (!sMetadataListeners.containsKey(device)) {
+                throw new IllegalArgumentException("device was not registered");
+            }
+            // Remove issued listener from the registered device
+            sMetadataListeners.get(device).removeIf((pair) -> (pair.first.equals(listener)));
+
+            if (sMetadataListeners.get(device).isEmpty()) {
+                // Unregister to Bluetooth service if all listeners are removed from
+                // the registered device
+                sMetadataListeners.remove(device);
+                final IBluetooth service = mService;
+                if (service == null) {
+                    // Bluetooth is OFF, do nothing to Bluetooth service.
+                    return true;
+                }
+                try {
+                    return service.unregisterMetadataListener(device);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "unregisterMetadataListener fail", e);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    /**
+     * This interface is used to implement {@link BluetoothAdapter} metadata listener.
+     * @hide
+     */
+    @SystemApi
+    public interface OnMetadataChangedListener {
+        /**
+         * Callback triggered if the metadata of {@link BluetoothDevice} registered in
+         * {@link #addOnMetadataChangedListener}.
+         *
+         * @param device changed {@link BluetoothDevice}.
+         * @param key changed metadata key, one of BluetoothDevice.METADATA_*.
+         * @param value the new value of metadata as byte array.
+         */
+        void onMetadataChanged(@NonNull BluetoothDevice device, int key,
+                @Nullable byte[] value);
+    }
+
+    /**
+     * Converts old constant of priority to the new for connection policy
+     *
+     * @param priority is the priority to convert to connection policy
+     * @return the equivalent connection policy constant to the priority
+     *
+     * @hide
+     */
+    public static @ConnectionPolicy int priorityToConnectionPolicy(int priority) {
+        switch(priority) {
+            case BluetoothProfile.PRIORITY_AUTO_CONNECT:
+                return BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+            case BluetoothProfile.PRIORITY_ON:
+                return BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+            case BluetoothProfile.PRIORITY_OFF:
+                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            case BluetoothProfile.PRIORITY_UNDEFINED:
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+            default:
+                Log.e(TAG, "setPriority: Invalid priority: " + priority);
+                return BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
+        }
+    }
+
+    /**
+     * Converts new constant of connection policy to the old for priority
+     *
+     * @param connectionPolicy is the connection policy to convert to priority
+     * @return the equivalent priority constant to the connectionPolicy
+     *
+     * @hide
+     */
+    public static int connectionPolicyToPriority(@ConnectionPolicy int connectionPolicy) {
+        switch(connectionPolicy) {
+            case BluetoothProfile.CONNECTION_POLICY_ALLOWED:
+                return BluetoothProfile.PRIORITY_ON;
+            case BluetoothProfile.CONNECTION_POLICY_FORBIDDEN:
+                return BluetoothProfile.PRIORITY_OFF;
+            case BluetoothProfile.CONNECTION_POLICY_UNKNOWN:
+                return BluetoothProfile.PRIORITY_UNDEFINED;
+        }
+        return BluetoothProfile.PRIORITY_UNDEFINED;
+    }
+}
diff --git a/android/bluetooth/BluetoothAssignedNumbers.java b/android/bluetooth/BluetoothAssignedNumbers.java
new file mode 100644
index 0000000..41a34e0
--- /dev/null
+++ b/android/bluetooth/BluetoothAssignedNumbers.java
@@ -0,0 +1,1171 @@
+/*
+ * Copyright (C) 2010 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;
+
+/**
+ * Bluetooth Assigned Numbers.
+ * <p>
+ * For now we only include Company ID values.
+ *
+ * @see <a href="https://www.bluetooth.org/technical/assignednumbers/identifiers.htm"> The Official
+ * Bluetooth SIG Member Website | Company Identifiers</a>
+ */
+public class BluetoothAssignedNumbers {
+
+    // Bluetooth SIG Company ID values
+    /*
+     * Ericsson Technology Licensing.
+     */
+    public static final int ERICSSON_TECHNOLOGY = 0x0000;
+
+    /*
+     * Nokia Mobile Phones.
+     */
+    public static final int NOKIA_MOBILE_PHONES = 0x0001;
+
+    /*
+     * Intel Corp.
+     */
+    public static final int INTEL = 0x0002;
+
+    /*
+     * IBM Corp.
+     */
+    public static final int IBM = 0x0003;
+
+    /*
+     * Toshiba Corp.
+     */
+    public static final int TOSHIBA = 0x0004;
+
+    /*
+     * 3Com.
+     */
+    public static final int THREECOM = 0x0005;
+
+    /*
+     * Microsoft.
+     */
+    public static final int MICROSOFT = 0x0006;
+
+    /*
+     * Lucent.
+     */
+    public static final int LUCENT = 0x0007;
+
+    /*
+     * Motorola.
+     */
+    public static final int MOTOROLA = 0x0008;
+
+    /*
+     * Infineon Technologies AG.
+     */
+    public static final int INFINEON_TECHNOLOGIES = 0x0009;
+
+    /*
+     * Cambridge Silicon Radio.
+     */
+    public static final int CAMBRIDGE_SILICON_RADIO = 0x000A;
+
+    /*
+     * Silicon Wave.
+     */
+    public static final int SILICON_WAVE = 0x000B;
+
+    /*
+     * Digianswer A/S.
+     */
+    public static final int DIGIANSWER = 0x000C;
+
+    /*
+     * Texas Instruments Inc.
+     */
+    public static final int TEXAS_INSTRUMENTS = 0x000D;
+
+    /*
+     * Parthus Technologies Inc.
+     */
+    public static final int PARTHUS_TECHNOLOGIES = 0x000E;
+
+    /*
+     * Broadcom Corporation.
+     */
+    public static final int BROADCOM = 0x000F;
+
+    /*
+     * Mitel Semiconductor.
+     */
+    public static final int MITEL_SEMICONDUCTOR = 0x0010;
+
+    /*
+     * Widcomm, Inc.
+     */
+    public static final int WIDCOMM = 0x0011;
+
+    /*
+     * Zeevo, Inc.
+     */
+    public static final int ZEEVO = 0x0012;
+
+    /*
+     * Atmel Corporation.
+     */
+    public static final int ATMEL = 0x0013;
+
+    /*
+     * Mitsubishi Electric Corporation.
+     */
+    public static final int MITSUBISHI_ELECTRIC = 0x0014;
+
+    /*
+     * RTX Telecom A/S.
+     */
+    public static final int RTX_TELECOM = 0x0015;
+
+    /*
+     * KC Technology Inc.
+     */
+    public static final int KC_TECHNOLOGY = 0x0016;
+
+    /*
+     * Newlogic.
+     */
+    public static final int NEWLOGIC = 0x0017;
+
+    /*
+     * Transilica, Inc.
+     */
+    public static final int TRANSILICA = 0x0018;
+
+    /*
+     * Rohde & Schwarz GmbH & Co. KG.
+     */
+    public static final int ROHDE_AND_SCHWARZ = 0x0019;
+
+    /*
+     * TTPCom Limited.
+     */
+    public static final int TTPCOM = 0x001A;
+
+    /*
+     * Signia Technologies, Inc.
+     */
+    public static final int SIGNIA_TECHNOLOGIES = 0x001B;
+
+    /*
+     * Conexant Systems Inc.
+     */
+    public static final int CONEXANT_SYSTEMS = 0x001C;
+
+    /*
+     * Qualcomm.
+     */
+    public static final int QUALCOMM = 0x001D;
+
+    /*
+     * Inventel.
+     */
+    public static final int INVENTEL = 0x001E;
+
+    /*
+     * AVM Berlin.
+     */
+    public static final int AVM_BERLIN = 0x001F;
+
+    /*
+     * BandSpeed, Inc.
+     */
+    public static final int BANDSPEED = 0x0020;
+
+    /*
+     * Mansella Ltd.
+     */
+    public static final int MANSELLA = 0x0021;
+
+    /*
+     * NEC Corporation.
+     */
+    public static final int NEC = 0x0022;
+
+    /*
+     * WavePlus Technology Co., Ltd.
+     */
+    public static final int WAVEPLUS_TECHNOLOGY = 0x0023;
+
+    /*
+     * Alcatel.
+     */
+    public static final int ALCATEL = 0x0024;
+
+    /*
+     * Philips Semiconductors.
+     */
+    public static final int PHILIPS_SEMICONDUCTORS = 0x0025;
+
+    /*
+     * C Technologies.
+     */
+    public static final int C_TECHNOLOGIES = 0x0026;
+
+    /*
+     * Open Interface.
+     */
+    public static final int OPEN_INTERFACE = 0x0027;
+
+    /*
+     * R F Micro Devices.
+     */
+    public static final int RF_MICRO_DEVICES = 0x0028;
+
+    /*
+     * Hitachi Ltd.
+     */
+    public static final int HITACHI = 0x0029;
+
+    /*
+     * Symbol Technologies, Inc.
+     */
+    public static final int SYMBOL_TECHNOLOGIES = 0x002A;
+
+    /*
+     * Tenovis.
+     */
+    public static final int TENOVIS = 0x002B;
+
+    /*
+     * Macronix International Co. Ltd.
+     */
+    public static final int MACRONIX = 0x002C;
+
+    /*
+     * GCT Semiconductor.
+     */
+    public static final int GCT_SEMICONDUCTOR = 0x002D;
+
+    /*
+     * Norwood Systems.
+     */
+    public static final int NORWOOD_SYSTEMS = 0x002E;
+
+    /*
+     * MewTel Technology Inc.
+     */
+    public static final int MEWTEL_TECHNOLOGY = 0x002F;
+
+    /*
+     * ST Microelectronics.
+     */
+    public static final int ST_MICROELECTRONICS = 0x0030;
+
+    /*
+     * Synopsys.
+     */
+    public static final int SYNOPSYS = 0x0031;
+
+    /*
+     * Red-M (Communications) Ltd.
+     */
+    public static final int RED_M = 0x0032;
+
+    /*
+     * Commil Ltd.
+     */
+    public static final int COMMIL = 0x0033;
+
+    /*
+     * Computer Access Technology Corporation (CATC).
+     */
+    public static final int CATC = 0x0034;
+
+    /*
+     * Eclipse (HQ Espana) S.L.
+     */
+    public static final int ECLIPSE = 0x0035;
+
+    /*
+     * Renesas Technology Corp.
+     */
+    public static final int RENESAS_TECHNOLOGY = 0x0036;
+
+    /*
+     * Mobilian Corporation.
+     */
+    public static final int MOBILIAN_CORPORATION = 0x0037;
+
+    /*
+     * Terax.
+     */
+    public static final int TERAX = 0x0038;
+
+    /*
+     * Integrated System Solution Corp.
+     */
+    public static final int INTEGRATED_SYSTEM_SOLUTION = 0x0039;
+
+    /*
+     * Matsushita Electric Industrial Co., Ltd.
+     */
+    public static final int MATSUSHITA_ELECTRIC = 0x003A;
+
+    /*
+     * Gennum Corporation.
+     */
+    public static final int GENNUM = 0x003B;
+
+    /*
+     * Research In Motion.
+     */
+    public static final int RESEARCH_IN_MOTION = 0x003C;
+
+    /*
+     * IPextreme, Inc.
+     */
+    public static final int IPEXTREME = 0x003D;
+
+    /*
+     * Systems and Chips, Inc.
+     */
+    public static final int SYSTEMS_AND_CHIPS = 0x003E;
+
+    /*
+     * Bluetooth SIG, Inc.
+     */
+    public static final int BLUETOOTH_SIG = 0x003F;
+
+    /*
+     * Seiko Epson Corporation.
+     */
+    public static final int SEIKO_EPSON = 0x0040;
+
+    /*
+     * Integrated Silicon Solution Taiwan, Inc.
+     */
+    public static final int INTEGRATED_SILICON_SOLUTION = 0x0041;
+
+    /*
+     * CONWISE Technology Corporation Ltd.
+     */
+    public static final int CONWISE_TECHNOLOGY = 0x0042;
+
+    /*
+     * PARROT SA.
+     */
+    public static final int PARROT = 0x0043;
+
+    /*
+     * Socket Mobile.
+     */
+    public static final int SOCKET_MOBILE = 0x0044;
+
+    /*
+     * Atheros Communications, Inc.
+     */
+    public static final int ATHEROS_COMMUNICATIONS = 0x0045;
+
+    /*
+     * MediaTek, Inc.
+     */
+    public static final int MEDIATEK = 0x0046;
+
+    /*
+     * Bluegiga.
+     */
+    public static final int BLUEGIGA = 0x0047;
+
+    /*
+     * Marvell Technology Group Ltd.
+     */
+    public static final int MARVELL = 0x0048;
+
+    /*
+     * 3DSP Corporation.
+     */
+    public static final int THREE_DSP = 0x0049;
+
+    /*
+     * Accel Semiconductor Ltd.
+     */
+    public static final int ACCEL_SEMICONDUCTOR = 0x004A;
+
+    /*
+     * Continental Automotive Systems.
+     */
+    public static final int CONTINENTAL_AUTOMOTIVE = 0x004B;
+
+    /*
+     * Apple, Inc.
+     */
+    public static final int APPLE = 0x004C;
+
+    /*
+     * Staccato Communications, Inc.
+     */
+    public static final int STACCATO_COMMUNICATIONS = 0x004D;
+
+    /*
+     * Avago Technologies.
+     */
+    public static final int AVAGO = 0x004E;
+
+    /*
+     * APT Licensing Ltd.
+     */
+    public static final int APT_LICENSING = 0x004F;
+
+    /*
+     * SiRF Technology, Inc.
+     */
+    public static final int SIRF_TECHNOLOGY = 0x0050;
+
+    /*
+     * Tzero Technologies, Inc.
+     */
+    public static final int TZERO_TECHNOLOGIES = 0x0051;
+
+    /*
+     * J&M Corporation.
+     */
+    public static final int J_AND_M = 0x0052;
+
+    /*
+     * Free2move AB.
+     */
+    public static final int FREE2MOVE = 0x0053;
+
+    /*
+     * 3DiJoy Corporation.
+     */
+    public static final int THREE_DIJOY = 0x0054;
+
+    /*
+     * Plantronics, Inc.
+     */
+    public static final int PLANTRONICS = 0x0055;
+
+    /*
+     * Sony Ericsson Mobile Communications.
+     */
+    public static final int SONY_ERICSSON = 0x0056;
+
+    /*
+     * Harman International Industries, Inc.
+     */
+    public static final int HARMAN_INTERNATIONAL = 0x0057;
+
+    /*
+     * Vizio, Inc.
+     */
+    public static final int VIZIO = 0x0058;
+
+    /*
+     * Nordic Semiconductor ASA.
+     */
+    public static final int NORDIC_SEMICONDUCTOR = 0x0059;
+
+    /*
+     * EM Microelectronic-Marin SA.
+     */
+    public static final int EM_MICROELECTRONIC_MARIN = 0x005A;
+
+    /*
+     * Ralink Technology Corporation.
+     */
+    public static final int RALINK_TECHNOLOGY = 0x005B;
+
+    /*
+     * Belkin International, Inc.
+     */
+    public static final int BELKIN_INTERNATIONAL = 0x005C;
+
+    /*
+     * Realtek Semiconductor Corporation.
+     */
+    public static final int REALTEK_SEMICONDUCTOR = 0x005D;
+
+    /*
+     * Stonestreet One, LLC.
+     */
+    public static final int STONESTREET_ONE = 0x005E;
+
+    /*
+     * Wicentric, Inc.
+     */
+    public static final int WICENTRIC = 0x005F;
+
+    /*
+     * RivieraWaves S.A.S.
+     */
+    public static final int RIVIERAWAVES = 0x0060;
+
+    /*
+     * RDA Microelectronics.
+     */
+    public static final int RDA_MICROELECTRONICS = 0x0061;
+
+    /*
+     * Gibson Guitars.
+     */
+    public static final int GIBSON_GUITARS = 0x0062;
+
+    /*
+     * MiCommand Inc.
+     */
+    public static final int MICOMMAND = 0x0063;
+
+    /*
+     * Band XI International, LLC.
+     */
+    public static final int BAND_XI_INTERNATIONAL = 0x0064;
+
+    /*
+     * Hewlett-Packard Company.
+     */
+    public static final int HEWLETT_PACKARD = 0x0065;
+
+    /*
+     * 9Solutions Oy.
+     */
+    public static final int NINE_SOLUTIONS = 0x0066;
+
+    /*
+     * GN Netcom A/S.
+     */
+    public static final int GN_NETCOM = 0x0067;
+
+    /*
+     * General Motors.
+     */
+    public static final int GENERAL_MOTORS = 0x0068;
+
+    /*
+     * A&D Engineering, Inc.
+     */
+    public static final int A_AND_D_ENGINEERING = 0x0069;
+
+    /*
+     * MindTree Ltd.
+     */
+    public static final int MINDTREE = 0x006A;
+
+    /*
+     * Polar Electro OY.
+     */
+    public static final int POLAR_ELECTRO = 0x006B;
+
+    /*
+     * Beautiful Enterprise Co., Ltd.
+     */
+    public static final int BEAUTIFUL_ENTERPRISE = 0x006C;
+
+    /*
+     * BriarTek, Inc.
+     */
+    public static final int BRIARTEK = 0x006D;
+
+    /*
+     * Summit Data Communications, Inc.
+     */
+    public static final int SUMMIT_DATA_COMMUNICATIONS = 0x006E;
+
+    /*
+     * Sound ID.
+     */
+    public static final int SOUND_ID = 0x006F;
+
+    /*
+     * Monster, LLC.
+     */
+    public static final int MONSTER = 0x0070;
+
+    /*
+     * connectBlue AB.
+     */
+    public static final int CONNECTBLUE = 0x0071;
+
+    /*
+     * ShangHai Super Smart Electronics Co. Ltd.
+     */
+    public static final int SHANGHAI_SUPER_SMART_ELECTRONICS = 0x0072;
+
+    /*
+     * Group Sense Ltd.
+     */
+    public static final int GROUP_SENSE = 0x0073;
+
+    /*
+     * Zomm, LLC.
+     */
+    public static final int ZOMM = 0x0074;
+
+    /*
+     * Samsung Electronics Co. Ltd.
+     */
+    public static final int SAMSUNG_ELECTRONICS = 0x0075;
+
+    /*
+     * Creative Technology Ltd.
+     */
+    public static final int CREATIVE_TECHNOLOGY = 0x0076;
+
+    /*
+     * Laird Technologies.
+     */
+    public static final int LAIRD_TECHNOLOGIES = 0x0077;
+
+    /*
+     * Nike, Inc.
+     */
+    public static final int NIKE = 0x0078;
+
+    /*
+     * lesswire AG.
+     */
+    public static final int LESSWIRE = 0x0079;
+
+    /*
+     * MStar Semiconductor, Inc.
+     */
+    public static final int MSTAR_SEMICONDUCTOR = 0x007A;
+
+    /*
+     * Hanlynn Technologies.
+     */
+    public static final int HANLYNN_TECHNOLOGIES = 0x007B;
+
+    /*
+     * A & R Cambridge.
+     */
+    public static final int A_AND_R_CAMBRIDGE = 0x007C;
+
+    /*
+     * Seers Technology Co. Ltd.
+     */
+    public static final int SEERS_TECHNOLOGY = 0x007D;
+
+    /*
+     * Sports Tracking Technologies Ltd.
+     */
+    public static final int SPORTS_TRACKING_TECHNOLOGIES = 0x007E;
+
+    /*
+     * Autonet Mobile.
+     */
+    public static final int AUTONET_MOBILE = 0x007F;
+
+    /*
+     * DeLorme Publishing Company, Inc.
+     */
+    public static final int DELORME_PUBLISHING_COMPANY = 0x0080;
+
+    /*
+     * WuXi Vimicro.
+     */
+    public static final int WUXI_VIMICRO = 0x0081;
+
+    /*
+     * Sennheiser Communications A/S.
+     */
+    public static final int SENNHEISER_COMMUNICATIONS = 0x0082;
+
+    /*
+     * TimeKeeping Systems, Inc.
+     */
+    public static final int TIMEKEEPING_SYSTEMS = 0x0083;
+
+    /*
+     * Ludus Helsinki Ltd.
+     */
+    public static final int LUDUS_HELSINKI = 0x0084;
+
+    /*
+     * BlueRadios, Inc.
+     */
+    public static final int BLUERADIOS = 0x0085;
+
+    /*
+     * equinox AG.
+     */
+    public static final int EQUINOX_AG = 0x0086;
+
+    /*
+     * Garmin International, Inc.
+     */
+    public static final int GARMIN_INTERNATIONAL = 0x0087;
+
+    /*
+     * Ecotest.
+     */
+    public static final int ECOTEST = 0x0088;
+
+    /*
+     * GN ReSound A/S.
+     */
+    public static final int GN_RESOUND = 0x0089;
+
+    /*
+     * Jawbone.
+     */
+    public static final int JAWBONE = 0x008A;
+
+    /*
+     * Topcorn Positioning Systems, LLC.
+     */
+    public static final int TOPCORN_POSITIONING_SYSTEMS = 0x008B;
+
+    /*
+     * Qualcomm Labs, Inc.
+     */
+    public static final int QUALCOMM_LABS = 0x008C;
+
+    /*
+     * Zscan Software.
+     */
+    public static final int ZSCAN_SOFTWARE = 0x008D;
+
+    /*
+     * Quintic Corp.
+     */
+    public static final int QUINTIC = 0x008E;
+
+    /*
+     * Stollman E+V GmbH.
+     */
+    public static final int STOLLMAN_E_PLUS_V = 0x008F;
+
+    /*
+     * Funai Electric Co., Ltd.
+     */
+    public static final int FUNAI_ELECTRIC = 0x0090;
+
+    /*
+     * Advanced PANMOBIL Systems GmbH & Co. KG.
+     */
+    public static final int ADVANCED_PANMOBIL_SYSTEMS = 0x0091;
+
+    /*
+     * ThinkOptics, Inc.
+     */
+    public static final int THINKOPTICS = 0x0092;
+
+    /*
+     * Universal Electronics, Inc.
+     */
+    public static final int UNIVERSAL_ELECTRONICS = 0x0093;
+
+    /*
+     * Airoha Technology Corp.
+     */
+    public static final int AIROHA_TECHNOLOGY = 0x0094;
+
+    /*
+     * NEC Lighting, Ltd.
+     */
+    public static final int NEC_LIGHTING = 0x0095;
+
+    /*
+     * ODM Technology, Inc.
+     */
+    public static final int ODM_TECHNOLOGY = 0x0096;
+
+    /*
+     * Bluetrek Technologies Limited.
+     */
+    public static final int BLUETREK_TECHNOLOGIES = 0x0097;
+
+    /*
+     * zer01.tv GmbH.
+     */
+    public static final int ZER01_TV = 0x0098;
+
+    /*
+     * i.Tech Dynamic Global Distribution Ltd.
+     */
+    public static final int I_TECH_DYNAMIC_GLOBAL_DISTRIBUTION = 0x0099;
+
+    /*
+     * Alpwise.
+     */
+    public static final int ALPWISE = 0x009A;
+
+    /*
+     * Jiangsu Toppower Automotive Electronics Co., Ltd.
+     */
+    public static final int JIANGSU_TOPPOWER_AUTOMOTIVE_ELECTRONICS = 0x009B;
+
+    /*
+     * Colorfy, Inc.
+     */
+    public static final int COLORFY = 0x009C;
+
+    /*
+     * Geoforce Inc.
+     */
+    public static final int GEOFORCE = 0x009D;
+
+    /*
+     * Bose Corporation.
+     */
+    public static final int BOSE = 0x009E;
+
+    /*
+     * Suunto Oy.
+     */
+    public static final int SUUNTO = 0x009F;
+
+    /*
+     * Kensington Computer Products Group.
+     */
+    public static final int KENSINGTON_COMPUTER_PRODUCTS_GROUP = 0x00A0;
+
+    /*
+     * SR-Medizinelektronik.
+     */
+    public static final int SR_MEDIZINELEKTRONIK = 0x00A1;
+
+    /*
+     * Vertu Corporation Limited.
+     */
+    public static final int VERTU = 0x00A2;
+
+    /*
+     * Meta Watch Ltd.
+     */
+    public static final int META_WATCH = 0x00A3;
+
+    /*
+     * LINAK A/S.
+     */
+    public static final int LINAK = 0x00A4;
+
+    /*
+     * OTL Dynamics LLC.
+     */
+    public static final int OTL_DYNAMICS = 0x00A5;
+
+    /*
+     * Panda Ocean Inc.
+     */
+    public static final int PANDA_OCEAN = 0x00A6;
+
+    /*
+     * Visteon Corporation.
+     */
+    public static final int VISTEON = 0x00A7;
+
+    /*
+     * ARP Devices Limited.
+     */
+    public static final int ARP_DEVICES = 0x00A8;
+
+    /*
+     * Magneti Marelli S.p.A.
+     */
+    public static final int MAGNETI_MARELLI = 0x00A9;
+
+    /*
+     * CAEN RFID srl.
+     */
+    public static final int CAEN_RFID = 0x00AA;
+
+    /*
+     * Ingenieur-Systemgruppe Zahn GmbH.
+     */
+    public static final int INGENIEUR_SYSTEMGRUPPE_ZAHN = 0x00AB;
+
+    /*
+     * Green Throttle Games.
+     */
+    public static final int GREEN_THROTTLE_GAMES = 0x00AC;
+
+    /*
+     * Peter Systemtechnik GmbH.
+     */
+    public static final int PETER_SYSTEMTECHNIK = 0x00AD;
+
+    /*
+     * Omegawave Oy.
+     */
+    public static final int OMEGAWAVE = 0x00AE;
+
+    /*
+     * Cinetix.
+     */
+    public static final int CINETIX = 0x00AF;
+
+    /*
+     * Passif Semiconductor Corp.
+     */
+    public static final int PASSIF_SEMICONDUCTOR = 0x00B0;
+
+    /*
+     * Saris Cycling Group, Inc.
+     */
+    public static final int SARIS_CYCLING_GROUP = 0x00B1;
+
+    /*
+     * Bekey A/S.
+     */
+    public static final int BEKEY = 0x00B2;
+
+    /*
+     * Clarinox Technologies Pty. Ltd.
+     */
+    public static final int CLARINOX_TECHNOLOGIES = 0x00B3;
+
+    /*
+     * BDE Technology Co., Ltd.
+     */
+    public static final int BDE_TECHNOLOGY = 0x00B4;
+
+    /*
+     * Swirl Networks.
+     */
+    public static final int SWIRL_NETWORKS = 0x00B5;
+
+    /*
+     * Meso international.
+     */
+    public static final int MESO_INTERNATIONAL = 0x00B6;
+
+    /*
+     * TreLab Ltd.
+     */
+    public static final int TRELAB = 0x00B7;
+
+    /*
+     * Qualcomm Innovation Center, Inc. (QuIC).
+     */
+    public static final int QUALCOMM_INNOVATION_CENTER = 0x00B8;
+
+    /*
+     * Johnson Controls, Inc.
+     */
+    public static final int JOHNSON_CONTROLS = 0x00B9;
+
+    /*
+     * Starkey Laboratories Inc.
+     */
+    public static final int STARKEY_LABORATORIES = 0x00BA;
+
+    /*
+     * S-Power Electronics Limited.
+     */
+    public static final int S_POWER_ELECTRONICS = 0x00BB;
+
+    /*
+     * Ace Sensor Inc.
+     */
+    public static final int ACE_SENSOR = 0x00BC;
+
+    /*
+     * Aplix Corporation.
+     */
+    public static final int APLIX = 0x00BD;
+
+    /*
+     * AAMP of America.
+     */
+    public static final int AAMP_OF_AMERICA = 0x00BE;
+
+    /*
+     * Stalmart Technology Limited.
+     */
+    public static final int STALMART_TECHNOLOGY = 0x00BF;
+
+    /*
+     * AMICCOM Electronics Corporation.
+     */
+    public static final int AMICCOM_ELECTRONICS = 0x00C0;
+
+    /*
+     * Shenzhen Excelsecu Data Technology Co.,Ltd.
+     */
+    public static final int SHENZHEN_EXCELSECU_DATA_TECHNOLOGY = 0x00C1;
+
+    /*
+     * Geneq Inc.
+     */
+    public static final int GENEQ = 0x00C2;
+
+    /*
+     * adidas AG.
+     */
+    public static final int ADIDAS = 0x00C3;
+
+    /*
+     * LG Electronics.
+     */
+    public static final int LG_ELECTRONICS = 0x00C4;
+
+    /*
+     * Onset Computer Corporation.
+     */
+    public static final int ONSET_COMPUTER = 0x00C5;
+
+    /*
+     * Selfly BV.
+     */
+    public static final int SELFLY = 0x00C6;
+
+    /*
+     * Quuppa Oy.
+     */
+    public static final int QUUPPA = 0x00C7;
+
+    /*
+     * GeLo Inc.
+     */
+    public static final int GELO = 0x00C8;
+
+    /*
+     * Evluma.
+     */
+    public static final int EVLUMA = 0x00C9;
+
+    /*
+     * MC10.
+     */
+    public static final int MC10 = 0x00CA;
+
+    /*
+     * Binauric SE.
+     */
+    public static final int BINAURIC = 0x00CB;
+
+    /*
+     * Beats Electronics.
+     */
+    public static final int BEATS_ELECTRONICS = 0x00CC;
+
+    /*
+     * Microchip Technology Inc.
+     */
+    public static final int MICROCHIP_TECHNOLOGY = 0x00CD;
+
+    /*
+     * Elgato Systems GmbH.
+     */
+    public static final int ELGATO_SYSTEMS = 0x00CE;
+
+    /*
+     * ARCHOS SA.
+     */
+    public static final int ARCHOS = 0x00CF;
+
+    /*
+     * Dexcom, Inc.
+     */
+    public static final int DEXCOM = 0x00D0;
+
+    /*
+     * Polar Electro Europe B.V.
+     */
+    public static final int POLAR_ELECTRO_EUROPE = 0x00D1;
+
+    /*
+     * Dialog Semiconductor B.V.
+     */
+    public static final int DIALOG_SEMICONDUCTOR = 0x00D2;
+
+    /*
+     * Taixingbang Technology (HK) Co,. LTD.
+     */
+    public static final int TAIXINGBANG_TECHNOLOGY = 0x00D3;
+
+    /*
+     * Kawantech.
+     */
+    public static final int KAWANTECH = 0x00D4;
+
+    /*
+     * Austco Communication Systems.
+     */
+    public static final int AUSTCO_COMMUNICATION_SYSTEMS = 0x00D5;
+
+    /*
+     * Timex Group USA, Inc.
+     */
+    public static final int TIMEX_GROUP_USA = 0x00D6;
+
+    /*
+     * Qualcomm Technologies, Inc.
+     */
+    public static final int QUALCOMM_TECHNOLOGIES = 0x00D7;
+
+    /*
+     * Qualcomm Connected Experiences, Inc.
+     */
+    public static final int QUALCOMM_CONNECTED_EXPERIENCES = 0x00D8;
+
+    /*
+     * Voyetra Turtle Beach.
+     */
+    public static final int VOYETRA_TURTLE_BEACH = 0x00D9;
+
+    /*
+     * txtr GmbH.
+     */
+    public static final int TXTR = 0x00DA;
+
+    /*
+     * Biosentronics.
+     */
+    public static final int BIOSENTRONICS = 0x00DB;
+
+    /*
+     * Procter & Gamble.
+     */
+    public static final int PROCTER_AND_GAMBLE = 0x00DC;
+
+    /*
+     * Hosiden Corporation.
+     */
+    public static final int HOSIDEN = 0x00DD;
+
+    /*
+     * Muzik LLC.
+     */
+    public static final int MUZIK = 0x00DE;
+
+    /*
+     * Misfit Wearables Corp.
+     */
+    public static final int MISFIT_WEARABLES = 0x00DF;
+
+    /*
+     * Google.
+     */
+    public static final int GOOGLE = 0x00E0;
+
+    /*
+     * Danlers Ltd.
+     */
+    public static final int DANLERS = 0x00E1;
+
+    /*
+     * Semilink Inc.
+     */
+    public static final int SEMILINK = 0x00E2;
+
+    /*
+     * You can't instantiate one of these.
+     */
+    private BluetoothAssignedNumbers() {
+    }
+
+}
diff --git a/android/bluetooth/BluetoothAudioConfig.java b/android/bluetooth/BluetoothAudioConfig.java
new file mode 100644
index 0000000..9591a70
--- /dev/null
+++ b/android/bluetooth/BluetoothAudioConfig.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2009 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;
+
+/**
+ * Represents the audio configuration for a Bluetooth A2DP source device.
+ *
+ * {@see BluetoothA2dpSink}
+ *
+ * {@hide}
+ */
+public final class BluetoothAudioConfig implements Parcelable {
+
+    private final int mSampleRate;
+    private final int mChannelConfig;
+    private final int mAudioFormat;
+
+    public BluetoothAudioConfig(int sampleRate, int channelConfig, int audioFormat) {
+        mSampleRate = sampleRate;
+        mChannelConfig = channelConfig;
+        mAudioFormat = audioFormat;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothAudioConfig) {
+            BluetoothAudioConfig bac = (BluetoothAudioConfig) o;
+            return (bac.mSampleRate == mSampleRate && bac.mChannelConfig == mChannelConfig
+                    && bac.mAudioFormat == mAudioFormat);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mSampleRate | (mChannelConfig << 24) | (mAudioFormat << 28);
+    }
+
+    @Override
+    public String toString() {
+        return "{mSampleRate:" + mSampleRate + ",mChannelConfig:" + mChannelConfig
+                + ",mAudioFormat:" + mAudioFormat + "}";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothAudioConfig> CREATOR =
+            new Parcelable.Creator<BluetoothAudioConfig>() {
+                public BluetoothAudioConfig createFromParcel(Parcel in) {
+                    int sampleRate = in.readInt();
+                    int channelConfig = in.readInt();
+                    int audioFormat = in.readInt();
+                    return new BluetoothAudioConfig(sampleRate, channelConfig, audioFormat);
+                }
+
+                public BluetoothAudioConfig[] newArray(int size) {
+                    return new BluetoothAudioConfig[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mSampleRate);
+        out.writeInt(mChannelConfig);
+        out.writeInt(mAudioFormat);
+    }
+
+    /**
+     * Returns the sample rate in samples per second
+     *
+     * @return sample rate
+     */
+    public int getSampleRate() {
+        return mSampleRate;
+    }
+
+    /**
+     * Returns the channel configuration (either {@link android.media.AudioFormat#CHANNEL_IN_MONO}
+     * or {@link android.media.AudioFormat#CHANNEL_IN_STEREO})
+     *
+     * @return channel configuration
+     */
+    public int getChannelConfig() {
+        return mChannelConfig;
+    }
+
+    /**
+     * Returns the channel audio format (either {@link android.media.AudioFormat#ENCODING_PCM_16BIT}
+     * or {@link android.media.AudioFormat#ENCODING_PCM_8BIT}
+     *
+     * @return audio format
+     */
+    public int getAudioFormat() {
+        return mAudioFormat;
+    }
+}
diff --git a/android/bluetooth/BluetoothAvrcp.java b/android/bluetooth/BluetoothAvrcp.java
new file mode 100644
index 0000000..1a4c759
--- /dev/null
+++ b/android/bluetooth/BluetoothAvrcp.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 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;
+
+/**
+ * This class contains constants for Bluetooth AVRCP profile.
+ *
+ * {@hide}
+ */
+public final class BluetoothAvrcp {
+
+    /*
+     * State flags for Passthrough commands
+    */
+    public static final int PASSTHROUGH_STATE_PRESS = 0;
+    public static final int PASSTHROUGH_STATE_RELEASE = 1;
+
+    /*
+     * Operation IDs for Passthrough commands
+    */
+    public static final int PASSTHROUGH_ID_SELECT = 0x00;    /* select */
+    public static final int PASSTHROUGH_ID_UP = 0x01;    /* up */
+    public static final int PASSTHROUGH_ID_DOWN = 0x02;    /* down */
+    public static final int PASSTHROUGH_ID_LEFT = 0x03;    /* left */
+    public static final int PASSTHROUGH_ID_RIGHT = 0x04;    /* right */
+    public static final int PASSTHROUGH_ID_RIGHT_UP = 0x05;    /* right-up */
+    public static final int PASSTHROUGH_ID_RIGHT_DOWN = 0x06;    /* right-down */
+    public static final int PASSTHROUGH_ID_LEFT_UP = 0x07;    /* left-up */
+    public static final int PASSTHROUGH_ID_LEFT_DOWN = 0x08;    /* left-down */
+    public static final int PASSTHROUGH_ID_ROOT_MENU = 0x09;    /* root menu */
+    public static final int PASSTHROUGH_ID_SETUP_MENU = 0x0A;    /* setup menu */
+    public static final int PASSTHROUGH_ID_CONT_MENU = 0x0B;    /* contents menu */
+    public static final int PASSTHROUGH_ID_FAV_MENU = 0x0C;    /* favorite menu */
+    public static final int PASSTHROUGH_ID_EXIT = 0x0D;    /* exit */
+    public static final int PASSTHROUGH_ID_0 = 0x20;    /* 0 */
+    public static final int PASSTHROUGH_ID_1 = 0x21;    /* 1 */
+    public static final int PASSTHROUGH_ID_2 = 0x22;    /* 2 */
+    public static final int PASSTHROUGH_ID_3 = 0x23;    /* 3 */
+    public static final int PASSTHROUGH_ID_4 = 0x24;    /* 4 */
+    public static final int PASSTHROUGH_ID_5 = 0x25;    /* 5 */
+    public static final int PASSTHROUGH_ID_6 = 0x26;    /* 6 */
+    public static final int PASSTHROUGH_ID_7 = 0x27;    /* 7 */
+    public static final int PASSTHROUGH_ID_8 = 0x28;    /* 8 */
+    public static final int PASSTHROUGH_ID_9 = 0x29;    /* 9 */
+    public static final int PASSTHROUGH_ID_DOT = 0x2A;    /* dot */
+    public static final int PASSTHROUGH_ID_ENTER = 0x2B;    /* enter */
+    public static final int PASSTHROUGH_ID_CLEAR = 0x2C;    /* clear */
+    public static final int PASSTHROUGH_ID_CHAN_UP = 0x30;    /* channel up */
+    public static final int PASSTHROUGH_ID_CHAN_DOWN = 0x31;    /* channel down */
+    public static final int PASSTHROUGH_ID_PREV_CHAN = 0x32;    /* previous channel */
+    public static final int PASSTHROUGH_ID_SOUND_SEL = 0x33;    /* sound select */
+    public static final int PASSTHROUGH_ID_INPUT_SEL = 0x34;    /* input select */
+    public static final int PASSTHROUGH_ID_DISP_INFO = 0x35;    /* display information */
+    public static final int PASSTHROUGH_ID_HELP = 0x36;    /* help */
+    public static final int PASSTHROUGH_ID_PAGE_UP = 0x37;    /* page up */
+    public static final int PASSTHROUGH_ID_PAGE_DOWN = 0x38;    /* page down */
+    public static final int PASSTHROUGH_ID_POWER = 0x40;    /* power */
+    public static final int PASSTHROUGH_ID_VOL_UP = 0x41;    /* volume up */
+    public static final int PASSTHROUGH_ID_VOL_DOWN = 0x42;    /* volume down */
+    public static final int PASSTHROUGH_ID_MUTE = 0x43;    /* mute */
+    public static final int PASSTHROUGH_ID_PLAY = 0x44;    /* play */
+    public static final int PASSTHROUGH_ID_STOP = 0x45;    /* stop */
+    public static final int PASSTHROUGH_ID_PAUSE = 0x46;    /* pause */
+    public static final int PASSTHROUGH_ID_RECORD = 0x47;    /* record */
+    public static final int PASSTHROUGH_ID_REWIND = 0x48;    /* rewind */
+    public static final int PASSTHROUGH_ID_FAST_FOR = 0x49;    /* fast forward */
+    public static final int PASSTHROUGH_ID_EJECT = 0x4A;    /* eject */
+    public static final int PASSTHROUGH_ID_FORWARD = 0x4B;    /* forward */
+    public static final int PASSTHROUGH_ID_BACKWARD = 0x4C;    /* backward */
+    public static final int PASSTHROUGH_ID_ANGLE = 0x50;    /* angle */
+    public static final int PASSTHROUGH_ID_SUBPICT = 0x51;    /* subpicture */
+    public static final int PASSTHROUGH_ID_F1 = 0x71;    /* F1 */
+    public static final int PASSTHROUGH_ID_F2 = 0x72;    /* F2 */
+    public static final int PASSTHROUGH_ID_F3 = 0x73;    /* F3 */
+    public static final int PASSTHROUGH_ID_F4 = 0x74;    /* F4 */
+    public static final int PASSTHROUGH_ID_F5 = 0x75;    /* F5 */
+    public static final int PASSTHROUGH_ID_VENDOR = 0x7E;    /* vendor unique */
+    public static final int PASSTHROUGH_KEYPRESSED_RELEASE = 0x80;
+}
diff --git a/android/bluetooth/BluetoothAvrcpController.java b/android/bluetooth/BluetoothAvrcpController.java
new file mode 100644
index 0000000..4e7e441
--- /dev/null
+++ b/android/bluetooth/BluetoothAvrcpController.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2014 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.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the public APIs to control the Bluetooth AVRCP Controller. It currently
+ * supports player information, playback support and track metadata.
+ *
+ * <p>BluetoothAvrcpController is a proxy object for controlling the Bluetooth AVRCP
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothAvrcpController proxy object.
+ *
+ * {@hide}
+ */
+public final class BluetoothAvrcpController implements BluetoothProfile {
+    private static final String TAG = "BluetoothAvrcpController";
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the AVRCP Controller
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.avrcp-controller.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Intent used to broadcast the change in player application setting state on AVRCP AG.
+     *
+     * <p>This intent will have the following extras:
+     * <ul>
+     * <li> {@link #EXTRA_PLAYER_SETTING} - {@link BluetoothAvrcpPlayerSettings} containing the
+     * most recent player setting. </li>
+     * </ul>
+     */
+    public static final String ACTION_PLAYER_SETTING =
+            "android.bluetooth.avrcp-controller.profile.action.PLAYER_SETTING";
+
+    public static final String EXTRA_PLAYER_SETTING =
+            "android.bluetooth.avrcp-controller.profile.extra.PLAYER_SETTING";
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothAvrcpController> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.AVRCP_CONTROLLER,
+                    "BluetoothAvrcpController", IBluetoothAvrcpController.class.getName()) {
+                @Override
+                public IBluetoothAvrcpController getServiceInterface(IBinder service) {
+                    return IBluetoothAvrcpController.Stub.asInterface(
+                            Binder.allowBlocking(service));
+                }
+    };
+
+    /**
+     * Create a BluetoothAvrcpController proxy object for interacting with the local
+     * Bluetooth AVRCP service.
+     */
+    /*package*/ BluetoothAvrcpController(Context context, ServiceListener listener) {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+    }
+
+    /*package*/ void close() {
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothAvrcpController getService() {
+        return mProfileConnector.getService();
+    }
+
+    @Override
+    public void finalize() {
+        close();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        final IBluetoothAvrcpController service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        final IBluetoothAvrcpController service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
+        final IBluetoothAvrcpController service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Gets the player application settings.
+     *
+     * @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error.
+     */
+    public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "getPlayerSettings");
+        BluetoothAvrcpPlayerSettings settings = null;
+        final IBluetoothAvrcpController service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                settings = service.getPlayerSettings(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error talking to BT service in getMetadata() " + e);
+                return null;
+            }
+        }
+        return settings;
+    }
+
+    /**
+     * Sets the player app setting for current player.
+     * returns true in case setting is supported by remote, false otherwise
+     */
+    public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
+        if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
+        final IBluetoothAvrcpController service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.setPlayerApplicationSetting(plAppSetting);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error talking to BT service in setPlayerApplicationSetting() " + e);
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Group Navigation Command to Remote.
+     * possible keycode values: next_grp, previous_grp defined above
+     */
+    public void sendGroupNavigationCmd(BluetoothDevice device, int keyCode, int keyState) {
+        Log.d(TAG, "sendGroupNavigationCmd dev = " + device + " key " + keyCode + " State = "
+                + keyState);
+        final IBluetoothAvrcpController service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                service.sendGroupNavigationCmd(device, keyCode, keyState);
+                return;
+            } catch (RemoteException e) {
+                Log.e(TAG, "Error talking to BT service in sendGroupNavigationCmd()", e);
+                return;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+    }
+
+    private boolean isEnabled() {
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
+    }
+
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/android/bluetooth/BluetoothAvrcpPlayerSettings.java b/android/bluetooth/BluetoothAvrcpPlayerSettings.java
new file mode 100644
index 0000000..30aea1a
--- /dev/null
+++ b/android/bluetooth/BluetoothAvrcpPlayerSettings.java
@@ -0,0 +1,193 @@
+/*
+ * Copyright (C) 2015 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 android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class used to identify settings associated with the player on AG.
+ *
+ * {@hide}
+ */
+public final class BluetoothAvrcpPlayerSettings implements Parcelable {
+    public static final String TAG = "BluetoothAvrcpPlayerSettings";
+
+    /**
+     * Equalizer setting.
+     */
+    public static final int SETTING_EQUALIZER = 0x01;
+
+    /**
+     * Repeat setting.
+     */
+    public static final int SETTING_REPEAT = 0x02;
+
+    /**
+     * Shuffle setting.
+     */
+    public static final int SETTING_SHUFFLE = 0x04;
+
+    /**
+     * Scan mode setting.
+     */
+    public static final int SETTING_SCAN = 0x08;
+
+    /**
+     * Invalid state.
+     *
+     * Used for returning error codes.
+     */
+    public static final int STATE_INVALID = -1;
+
+    /**
+     * OFF state.
+     *
+     * Denotes a general OFF state. Applies to all settings.
+     */
+    public static final int STATE_OFF = 0x00;
+
+    /**
+     * ON state.
+     *
+     * Applies to {@link SETTING_EQUALIZER}.
+     */
+    public static final int STATE_ON = 0x01;
+
+    /**
+     * Single track repeat.
+     *
+     * Applies only to {@link SETTING_REPEAT}.
+     */
+    public static final int STATE_SINGLE_TRACK = 0x02;
+
+    /**
+     * All track repeat/shuffle.
+     *
+     * Applies to {@link #SETTING_REPEAT}, {@link #SETTING_SHUFFLE} and {@link #SETTING_SCAN}.
+     */
+    public static final int STATE_ALL_TRACK = 0x03;
+
+    /**
+     * Group repeat/shuffle.
+     *
+     * Applies to {@link #SETTING_REPEAT}, {@link #SETTING_SHUFFLE} and {@link #SETTING_SCAN}.
+     */
+    public static final int STATE_GROUP = 0x04;
+
+    /**
+     * List of supported settings ORed.
+     */
+    private int mSettings;
+
+    /**
+     * Hash map of current capability values.
+     */
+    private Map<Integer, Integer> mSettingsValue = new HashMap<Integer, Integer>();
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mSettings);
+        out.writeInt(mSettingsValue.size());
+        for (int k : mSettingsValue.keySet()) {
+            out.writeInt(k);
+            out.writeInt(mSettingsValue.get(k));
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothAvrcpPlayerSettings> CREATOR =
+            new Parcelable.Creator<BluetoothAvrcpPlayerSettings>() {
+        public BluetoothAvrcpPlayerSettings createFromParcel(Parcel in) {
+            return new BluetoothAvrcpPlayerSettings(in);
+        }
+
+        public BluetoothAvrcpPlayerSettings[] newArray(int size) {
+            return new BluetoothAvrcpPlayerSettings[size];
+        }
+    };
+
+    private BluetoothAvrcpPlayerSettings(Parcel in) {
+        mSettings = in.readInt();
+        int numSettings = in.readInt();
+        for (int i = 0; i < numSettings; i++) {
+            mSettingsValue.put(in.readInt(), in.readInt());
+        }
+    }
+
+    /**
+     * Create a new player settings object.
+     *
+     * @param settings a ORed value of SETTINGS_* defined above.
+     */
+    public BluetoothAvrcpPlayerSettings(int settings) {
+        mSettings = settings;
+    }
+
+    /**
+     * Get the supported settings.
+     *
+     * @return int ORed value of supported settings.
+     */
+    public int getSettings() {
+        return mSettings;
+    }
+
+    /**
+     * Add a setting value.
+     *
+     * The setting must be part of possible settings in {@link getSettings()}.
+     *
+     * @param setting setting config.
+     * @param value value for the setting.
+     * @throws IllegalStateException if the setting is not supported.
+     */
+    public void addSettingValue(int setting, int value) {
+        if ((setting & mSettings) == 0) {
+            Log.e(TAG, "Setting not supported: " + setting + " " + mSettings);
+            throw new IllegalStateException("Setting not supported: " + setting);
+        }
+        mSettingsValue.put(setting, value);
+    }
+
+    /**
+     * Get a setting value.
+     *
+     * The setting must be part of possible settings in {@link getSettings()}.
+     *
+     * @param setting setting config.
+     * @return value value for the setting.
+     * @throws IllegalStateException if the setting is not supported.
+     */
+    public int getSettingValue(int setting) {
+        if ((setting & mSettings) == 0) {
+            Log.e(TAG, "Setting not supported: " + setting + " " + mSettings);
+            throw new IllegalStateException("Setting not supported: " + setting);
+        }
+        Integer i = mSettingsValue.get(setting);
+        if (i == null) return -1;
+        return i;
+    }
+}
diff --git a/android/bluetooth/BluetoothClass.java b/android/bluetooth/BluetoothClass.java
new file mode 100644
index 0000000..905b0ce
--- /dev/null
+++ b/android/bluetooth/BluetoothClass.java
@@ -0,0 +1,439 @@
+/*
+ * Copyright (C) 2008 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.annotation.TestApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+/**
+ * Represents a Bluetooth class, which describes general characteristics
+ * and capabilities of a device. For example, a Bluetooth class will
+ * specify the general device type such as a phone, a computer, or
+ * headset, and whether it's capable of services such as audio or telephony.
+ *
+ * <p>Every Bluetooth class is composed of zero or more service classes, and
+ * exactly one device class. The device class is further broken down into major
+ * and minor device class components.
+ *
+ * <p>{@link BluetoothClass} is useful as a hint to roughly describe a device
+ * (for example to show an icon in the UI), but does not reliably describe which
+ * Bluetooth profiles or services are actually supported by a device. Accurate
+ * service discovery is done through SDP requests, which are automatically
+ * performed when creating an RFCOMM socket with {@link
+ * BluetoothDevice#createRfcommSocketToServiceRecord} and {@link
+ * BluetoothAdapter#listenUsingRfcommWithServiceRecord}</p>
+ *
+ * <p>Use {@link BluetoothDevice#getBluetoothClass} to retrieve the class for
+ * a remote device.
+ *
+ * <!--
+ * The Bluetooth class is a 32 bit field. The format of these bits is defined at
+ * http://www.bluetooth.org/Technical/AssignedNumbers/baseband.htm
+ * (login required). This class contains that 32 bit field, and provides
+ * constants and methods to determine which Service Class(es) and Device Class
+ * are encoded in that field.
+ * -->
+ */
+public final class BluetoothClass implements Parcelable {
+    /**
+     * Legacy error value. Applications should use null instead.
+     *
+     * @hide
+     */
+    public static final int ERROR = 0xFF000000;
+
+    private final int mClass;
+
+    /** @hide */
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public BluetoothClass(int classInt) {
+        mClass = classInt;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothClass) {
+            return mClass == ((BluetoothClass) o).mClass;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mClass;
+    }
+
+    @Override
+    public String toString() {
+        return Integer.toHexString(mClass);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothClass> CREATOR =
+            new Parcelable.Creator<BluetoothClass>() {
+                public BluetoothClass createFromParcel(Parcel in) {
+                    return new BluetoothClass(in.readInt());
+                }
+
+                public BluetoothClass[] newArray(int size) {
+                    return new BluetoothClass[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mClass);
+    }
+
+    /**
+     * Defines all service class constants.
+     * <p>Each {@link BluetoothClass} encodes zero or more service classes.
+     */
+    public static final class Service {
+        private static final int BITMASK = 0xFFE000;
+
+        public static final int LIMITED_DISCOVERABILITY = 0x002000;
+        public static final int POSITIONING = 0x010000;
+        public static final int NETWORKING = 0x020000;
+        public static final int RENDER = 0x040000;
+        public static final int CAPTURE = 0x080000;
+        public static final int OBJECT_TRANSFER = 0x100000;
+        public static final int AUDIO = 0x200000;
+        public static final int TELEPHONY = 0x400000;
+        public static final int INFORMATION = 0x800000;
+    }
+
+    /**
+     * Return true if the specified service class is supported by this
+     * {@link BluetoothClass}.
+     * <p>Valid service classes are the public constants in
+     * {@link BluetoothClass.Service}. For example, {@link
+     * BluetoothClass.Service#AUDIO}.
+     *
+     * @param service valid service class
+     * @return true if the service class is supported
+     */
+    public boolean hasService(int service) {
+        return ((mClass & Service.BITMASK & service) != 0);
+    }
+
+    /**
+     * Defines all device class constants.
+     * <p>Each {@link BluetoothClass} encodes exactly one device class, with
+     * major and minor components.
+     * <p>The constants in {@link
+     * BluetoothClass.Device} represent a combination of major and minor
+     * device components (the complete device class). The constants in {@link
+     * BluetoothClass.Device.Major} represent only major device classes.
+     * <p>See {@link BluetoothClass.Service} for service class constants.
+     */
+    public static class Device {
+        private static final int BITMASK = 0x1FFC;
+
+        /**
+         * Defines all major device class constants.
+         * <p>See {@link BluetoothClass.Device} for minor classes.
+         */
+        public static class Major {
+            private static final int BITMASK = 0x1F00;
+
+            public static final int MISC = 0x0000;
+            public static final int COMPUTER = 0x0100;
+            public static final int PHONE = 0x0200;
+            public static final int NETWORKING = 0x0300;
+            public static final int AUDIO_VIDEO = 0x0400;
+            public static final int PERIPHERAL = 0x0500;
+            public static final int IMAGING = 0x0600;
+            public static final int WEARABLE = 0x0700;
+            public static final int TOY = 0x0800;
+            public static final int HEALTH = 0x0900;
+            public static final int UNCATEGORIZED = 0x1F00;
+        }
+
+        // Devices in the COMPUTER major class
+        public static final int COMPUTER_UNCATEGORIZED = 0x0100;
+        public static final int COMPUTER_DESKTOP = 0x0104;
+        public static final int COMPUTER_SERVER = 0x0108;
+        public static final int COMPUTER_LAPTOP = 0x010C;
+        public static final int COMPUTER_HANDHELD_PC_PDA = 0x0110;
+        public static final int COMPUTER_PALM_SIZE_PC_PDA = 0x0114;
+        public static final int COMPUTER_WEARABLE = 0x0118;
+
+        // Devices in the PHONE major class
+        public static final int PHONE_UNCATEGORIZED = 0x0200;
+        public static final int PHONE_CELLULAR = 0x0204;
+        public static final int PHONE_CORDLESS = 0x0208;
+        public static final int PHONE_SMART = 0x020C;
+        public static final int PHONE_MODEM_OR_GATEWAY = 0x0210;
+        public static final int PHONE_ISDN = 0x0214;
+
+        // Minor classes for the AUDIO_VIDEO major class
+        public static final int AUDIO_VIDEO_UNCATEGORIZED = 0x0400;
+        public static final int AUDIO_VIDEO_WEARABLE_HEADSET = 0x0404;
+        public static final int AUDIO_VIDEO_HANDSFREE = 0x0408;
+        //public static final int AUDIO_VIDEO_RESERVED              = 0x040C;
+        public static final int AUDIO_VIDEO_MICROPHONE = 0x0410;
+        public static final int AUDIO_VIDEO_LOUDSPEAKER = 0x0414;
+        public static final int AUDIO_VIDEO_HEADPHONES = 0x0418;
+        public static final int AUDIO_VIDEO_PORTABLE_AUDIO = 0x041C;
+        public static final int AUDIO_VIDEO_CAR_AUDIO = 0x0420;
+        public static final int AUDIO_VIDEO_SET_TOP_BOX = 0x0424;
+        public static final int AUDIO_VIDEO_HIFI_AUDIO = 0x0428;
+        public static final int AUDIO_VIDEO_VCR = 0x042C;
+        public static final int AUDIO_VIDEO_VIDEO_CAMERA = 0x0430;
+        public static final int AUDIO_VIDEO_CAMCORDER = 0x0434;
+        public static final int AUDIO_VIDEO_VIDEO_MONITOR = 0x0438;
+        public static final int AUDIO_VIDEO_VIDEO_DISPLAY_AND_LOUDSPEAKER = 0x043C;
+        public static final int AUDIO_VIDEO_VIDEO_CONFERENCING = 0x0440;
+        //public static final int AUDIO_VIDEO_RESERVED              = 0x0444;
+        public static final int AUDIO_VIDEO_VIDEO_GAMING_TOY = 0x0448;
+
+        // Devices in the WEARABLE major class
+        public static final int WEARABLE_UNCATEGORIZED = 0x0700;
+        public static final int WEARABLE_WRIST_WATCH = 0x0704;
+        public static final int WEARABLE_PAGER = 0x0708;
+        public static final int WEARABLE_JACKET = 0x070C;
+        public static final int WEARABLE_HELMET = 0x0710;
+        public static final int WEARABLE_GLASSES = 0x0714;
+
+        // Devices in the TOY major class
+        public static final int TOY_UNCATEGORIZED = 0x0800;
+        public static final int TOY_ROBOT = 0x0804;
+        public static final int TOY_VEHICLE = 0x0808;
+        public static final int TOY_DOLL_ACTION_FIGURE = 0x080C;
+        public static final int TOY_CONTROLLER = 0x0810;
+        public static final int TOY_GAME = 0x0814;
+
+        // Devices in the HEALTH major class
+        public static final int HEALTH_UNCATEGORIZED = 0x0900;
+        public static final int HEALTH_BLOOD_PRESSURE = 0x0904;
+        public static final int HEALTH_THERMOMETER = 0x0908;
+        public static final int HEALTH_WEIGHING = 0x090C;
+        public static final int HEALTH_GLUCOSE = 0x0910;
+        public static final int HEALTH_PULSE_OXIMETER = 0x0914;
+        public static final int HEALTH_PULSE_RATE = 0x0918;
+        public static final int HEALTH_DATA_DISPLAY = 0x091C;
+
+        // Devices in PERIPHERAL major class
+        /**
+         * @hide
+         */
+        public static final int PERIPHERAL_NON_KEYBOARD_NON_POINTING = 0x0500;
+        /**
+         * @hide
+         */
+        public static final int PERIPHERAL_KEYBOARD = 0x0540;
+        /**
+         * @hide
+         */
+        public static final int PERIPHERAL_POINTING = 0x0580;
+        /**
+         * @hide
+         */
+        public static final int PERIPHERAL_KEYBOARD_POINTING = 0x05C0;
+    }
+
+    /**
+     * Return the major device class component of this {@link BluetoothClass}.
+     * <p>Values returned from this function can be compared with the
+     * public constants in {@link BluetoothClass.Device.Major} to determine
+     * which major class is encoded in this Bluetooth class.
+     *
+     * @return major device class component
+     */
+    public int getMajorDeviceClass() {
+        return (mClass & Device.Major.BITMASK);
+    }
+
+    /**
+     * Return the (major and minor) device class component of this
+     * {@link BluetoothClass}.
+     * <p>Values returned from this function can be compared with the
+     * public constants in {@link BluetoothClass.Device} to determine which
+     * device class is encoded in this Bluetooth class.
+     *
+     * @return device class component
+     */
+    public int getDeviceClass() {
+        return (mClass & Device.BITMASK);
+    }
+
+    /**
+     * Return the Bluetooth Class of Device (CoD) value including the
+     * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and
+     * minor device fields.
+     *
+     * <p>This value is an integer representation of Bluetooth CoD as in
+     * Bluetooth specification.
+     *
+     * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a>
+     *
+     * @hide
+     */
+    @TestApi
+    public int getClassOfDevice() {
+        return mClass;
+    }
+
+    /**
+     * Return the Bluetooth Class of Device (CoD) value including the
+     * {@link BluetoothClass.Service}, {@link BluetoothClass.Device.Major} and
+     * minor device fields.
+     *
+     * <p>This value is a byte array representation of Bluetooth CoD as in
+     * Bluetooth specification.
+     *
+     * <p>Bluetooth COD information is 3 bytes, but stored as an int. Hence the
+     * MSB is useless and needs to be thrown away. The lower 3 bytes are
+     * converted into a byte array MSB to LSB. Hence, using BIG_ENDIAN.
+     *
+     * @see <a href="Bluetooth CoD">https://www.bluetooth.com/specifications/assigned-numbers/baseband</a>
+     *
+     * @hide
+     */
+    public byte[] getClassOfDeviceBytes() {
+        byte[] bytes = ByteBuffer.allocate(4)
+                .order(ByteOrder.BIG_ENDIAN)
+                .putInt(mClass)
+                .array();
+
+        // Discard the top byte
+        return Arrays.copyOfRange(bytes, 1, bytes.length);
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROFILE_HEADSET = 0;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int PROFILE_A2DP = 1;
+    /** @hide */
+    public static final int PROFILE_OPP = 2;
+    /** @hide */
+    public static final int PROFILE_HID = 3;
+    /** @hide */
+    public static final int PROFILE_PANU = 4;
+    /** @hide */
+    public static final int PROFILE_NAP = 5;
+    /** @hide */
+    public static final int PROFILE_A2DP_SINK = 6;
+
+    /**
+     * Check class bits for possible bluetooth profile support.
+     * This is a simple heuristic that tries to guess if a device with the
+     * given class bits might support specified profile. It is not accurate for all
+     * devices. It tries to err on the side of false positives.
+     *
+     * @param profile The profile to be checked
+     * @return True if this device might support specified profile.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean doesClassMatch(int profile) {
+        if (profile == PROFILE_A2DP) {
+            if (hasService(Service.RENDER)) {
+                return true;
+            }
+            // By the A2DP spec, sinks must indicate the RENDER service.
+            // However we found some that do not (Chordette). So lets also
+            // match on some other class bits.
+            switch (getDeviceClass()) {
+                case Device.AUDIO_VIDEO_HIFI_AUDIO:
+                case Device.AUDIO_VIDEO_HEADPHONES:
+                case Device.AUDIO_VIDEO_LOUDSPEAKER:
+                case Device.AUDIO_VIDEO_CAR_AUDIO:
+                    return true;
+                default:
+                    return false;
+            }
+        } else if (profile == PROFILE_A2DP_SINK) {
+            if (hasService(Service.CAPTURE)) {
+                return true;
+            }
+            // By the A2DP spec, srcs must indicate the CAPTURE service.
+            // However if some device that do not, we try to
+            // match on some other class bits.
+            switch (getDeviceClass()) {
+                case Device.AUDIO_VIDEO_HIFI_AUDIO:
+                case Device.AUDIO_VIDEO_SET_TOP_BOX:
+                case Device.AUDIO_VIDEO_VCR:
+                    return true;
+                default:
+                    return false;
+            }
+        } else if (profile == PROFILE_HEADSET) {
+            // The render service class is required by the spec for HFP, so is a
+            // pretty good signal
+            if (hasService(Service.RENDER)) {
+                return true;
+            }
+            // Just in case they forgot the render service class
+            switch (getDeviceClass()) {
+                case Device.AUDIO_VIDEO_HANDSFREE:
+                case Device.AUDIO_VIDEO_WEARABLE_HEADSET:
+                case Device.AUDIO_VIDEO_CAR_AUDIO:
+                    return true;
+                default:
+                    return false;
+            }
+        } else if (profile == PROFILE_OPP) {
+            if (hasService(Service.OBJECT_TRANSFER)) {
+                return true;
+            }
+
+            switch (getDeviceClass()) {
+                case Device.COMPUTER_UNCATEGORIZED:
+                case Device.COMPUTER_DESKTOP:
+                case Device.COMPUTER_SERVER:
+                case Device.COMPUTER_LAPTOP:
+                case Device.COMPUTER_HANDHELD_PC_PDA:
+                case Device.COMPUTER_PALM_SIZE_PC_PDA:
+                case Device.COMPUTER_WEARABLE:
+                case Device.PHONE_UNCATEGORIZED:
+                case Device.PHONE_CELLULAR:
+                case Device.PHONE_CORDLESS:
+                case Device.PHONE_SMART:
+                case Device.PHONE_MODEM_OR_GATEWAY:
+                case Device.PHONE_ISDN:
+                    return true;
+                default:
+                    return false;
+            }
+        } else if (profile == PROFILE_HID) {
+            return (getDeviceClass() & Device.Major.PERIPHERAL) == Device.Major.PERIPHERAL;
+        } else if (profile == PROFILE_PANU || profile == PROFILE_NAP) {
+            // No good way to distinguish between the two, based on class bits.
+            if (hasService(Service.NETWORKING)) {
+                return true;
+            }
+            return (getDeviceClass() & Device.Major.NETWORKING) == Device.Major.NETWORKING;
+        } else {
+            return false;
+        }
+    }
+}
diff --git a/android/bluetooth/BluetoothCodecConfig.java b/android/bluetooth/BluetoothCodecConfig.java
new file mode 100644
index 0000000..d2a1535
--- /dev/null
+++ b/android/bluetooth/BluetoothCodecConfig.java
@@ -0,0 +1,628 @@
+/*
+ * 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.annotation.IntDef;
+import android.annotation.NonNull;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * Represents the codec configuration for a Bluetooth A2DP source device.
+ *
+ * {@see BluetoothA2dp}
+ *
+ * {@hide}
+ */
+public final class BluetoothCodecConfig implements Parcelable {
+    // Add an entry for each source codec here.
+    // NOTE: The values should be same as those listed in the following file:
+    //   hardware/libhardware/include/hardware/bt_av.h
+
+    /** @hide */
+    @IntDef(prefix = "SOURCE_CODEC_TYPE_", value = {
+            SOURCE_CODEC_TYPE_SBC,
+            SOURCE_CODEC_TYPE_AAC,
+            SOURCE_CODEC_TYPE_APTX,
+            SOURCE_CODEC_TYPE_APTX_HD,
+            SOURCE_CODEC_TYPE_LDAC,
+            SOURCE_CODEC_TYPE_MAX,
+            SOURCE_CODEC_TYPE_INVALID
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SourceCodecType {}
+
+    public static final int SOURCE_CODEC_TYPE_SBC = 0;
+
+    public static final int SOURCE_CODEC_TYPE_AAC = 1;
+
+    public static final int SOURCE_CODEC_TYPE_APTX = 2;
+
+    public static final int SOURCE_CODEC_TYPE_APTX_HD = 3;
+
+    public static final int SOURCE_CODEC_TYPE_LDAC = 4;
+
+    public static final int SOURCE_CODEC_TYPE_MAX = 5;
+
+
+    public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
+
+    /** @hide */
+    @IntDef(prefix = "CODEC_PRIORITY_", value = {
+            CODEC_PRIORITY_DISABLED,
+            CODEC_PRIORITY_DEFAULT,
+            CODEC_PRIORITY_HIGHEST
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CodecPriority {}
+
+    public static final int CODEC_PRIORITY_DISABLED = -1;
+
+    public static final int CODEC_PRIORITY_DEFAULT = 0;
+
+    public static final int CODEC_PRIORITY_HIGHEST = 1000 * 1000;
+
+
+    /** @hide */
+    @IntDef(prefix = "SAMPLE_RATE_", value = {
+            SAMPLE_RATE_NONE,
+            SAMPLE_RATE_44100,
+            SAMPLE_RATE_48000,
+            SAMPLE_RATE_88200,
+            SAMPLE_RATE_96000,
+            SAMPLE_RATE_176400,
+            SAMPLE_RATE_192000
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SampleRate {}
+
+    public static final int SAMPLE_RATE_NONE = 0;
+
+    public static final int SAMPLE_RATE_44100 = 0x1 << 0;
+
+    public static final int SAMPLE_RATE_48000 = 0x1 << 1;
+
+    public static final int SAMPLE_RATE_88200 = 0x1 << 2;
+
+    public static final int SAMPLE_RATE_96000 = 0x1 << 3;
+
+    public static final int SAMPLE_RATE_176400 = 0x1 << 4;
+
+    public static final int SAMPLE_RATE_192000 = 0x1 << 5;
+
+
+    /** @hide */
+    @IntDef(prefix = "BITS_PER_SAMPLE_", value = {
+            BITS_PER_SAMPLE_NONE,
+            BITS_PER_SAMPLE_16,
+            BITS_PER_SAMPLE_24,
+            BITS_PER_SAMPLE_32
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BitsPerSample {}
+
+    public static final int BITS_PER_SAMPLE_NONE = 0;
+
+    public static final int BITS_PER_SAMPLE_16 = 0x1 << 0;
+
+    public static final int BITS_PER_SAMPLE_24 = 0x1 << 1;
+
+    public static final int BITS_PER_SAMPLE_32 = 0x1 << 2;
+
+
+    /** @hide */
+    @IntDef(prefix = "CHANNEL_MODE_", value = {
+            CHANNEL_MODE_NONE,
+            CHANNEL_MODE_MONO,
+            CHANNEL_MODE_STEREO
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ChannelMode {}
+
+    public static final int CHANNEL_MODE_NONE = 0;
+
+    public static final int CHANNEL_MODE_MONO = 0x1 << 0;
+
+    public static final int CHANNEL_MODE_STEREO = 0x1 << 1;
+
+    private final @SourceCodecType int mCodecType;
+    private @CodecPriority int mCodecPriority;
+    private final @SampleRate int mSampleRate;
+    private final @BitsPerSample int mBitsPerSample;
+    private final @ChannelMode int mChannelMode;
+    private final long mCodecSpecific1;
+    private final long mCodecSpecific2;
+    private final long mCodecSpecific3;
+    private final long mCodecSpecific4;
+
+    public BluetoothCodecConfig(@SourceCodecType int codecType, @CodecPriority int codecPriority,
+            @SampleRate int sampleRate, @BitsPerSample int bitsPerSample,
+            @ChannelMode int channelMode, long codecSpecific1,
+            long codecSpecific2, long codecSpecific3,
+            long codecSpecific4) {
+        mCodecType = codecType;
+        mCodecPriority = codecPriority;
+        mSampleRate = sampleRate;
+        mBitsPerSample = bitsPerSample;
+        mChannelMode = channelMode;
+        mCodecSpecific1 = codecSpecific1;
+        mCodecSpecific2 = codecSpecific2;
+        mCodecSpecific3 = codecSpecific3;
+        mCodecSpecific4 = codecSpecific4;
+    }
+
+    public BluetoothCodecConfig(@SourceCodecType int codecType) {
+        mCodecType = codecType;
+        mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
+        mSampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE;
+        mBitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE;
+        mChannelMode = BluetoothCodecConfig.CHANNEL_MODE_NONE;
+        mCodecSpecific1 = 0;
+        mCodecSpecific2 = 0;
+        mCodecSpecific3 = 0;
+        mCodecSpecific4 = 0;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothCodecConfig) {
+            BluetoothCodecConfig other = (BluetoothCodecConfig) o;
+            return (other.mCodecType == mCodecType
+                    && other.mCodecPriority == mCodecPriority
+                    && other.mSampleRate == mSampleRate
+                    && other.mBitsPerSample == mBitsPerSample
+                    && other.mChannelMode == mChannelMode
+                    && other.mCodecSpecific1 == mCodecSpecific1
+                    && other.mCodecSpecific2 == mCodecSpecific2
+                    && other.mCodecSpecific3 == mCodecSpecific3
+                    && other.mCodecSpecific4 == mCodecSpecific4);
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash based on the config values
+     *
+     * @return a hash based on the config values
+     * @hide
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mCodecType, mCodecPriority, mSampleRate,
+                mBitsPerSample, mChannelMode, mCodecSpecific1,
+                mCodecSpecific2, mCodecSpecific3, mCodecSpecific4);
+    }
+
+    /**
+     * Checks whether the object contains valid codec configuration.
+     *
+     * @return true if the object contains valid codec configuration, otherwise false.
+     * @hide
+     */
+    public boolean isValid() {
+        return (mSampleRate != SAMPLE_RATE_NONE)
+                && (mBitsPerSample != BITS_PER_SAMPLE_NONE)
+                && (mChannelMode != CHANNEL_MODE_NONE);
+    }
+
+    /**
+     * Adds capability string to an existing string.
+     *
+     * @param prevStr the previous string with the capabilities. Can be a null pointer.
+     * @param capStr the capability string to append to prevStr argument.
+     * @return the result string in the form "prevStr|capStr".
+     */
+    private static String appendCapabilityToString(String prevStr,
+            String capStr) {
+        if (prevStr == null) {
+            return capStr;
+        }
+        return prevStr + "|" + capStr;
+    }
+
+    @Override
+    public String toString() {
+        String sampleRateStr = null;
+        if (mSampleRate == SAMPLE_RATE_NONE) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "NONE");
+        }
+        if ((mSampleRate & SAMPLE_RATE_44100) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "44100");
+        }
+        if ((mSampleRate & SAMPLE_RATE_48000) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "48000");
+        }
+        if ((mSampleRate & SAMPLE_RATE_88200) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "88200");
+        }
+        if ((mSampleRate & SAMPLE_RATE_96000) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "96000");
+        }
+        if ((mSampleRate & SAMPLE_RATE_176400) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "176400");
+        }
+        if ((mSampleRate & SAMPLE_RATE_192000) != 0) {
+            sampleRateStr = appendCapabilityToString(sampleRateStr, "192000");
+        }
+
+        String bitsPerSampleStr = null;
+        if (mBitsPerSample == BITS_PER_SAMPLE_NONE) {
+            bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "NONE");
+        }
+        if ((mBitsPerSample & BITS_PER_SAMPLE_16) != 0) {
+            bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "16");
+        }
+        if ((mBitsPerSample & BITS_PER_SAMPLE_24) != 0) {
+            bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "24");
+        }
+        if ((mBitsPerSample & BITS_PER_SAMPLE_32) != 0) {
+            bitsPerSampleStr = appendCapabilityToString(bitsPerSampleStr, "32");
+        }
+
+        String channelModeStr = null;
+        if (mChannelMode == CHANNEL_MODE_NONE) {
+            channelModeStr = appendCapabilityToString(channelModeStr, "NONE");
+        }
+        if ((mChannelMode & CHANNEL_MODE_MONO) != 0) {
+            channelModeStr = appendCapabilityToString(channelModeStr, "MONO");
+        }
+        if ((mChannelMode & CHANNEL_MODE_STEREO) != 0) {
+            channelModeStr = appendCapabilityToString(channelModeStr, "STEREO");
+        }
+
+        return "{codecName:" + getCodecName()
+                + ",mCodecType:" + mCodecType
+                + ",mCodecPriority:" + mCodecPriority
+                + ",mSampleRate:" + String.format("0x%x", mSampleRate)
+                + "(" + sampleRateStr + ")"
+                + ",mBitsPerSample:" + String.format("0x%x", mBitsPerSample)
+                + "(" + bitsPerSampleStr + ")"
+                + ",mChannelMode:" + String.format("0x%x", mChannelMode)
+                + "(" + channelModeStr + ")"
+                + ",mCodecSpecific1:" + mCodecSpecific1
+                + ",mCodecSpecific2:" + mCodecSpecific2
+                + ",mCodecSpecific3:" + mCodecSpecific3
+                + ",mCodecSpecific4:" + mCodecSpecific4 + "}";
+    }
+
+    /**
+     * Always returns 0
+     *
+     * @return 0
+     * @hide
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothCodecConfig> CREATOR =
+            new Parcelable.Creator<BluetoothCodecConfig>() {
+                public BluetoothCodecConfig createFromParcel(Parcel in) {
+                    final int codecType = in.readInt();
+                    final int codecPriority = in.readInt();
+                    final int sampleRate = in.readInt();
+                    final int bitsPerSample = in.readInt();
+                    final int channelMode = in.readInt();
+                    final long codecSpecific1 = in.readLong();
+                    final long codecSpecific2 = in.readLong();
+                    final long codecSpecific3 = in.readLong();
+                    final long codecSpecific4 = in.readLong();
+                    return new BluetoothCodecConfig(codecType, codecPriority,
+                            sampleRate, bitsPerSample,
+                            channelMode, codecSpecific1,
+                            codecSpecific2, codecSpecific3,
+                            codecSpecific4);
+                }
+
+                public BluetoothCodecConfig[] newArray(int size) {
+                    return new BluetoothCodecConfig[size];
+                }
+            };
+
+    /**
+     * Flattens the object to a parcel
+     *
+     * @param out The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *
+     * @hide
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mCodecType);
+        out.writeInt(mCodecPriority);
+        out.writeInt(mSampleRate);
+        out.writeInt(mBitsPerSample);
+        out.writeInt(mChannelMode);
+        out.writeLong(mCodecSpecific1);
+        out.writeLong(mCodecSpecific2);
+        out.writeLong(mCodecSpecific3);
+        out.writeLong(mCodecSpecific4);
+    }
+
+    /**
+     * Gets the codec name.
+     *
+     * @return the codec name
+     */
+    public @NonNull String getCodecName() {
+        switch (mCodecType) {
+            case SOURCE_CODEC_TYPE_SBC:
+                return "SBC";
+            case SOURCE_CODEC_TYPE_AAC:
+                return "AAC";
+            case SOURCE_CODEC_TYPE_APTX:
+                return "aptX";
+            case SOURCE_CODEC_TYPE_APTX_HD:
+                return "aptX HD";
+            case SOURCE_CODEC_TYPE_LDAC:
+                return "LDAC";
+            case SOURCE_CODEC_TYPE_INVALID:
+                return "INVALID CODEC";
+            default:
+                break;
+        }
+        return "UNKNOWN CODEC(" + mCodecType + ")";
+    }
+
+    /**
+     * Gets the codec type.
+     * See {@link android.bluetooth.BluetoothCodecConfig#SOURCE_CODEC_TYPE_SBC}.
+     *
+     * @return the codec type
+     */
+    public @SourceCodecType int getCodecType() {
+        return mCodecType;
+    }
+
+    /**
+     * Checks whether the codec is mandatory.
+     *
+     * @return true if the codec is mandatory, otherwise false.
+     */
+    public boolean isMandatoryCodec() {
+        return mCodecType == SOURCE_CODEC_TYPE_SBC;
+    }
+
+    /**
+     * Gets the codec selection priority.
+     * The codec selection priority is relative to other codecs: larger value
+     * means higher priority. If 0, reset to default.
+     *
+     * @return the codec priority
+     */
+    public @CodecPriority int getCodecPriority() {
+        return mCodecPriority;
+    }
+
+    /**
+     * Sets the codec selection priority.
+     * The codec selection priority is relative to other codecs: larger value
+     * means higher priority. If 0, reset to default.
+     *
+     * @param codecPriority the codec priority
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void setCodecPriority(@CodecPriority int codecPriority) {
+        mCodecPriority = codecPriority;
+    }
+
+    /**
+     * Gets the codec sample rate. The value can be a bitmask with all
+     * supported sample rates:
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_NONE} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_44100} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_48000} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_88200} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_96000} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_176400} or
+     * {@link android.bluetooth.BluetoothCodecConfig#SAMPLE_RATE_192000}
+     *
+     * @return the codec sample rate
+     */
+    public @SampleRate int getSampleRate() {
+        return mSampleRate;
+    }
+
+    /**
+     * Gets the codec bits per sample. The value can be a bitmask with all
+     * bits per sample supported:
+     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_NONE} or
+     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_16} or
+     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_24} or
+     * {@link android.bluetooth.BluetoothCodecConfig#BITS_PER_SAMPLE_32}
+     *
+     * @return the codec bits per sample
+     */
+    public @BitsPerSample int getBitsPerSample() {
+        return mBitsPerSample;
+    }
+
+    /**
+     * Gets the codec channel mode. The value can be a bitmask with all
+     * supported channel modes:
+     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_NONE} or
+     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_MONO} or
+     * {@link android.bluetooth.BluetoothCodecConfig#CHANNEL_MODE_STEREO}
+     *
+     * @return the codec channel mode
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public @ChannelMode int getChannelMode() {
+        return mChannelMode;
+    }
+
+    /**
+     * Gets a codec specific value1.
+     *
+     * @return a codec specific value1.
+     */
+    public long getCodecSpecific1() {
+        return mCodecSpecific1;
+    }
+
+    /**
+     * Gets a codec specific value2.
+     *
+     * @return a codec specific value2
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getCodecSpecific2() {
+        return mCodecSpecific2;
+    }
+
+    /**
+     * Gets a codec specific value3.
+     *
+     * @return a codec specific value3
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getCodecSpecific3() {
+        return mCodecSpecific3;
+    }
+
+    /**
+     * Gets a codec specific value4.
+     *
+     * @return a codec specific value4
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public long getCodecSpecific4() {
+        return mCodecSpecific4;
+    }
+
+    /**
+     * Checks whether a value set presented by a bitmask has zero or single bit
+     *
+     * @param valueSet the value set presented by a bitmask
+     * @return true if the valueSet contains zero or single bit, otherwise false.
+     * @hide
+     */
+    private static boolean hasSingleBit(int valueSet) {
+        return (valueSet == 0 || (valueSet & (valueSet - 1)) == 0);
+    }
+
+    /**
+     * Checks whether the object contains none or single sample rate.
+     *
+     * @return true if the object contains none or single sample rate, otherwise false.
+     * @hide
+     */
+    public boolean hasSingleSampleRate() {
+        return hasSingleBit(mSampleRate);
+    }
+
+    /**
+     * Checks whether the object contains none or single bits per sample.
+     *
+     * @return true if the object contains none or single bits per sample, otherwise false.
+     * @hide
+     */
+    public boolean hasSingleBitsPerSample() {
+        return hasSingleBit(mBitsPerSample);
+    }
+
+    /**
+     * Checks whether the object contains none or single channel mode.
+     *
+     * @return true if the object contains none or single channel mode, otherwise false.
+     * @hide
+     */
+    public boolean hasSingleChannelMode() {
+        return hasSingleBit(mChannelMode);
+    }
+
+    /**
+     * Checks whether the audio feeding parameters are same.
+     *
+     * @param other the codec config to compare against
+     * @return true if the audio feeding parameters are same, otherwise false
+     * @hide
+     */
+    public boolean sameAudioFeedingParameters(BluetoothCodecConfig other) {
+        return (other != null && other.mSampleRate == mSampleRate
+                && other.mBitsPerSample == mBitsPerSample
+                && other.mChannelMode == mChannelMode);
+    }
+
+    /**
+     * Checks whether another codec config has the similar feeding parameters.
+     * Any parameters with NONE value will be considered to be a wildcard matching.
+     *
+     * @param other the codec config to compare against
+     * @return true if the audio feeding parameters are similar, otherwise false.
+     * @hide
+     */
+    public boolean similarCodecFeedingParameters(BluetoothCodecConfig other) {
+        if (other == null || mCodecType != other.mCodecType) {
+            return false;
+        }
+        int sampleRate = other.mSampleRate;
+        if (mSampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE
+                || sampleRate == BluetoothCodecConfig.SAMPLE_RATE_NONE) {
+            sampleRate = mSampleRate;
+        }
+        int bitsPerSample = other.mBitsPerSample;
+        if (mBitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE
+                || bitsPerSample == BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) {
+            bitsPerSample = mBitsPerSample;
+        }
+        int channelMode = other.mChannelMode;
+        if (mChannelMode == BluetoothCodecConfig.CHANNEL_MODE_NONE
+                || channelMode == BluetoothCodecConfig.CHANNEL_MODE_NONE) {
+            channelMode = mChannelMode;
+        }
+        return sameAudioFeedingParameters(new BluetoothCodecConfig(
+                mCodecType, /* priority */ 0, sampleRate, bitsPerSample, channelMode,
+                /* specific1 */ 0, /* specific2 */ 0, /* specific3 */ 0,
+                /* specific4 */ 0));
+    }
+
+    /**
+     * Checks whether the codec specific parameters are the same.
+     *
+     * @param other the codec config to compare against
+     * @return true if the codec specific parameters are the same, otherwise false.
+     * @hide
+     */
+    public boolean sameCodecSpecificParameters(BluetoothCodecConfig other) {
+        if (other == null && mCodecType != other.mCodecType) {
+            return false;
+        }
+        // Currently we only care about the LDAC Playback Quality at CodecSpecific1
+        switch (mCodecType) {
+            case SOURCE_CODEC_TYPE_LDAC:
+                if (mCodecSpecific1 != other.mCodecSpecific1) {
+                    return false;
+                }
+                // fall through
+            default:
+                return true;
+        }
+    }
+}
diff --git a/android/bluetooth/BluetoothCodecStatus.java b/android/bluetooth/BluetoothCodecStatus.java
new file mode 100644
index 0000000..1e394b8
--- /dev/null
+++ b/android/bluetooth/BluetoothCodecStatus.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2017 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.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Arrays;
+import java.util.Objects;
+
+/**
+ * Represents the codec status (configuration and capability) for a Bluetooth
+ * A2DP source device.
+ *
+ * {@see BluetoothA2dp}
+ *
+ * {@hide}
+ */
+public final class BluetoothCodecStatus implements Parcelable {
+    /**
+     * Extra for the codec configuration intents of the individual profiles.
+     *
+     * This extra represents the current codec status of the A2DP
+     * profile.
+     */
+    public static final String EXTRA_CODEC_STATUS =
+            "android.bluetooth.extra.CODEC_STATUS";
+
+    private final @Nullable BluetoothCodecConfig mCodecConfig;
+    private final BluetoothCodecConfig[] mCodecsLocalCapabilities;
+    private final BluetoothCodecConfig[] mCodecsSelectableCapabilities;
+
+    public BluetoothCodecStatus(@Nullable BluetoothCodecConfig codecConfig,
+            @Nullable BluetoothCodecConfig[] codecsLocalCapabilities,
+            @Nullable BluetoothCodecConfig[] codecsSelectableCapabilities) {
+        mCodecConfig = codecConfig;
+        mCodecsLocalCapabilities = codecsLocalCapabilities;
+        mCodecsSelectableCapabilities = codecsSelectableCapabilities;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothCodecStatus) {
+            BluetoothCodecStatus other = (BluetoothCodecStatus) o;
+            return (Objects.equals(other.mCodecConfig, mCodecConfig)
+                    && sameCapabilities(other.mCodecsLocalCapabilities, mCodecsLocalCapabilities)
+                    && sameCapabilities(other.mCodecsSelectableCapabilities,
+                    mCodecsSelectableCapabilities));
+        }
+        return false;
+    }
+
+    /**
+     * Checks whether two arrays of capabilities contain same capabilities.
+     * The order of the capabilities in each array is ignored.
+     *
+     * @param c1 the first array of capabilities to compare
+     * @param c2 the second array of capabilities to compare
+     * @return true if both arrays contain same capabilities
+     * @hide
+     */
+    public static boolean sameCapabilities(BluetoothCodecConfig[] c1,
+                                           BluetoothCodecConfig[] c2) {
+        if (c1 == null) {
+            return (c2 == null);
+        }
+        if (c2 == null) {
+            return false;
+        }
+        if (c1.length != c2.length) {
+            return false;
+        }
+        return Arrays.asList(c1).containsAll(Arrays.asList(c2));
+    }
+
+    /**
+     * Checks whether the codec config matches the selectable capabilities.
+     * Any parameters of the codec config with NONE value will be considered a wildcard matching.
+     *
+     * @param codecConfig the codec config to compare against
+     * @return true if the codec config matches, otherwise false
+     * @hide
+     */
+    public boolean isCodecConfigSelectable(BluetoothCodecConfig codecConfig) {
+        if (codecConfig == null || !codecConfig.hasSingleSampleRate()
+                || !codecConfig.hasSingleBitsPerSample() || !codecConfig.hasSingleChannelMode()) {
+            return false;
+        }
+        for (BluetoothCodecConfig selectableConfig : mCodecsSelectableCapabilities) {
+            if (codecConfig.getCodecType() != selectableConfig.getCodecType()) {
+                continue;
+            }
+            int sampleRate = codecConfig.getSampleRate();
+            if ((sampleRate & selectableConfig.getSampleRate()) == 0
+                    && sampleRate != BluetoothCodecConfig.SAMPLE_RATE_NONE) {
+                continue;
+            }
+            int bitsPerSample = codecConfig.getBitsPerSample();
+            if ((bitsPerSample & selectableConfig.getBitsPerSample()) == 0
+                    && bitsPerSample != BluetoothCodecConfig.BITS_PER_SAMPLE_NONE) {
+                continue;
+            }
+            int channelMode = codecConfig.getChannelMode();
+            if ((channelMode & selectableConfig.getChannelMode()) == 0
+                    && channelMode != BluetoothCodecConfig.CHANNEL_MODE_NONE) {
+                continue;
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Returns a hash based on the codec config and local capabilities
+     *
+     * @return a hash based on the config values
+     * @hide
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mCodecConfig, mCodecsLocalCapabilities,
+                mCodecsLocalCapabilities);
+    }
+
+    @Override
+    public String toString() {
+        return "{mCodecConfig:" + mCodecConfig
+                + ",mCodecsLocalCapabilities:" + Arrays.toString(mCodecsLocalCapabilities)
+                + ",mCodecsSelectableCapabilities:" + Arrays.toString(mCodecsSelectableCapabilities)
+                + "}";
+    }
+
+    /**
+     * Always returns 0
+     *
+     * @return 0
+     * @hide
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothCodecStatus> CREATOR =
+            new Parcelable.Creator<BluetoothCodecStatus>() {
+                public BluetoothCodecStatus createFromParcel(Parcel in) {
+                    final BluetoothCodecConfig codecConfig = in.readTypedObject(
+                            BluetoothCodecConfig.CREATOR);
+                    final BluetoothCodecConfig[] codecsLocalCapabilities = in.createTypedArray(
+                            BluetoothCodecConfig.CREATOR);
+                    final BluetoothCodecConfig[] codecsSelectableCapabilities = in.createTypedArray(
+                            BluetoothCodecConfig.CREATOR);
+
+                    return new BluetoothCodecStatus(codecConfig,
+                            codecsLocalCapabilities,
+                            codecsSelectableCapabilities);
+                }
+
+                public BluetoothCodecStatus[] newArray(int size) {
+                    return new BluetoothCodecStatus[size];
+                }
+            };
+
+    /**
+     * Flattens the object to a parcel
+     *
+     * @param out The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *
+     * @hide
+     */
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeTypedObject(mCodecConfig, 0);
+        out.writeTypedArray(mCodecsLocalCapabilities, 0);
+        out.writeTypedArray(mCodecsSelectableCapabilities, 0);
+    }
+
+    /**
+     * Gets the current codec configuration.
+     *
+     * @return the current codec configuration
+     */
+    public @Nullable BluetoothCodecConfig getCodecConfig() {
+        return mCodecConfig;
+    }
+
+    /**
+     * Gets the codecs local capabilities.
+     *
+     * @return an array with the codecs local capabilities
+     */
+    public @Nullable BluetoothCodecConfig[] getCodecsLocalCapabilities() {
+        return mCodecsLocalCapabilities;
+    }
+
+    /**
+     * Gets the codecs selectable capabilities.
+     *
+     * @return an array with the codecs selectable capabilities
+     */
+    public @Nullable BluetoothCodecConfig[] getCodecsSelectableCapabilities() {
+        return mCodecsSelectableCapabilities;
+    }
+}
diff --git a/android/bluetooth/BluetoothDevice.java b/android/bluetooth/BluetoothDevice.java
new file mode 100644
index 0000000..594e5ff
--- /dev/null
+++ b/android/bluetooth/BluetoothDevice.java
@@ -0,0 +1,2297 @@
+/*
+ * Copyright (C) 2009 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.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.app.PropertyInvalidatedCache;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.UUID;
+
+/**
+ * Represents a remote Bluetooth device. A {@link BluetoothDevice} lets you
+ * create a connection with the respective device or query information about
+ * it, such as the name, address, class, and bonding state.
+ *
+ * <p>This class is really just a thin wrapper for a Bluetooth hardware
+ * address. Objects of this class are immutable. Operations on this class
+ * are performed on the remote Bluetooth hardware address, using the
+ * {@link BluetoothAdapter} that was used to create this {@link
+ * BluetoothDevice}.
+ *
+ * <p>To get a {@link BluetoothDevice}, use
+ * {@link BluetoothAdapter#getRemoteDevice(String)
+ * BluetoothAdapter.getRemoteDevice(String)} to create one representing a device
+ * of a known MAC address (which you can get through device discovery with
+ * {@link BluetoothAdapter}) or get one from the set of bonded devices
+ * returned by {@link BluetoothAdapter#getBondedDevices()
+ * BluetoothAdapter.getBondedDevices()}. You can then open a
+ * {@link BluetoothSocket} for communication with the remote device, using
+ * {@link #createRfcommSocketToServiceRecord(UUID)} over Bluetooth BR/EDR or using
+ * {@link #createL2capChannel(int)} over Bluetooth LE.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>
+ * For more information about using Bluetooth, read the <a href=
+ * "{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer
+ * guide.
+ * </p>
+ * </div>
+ *
+ * {@see BluetoothAdapter}
+ * {@see BluetoothSocket}
+ */
+public final class BluetoothDevice implements Parcelable {
+    private static final String TAG = "BluetoothDevice";
+    private static final boolean DBG = false;
+
+    /**
+     * Connection state bitmask as returned by getConnectionState.
+     */
+    private static final int CONNECTION_STATE_DISCONNECTED = 0;
+    private static final int CONNECTION_STATE_CONNECTED = 1;
+    private static final int CONNECTION_STATE_ENCRYPTED_BREDR = 2;
+    private static final int CONNECTION_STATE_ENCRYPTED_LE = 4;
+
+    /**
+     * Sentinel error value for this class. Guaranteed to not equal any other
+     * integer constant in this class. Provided as a convenience for functions
+     * that require a sentinel error value, for example:
+     * <p><code>Intent.getIntExtra(BluetoothDevice.EXTRA_BOND_STATE,
+     * BluetoothDevice.ERROR)</code>
+     */
+    public static final int ERROR = Integer.MIN_VALUE;
+
+    /**
+     * Broadcast Action: Remote device discovered.
+     * <p>Sent when a remote device is found during discovery.
+     * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
+     * #EXTRA_CLASS}. Can contain the extra fields {@link #EXTRA_NAME} and/or
+     * {@link #EXTRA_RSSI} if they are available.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} and
+     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION} to receive.
+     */
+    // TODO: Change API to not broadcast RSSI if not available (incoming connection)
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_FOUND =
+            "android.bluetooth.device.action.FOUND";
+
+    /**
+     * Broadcast Action: Bluetooth class of a remote device has changed.
+     * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
+     * #EXTRA_CLASS}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     * {@see BluetoothClass}
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CLASS_CHANGED =
+            "android.bluetooth.device.action.CLASS_CHANGED";
+
+    /**
+     * Broadcast Action: Indicates a low level (ACL) connection has been
+     * established with a remote device.
+     * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
+     * <p>ACL connections are managed automatically by the Android Bluetooth
+     * stack.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_ACL_CONNECTED =
+            "android.bluetooth.device.action.ACL_CONNECTED";
+
+    /**
+     * Broadcast Action: Indicates that a low level (ACL) disconnection has
+     * been requested for a remote device, and it will soon be disconnected.
+     * <p>This is useful for graceful disconnection. Applications should use
+     * this intent as a hint to immediately terminate higher level connections
+     * (RFCOMM, L2CAP, or profile connections) to the remote device.
+     * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_ACL_DISCONNECT_REQUESTED =
+            "android.bluetooth.device.action.ACL_DISCONNECT_REQUESTED";
+
+    /**
+     * Broadcast Action: Indicates a low level (ACL) disconnection from a
+     * remote device.
+     * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
+     * <p>ACL connections are managed automatically by the Android Bluetooth
+     * stack.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_ACL_DISCONNECTED =
+            "android.bluetooth.device.action.ACL_DISCONNECTED";
+
+    /**
+     * Broadcast Action: Indicates the friendly name of a remote device has
+     * been retrieved for the first time, or changed since the last retrieval.
+     * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
+     * #EXTRA_NAME}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_NAME_CHANGED =
+            "android.bluetooth.device.action.NAME_CHANGED";
+
+    /**
+     * Broadcast Action: Indicates the alias of a remote device has been
+     * changed.
+     * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    @SuppressLint("ActionValue")
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_ALIAS_CHANGED =
+            "android.bluetooth.device.action.ALIAS_CHANGED";
+
+    /**
+     * Broadcast Action: Indicates a change in the bond state of a remote
+     * device. For example, if a device is bonded (paired).
+     * <p>Always contains the extra fields {@link #EXTRA_DEVICE}, {@link
+     * #EXTRA_BOND_STATE} and {@link #EXTRA_PREVIOUS_BOND_STATE}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     */
+    // Note: When EXTRA_BOND_STATE is BOND_NONE then this will also
+    // contain a hidden extra field EXTRA_REASON with the result code.
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_BOND_STATE_CHANGED =
+            "android.bluetooth.device.action.BOND_STATE_CHANGED";
+
+    /**
+     * Broadcast Action: Indicates the battery level of a remote device has
+     * been retrieved for the first time, or changed since the last retrieval
+     * <p>Always contains the extra fields {@link #EXTRA_DEVICE} and {@link
+     * #EXTRA_BATTERY_LEVEL}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_BATTERY_LEVEL_CHANGED =
+            "android.bluetooth.device.action.BATTERY_LEVEL_CHANGED";
+
+    /**
+     * Used as an Integer extra field in {@link #ACTION_BATTERY_LEVEL_CHANGED}
+     * intent. It contains the most recently retrieved battery level information
+     * ranging from 0% to 100% for a remote device, {@link #BATTERY_LEVEL_UNKNOWN}
+     * when the valid is unknown or there is an error
+     *
+     * @hide
+     */
+    public static final String EXTRA_BATTERY_LEVEL =
+            "android.bluetooth.device.extra.BATTERY_LEVEL";
+
+    /**
+     * Used as the unknown value for {@link #EXTRA_BATTERY_LEVEL} and {@link #getBatteryLevel()}
+     *
+     * @hide
+     */
+    public static final int BATTERY_LEVEL_UNKNOWN = -1;
+
+    /**
+     * Used as an error value for {@link #getBatteryLevel()} to represent bluetooth is off
+     *
+     * @hide
+     */
+    public static final int BATTERY_LEVEL_BLUETOOTH_OFF = -100;
+
+    /**
+     * Used as a Parcelable {@link BluetoothDevice} extra field in every intent
+     * broadcast by this class. It contains the {@link BluetoothDevice} that
+     * the intent applies to.
+     */
+    public static final String EXTRA_DEVICE = "android.bluetooth.device.extra.DEVICE";
+
+    /**
+     * Used as a String extra field in {@link #ACTION_NAME_CHANGED} and {@link
+     * #ACTION_FOUND} intents. It contains the friendly Bluetooth name.
+     */
+    public static final String EXTRA_NAME = "android.bluetooth.device.extra.NAME";
+
+    /**
+     * Used as an optional short extra field in {@link #ACTION_FOUND} intents.
+     * Contains the RSSI value of the remote device as reported by the
+     * Bluetooth hardware.
+     */
+    public static final String EXTRA_RSSI = "android.bluetooth.device.extra.RSSI";
+
+    /**
+     * Used as a Parcelable {@link BluetoothClass} extra field in {@link
+     * #ACTION_FOUND} and {@link #ACTION_CLASS_CHANGED} intents.
+     */
+    public static final String EXTRA_CLASS = "android.bluetooth.device.extra.CLASS";
+
+    /**
+     * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents.
+     * Contains the bond state of the remote device.
+     * <p>Possible values are:
+     * {@link #BOND_NONE},
+     * {@link #BOND_BONDING},
+     * {@link #BOND_BONDED}.
+     */
+    public static final String EXTRA_BOND_STATE = "android.bluetooth.device.extra.BOND_STATE";
+    /**
+     * Used as an int extra field in {@link #ACTION_BOND_STATE_CHANGED} intents.
+     * Contains the previous bond state of the remote device.
+     * <p>Possible values are:
+     * {@link #BOND_NONE},
+     * {@link #BOND_BONDING},
+     * {@link #BOND_BONDED}.
+     */
+    public static final String EXTRA_PREVIOUS_BOND_STATE =
+            "android.bluetooth.device.extra.PREVIOUS_BOND_STATE";
+    /**
+     * Indicates the remote device is not bonded (paired).
+     * <p>There is no shared link key with the remote device, so communication
+     * (if it is allowed at all) will be unauthenticated and unencrypted.
+     */
+    public static final int BOND_NONE = 10;
+    /**
+     * Indicates bonding (pairing) is in progress with the remote device.
+     */
+    public static final int BOND_BONDING = 11;
+    /**
+     * Indicates the remote device is bonded (paired).
+     * <p>A shared link keys exists locally for the remote device, so
+     * communication can be authenticated and encrypted.
+     * <p><i>Being bonded (paired) with a remote device does not necessarily
+     * mean the device is currently connected. It just means that the pending
+     * procedure was completed at some earlier time, and the link key is still
+     * stored locally, ready to use on the next connection.
+     * </i>
+     */
+    public static final int BOND_BONDED = 12;
+
+    /**
+     * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST}
+     * intents for unbond reason.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final String EXTRA_REASON = "android.bluetooth.device.extra.REASON";
+
+    /**
+     * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST}
+     * intents to indicate pairing method used. Possible values are:
+     * {@link #PAIRING_VARIANT_PIN},
+     * {@link #PAIRING_VARIANT_PASSKEY_CONFIRMATION},
+     */
+    public static final String EXTRA_PAIRING_VARIANT =
+            "android.bluetooth.device.extra.PAIRING_VARIANT";
+
+    /**
+     * Used as an int extra field in {@link #ACTION_PAIRING_REQUEST}
+     * intents as the value of passkey.
+     */
+    public static final String EXTRA_PAIRING_KEY = "android.bluetooth.device.extra.PAIRING_KEY";
+
+    /**
+     * Bluetooth device type, Unknown
+     */
+    public static final int DEVICE_TYPE_UNKNOWN = 0;
+
+    /**
+     * Bluetooth device type, Classic - BR/EDR devices
+     */
+    public static final int DEVICE_TYPE_CLASSIC = 1;
+
+    /**
+     * Bluetooth device type, Low Energy - LE-only
+     */
+    public static final int DEVICE_TYPE_LE = 2;
+
+    /**
+     * Bluetooth device type, Dual Mode - BR/EDR/LE
+     */
+    public static final int DEVICE_TYPE_DUAL = 3;
+
+
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @UnsupportedAppUsage
+    public static final String ACTION_SDP_RECORD =
+            "android.bluetooth.device.action.SDP_RECORD";
+
+    /**
+     * Maximum length of a metadata entry, this is to avoid exploding Bluetooth
+     * disk usage
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MAX_LENGTH = 2048;
+
+    /**
+     * Manufacturer name of this Bluetooth device
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MANUFACTURER_NAME = 0;
+
+    /**
+     * Model name of this Bluetooth device
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MODEL_NAME = 1;
+
+    /**
+     * Software version of this Bluetooth device
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_SOFTWARE_VERSION = 2;
+
+    /**
+     * Hardware version of this Bluetooth device
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_HARDWARE_VERSION = 3;
+
+    /**
+     * Package name of the companion app, if any
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_COMPANION_APP = 4;
+
+    /**
+     * URI to the main icon shown on the settings UI
+     * Data type should be {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_MAIN_ICON = 5;
+
+    /**
+     * Whether this device is an untethered headset with left, right and case
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_IS_UNTETHERED_HEADSET = 6;
+
+    /**
+     * URI to icon of the left headset
+     * Data type should be {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_LEFT_ICON = 7;
+
+    /**
+     * URI to icon of the right headset
+     * Data type should be {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_RIGHT_ICON = 8;
+
+    /**
+     * URI to icon of the headset charging case
+     * Data type should be {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_CASE_ICON = 9;
+
+    /**
+     * Battery level of left headset
+     * Data type should be {@String} 0-100 as {@link Byte} array, otherwise
+     * as invalid.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_LEFT_BATTERY = 10;
+
+    /**
+     * Battery level of rigth headset
+     * Data type should be {@String} 0-100 as {@link Byte} array, otherwise
+     * as invalid.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_RIGHT_BATTERY = 11;
+
+    /**
+     * Battery level of the headset charging case
+     * Data type should be {@String} 0-100 as {@link Byte} array, otherwise
+     * as invalid.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_CASE_BATTERY = 12;
+
+    /**
+     * Whether the left headset is charging
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_LEFT_CHARGING = 13;
+
+    /**
+     * Whether the right headset is charging
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_RIGHT_CHARGING = 14;
+
+    /**
+     * Whether the headset charging case is charging
+     * Data type should be {@String} as {@link Byte} array.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_UNTETHERED_CASE_CHARGING = 15;
+
+    /**
+     * URI to the enhanced settings UI slice
+     * Data type should be {@String} as {@link Byte} array, null means
+     * the UI does not exist.
+     * @hide
+     */
+    @SystemApi
+    public static final int METADATA_ENHANCED_SETTINGS_UI_URI = 16;
+
+    /**
+     * Broadcast Action: This intent is used to broadcast the {@link UUID}
+     * wrapped as a {@link android.os.ParcelUuid} of the remote device after it
+     * has been fetched. This intent is sent only when the UUIDs of the remote
+     * device are requested to be fetched using Service Discovery Protocol
+     * <p> Always contains the extra field {@link #EXTRA_DEVICE}
+     * <p> Always contains the extra field {@link #EXTRA_UUID}
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_UUID =
+            "android.bluetooth.device.action.UUID";
+
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_MAS_INSTANCE =
+            "android.bluetooth.device.action.MAS_INSTANCE";
+
+    /**
+     * Broadcast Action: Indicates a failure to retrieve the name of a remote
+     * device.
+     * <p>Always contains the extra field {@link #EXTRA_DEVICE}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive.
+     *
+     * @hide
+     */
+    //TODO: is this actually useful?
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_NAME_FAILED =
+            "android.bluetooth.device.action.NAME_FAILED";
+
+    /**
+     * Broadcast Action: This intent is used to broadcast PAIRING REQUEST
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} to
+     * receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PAIRING_REQUEST =
+            "android.bluetooth.device.action.PAIRING_REQUEST";
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @UnsupportedAppUsage
+    public static final String ACTION_PAIRING_CANCEL =
+            "android.bluetooth.device.action.PAIRING_CANCEL";
+
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_ACCESS_REQUEST =
+            "android.bluetooth.device.action.CONNECTION_ACCESS_REQUEST";
+
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_ACCESS_REPLY =
+            "android.bluetooth.device.action.CONNECTION_ACCESS_REPLY";
+
+    /** @hide */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_ACCESS_CANCEL =
+            "android.bluetooth.device.action.CONNECTION_ACCESS_CANCEL";
+
+    /**
+     * Intent to broadcast silence mode changed.
+     * Alway contains the extra field {@link #EXTRA_DEVICE}
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @SystemApi
+    public static final String ACTION_SILENCE_MODE_CHANGED =
+            "android.bluetooth.device.action.SILENCE_MODE_CHANGED";
+
+    /**
+     * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intent.
+     *
+     * @hide
+     */
+    public static final String EXTRA_ACCESS_REQUEST_TYPE =
+            "android.bluetooth.device.extra.ACCESS_REQUEST_TYPE";
+
+    /** @hide */
+    public static final int REQUEST_TYPE_PROFILE_CONNECTION = 1;
+
+    /** @hide */
+    public static final int REQUEST_TYPE_PHONEBOOK_ACCESS = 2;
+
+    /** @hide */
+    public static final int REQUEST_TYPE_MESSAGE_ACCESS = 3;
+
+    /** @hide */
+    public static final int REQUEST_TYPE_SIM_ACCESS = 4;
+
+    /**
+     * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents,
+     * Contains package name to return reply intent to.
+     *
+     * @hide
+     */
+    public static final String EXTRA_PACKAGE_NAME = "android.bluetooth.device.extra.PACKAGE_NAME";
+
+    /**
+     * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REQUEST} intents,
+     * Contains class name to return reply intent to.
+     *
+     * @hide
+     */
+    public static final String EXTRA_CLASS_NAME = "android.bluetooth.device.extra.CLASS_NAME";
+
+    /**
+     * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REPLY} intent.
+     *
+     * @hide
+     */
+    public static final String EXTRA_CONNECTION_ACCESS_RESULT =
+            "android.bluetooth.device.extra.CONNECTION_ACCESS_RESULT";
+
+    /** @hide */
+    public static final int CONNECTION_ACCESS_YES = 1;
+
+    /** @hide */
+    public static final int CONNECTION_ACCESS_NO = 2;
+
+    /**
+     * Used as an extra field in {@link #ACTION_CONNECTION_ACCESS_REPLY} intents,
+     * Contains boolean to indicate if the allowed response is once-for-all so that
+     * next request will be granted without asking user again.
+     *
+     * @hide
+     */
+    public static final String EXTRA_ALWAYS_ALLOWED =
+            "android.bluetooth.device.extra.ALWAYS_ALLOWED";
+
+    /**
+     * A bond attempt succeeded
+     *
+     * @hide
+     */
+    public static final int BOND_SUCCESS = 0;
+
+    /**
+     * A bond attempt failed because pins did not match, or remote device did
+     * not respond to pin request in time
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int UNBOND_REASON_AUTH_FAILED = 1;
+
+    /**
+     * A bond attempt failed because the other side explicitly rejected
+     * bonding
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int UNBOND_REASON_AUTH_REJECTED = 2;
+
+    /**
+     * A bond attempt failed because we canceled the bonding process
+     *
+     * @hide
+     */
+    public static final int UNBOND_REASON_AUTH_CANCELED = 3;
+
+    /**
+     * A bond attempt failed because we could not contact the remote device
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int UNBOND_REASON_REMOTE_DEVICE_DOWN = 4;
+
+    /**
+     * A bond attempt failed because a discovery is in progress
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int UNBOND_REASON_DISCOVERY_IN_PROGRESS = 5;
+
+    /**
+     * A bond attempt failed because of authentication timeout
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int UNBOND_REASON_AUTH_TIMEOUT = 6;
+
+    /**
+     * A bond attempt failed because of repeated attempts
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int UNBOND_REASON_REPEATED_ATTEMPTS = 7;
+
+    /**
+     * A bond attempt failed because we received an Authentication Cancel
+     * by remote end
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int UNBOND_REASON_REMOTE_AUTH_CANCELED = 8;
+
+    /**
+     * An existing bond was explicitly revoked
+     *
+     * @hide
+     */
+    public static final int UNBOND_REASON_REMOVED = 9;
+
+    /**
+     * The user will be prompted to enter a pin or
+     * an app will enter a pin for user.
+     */
+    public static final int PAIRING_VARIANT_PIN = 0;
+
+    /**
+     * The user will be prompted to enter a passkey
+     *
+     * @hide
+     */
+    public static final int PAIRING_VARIANT_PASSKEY = 1;
+
+    /**
+     * The user will be prompted to confirm the passkey displayed on the screen or
+     * an app will confirm the passkey for the user.
+     */
+    public static final int PAIRING_VARIANT_PASSKEY_CONFIRMATION = 2;
+
+    /**
+     * The user will be prompted to accept or deny the incoming pairing request
+     *
+     * @hide
+     */
+    public static final int PAIRING_VARIANT_CONSENT = 3;
+
+    /**
+     * The user will be prompted to enter the passkey displayed on remote device
+     * This is used for Bluetooth 2.1 pairing.
+     *
+     * @hide
+     */
+    public static final int PAIRING_VARIANT_DISPLAY_PASSKEY = 4;
+
+    /**
+     * The user will be prompted to enter the PIN displayed on remote device.
+     * This is used for Bluetooth 2.0 pairing.
+     *
+     * @hide
+     */
+    public static final int PAIRING_VARIANT_DISPLAY_PIN = 5;
+
+    /**
+     * The user will be prompted to accept or deny the OOB pairing request
+     *
+     * @hide
+     */
+    public static final int PAIRING_VARIANT_OOB_CONSENT = 6;
+
+    /**
+     * The user will be prompted to enter a 16 digit pin or
+     * an app will enter a 16 digit pin for user.
+     *
+     * @hide
+     */
+    public static final int PAIRING_VARIANT_PIN_16_DIGITS = 7;
+
+    /**
+     * Used as an extra field in {@link #ACTION_UUID} intents,
+     * Contains the {@link android.os.ParcelUuid}s of the remote device which
+     * is a parcelable version of {@link UUID}.
+     */
+    public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID";
+
+    /** @hide */
+    public static final String EXTRA_SDP_RECORD =
+            "android.bluetooth.device.extra.SDP_RECORD";
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final String EXTRA_SDP_SEARCH_STATUS =
+            "android.bluetooth.device.extra.SDP_SEARCH_STATUS";
+
+    /** @hide */
+    @IntDef(prefix = "ACCESS_", value = {ACCESS_UNKNOWN,
+            ACCESS_ALLOWED, ACCESS_REJECTED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AccessPermission{}
+
+    /**
+     * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission},
+     * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ACCESS_UNKNOWN = 0;
+
+    /**
+     * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission},
+     * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ACCESS_ALLOWED = 1;
+
+    /**
+     * For {@link #getPhonebookAccessPermission}, {@link #setPhonebookAccessPermission},
+     * {@link #getMessageAccessPermission} and {@link #setMessageAccessPermission}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ACCESS_REJECTED = 2;
+
+    /**
+     * No preference of physical transport for GATT connections to remote dual-mode devices
+     */
+    public static final int TRANSPORT_AUTO = 0;
+
+    /**
+     * Prefer BR/EDR transport for GATT connections to remote dual-mode devices
+     */
+    public static final int TRANSPORT_BREDR = 1;
+
+    /**
+     * Prefer LE transport for GATT connections to remote dual-mode devices
+     */
+    public static final int TRANSPORT_LE = 2;
+
+    /**
+     * Bluetooth LE 1M PHY. Used to refer to LE 1M Physical Channel for advertising, scanning or
+     * connection.
+     */
+    public static final int PHY_LE_1M = 1;
+
+    /**
+     * Bluetooth LE 2M PHY. Used to refer to LE 2M Physical Channel for advertising, scanning or
+     * connection.
+     */
+    public static final int PHY_LE_2M = 2;
+
+    /**
+     * Bluetooth LE Coded PHY. Used to refer to LE Coded Physical Channel for advertising, scanning
+     * or connection.
+     */
+    public static final int PHY_LE_CODED = 3;
+
+    /**
+     * Bluetooth LE 1M PHY mask. Used to specify LE 1M Physical Channel as one of many available
+     * options in a bitmask.
+     */
+    public static final int PHY_LE_1M_MASK = 1;
+
+    /**
+     * Bluetooth LE 2M PHY mask. Used to specify LE 2M Physical Channel as one of many available
+     * options in a bitmask.
+     */
+    public static final int PHY_LE_2M_MASK = 2;
+
+    /**
+     * Bluetooth LE Coded PHY mask. Used to specify LE Coded Physical Channel as one of many
+     * available options in a bitmask.
+     */
+    public static final int PHY_LE_CODED_MASK = 4;
+
+    /**
+     * No preferred coding when transmitting on the LE Coded PHY.
+     */
+    public static final int PHY_OPTION_NO_PREFERRED = 0;
+
+    /**
+     * Prefer the S=2 coding to be used when transmitting on the LE Coded PHY.
+     */
+    public static final int PHY_OPTION_S2 = 1;
+
+    /**
+     * Prefer the S=8 coding to be used when transmitting on the LE Coded PHY.
+     */
+    public static final int PHY_OPTION_S8 = 2;
+
+
+    /** @hide */
+    public static final String EXTRA_MAS_INSTANCE =
+            "android.bluetooth.device.extra.MAS_INSTANCE";
+
+    /**
+     * Lazy initialization. Guaranteed final after first object constructed, or
+     * getService() called.
+     * TODO: Unify implementation of sService amongst BluetoothFoo API's
+     */
+    private static volatile IBluetooth sService;
+
+    private final String mAddress;
+
+    /*package*/
+    @UnsupportedAppUsage
+    static IBluetooth getService() {
+        synchronized (BluetoothDevice.class) {
+            if (sService == null) {
+                BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+                sService = adapter.getBluetoothService(sStateChangeCallback);
+            }
+        }
+        return sService;
+    }
+
+    static IBluetoothManagerCallback sStateChangeCallback = new IBluetoothManagerCallback.Stub() {
+
+        public void onBluetoothServiceUp(IBluetooth bluetoothService)
+                throws RemoteException {
+            synchronized (BluetoothDevice.class) {
+                if (sService == null) {
+                    sService = bluetoothService;
+                }
+            }
+        }
+
+        public void onBluetoothServiceDown()
+                throws RemoteException {
+            synchronized (BluetoothDevice.class) {
+                sService = null;
+            }
+        }
+
+        public void onBrEdrDown() {
+            if (DBG) Log.d(TAG, "onBrEdrDown: reached BLE ON state");
+        }
+    };
+
+    /**
+     * Create a new BluetoothDevice
+     * Bluetooth MAC address must be upper case, such as "00:11:22:33:AA:BB",
+     * and is validated in this constructor.
+     *
+     * @param address valid Bluetooth MAC address
+     * @throws RuntimeException Bluetooth is not available on this platform
+     * @throws IllegalArgumentException address is invalid
+     * @hide
+     */
+    @UnsupportedAppUsage
+    /*package*/ BluetoothDevice(String address) {
+        getService();  // ensures sService is initialized
+        if (!BluetoothAdapter.checkBluetoothAddress(address)) {
+            throw new IllegalArgumentException(address + " is not a valid Bluetooth address");
+        }
+
+        mAddress = address;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothDevice) {
+            return mAddress.equals(((BluetoothDevice) o).getAddress());
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mAddress.hashCode();
+    }
+
+    /**
+     * Returns a string representation of this BluetoothDevice.
+     * <p>Currently this is the Bluetooth hardware address, for example
+     * "00:11:22:AA:BB:CC". However, you should always use {@link #getAddress}
+     * if you explicitly require the Bluetooth hardware address in case the
+     * {@link #toString} representation changes in the future.
+     *
+     * @return string representation of this BluetoothDevice
+     */
+    @Override
+    public String toString() {
+        return mAddress;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothDevice> CREATOR =
+            new Parcelable.Creator<BluetoothDevice>() {
+                public BluetoothDevice createFromParcel(Parcel in) {
+                    return new BluetoothDevice(in.readString());
+                }
+
+                public BluetoothDevice[] newArray(int size) {
+                    return new BluetoothDevice[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeString(mAddress);
+    }
+
+    /**
+     * Returns the hardware address of this BluetoothDevice.
+     * <p> For example, "00:11:22:AA:BB:CC".
+     *
+     * @return Bluetooth hardware address as string
+     */
+    public String getAddress() {
+        if (DBG) Log.d(TAG, "mAddress: " + mAddress);
+        return mAddress;
+    }
+
+    /**
+     * Get the friendly Bluetooth name of the remote device.
+     *
+     * <p>The local adapter will automatically retrieve remote names when
+     * performing a device scan, and will cache them. This method just returns
+     * the name for this device from the cache.
+     *
+     * @return the Bluetooth name, or null if there was a problem.
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public String getName() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot get Remote Device name");
+            return null;
+        }
+        try {
+            String name = service.getRemoteName(this);
+            if (name != null) {
+                return name.replaceAll("[\\t\\n\\r]+", " ");
+            }
+            return null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return null;
+    }
+
+    /**
+     * Get the Bluetooth device type of the remote device.
+     *
+     * @return the device type {@link #DEVICE_TYPE_CLASSIC}, {@link #DEVICE_TYPE_LE} {@link
+     * #DEVICE_TYPE_DUAL}. {@link #DEVICE_TYPE_UNKNOWN} if it's not available
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public int getType() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot get Remote Device type");
+            return DEVICE_TYPE_UNKNOWN;
+        }
+        try {
+            return service.getRemoteType(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return DEVICE_TYPE_UNKNOWN;
+    }
+
+    /**
+     * Get the Bluetooth alias of the remote device.
+     * <p>Alias is the locally modified name of a remote device.
+     *
+     * @return the Bluetooth alias, the friendly device name if no alias, or
+     * null if there was a problem
+     */
+    @Nullable
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public String getAlias() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot get Remote Device Alias");
+            return null;
+        }
+        try {
+            String alias = service.getRemoteAlias(this);
+            if (alias == null) {
+                return getName();
+            }
+            return alias;
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return null;
+    }
+
+    /**
+     * Set the Bluetooth alias of the remote device.
+     * <p>Alias is the locally modified name of a remote device.
+     * <p>This methoid overwrites the alias. The changed
+     * alias is saved in the local storage so that the change
+     * is preserved over power cycle.
+     *
+     * @return true on success, false on error
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean setAlias(@NonNull String alias) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot set Remote Device name");
+            return false;
+        }
+        try {
+            return service.setRemoteAlias(this, alias);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Get the most recent identified battery level of this Bluetooth device
+     *
+     * @return Battery level in percents from 0 to 100, {@link #BATTERY_LEVEL_BLUETOOTH_OFF} if
+     * Bluetooth is disabled or {@link #BATTERY_LEVEL_UNKNOWN} if device is disconnected, or does
+     * not have any battery reporting service, or return value is invalid
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public int getBatteryLevel() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "Bluetooth disabled. Cannot get remote device battery level");
+            return BATTERY_LEVEL_BLUETOOTH_OFF;
+        }
+        try {
+            return service.getBatteryLevel(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return BATTERY_LEVEL_UNKNOWN;
+    }
+
+    /**
+     * Start the bonding (pairing) process with the remote device.
+     * <p>This is an asynchronous call, it will return immediately. Register
+     * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when
+     * the bonding process completes, and its result.
+     * <p>Android system services will handle the necessary user interactions
+     * to confirm and complete the bonding process.
+     *
+     * @return false on immediate error, true if bonding will begin
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean createBond() {
+        return createBond(TRANSPORT_AUTO);
+    }
+
+    /**
+     * Start the bonding (pairing) process with the remote device using the
+     * specified transport.
+     *
+     * <p>This is an asynchronous call, it will return immediately. Register
+     * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when
+     * the bonding process completes, and its result.
+     * <p>Android system services will handle the necessary user interactions
+     * to confirm and complete the bonding process.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+     *
+     * @param transport The transport to use for the pairing procedure.
+     * @return false on immediate error, true if bonding will begin
+     * @throws IllegalArgumentException if an invalid transport was specified
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean createBond(int transport) {
+        return createBondOutOfBand(transport, null);
+    }
+
+    /**
+     * Start the bonding (pairing) process with the remote device using the
+     * Out Of Band mechanism.
+     *
+     * <p>This is an asynchronous call, it will return immediately. Register
+     * for {@link #ACTION_BOND_STATE_CHANGED} intents to be notified when
+     * the bonding process completes, and its result.
+     *
+     * <p>Android system services will handle the necessary user interactions
+     * to confirm and complete the bonding process.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+     *
+     * @param transport - Transport to use
+     * @param oobData - Out Of Band data
+     * @return false on immediate error, true if bonding will begin
+     * @hide
+     */
+    public boolean createBondOutOfBand(int transport, OobData oobData) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.w(TAG, "BT not enabled, createBondOutOfBand failed");
+            return false;
+        }
+        try {
+            return service.createBond(this, transport, oobData);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Gets whether bonding was initiated locally
+     *
+     * @return true if bonding is initiated locally, false otherwise
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean isBondingInitiatedLocally() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.w(TAG, "BT not enabled, isBondingInitiatedLocally failed");
+            return false;
+        }
+        try {
+            return service.isBondingInitiatedLocally(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Set the Out Of Band data for a remote device to be used later
+     * in the pairing mechanism. Users can obtain this data through other
+     * trusted channels
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+     *
+     * @param hash Simple Secure pairing hash
+     * @param randomizer The random key obtained using OOB
+     * @return false on error; true otherwise
+     * @hide
+     */
+    public boolean setDeviceOutOfBandData(byte[] hash, byte[] randomizer) {
+        //TODO(BT)
+      /*
+      try {
+        return sService.setDeviceOutOfBandData(this, hash, randomizer);
+      } catch (RemoteException e) {Log.e(TAG, "", e);} */
+        return false;
+    }
+
+    /**
+     * Cancel an in-progress bonding request started with {@link #createBond}.
+     *
+     * @return true on success, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean cancelBondProcess() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot cancel Remote Device bond");
+            return false;
+        }
+        try {
+            Log.i(TAG, "cancelBondProcess() for device " + getAddress()
+                    + " called by pid: " + Process.myPid()
+                    + " tid: " + Process.myTid());
+            return service.cancelBondProcess(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Remove bond (pairing) with the remote device.
+     * <p>Delete the link key associated with the remote device, and
+     * immediately terminate connections to that device that require
+     * authentication and encryption.
+     *
+     * @return true on success, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean removeBond() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot remove Remote Device bond");
+            return false;
+        }
+        try {
+            Log.i(TAG, "removeBond() for device " + getAddress()
+                    + " called by pid: " + Process.myPid()
+                    + " tid: " + Process.myTid());
+            return service.removeBond(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    private static final String BLUETOOTH_BONDING_CACHE_PROPERTY =
+            "cache_key.bluetooth.get_bond_state";
+    private final PropertyInvalidatedCache<BluetoothDevice, Integer> mBluetoothBondCache =
+            new PropertyInvalidatedCache<BluetoothDevice, Integer>(
+                8, BLUETOOTH_BONDING_CACHE_PROPERTY) {
+                @Override
+                protected Integer recompute(BluetoothDevice query) {
+                    try {
+                        return sService.getBondState(query);
+                    } catch (RemoteException e) {
+                        throw e.rethrowAsRuntimeException();
+                    }
+                }
+            };
+
+    /** @hide */
+    public void disableBluetoothGetBondStateCache() {
+        mBluetoothBondCache.disableLocal();
+    }
+
+    /** @hide */
+    public static void invalidateBluetoothGetBondStateCache() {
+        PropertyInvalidatedCache.invalidateCache(BLUETOOTH_BONDING_CACHE_PROPERTY);
+    }
+
+    /**
+     * Get the bond state of the remote device.
+     * <p>Possible values for the bond state are:
+     * {@link #BOND_NONE},
+     * {@link #BOND_BONDING},
+     * {@link #BOND_BONDED}.
+     *
+     * @return the bond state
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public int getBondState() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot get bond state");
+            return BOND_NONE;
+        }
+        try {
+            return mBluetoothBondCache.query(this);
+        } catch (RuntimeException e) {
+            if (e.getCause() instanceof RemoteException) {
+                Log.e(TAG, "", e);
+            } else {
+                throw e;
+            }
+        }
+        return BOND_NONE;
+    }
+
+    /**
+     * Returns whether there is an open connection to this device.
+     *
+     * @return True if there is at least one open connection to this device.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean isConnected() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            // BT is not enabled, we cannot be connected.
+            return false;
+        }
+        try {
+            return service.getConnectionState(this) != CONNECTION_STATE_DISCONNECTED;
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /**
+     * Returns whether there is an open connection to this device
+     * that has been encrypted.
+     *
+     * @return True if there is at least one encrypted connection to this device.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean isEncrypted() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            // BT is not enabled, we cannot be connected.
+            return false;
+        }
+        try {
+            return service.getConnectionState(this) > CONNECTION_STATE_CONNECTED;
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /**
+     * Get the Bluetooth class of the remote device.
+     *
+     * @return Bluetooth class object, or null on error
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public BluetoothClass getBluetoothClass() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot get Bluetooth Class");
+            return null;
+        }
+        try {
+            int classInt = service.getRemoteClass(this);
+            if (classInt == BluetoothClass.ERROR) return null;
+            return new BluetoothClass(classInt);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return null;
+    }
+
+    /**
+     * Returns the supported features (UUIDs) of the remote device.
+     *
+     * <p>This method does not start a service discovery procedure to retrieve the UUIDs
+     * from the remote device. Instead, the local cached copy of the service
+     * UUIDs are returned.
+     * <p>Use {@link #fetchUuidsWithSdp} if fresh UUIDs are desired.
+     *
+     * @return the supported features (UUIDs) of the remote device, or null on error
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public ParcelUuid[] getUuids() {
+        final IBluetooth service = sService;
+        if (service == null || !isBluetoothEnabled()) {
+            Log.e(TAG, "BT not enabled. Cannot get remote device Uuids");
+            return null;
+        }
+        try {
+            return service.getRemoteUuids(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return null;
+    }
+
+    /**
+     * Perform a service discovery on the remote device to get the UUIDs supported.
+     *
+     * <p>This API is asynchronous and {@link #ACTION_UUID} intent is sent,
+     * with the UUIDs supported by the remote end. If there is an error
+     * in getting the SDP records or if the process takes a long time,
+     * {@link #ACTION_UUID} intent is sent with the UUIDs that is currently
+     * present in the cache. Clients should use the {@link #getUuids} to get UUIDs
+     * if service discovery is not to be performed.
+     *
+     * @return False if the sanity check fails, True if the process of initiating an ACL connection
+     * to the remote device was started.
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public boolean fetchUuidsWithSdp() {
+        final IBluetooth service = sService;
+        if (service == null || !isBluetoothEnabled()) {
+            Log.e(TAG, "BT not enabled. Cannot fetchUuidsWithSdp");
+            return false;
+        }
+        try {
+            return service.fetchRemoteUuids(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Perform a service discovery on the remote device to get the SDP records associated
+     * with the specified UUID.
+     *
+     * <p>This API is asynchronous and {@link #ACTION_SDP_RECORD} intent is sent,
+     * with the SDP records found on the remote end. If there is an error
+     * in getting the SDP records or if the process takes a long time,
+     * {@link #ACTION_SDP_RECORD} intent is sent with an status value in
+     * {@link #EXTRA_SDP_SEARCH_STATUS} different from 0.
+     * Detailed status error codes can be found by members of the Bluetooth package in
+     * the AbstractionLayer class.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}.
+     * The SDP record data will be stored in the intent as {@link #EXTRA_SDP_RECORD}.
+     * The object type will match one of the SdpXxxRecord types, depending on the UUID searched
+     * for.
+     *
+     * @return False if the sanity check fails, True if the process
+     *               of initiating an ACL connection to the remote device
+     *               was started.
+     */
+    /** @hide */
+    public boolean sdpSearch(ParcelUuid uuid) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot query remote device sdp records");
+            return false;
+        }
+        try {
+            return service.sdpSearch(this, uuid);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN}
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}.
+     *
+     * @return true pin has been set false for error
+     */
+    public boolean setPin(byte[] pin) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot set Remote Device pin");
+            return false;
+        }
+        try {
+            return service.setPin(this, true, pin.length, pin);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Set the pin during pairing when the pairing method is {@link #PAIRING_VARIANT_PIN}
+     *
+     * @return true pin has been set false for error
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setPin(@NonNull String pin) {
+        byte[] pinBytes = convertPinToBytes(pin);
+        if (pinBytes == null) {
+            return false;
+        }
+        return setPin(pinBytes);
+    }
+
+    /**
+     * Confirm passkey for {@link #PAIRING_VARIANT_PASSKEY_CONFIRMATION} pairing.
+     *
+     * @return true confirmation has been sent out false for error
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPairingConfirmation(boolean confirm) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot set pairing confirmation");
+            return false;
+        }
+        try {
+            return service.setPairingConfirmation(this, confirm);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Cancels pairing to this device
+     *
+     * @return true if pairing cancelled successfully, false otherwise
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean cancelPairing() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "BT not enabled. Cannot cancel pairing");
+            return false;
+        }
+        try {
+            return service.cancelBondProcess(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    boolean isBluetoothEnabled() {
+        boolean ret = false;
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null && adapter.isEnabled()) {
+            ret = true;
+        }
+        return ret;
+    }
+
+    /**
+     * Gets whether the phonebook access is allowed for this bluetooth device
+     *
+     * @return Whether the phonebook access is allowed to this device. Can be {@link
+     * #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link #ACCESS_REJECTED}.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @AccessPermission int getPhonebookAccessPermission() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            return ACCESS_UNKNOWN;
+        }
+        try {
+            return service.getPhonebookAccessPermission(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return ACCESS_UNKNOWN;
+    }
+
+    /**
+     * Sets whether the {@link BluetoothDevice} enters silence mode. Audio will not
+     * be routed to the {@link BluetoothDevice} if set to {@code true}.
+     *
+     * When the {@link BluetoothDevice} enters silence mode, and the {@link BluetoothDevice}
+     * is an active device (for A2DP or HFP), the active device for that profile
+     * will be set to null.
+     * If the {@link BluetoothDevice} exits silence mode while the A2DP or HFP
+     * active device is null, the {@link BluetoothDevice} will be set as the
+     * active device for that profile.
+     * If the {@link BluetoothDevice} is disconnected, it exits silence mode.
+     * If the {@link BluetoothDevice} is set as the active device for A2DP or
+     * HFP, while silence mode is enabled, then the device will exit silence mode.
+     * If the {@link BluetoothDevice} is in silence mode, AVRCP position change
+     * event and HFP AG indicators will be disabled.
+     * If the {@link BluetoothDevice} is not connected with A2DP or HFP, it cannot
+     * enter silence mode.
+     *
+     * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
+     *
+     * @param silence true to enter silence mode, false to exit
+     * @return true on success, false on error.
+     * @throws IllegalStateException if Bluetooth is not turned ON.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setSilenceMode(boolean silence) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            throw new IllegalStateException("Bluetooth is not turned ON");
+        }
+        try {
+            return service.setSilenceMode(this, silence);
+        } catch (RemoteException e) {
+            Log.e(TAG, "setSilenceMode fail", e);
+            return false;
+        }
+    }
+
+    /**
+     * Check whether the {@link BluetoothDevice} is in silence mode
+     *
+     * <p> Requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED}.
+     *
+     * @return true on device in silence mode, otherwise false.
+     * @throws IllegalStateException if Bluetooth is not turned ON.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean isInSilenceMode() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            throw new IllegalStateException("Bluetooth is not turned ON");
+        }
+        try {
+            return service.getSilenceMode(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "isInSilenceMode fail", e);
+            return false;
+        }
+    }
+
+    /**
+     * Sets whether the phonebook access is allowed to this device.
+     *
+     * @param value Can be {@link #ACCESS_UNKNOWN}, {@link #ACCESS_ALLOWED} or {@link
+     * #ACCESS_REJECTED}.
+     * @return Whether the value has been successfully set.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPhonebookAccessPermission(@AccessPermission int value) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            return false;
+        }
+        try {
+            return service.setPhonebookAccessPermission(this, value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Gets whether message access is allowed to this bluetooth device
+     *
+     * @return Whether the message access is allowed to this device.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @AccessPermission int getMessageAccessPermission() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            return ACCESS_UNKNOWN;
+        }
+        try {
+            return service.getMessageAccessPermission(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return ACCESS_UNKNOWN;
+    }
+
+    /**
+     * Sets whether the message access is allowed to this device.
+     *
+     * @param value Can be {@link #ACCESS_UNKNOWN} if the device is unbonded,
+     * {@link #ACCESS_ALLOWED} if the permission is being granted, or {@link #ACCESS_REJECTED} if
+     * the permission is not being granted.
+     * @return Whether the value has been successfully set.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setMessageAccessPermission(@AccessPermission int value) {
+        // Validates param value is one of the accepted constants
+        if (value != ACCESS_ALLOWED && value != ACCESS_REJECTED && value != ACCESS_UNKNOWN) {
+            throw new IllegalArgumentException(value + "is not a valid AccessPermission value");
+        }
+        final IBluetooth service = sService;
+        if (service == null) {
+            return false;
+        }
+        try {
+            return service.setMessageAccessPermission(this, value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Gets whether sim access is allowed for this bluetooth device
+     *
+     * @return Whether the Sim access is allowed to this device.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @AccessPermission int getSimAccessPermission() {
+        final IBluetooth service = sService;
+        if (service == null) {
+            return ACCESS_UNKNOWN;
+        }
+        try {
+            return service.getSimAccessPermission(this);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return ACCESS_UNKNOWN;
+    }
+
+    /**
+     * Sets whether the Sim access is allowed to this device.
+     *
+     * @param value Can be {@link #ACCESS_UNKNOWN} if the device is unbonded,
+     * {@link #ACCESS_ALLOWED} if the permission is being granted, or {@link #ACCESS_REJECTED} if
+     * the permission is not being granted.
+     * @return Whether the value has been successfully set.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setSimAccessPermission(int value) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            return false;
+        }
+        try {
+            return service.setSimAccessPermission(this, value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return false;
+    }
+
+    /**
+     * Create an RFCOMM {@link BluetoothSocket} ready to start a secure
+     * outgoing connection to this remote device on given channel.
+     * <p>The remote device will be authenticated and communication on this
+     * socket will be encrypted.
+     * <p> Use this socket only if an authenticated socket link is possible.
+     * Authentication refers to the authentication of the link key to
+     * prevent man-in-the-middle type of attacks.
+     * For example, for Bluetooth 2.1 devices, if any of the devices does not
+     * have an input and output capability or just has the ability to
+     * display a numeric key, a secure socket connection is not possible.
+     * In such a case, use {@link createInsecureRfcommSocket}.
+     * For more details, refer to the Security Model section 5.2 (vol 3) of
+     * Bluetooth Core Specification version 2.1 + EDR.
+     * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
+     * connection.
+     * <p>Valid RFCOMM channels are in range 1 to 30.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param channel RFCOMM channel to connect to
+     * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public BluetoothSocket createRfcommSocket(int channel) throws IOException {
+        if (!isBluetoothEnabled()) {
+            Log.e(TAG, "Bluetooth is not enabled");
+            throw new IOException();
+        }
+        return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, channel,
+                null);
+    }
+
+    /**
+     * Create an L2cap {@link BluetoothSocket} ready to start a secure
+     * outgoing connection to this remote device on given channel.
+     * <p>The remote device will be authenticated and communication on this
+     * socket will be encrypted.
+     * <p> Use this socket only if an authenticated socket link is possible.
+     * Authentication refers to the authentication of the link key to
+     * prevent man-in-the-middle type of attacks.
+     * For example, for Bluetooth 2.1 devices, if any of the devices does not
+     * have an input and output capability or just has the ability to
+     * display a numeric key, a secure socket connection is not possible.
+     * In such a case, use {@link createInsecureRfcommSocket}.
+     * For more details, refer to the Security Model section 5.2 (vol 3) of
+     * Bluetooth Core Specification version 2.1 + EDR.
+     * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
+     * connection.
+     * <p>Valid L2CAP PSM channels are in range 1 to 2^16.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param channel L2cap PSM/channel to connect to
+     * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions
+     * @hide
+     */
+    public BluetoothSocket createL2capSocket(int channel) throws IOException {
+        return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, true, true, this, channel,
+                null);
+    }
+
+    /**
+     * Create an L2cap {@link BluetoothSocket} ready to start an insecure
+     * outgoing connection to this remote device on given channel.
+     * <p>The remote device will be not authenticated and communication on this
+     * socket will not be encrypted.
+     * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
+     * connection.
+     * <p>Valid L2CAP PSM channels are in range 1 to 2^16.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH}
+     *
+     * @param channel L2cap PSM/channel to connect to
+     * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions
+     * @hide
+     */
+    public BluetoothSocket createInsecureL2capSocket(int channel) throws IOException {
+        return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP, -1, false, false, this, channel,
+                null);
+    }
+
+    /**
+     * Create an RFCOMM {@link BluetoothSocket} ready to start a secure
+     * outgoing connection to this remote device using SDP lookup of uuid.
+     * <p>This is designed to be used with {@link
+     * BluetoothAdapter#listenUsingRfcommWithServiceRecord} for peer-peer
+     * Bluetooth applications.
+     * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
+     * connection. This will also perform an SDP lookup of the given uuid to
+     * determine which channel to connect to.
+     * <p>The remote device will be authenticated and communication on this
+     * socket will be encrypted.
+     * <p> Use this socket only if an authenticated socket link is possible.
+     * Authentication refers to the authentication of the link key to
+     * prevent man-in-the-middle type of attacks.
+     * For example, for Bluetooth 2.1 devices, if any of the devices does not
+     * have an input and output capability or just has the ability to
+     * display a numeric key, a secure socket connection is not possible.
+     * In such a case, use {@link #createInsecureRfcommSocketToServiceRecord}.
+     * For more details, refer to the Security Model section 5.2 (vol 3) of
+     * Bluetooth Core Specification version 2.1 + EDR.
+     * <p>Hint: If you are connecting to a Bluetooth serial board then try
+     * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB.
+     * However if you are connecting to an Android peer then please generate
+     * your own unique UUID.
+     *
+     * @param uuid service record uuid to lookup RFCOMM channel
+     * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public BluetoothSocket createRfcommSocketToServiceRecord(UUID uuid) throws IOException {
+        if (!isBluetoothEnabled()) {
+            Log.e(TAG, "Bluetooth is not enabled");
+            throw new IOException();
+        }
+
+        return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, true, true, this, -1,
+                new ParcelUuid(uuid));
+    }
+
+    /**
+     * Create an RFCOMM {@link BluetoothSocket} socket ready to start an insecure
+     * outgoing connection to this remote device using SDP lookup of uuid.
+     * <p> The communication channel will not have an authenticated link key
+     * i.e it will be subject to man-in-the-middle attacks. For Bluetooth 2.1
+     * devices, the link key will be encrypted, as encryption is mandatory.
+     * For legacy devices (pre Bluetooth 2.1 devices) the link key will
+     * be not be encrypted. Use {@link #createRfcommSocketToServiceRecord} if an
+     * encrypted and authenticated communication channel is desired.
+     * <p>This is designed to be used with {@link
+     * BluetoothAdapter#listenUsingInsecureRfcommWithServiceRecord} for peer-peer
+     * Bluetooth applications.
+     * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing
+     * connection. This will also perform an SDP lookup of the given uuid to
+     * determine which channel to connect to.
+     * <p>The remote device will be authenticated and communication on this
+     * socket will be encrypted.
+     * <p>Hint: If you are connecting to a Bluetooth serial board then try
+     * using the well-known SPP UUID 00001101-0000-1000-8000-00805F9B34FB.
+     * However if you are connecting to an Android peer then please generate
+     * your own unique UUID.
+     *
+     * @param uuid service record uuid to lookup RFCOMM channel
+     * @return a RFCOMM BluetoothServerSocket ready for an outgoing connection
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public BluetoothSocket createInsecureRfcommSocketToServiceRecord(UUID uuid) throws IOException {
+        if (!isBluetoothEnabled()) {
+            Log.e(TAG, "Bluetooth is not enabled");
+            throw new IOException();
+        }
+        return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, -1,
+                new ParcelUuid(uuid));
+    }
+
+    /**
+     * Construct an insecure RFCOMM socket ready to start an outgoing
+     * connection.
+     * Call #connect on the returned #BluetoothSocket to begin the connection.
+     * The remote device will not be authenticated and communication on this
+     * socket will not be encrypted.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     *
+     * @param port remote port
+     * @return An RFCOMM BluetoothSocket
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * permissions.
+     * @hide
+     */
+    @UnsupportedAppUsage(publicAlternatives = "Use "
+            + "{@link #createInsecureRfcommSocketToServiceRecord} instead.")
+    public BluetoothSocket createInsecureRfcommSocket(int port) throws IOException {
+        if (!isBluetoothEnabled()) {
+            Log.e(TAG, "Bluetooth is not enabled");
+            throw new IOException();
+        }
+        return new BluetoothSocket(BluetoothSocket.TYPE_RFCOMM, -1, false, false, this, port,
+                null);
+    }
+
+    /**
+     * Construct a SCO socket ready to start an outgoing connection.
+     * Call #connect on the returned #BluetoothSocket to begin the connection.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     *
+     * @return a SCO BluetoothSocket
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public BluetoothSocket createScoSocket() throws IOException {
+        if (!isBluetoothEnabled()) {
+            Log.e(TAG, "Bluetooth is not enabled");
+            throw new IOException();
+        }
+        return new BluetoothSocket(BluetoothSocket.TYPE_SCO, -1, true, true, this, -1, null);
+    }
+
+    /**
+     * Check that a pin is valid and convert to byte array.
+     *
+     * Bluetooth pin's are 1 to 16 bytes of UTF-8 characters.
+     *
+     * @param pin pin as java String
+     * @return the pin code as a UTF-8 byte array, or null if it is an invalid Bluetooth pin.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static byte[] convertPinToBytes(String pin) {
+        if (pin == null) {
+            return null;
+        }
+        byte[] pinBytes;
+        try {
+            pinBytes = pin.getBytes("UTF-8");
+        } catch (UnsupportedEncodingException uee) {
+            Log.e(TAG, "UTF-8 not supported?!?");  // this should not happen
+            return null;
+        }
+        if (pinBytes.length <= 0 || pinBytes.length > 16) {
+            return null;
+        }
+        return pinBytes;
+    }
+
+    /**
+     * Connect to GATT Server hosted by this device. Caller acts as GATT client.
+     * The callback is used to deliver results to Caller, such as connection status as well
+     * as any further GATT client operations.
+     * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
+     * GATT client operations.
+     *
+     * @param callback GATT callback handler that will receive asynchronous callbacks.
+     * @param autoConnect Whether to directly connect to the remote device (false) or to
+     * automatically connect as soon as the remote device becomes available (true).
+     * @throws IllegalArgumentException if callback is null
+     */
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+            BluetoothGattCallback callback) {
+        return (connectGatt(context, autoConnect, callback, TRANSPORT_AUTO));
+    }
+
+    /**
+     * Connect to GATT Server hosted by this device. Caller acts as GATT client.
+     * The callback is used to deliver results to Caller, such as connection status as well
+     * as any further GATT client operations.
+     * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
+     * GATT client operations.
+     *
+     * @param callback GATT callback handler that will receive asynchronous callbacks.
+     * @param autoConnect Whether to directly connect to the remote device (false) or to
+     * automatically connect as soon as the remote device becomes available (true).
+     * @param transport preferred transport for GATT connections to remote dual-mode devices {@link
+     * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link
+     * BluetoothDevice#TRANSPORT_LE}
+     * @throws IllegalArgumentException if callback is null
+     */
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+            BluetoothGattCallback callback, int transport) {
+        return (connectGatt(context, autoConnect, callback, transport, PHY_LE_1M_MASK));
+    }
+
+    /**
+     * Connect to GATT Server hosted by this device. Caller acts as GATT client.
+     * The callback is used to deliver results to Caller, such as connection status as well
+     * as any further GATT client operations.
+     * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
+     * GATT client operations.
+     *
+     * @param callback GATT callback handler that will receive asynchronous callbacks.
+     * @param autoConnect Whether to directly connect to the remote device (false) or to
+     * automatically connect as soon as the remote device becomes available (true).
+     * @param transport preferred transport for GATT connections to remote dual-mode devices {@link
+     * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link
+     * BluetoothDevice#TRANSPORT_LE}
+     * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of {@link
+     * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
+     * BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect if {@code autoConnect}
+     * is set to true.
+     * @throws NullPointerException if callback is null
+     */
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+            BluetoothGattCallback callback, int transport, int phy) {
+        return connectGatt(context, autoConnect, callback, transport, phy, null);
+    }
+
+    /**
+     * Connect to GATT Server hosted by this device. Caller acts as GATT client.
+     * The callback is used to deliver results to Caller, such as connection status as well
+     * as any further GATT client operations.
+     * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
+     * GATT client operations.
+     *
+     * @param callback GATT callback handler that will receive asynchronous callbacks.
+     * @param autoConnect Whether to directly connect to the remote device (false) or to
+     * automatically connect as soon as the remote device becomes available (true).
+     * @param transport preferred transport for GATT connections to remote dual-mode devices {@link
+     * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link
+     * BluetoothDevice#TRANSPORT_LE}
+     * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of {@link
+     * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, an d{@link
+     * BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect if {@code autoConnect}
+     * is set to true.
+     * @param handler The handler to use for the callback. If {@code null}, callbacks will happen on
+     * an un-specified background thread.
+     * @throws NullPointerException if callback is null
+     */
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+            BluetoothGattCallback callback, int transport, int phy,
+            Handler handler) {
+        return connectGatt(context, autoConnect, callback, transport, false, phy, handler);
+    }
+
+    /**
+     * Connect to GATT Server hosted by this device. Caller acts as GATT client.
+     * The callback is used to deliver results to Caller, such as connection status as well
+     * as any further GATT client operations.
+     * The method returns a BluetoothGatt instance. You can use BluetoothGatt to conduct
+     * GATT client operations.
+     *
+     * @param callback GATT callback handler that will receive asynchronous callbacks.
+     * @param autoConnect Whether to directly connect to the remote device (false) or to
+     * automatically connect as soon as the remote device becomes available (true).
+     * @param transport preferred transport for GATT connections to remote dual-mode devices {@link
+     * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link
+     * BluetoothDevice#TRANSPORT_LE}
+     * @param opportunistic Whether this GATT client is opportunistic. An opportunistic GATT client
+     * does not hold a GATT connection. It automatically disconnects when no other GATT connections
+     * are active for the remote device.
+     * @param phy preferred PHY for connections to remote LE device. Bitwise OR of any of {@link
+     * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, an d{@link
+     * BluetoothDevice#PHY_LE_CODED_MASK}. This option does not take effect if {@code autoConnect}
+     * is set to true.
+     * @param handler The handler to use for the callback. If {@code null}, callbacks will happen on
+     * an un-specified background thread.
+     * @return A BluetoothGatt instance. You can use BluetoothGatt to conduct GATT client
+     * operations.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public BluetoothGatt connectGatt(Context context, boolean autoConnect,
+            BluetoothGattCallback callback, int transport,
+            boolean opportunistic, int phy, Handler handler) {
+        if (callback == null) {
+            throw new NullPointerException("callback is null");
+        }
+
+        // TODO(Bluetooth) check whether platform support BLE
+        //     Do the check here or in GattServer?
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        IBluetoothManager managerService = adapter.getBluetoothManager();
+        try {
+            IBluetoothGatt iGatt = managerService.getBluetoothGatt();
+            if (iGatt == null) {
+                // BLE is not supported
+                return null;
+            }
+            BluetoothGatt gatt = new BluetoothGatt(iGatt, this, transport, opportunistic, phy);
+            gatt.connect(autoConnect, callback, handler);
+            return gatt;
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+        return null;
+    }
+
+    /**
+     * Create a Bluetooth L2CAP Connection-oriented Channel (CoC) {@link BluetoothSocket} that can
+     * be used to start a secure outgoing connection to the remote device with the same dynamic
+     * protocol/service multiplexer (PSM) value. The supported Bluetooth transport is LE only.
+     * <p>This is designed to be used with {@link BluetoothAdapter#listenUsingL2capChannel()} for
+     * peer-peer Bluetooth applications.
+     * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing connection.
+     * <p>Application using this API is responsible for obtaining PSM value from remote device.
+     * <p>The remote device will be authenticated and communication on this socket will be
+     * encrypted.
+     * <p> Use this socket if an authenticated socket link is possible. Authentication refers
+     * to the authentication of the link key to prevent man-in-the-middle type of attacks.
+     *
+     * @param psm dynamic PSM value from remote device
+     * @return a CoC #BluetoothSocket ready for an outgoing connection
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @NonNull BluetoothSocket createL2capChannel(int psm) throws IOException {
+        if (!isBluetoothEnabled()) {
+            Log.e(TAG, "createL2capChannel: Bluetooth is not enabled");
+            throw new IOException();
+        }
+        if (DBG) Log.d(TAG, "createL2capChannel: psm=" + psm);
+        return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP_LE, -1, true, true, this, psm,
+                null);
+    }
+
+    /**
+     * Create a Bluetooth L2CAP Connection-oriented Channel (CoC) {@link BluetoothSocket} that can
+     * be used to start a secure outgoing connection to the remote device with the same dynamic
+     * protocol/service multiplexer (PSM) value. The supported Bluetooth transport is LE only.
+     * <p>This is designed to be used with {@link
+     * BluetoothAdapter#listenUsingInsecureL2capChannel()} for peer-peer Bluetooth applications.
+     * <p>Use {@link BluetoothSocket#connect} to initiate the outgoing connection.
+     * <p>Application using this API is responsible for obtaining PSM value from remote device.
+     * <p> The communication channel may not have an authenticated link key, i.e. it may be subject
+     * to man-in-the-middle attacks. Use {@link #createL2capChannel(int)} if an encrypted and
+     * authenticated communication channel is possible.
+     *
+     * @param psm dynamic PSM value from remote device
+     * @return a CoC #BluetoothSocket ready for an outgoing connection
+     * @throws IOException on error, for example Bluetooth not available, or insufficient
+     * permissions
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @NonNull BluetoothSocket createInsecureL2capChannel(int psm) throws IOException {
+        if (!isBluetoothEnabled()) {
+            Log.e(TAG, "createInsecureL2capChannel: Bluetooth is not enabled");
+            throw new IOException();
+        }
+        if (DBG) {
+            Log.d(TAG, "createInsecureL2capChannel: psm=" + psm);
+        }
+        return new BluetoothSocket(BluetoothSocket.TYPE_L2CAP_LE, -1, false, false, this, psm,
+                null);
+    }
+
+    /**
+     * Set a keyed metadata of this {@link BluetoothDevice} to a
+     * {@link String} value.
+     * Only bonded devices's metadata will be persisted across Bluetooth
+     * restart.
+     * Metadata will be removed when the device's bond state is moved to
+     * {@link #BOND_NONE}.
+     *
+     * @param key must be within the list of BluetoothDevice.METADATA_*
+     * @param value a byte array data to set for key. Must be less than
+     * {@link BluetoothAdapter#METADATA_MAX_LENGTH} characters in length
+     * @return true on success, false on error
+     * @hide
+    */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setMetadata(int key, @NonNull byte[] value) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "Bluetooth is not enabled. Cannot set metadata");
+            return false;
+        }
+        if (value.length > METADATA_MAX_LENGTH) {
+            throw new IllegalArgumentException("value length is " + value.length
+                    + ", should not over " + METADATA_MAX_LENGTH);
+        }
+        try {
+            return service.setMetadata(this, key, value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "setMetadata fail", e);
+            return false;
+        }
+    }
+
+    /**
+     * Get a keyed metadata for this {@link BluetoothDevice} as {@link String}
+     *
+     * @param key must be within the list of BluetoothDevice.METADATA_*
+     * @return Metadata of the key as byte array, null on error or not found
+     * @hide
+     */
+    @SystemApi
+    @Nullable
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public byte[] getMetadata(int key) {
+        final IBluetooth service = sService;
+        if (service == null) {
+            Log.e(TAG, "Bluetooth is not enabled. Cannot get metadata");
+            return null;
+        }
+        try {
+            return service.getMetadata(this, key);
+        } catch (RemoteException e) {
+            Log.e(TAG, "getMetadata fail", e);
+            return null;
+        }
+    }
+}
diff --git a/android/bluetooth/BluetoothDevicePicker.java b/android/bluetooth/BluetoothDevicePicker.java
new file mode 100644
index 0000000..09b0a80
--- /dev/null
+++ b/android/bluetooth/BluetoothDevicePicker.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2009 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.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+
+/**
+ * A helper to show a system "Device Picker" activity to the user.
+ *
+ * @hide
+ */
+public interface BluetoothDevicePicker {
+    public static final String EXTRA_NEED_AUTH =
+            "android.bluetooth.devicepicker.extra.NEED_AUTH";
+    public static final String EXTRA_FILTER_TYPE =
+            "android.bluetooth.devicepicker.extra.FILTER_TYPE";
+    public static final String EXTRA_LAUNCH_PACKAGE =
+            "android.bluetooth.devicepicker.extra.LAUNCH_PACKAGE";
+    public static final String EXTRA_LAUNCH_CLASS =
+            "android.bluetooth.devicepicker.extra.DEVICE_PICKER_LAUNCH_CLASS";
+
+    /**
+     * Broadcast when one BT device is selected from BT device picker screen.
+     * Selected {@link BluetoothDevice} is returned in extra data named
+     * {@link BluetoothDevice#EXTRA_DEVICE}.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_DEVICE_SELECTED =
+            "android.bluetooth.devicepicker.action.DEVICE_SELECTED";
+
+    /**
+     * Broadcast when someone want to select one BT device from devices list.
+     * This intent contains below extra data:
+     * - {@link #EXTRA_NEED_AUTH} (boolean): if need authentication
+     * - {@link #EXTRA_FILTER_TYPE} (int): what kinds of device should be
+     * listed
+     * - {@link #EXTRA_LAUNCH_PACKAGE} (string): where(which package) this
+     * intent come from
+     * - {@link #EXTRA_LAUNCH_CLASS} (string): where(which class) this intent
+     * come from
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_LAUNCH =
+            "android.bluetooth.devicepicker.action.LAUNCH";
+
+    /** Ask device picker to show all kinds of BT devices */
+    public static final int FILTER_TYPE_ALL = 0;
+    /** Ask device picker to show BT devices that support AUDIO profiles */
+    public static final int FILTER_TYPE_AUDIO = 1;
+    /** Ask device picker to show BT devices that support Object Transfer */
+    public static final int FILTER_TYPE_TRANSFER = 2;
+    /**
+     * Ask device picker to show BT devices that support
+     * Personal Area Networking User (PANU) profile
+     */
+    public static final int FILTER_TYPE_PANU = 3;
+    /** Ask device picker to show BT devices that support Network Access Point (NAP) profile */
+    public static final int FILTER_TYPE_NAP = 4;
+}
diff --git a/android/bluetooth/BluetoothGatt.java b/android/bluetooth/BluetoothGatt.java
new file mode 100644
index 0000000..f877f04
--- /dev/null
+++ b/android/bluetooth/BluetoothGatt.java
@@ -0,0 +1,1591 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+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>To connect to a remote peripheral device, create a {@link BluetoothGattCallback}
+ * and call {@link BluetoothDevice#connectGatt} to get a instance of this class.
+ * GATT capable devices can be discovered using the Bluetooth device discovery or BLE
+ * scan process.
+ */
+public final class BluetoothGatt implements BluetoothProfile {
+    private static final String TAG = "BluetoothGatt";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    @UnsupportedAppUsage
+    private IBluetoothGatt mService;
+    @UnsupportedAppUsage
+    private volatile BluetoothGattCallback mCallback;
+    private Handler mHandler;
+    @UnsupportedAppUsage
+    private int mClientIf;
+    private BluetoothDevice mDevice;
+    @UnsupportedAppUsage
+    private boolean mAutoConnect;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private int mAuthRetryState;
+    private int mConnState;
+    private final Object mStateLock = new Object();
+    private final Object mDeviceBusyLock = new Object();
+    @UnsupportedAppUsage
+    private Boolean mDeviceBusy = false;
+    @UnsupportedAppUsage
+    private int mTransport;
+    private int mPhy;
+    private boolean mOpportunistic;
+
+    private static final int AUTH_RETRY_STATE_IDLE = 0;
+    private static final int AUTH_RETRY_STATE_NO_MITM = 1;
+    private static final int AUTH_RETRY_STATE_MITM = 2;
+
+    private static final int CONN_STATE_IDLE = 0;
+    private static final int CONN_STATE_CONNECTING = 1;
+    private static final int CONN_STATE_CONNECTED = 2;
+    private static final int CONN_STATE_DISCONNECTING = 3;
+    private static final int CONN_STATE_CLOSED = 4;
+
+    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;
+
+    /** A remote device connection is congested. */
+    public static final int GATT_CONNECTION_CONGESTED = 0x8f;
+
+    /** A GATT operation failed, errors other than the above */
+    public static final int GATT_FAILURE = 0x101;
+
+    /**
+     * Connection parameter update - Use the connection parameters recommended by the
+     * Bluetooth SIG. This is the default value if no connection parameter update
+     * is requested.
+     */
+    public static final int CONNECTION_PRIORITY_BALANCED = 0;
+
+    /**
+     * Connection parameter update - Request a high priority, low latency connection.
+     * An application should only request high priority connection parameters to transfer large
+     * amounts of data over LE quickly. Once the transfer is complete, the application should
+     * request {@link BluetoothGatt#CONNECTION_PRIORITY_BALANCED} connection parameters to reduce
+     * energy use.
+     */
+    public static final int CONNECTION_PRIORITY_HIGH = 1;
+
+    /** Connection parameter update - Request low power, reduced data rate connection parameters. */
+    public static final int CONNECTION_PRIORITY_LOW_POWER = 2;
+
+    /**
+     * 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 GATT callbacks. Overrides the default BluetoothGattCallback implementation.
+     */
+    private final IBluetoothGattCallback mBluetoothGattCallback =
+            new IBluetoothGattCallback.Stub() {
+                /**
+                 * Application interface registered - app is ready to go
+                 * @hide
+                 */
+                @Override
+                public void onClientRegistered(int status, int clientIf) {
+                    if (DBG) {
+                        Log.d(TAG, "onClientRegistered() - status=" + status
+                                + " clientIf=" + clientIf);
+                    }
+                    if (VDBG) {
+                        synchronized (mStateLock) {
+                            if (mConnState != CONN_STATE_CONNECTING) {
+                                Log.e(TAG, "Bad connection state: " + mConnState);
+                            }
+                        }
+                    }
+                    mClientIf = clientIf;
+                    if (status != GATT_SUCCESS) {
+                        runOrQueueCallback(new Runnable() {
+                            @Override
+                            public void run() {
+                                final BluetoothGattCallback callback = mCallback;
+                                if (callback != null) {
+                                    callback.onConnectionStateChange(BluetoothGatt.this,
+                                            GATT_FAILURE,
+                                            BluetoothProfile.STATE_DISCONNECTED);
+                                }
+                            }
+                        });
+
+                        synchronized (mStateLock) {
+                            mConnState = CONN_STATE_IDLE;
+                        }
+                        return;
+                    }
+                    try {
+                        mService.clientConnect(mClientIf, mDevice.getAddress(),
+                                !mAutoConnect, mTransport, mOpportunistic,
+                                mPhy); // autoConnect is inverse of "isDirect"
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "", e);
+                    }
+                }
+
+                /**
+                 * Phy update callback
+                 * @hide
+                 */
+                @Override
+                public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) {
+                    if (DBG) {
+                        Log.d(TAG, "onPhyUpdate() - status=" + status
+                                + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy);
+                    }
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onPhyUpdate(BluetoothGatt.this, txPhy, rxPhy, status);
+                            }
+                        }
+                    });
+                }
+
+                /**
+                 * Phy read callback
+                 * @hide
+                 */
+                @Override
+                public void onPhyRead(String address, int txPhy, int rxPhy, int status) {
+                    if (DBG) {
+                        Log.d(TAG, "onPhyRead() - status=" + status
+                                + " address=" + address + " txPhy=" + txPhy + " rxPhy=" + rxPhy);
+                    }
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onPhyRead(BluetoothGatt.this, txPhy, rxPhy, status);
+                            }
+                        }
+                    });
+                }
+
+                /**
+                 * Client connection state changed
+                 * @hide
+                 */
+                @Override
+                public void onClientConnectionState(int status, int clientIf,
+                        boolean connected, String address) {
+                    if (DBG) {
+                        Log.d(TAG, "onClientConnectionState() - status=" + status
+                                + " clientIf=" + clientIf + " device=" + address);
+                    }
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+                    int profileState = connected ? BluetoothProfile.STATE_CONNECTED :
+                            BluetoothProfile.STATE_DISCONNECTED;
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onConnectionStateChange(BluetoothGatt.this, status,
+                                        profileState);
+                            }
+                        }
+                    });
+
+                    synchronized (mStateLock) {
+                        if (connected) {
+                            mConnState = CONN_STATE_CONNECTED;
+                        } else {
+                            mConnState = CONN_STATE_IDLE;
+                        }
+                    }
+
+                    synchronized (mDeviceBusyLock) {
+                        mDeviceBusy = false;
+                    }
+                }
+
+                /**
+                 * 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
+                 */
+                @Override
+                public void onSearchComplete(String address, List<BluetoothGattService> services,
+                        int status) {
+                    if (DBG) {
+                        Log.d(TAG,
+                                "onSearchComplete() = Device=" + address + " Status=" + status);
+                    }
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    for (BluetoothGattService s : services) {
+                        //services we receive don't have device set properly.
+                        s.setDevice(mDevice);
+                    }
+
+                    mServices.addAll(services);
+
+                    // Fix references to included services, as they doesn't point to right objects.
+                    for (BluetoothGattService fixedService : mServices) {
+                        ArrayList<BluetoothGattService> includedServices =
+                                new ArrayList(fixedService.getIncludedServices());
+                        fixedService.getIncludedServices().clear();
+
+                        for (BluetoothGattService brokenRef : includedServices) {
+                            BluetoothGattService includedService = getService(mDevice,
+                                    brokenRef.getUuid(), brokenRef.getInstanceId());
+                            if (includedService != null) {
+                                fixedService.addIncludedService(includedService);
+                            } else {
+                                Log.e(TAG, "Broken GATT database: can't find included service.");
+                            }
+                        }
+                    }
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onServicesDiscovered(BluetoothGatt.this, status);
+                            }
+                        }
+                    });
+                }
+
+                /**
+                 * Remote characteristic has been read.
+                 * Updates the internal value.
+                 * @hide
+                 */
+                @Override
+                public void onCharacteristicRead(String address, int status, int handle,
+                        byte[] value) {
+                    if (VDBG) {
+                        Log.d(TAG, "onCharacteristicRead() - Device=" + address
+                                + " handle=" + handle + " Status=" + status);
+                    }
+
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    synchronized (mDeviceBusyLock) {
+                        mDeviceBusy = false;
+                    }
+
+                    if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                            || status == GATT_INSUFFICIENT_ENCRYPTION)
+                            && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
+                        try {
+                            final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
+                                    ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
+                            mService.readCharacteristic(mClientIf, address, handle, authReq);
+                            mAuthRetryState++;
+                            return;
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "", e);
+                        }
+                    }
+
+                    mAuthRetryState = AUTH_RETRY_STATE_IDLE;
+
+                    BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice,
+                            handle);
+                    if (characteristic == null) {
+                        Log.w(TAG, "onCharacteristicRead() failed to find characteristic!");
+                        return;
+                    }
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                if (status == 0) characteristic.setValue(value);
+                                callback.onCharacteristicRead(BluetoothGatt.this, characteristic,
+                                        status);
+                            }
+                        }
+                    });
+                }
+
+                /**
+                 * Characteristic has been written to the remote device.
+                 * Let the app know how we did...
+                 * @hide
+                 */
+                @Override
+                public void onCharacteristicWrite(String address, int status, int handle) {
+                    if (VDBG) {
+                        Log.d(TAG, "onCharacteristicWrite() - Device=" + address
+                                + " handle=" + handle + " Status=" + status);
+                    }
+
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    synchronized (mDeviceBusyLock) {
+                        mDeviceBusy = false;
+                    }
+
+                    BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice,
+                            handle);
+                    if (characteristic == null) return;
+
+                    if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                            || status == GATT_INSUFFICIENT_ENCRYPTION)
+                            && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
+                        try {
+                            final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
+                                    ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
+                            mService.writeCharacteristic(mClientIf, address, handle,
+                                    characteristic.getWriteType(), authReq,
+                                    characteristic.getValue());
+                            mAuthRetryState++;
+                            return;
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "", e);
+                        }
+                    }
+
+                    mAuthRetryState = AUTH_RETRY_STATE_IDLE;
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onCharacteristicWrite(BluetoothGatt.this, characteristic,
+                                        status);
+                            }
+                        }
+                    });
+                }
+
+                /**
+                 * Remote characteristic has been updated.
+                 * Updates the internal value.
+                 * @hide
+                 */
+                @Override
+                public void onNotify(String address, int handle, byte[] value) {
+                    if (VDBG) Log.d(TAG, "onNotify() - Device=" + address + " handle=" + handle);
+
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    BluetoothGattCharacteristic characteristic = getCharacteristicById(mDevice,
+                            handle);
+                    if (characteristic == null) return;
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                characteristic.setValue(value);
+                                callback.onCharacteristicChanged(BluetoothGatt.this,
+                                        characteristic);
+                            }
+                        }
+                    });
+                }
+
+                /**
+                 * Descriptor has been read.
+                 * @hide
+                 */
+                @Override
+                public void onDescriptorRead(String address, int status, int handle, byte[] value) {
+                    if (VDBG) {
+                        Log.d(TAG,
+                                "onDescriptorRead() - Device=" + address + " handle=" + handle);
+                    }
+
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    synchronized (mDeviceBusyLock) {
+                        mDeviceBusy = false;
+                    }
+
+                    BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
+                    if (descriptor == null) return;
+
+
+                    if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                            || status == GATT_INSUFFICIENT_ENCRYPTION)
+                            && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
+                        try {
+                            final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
+                                    ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
+                            mService.readDescriptor(mClientIf, address, handle, authReq);
+                            mAuthRetryState++;
+                            return;
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "", e);
+                        }
+                    }
+
+                    mAuthRetryState = AUTH_RETRY_STATE_IDLE;
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                if (status == 0) descriptor.setValue(value);
+                                callback.onDescriptorRead(BluetoothGatt.this, descriptor, status);
+                            }
+                        }
+                    });
+                }
+
+                /**
+                 * Descriptor write operation complete.
+                 * @hide
+                 */
+                @Override
+                public void onDescriptorWrite(String address, int status, int handle) {
+                    if (VDBG) {
+                        Log.d(TAG,
+                                "onDescriptorWrite() - Device=" + address + " handle=" + handle);
+                    }
+
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    synchronized (mDeviceBusyLock) {
+                        mDeviceBusy = false;
+                    }
+
+                    BluetoothGattDescriptor descriptor = getDescriptorById(mDevice, handle);
+                    if (descriptor == null) return;
+
+                    if ((status == GATT_INSUFFICIENT_AUTHENTICATION
+                            || status == GATT_INSUFFICIENT_ENCRYPTION)
+                            && (mAuthRetryState != AUTH_RETRY_STATE_MITM)) {
+                        try {
+                            final int authReq = (mAuthRetryState == AUTH_RETRY_STATE_IDLE)
+                                    ? AUTHENTICATION_NO_MITM : AUTHENTICATION_MITM;
+                            mService.writeDescriptor(mClientIf, address, handle,
+                                    authReq, descriptor.getValue());
+                            mAuthRetryState++;
+                            return;
+                        } catch (RemoteException e) {
+                            Log.e(TAG, "", e);
+                        }
+                    }
+
+                    mAuthRetryState = AUTH_RETRY_STATE_IDLE;
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onDescriptorWrite(BluetoothGatt.this, descriptor, status);
+                            }
+                        }
+                    });
+                }
+
+                /**
+                 * Prepared write transaction completed (or aborted)
+                 * @hide
+                 */
+                @Override
+                public void onExecuteWrite(String address, int status) {
+                    if (VDBG) {
+                        Log.d(TAG, "onExecuteWrite() - Device=" + address
+                                + " status=" + status);
+                    }
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    synchronized (mDeviceBusyLock) {
+                        mDeviceBusy = false;
+                    }
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onReliableWriteCompleted(BluetoothGatt.this, status);
+                            }
+                        }
+                    });
+                }
+
+                /**
+                 * Remote device RSSI has been read
+                 * @hide
+                 */
+                @Override
+                public void onReadRemoteRssi(String address, int rssi, int status) {
+                    if (VDBG) {
+                        Log.d(TAG, "onReadRemoteRssi() - Device=" + address
+                                + " rssi=" + rssi + " status=" + status);
+                    }
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onReadRemoteRssi(BluetoothGatt.this, rssi, status);
+                            }
+                        }
+                    });
+                }
+
+                /**
+                 * Callback invoked when the MTU for a given connection changes
+                 * @hide
+                 */
+                @Override
+                public void onConfigureMTU(String address, int mtu, int status) {
+                    if (DBG) {
+                        Log.d(TAG, "onConfigureMTU() - Device=" + address
+                                + " mtu=" + mtu + " status=" + status);
+                    }
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onMtuChanged(BluetoothGatt.this, mtu, status);
+                            }
+                        }
+                    });
+                }
+
+                /**
+                 * Callback invoked when the given connection is updated
+                 * @hide
+                 */
+                @Override
+                public void onConnectionUpdated(String address, int interval, int latency,
+                        int timeout, int status) {
+                    if (DBG) {
+                        Log.d(TAG, "onConnectionUpdated() - Device=" + address
+                                + " interval=" + interval + " latency=" + latency
+                                + " timeout=" + timeout + " status=" + status);
+                    }
+                    if (!address.equals(mDevice.getAddress())) {
+                        return;
+                    }
+
+                    runOrQueueCallback(new Runnable() {
+                        @Override
+                        public void run() {
+                            final BluetoothGattCallback callback = mCallback;
+                            if (callback != null) {
+                                callback.onConnectionUpdated(BluetoothGatt.this, interval, latency,
+                                        timeout, status);
+                            }
+                        }
+                    });
+                }
+            };
+
+    /*package*/ BluetoothGatt(IBluetoothGatt iGatt, BluetoothDevice device,
+            int transport, boolean opportunistic, int phy) {
+        mService = iGatt;
+        mDevice = device;
+        mTransport = transport;
+        mPhy = phy;
+        mOpportunistic = opportunistic;
+        mServices = new ArrayList<BluetoothGattService>();
+
+        mConnState = CONN_STATE_IDLE;
+        mAuthRetryState = AUTH_RETRY_STATE_IDLE;
+    }
+
+    /**
+     * Close this Bluetooth GATT client.
+     *
+     * Application should call this method as early as possible after it is done with
+     * this GATT client.
+     */
+    public void close() {
+        if (DBG) Log.d(TAG, "close()");
+
+        unregisterApp();
+        mConnState = CONN_STATE_CLOSED;
+        mAuthRetryState = AUTH_RETRY_STATE_IDLE;
+    }
+
+    /**
+     * Returns a service by UUID, instance and type.
+     *
+     * @hide
+     */
+    /*package*/ BluetoothGattService getService(BluetoothDevice device, UUID uuid,
+            int instanceId) {
+        for (BluetoothGattService svc : mServices) {
+            if (svc.getDevice().equals(device)
+                    && svc.getInstanceId() == instanceId
+                    && svc.getUuid().equals(uuid)) {
+                return svc;
+            }
+        }
+        return null;
+    }
+
+
+    /**
+     * Returns a characteristic with id equal to instanceId.
+     *
+     * @hide
+     */
+    /*package*/ BluetoothGattCharacteristic getCharacteristicById(BluetoothDevice device,
+            int instanceId) {
+        for (BluetoothGattService svc : mServices) {
+            for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
+                if (charac.getInstanceId() == instanceId) {
+                    return charac;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a descriptor with id equal to instanceId.
+     *
+     * @hide
+     */
+    /*package*/ BluetoothGattDescriptor getDescriptorById(BluetoothDevice device, int instanceId) {
+        for (BluetoothGattService svc : mServices) {
+            for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
+                for (BluetoothGattDescriptor desc : charac.getDescriptors()) {
+                    if (desc.getInstanceId() == instanceId) {
+                        return desc;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Queue the runnable on a {@link Handler} provided by the user, or execute the runnable
+     * immediately if no Handler was provided.
+     */
+    private void runOrQueueCallback(final Runnable cb) {
+        if (mHandler == null) {
+            try {
+                cb.run();
+            } catch (Exception ex) {
+                Log.w(TAG, "Unhandled exception in callback", ex);
+            }
+        } else {
+            mHandler.post(cb);
+        }
+    }
+
+    /**
+     * Register an application callback to start using GATT.
+     *
+     * <p>This is an asynchronous call. The callback {@link BluetoothGattCallback#onAppRegistered}
+     * 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 If true, the callback will be called to notify success or failure, false on immediate
+     * error
+     */
+    private boolean registerApp(BluetoothGattCallback callback, Handler handler) {
+        if (DBG) Log.d(TAG, "registerApp()");
+        if (mService == null) return false;
+
+        mCallback = callback;
+        mHandler = handler;
+        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.
+     */
+    @UnsupportedAppUsage
+    private 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);
+        }
+    }
+
+    /**
+     * 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 parameter 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 true.
+     *
+     * <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
+     */
+    @UnsupportedAppUsage
+    /*package*/ boolean connect(Boolean autoConnect, BluetoothGattCallback callback,
+            Handler handler) {
+        if (DBG) {
+            Log.d(TAG,
+                    "connect() - device: " + mDevice.getAddress() + ", auto: " + autoConnect);
+        }
+        synchronized (mStateLock) {
+            if (mConnState != CONN_STATE_IDLE) {
+                throw new IllegalStateException("Not idle");
+            }
+            mConnState = CONN_STATE_CONNECTING;
+        }
+
+        mAutoConnect = autoConnect;
+
+        if (!registerApp(callback, handler)) {
+            synchronized (mStateLock) {
+                mConnState = CONN_STATE_IDLE;
+            }
+            Log.e(TAG, "Failed to register callback");
+            return false;
+        }
+
+        // The connection will continue in the onClientRegistered callback
+        return true;
+    }
+
+    /**
+     * Disconnects an established connection, or cancels a connection attempt
+     * currently in progress.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     */
+    public void disconnect() {
+        if (DBG) Log.d(TAG, "cancelOpen() - device: " + mDevice.getAddress());
+        if (mService == null || mClientIf == 0) return;
+
+        try {
+            mService.clientDisconnect(mClientIf, mDevice.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    /**
+     * Connect back to remote device.
+     *
+     * <p>This method is used to re-connect to a remote device after the
+     * connection has been dropped. If the device is not in range, the
+     * re-connection will be triggered once the device is back in range.
+     *
+     * @return true, if the connection attempt was initiated successfully
+     */
+    public boolean connect() {
+        try {
+            mService.clientConnect(mClientIf, mDevice.getAddress(), false, mTransport,
+                    mOpportunistic, mPhy); // autoConnect is inverse of "isDirect"
+            return true;
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+    }
+
+    /**
+     * Set the preferred connection PHY for this app. Please note that this is just a
+     * recommendation, whether the PHY change will happen depends on other applications preferences,
+     * local and remote controller capabilities. Controller can override these settings.
+     * <p>
+     * {@link BluetoothGattCallback#onPhyUpdate} will be triggered as a result of this call, even
+     * if no PHY change happens. It is also triggered when remote device updates the PHY.
+     *
+     * @param txPhy preferred transmitter PHY. Bitwise OR of any of {@link
+     * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
+     * BluetoothDevice#PHY_LE_CODED_MASK}.
+     * @param rxPhy preferred receiver PHY. Bitwise OR of any of {@link
+     * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
+     * BluetoothDevice#PHY_LE_CODED_MASK}.
+     * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one
+     * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or
+     * {@link BluetoothDevice#PHY_OPTION_S8}
+     */
+    public void setPreferredPhy(int txPhy, int rxPhy, int phyOptions) {
+        try {
+            mService.clientSetPreferredPhy(mClientIf, mDevice.getAddress(), txPhy, rxPhy,
+                    phyOptions);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    /**
+     * Read the current transmitter PHY and receiver PHY of the connection. The values are returned
+     * in {@link BluetoothGattCallback#onPhyRead}
+     */
+    public void readPhy() {
+        try {
+            mService.clientReadPhy(mClientIf, mDevice.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    /**
+     * Return the remote bluetooth device this GATT client targets to
+     *
+     * @return remote bluetooth device
+     */
+    public BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * 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.
+     *
+     * @return true, if the remote service discovery has been started
+     */
+    public boolean discoverServices() {
+        if (DBG) Log.d(TAG, "discoverServices() - device: " + mDevice.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        mServices.clear();
+
+        try {
+            mService.discoverServices(mClientIf, mDevice.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Discovers a service by UUID. This is exposed only for passing PTS tests.
+     * It should never be used by real applications. The service is not searched
+     * for characteristics and descriptors, or returned in any callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return true, if the remote service discovery has been started
+     * @hide
+     */
+    public boolean discoverServiceByUuid(UUID uuid) {
+        if (DBG) Log.d(TAG, "discoverServiceByUuid() - device: " + mDevice.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        mServices.clear();
+
+        try {
+            mService.discoverServiceByUuid(mClientIf, mDevice.getAddress(), new ParcelUuid(uuid));
+        } 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.
+     *
+     * @return List of services on the remote device. Returns an empty list if service discovery has
+     * not yet been performed.
+     */
+    public List<BluetoothGattService> getServices() {
+        List<BluetoothGattService> result =
+                new ArrayList<BluetoothGattService>();
+
+        for (BluetoothGattService service : mServices) {
+            if (service.getDevice().equals(mDevice)) {
+                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 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(UUID uuid) {
+        for (BluetoothGattService service : mServices) {
+            if (service.getDevice().equals(mDevice) && 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 (VDBG) 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;
+
+        synchronized (mDeviceBusyLock) {
+            if (mDeviceBusy) return false;
+            mDeviceBusy = true;
+        }
+
+        try {
+            mService.readCharacteristic(mClientIf, device.getAddress(),
+                    characteristic.getInstanceId(), AUTHENTICATION_NONE);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            mDeviceBusy = false;
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Reads the characteristic using its UUID 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 uuid UUID of characteristic to read from the remote device
+     * @return true, if the read operation was initiated successfully
+     * @hide
+     */
+    public boolean readUsingCharacteristicUuid(UUID uuid, int startHandle, int endHandle) {
+        if (VDBG) Log.d(TAG, "readUsingCharacteristicUuid() - uuid: " + uuid);
+        if (mService == null || mClientIf == 0) return false;
+
+        synchronized (mDeviceBusyLock) {
+            if (mDeviceBusy) return false;
+            mDeviceBusy = true;
+        }
+
+        try {
+            mService.readUsingCharacteristicUuid(mClientIf, mDevice.getAddress(),
+                    new ParcelUuid(uuid), startHandle, endHandle, AUTHENTICATION_NONE);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            mDeviceBusy = false;
+            return false;
+        }
+
+        return true;
+    }
+
+
+    /**
+     * Writes a given characteristic and its 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 (VDBG) Log.d(TAG, "writeCharacteristic() - uuid: " + characteristic.getUuid());
+        if (mService == null || mClientIf == 0 || characteristic.getValue() == null) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        BluetoothDevice device = service.getDevice();
+        if (device == null) return false;
+
+        synchronized (mDeviceBusyLock) {
+            if (mDeviceBusy) return false;
+            mDeviceBusy = true;
+        }
+
+        try {
+            mService.writeCharacteristic(mClientIf, device.getAddress(),
+                    characteristic.getInstanceId(), characteristic.getWriteType(),
+                    AUTHENTICATION_NONE, characteristic.getValue());
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            mDeviceBusy = false;
+            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 (VDBG) 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;
+
+        synchronized (mDeviceBusyLock) {
+            if (mDeviceBusy) return false;
+            mDeviceBusy = true;
+        }
+
+        try {
+            mService.readDescriptor(mClientIf, device.getAddress(),
+                    descriptor.getInstanceId(), AUTHENTICATION_NONE);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            mDeviceBusy = false;
+            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 (VDBG) Log.d(TAG, "writeDescriptor() - uuid: " + descriptor.getUuid());
+        if (mService == null || mClientIf == 0 || descriptor.getValue() == null) 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;
+
+        synchronized (mDeviceBusyLock) {
+            if (mDeviceBusy) return false;
+            mDeviceBusy = true;
+        }
+
+        try {
+            mService.writeDescriptor(mClientIf, device.getAddress(), descriptor.getInstanceId(),
+                    AUTHENTICATION_NONE, descriptor.getValue());
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            mDeviceBusy = false;
+            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 committing any values on the
+     * remote device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return true, if the reliable write transaction has been initiated
+     */
+    public boolean beginReliableWrite() {
+        if (VDBG) Log.d(TAG, "beginReliableWrite() - device: " + mDevice.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.beginReliableWrite(mClientIf, mDevice.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.
+     *
+     * @return true, if the request to execute the transaction has been sent
+     */
+    public boolean executeReliableWrite() {
+        if (VDBG) Log.d(TAG, "executeReliableWrite() - device: " + mDevice.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        synchronized (mDeviceBusyLock) {
+            if (mDeviceBusy) return false;
+            mDeviceBusy = true;
+        }
+
+        try {
+            mService.endReliableWrite(mClientIf, mDevice.getAddress(), true);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            mDeviceBusy = false;
+            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.
+     */
+    public void abortReliableWrite() {
+        if (VDBG) Log.d(TAG, "abortReliableWrite() - device: " + mDevice.getAddress());
+        if (mService == null || mClientIf == 0) return;
+
+        try {
+            mService.endReliableWrite(mClientIf, mDevice.getAddress(), false);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    /**
+     * @deprecated Use {@link #abortReliableWrite()}
+     */
+    @Deprecated
+    public void abortReliableWrite(BluetoothDevice mDevice) {
+        abortReliableWrite();
+    }
+
+    /**
+     * 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(),
+                    characteristic.getInstanceId(), 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
+     */
+    @UnsupportedAppUsage
+    public boolean refresh() {
+        if (DBG) Log.d(TAG, "refresh() - device: " + mDevice.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.refreshDevice(mClientIf, mDevice.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.
+     *
+     * @return true, if the RSSI value has been requested successfully
+     */
+    public boolean readRemoteRssi() {
+        if (DBG) Log.d(TAG, "readRssi() - device: " + mDevice.getAddress());
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.readRemoteRssi(mClientIf, mDevice.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Request an MTU size used for a given connection.
+     *
+     * <p>When performing a write request operation (write without response),
+     * the data sent is truncated to the MTU size. This function may be used
+     * to request a larger MTU size to be able to send more data at once.
+     *
+     * <p>A {@link BluetoothGattCallback#onMtuChanged} callback will indicate
+     * whether this operation was successful.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return true, if the new MTU value has been requested successfully
+     */
+    public boolean requestMtu(int mtu) {
+        if (DBG) {
+            Log.d(TAG, "configureMTU() - device: " + mDevice.getAddress()
+                    + " mtu: " + mtu);
+        }
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.configureMTU(mClientIf, mDevice.getAddress(), mtu);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Request a connection parameter update.
+     *
+     * <p>This function will send a connection parameter update request to the
+     * remote device.
+     *
+     * @param connectionPriority Request a specific connection priority. Must be one of {@link
+     * BluetoothGatt#CONNECTION_PRIORITY_BALANCED}, {@link BluetoothGatt#CONNECTION_PRIORITY_HIGH}
+     * or {@link BluetoothGatt#CONNECTION_PRIORITY_LOW_POWER}.
+     * @throws IllegalArgumentException If the parameters are outside of their specified range.
+     */
+    public boolean requestConnectionPriority(int connectionPriority) {
+        if (connectionPriority < CONNECTION_PRIORITY_BALANCED
+                || connectionPriority > CONNECTION_PRIORITY_LOW_POWER) {
+            throw new IllegalArgumentException("connectionPriority not within valid range");
+        }
+
+        if (DBG) Log.d(TAG, "requestConnectionPriority() - params: " + connectionPriority);
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.connectionParameterUpdate(mClientIf, mDevice.getAddress(), connectionPriority);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Request an LE connection parameter update.
+     *
+     * <p>This function will send an LE connection parameters update request to the remote device.
+     *
+     * @return true, if the request is send to the Bluetooth stack.
+     * @hide
+     */
+    public boolean requestLeConnectionUpdate(int minConnectionInterval, int maxConnectionInterval,
+                                             int slaveLatency, int supervisionTimeout,
+                                             int minConnectionEventLen, int maxConnectionEventLen) {
+        if (DBG) {
+            Log.d(TAG, "requestLeConnectionUpdate() - min=(" + minConnectionInterval
+                        + ")" + (1.25 * minConnectionInterval)
+                        + "msec, max=(" + maxConnectionInterval + ")"
+                        + (1.25 * maxConnectionInterval) + "msec, latency=" + slaveLatency
+                        + ", timeout=" + supervisionTimeout + "msec" + ", min_ce="
+                        + minConnectionEventLen + ", max_ce=" + maxConnectionEventLen);
+        }
+        if (mService == null || mClientIf == 0) return false;
+
+        try {
+            mService.leConnectionUpdate(mClientIf, mDevice.getAddress(),
+                                        minConnectionInterval, maxConnectionInterval,
+                                        slaveLatency, supervisionTimeout,
+                                        minConnectionEventLen, maxConnectionEventLen);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+     * with {@link BluetoothProfile#GATT} as argument
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
+    }
+
+    /**
+     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+     * with {@link BluetoothProfile#GATT} as argument
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        throw new UnsupportedOperationException(
+                "Use BluetoothManager#getConnectedDevices instead.");
+    }
+
+    /**
+     * Not supported - please use
+     * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
+     * with {@link BluetoothProfile#GATT} as first argument
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        throw new UnsupportedOperationException(
+                "Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
+    }
+}
diff --git a/android/bluetooth/BluetoothGattCallback.java b/android/bluetooth/BluetoothGattCallback.java
new file mode 100644
index 0000000..cf82a33
--- /dev/null
+++ b/android/bluetooth/BluetoothGattCallback.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * This abstract class is used to implement {@link BluetoothGatt} callbacks.
+ */
+public abstract class BluetoothGattCallback {
+
+    /**
+     * Callback triggered as result of {@link BluetoothGatt#setPreferredPhy}, or as a result of
+     * remote device changing the PHY.
+     *
+     * @param gatt GATT client
+     * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link
+     * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}.
+     * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link
+     * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}.
+     * @param status Status of the PHY update operation. {@link BluetoothGatt#GATT_SUCCESS} if the
+     * operation succeeds.
+     */
+    public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
+    }
+
+    /**
+     * Callback triggered as result of {@link BluetoothGatt#readPhy}
+     *
+     * @param gatt GATT client
+     * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link
+     * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}.
+     * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link
+     * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}.
+     * @param status Status of the PHY read operation. {@link BluetoothGatt#GATT_SUCCESS} if the
+     * operation succeeds.
+     */
+    public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {
+    }
+
+    /**
+     * Callback indicating when GATT client has connected/disconnected to/from a remote
+     * GATT server.
+     *
+     * @param gatt GATT client
+     * @param status Status of the connect or disconnect operation. {@link
+     * BluetoothGatt#GATT_SUCCESS} if the operation succeeds.
+     * @param newState Returns the new connection state. Can be one of {@link
+     * BluetoothProfile#STATE_DISCONNECTED} or {@link BluetoothProfile#STATE_CONNECTED}
+     */
+    public void onConnectionStateChange(BluetoothGatt gatt, int status,
+            int newState) {
+    }
+
+    /**
+     * Callback invoked when the list of remote services, characteristics and descriptors
+     * for the remote device have been updated, ie new services have been discovered.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#discoverServices}
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the remote device has been explored
+     * successfully.
+     */
+    public void onServicesDiscovered(BluetoothGatt gatt, int status) {
+    }
+
+    /**
+     * Callback reporting the result of a characteristic read operation.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#readCharacteristic}
+     * @param characteristic Characteristic that was read from the associated remote device.
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed
+     * successfully.
+     */
+    public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
+            int status) {
+    }
+
+    /**
+     * Callback indicating the result of a characteristic write operation.
+     *
+     * <p>If this callback is invoked while a reliable write transaction is
+     * in progress, the value of the characteristic represents the value
+     * reported by the remote device. An application should compare this
+     * value to the desired value to be written. If the values don't match,
+     * the application must abort the reliable write transaction.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#writeCharacteristic}
+     * @param characteristic Characteristic that was written to the associated remote device.
+     * @param status The result of the write operation {@link BluetoothGatt#GATT_SUCCESS} if the
+     * operation succeeds.
+     */
+    public void onCharacteristicWrite(BluetoothGatt gatt,
+            BluetoothGattCharacteristic characteristic, int status) {
+    }
+
+    /**
+     * Callback triggered as a result of a remote characteristic notification.
+     *
+     * @param gatt GATT client the characteristic is associated with
+     * @param characteristic Characteristic that has been updated as a result of a remote
+     * notification event.
+     */
+    public void onCharacteristicChanged(BluetoothGatt gatt,
+            BluetoothGattCharacteristic characteristic) {
+    }
+
+    /**
+     * Callback reporting the result of a descriptor read operation.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#readDescriptor}
+     * @param descriptor Descriptor that was read from the associated remote device.
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed
+     * successfully
+     */
+    public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+            int status) {
+    }
+
+    /**
+     * Callback indicating the result of a descriptor write operation.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#writeDescriptor}
+     * @param descriptor Descriptor that was writte to the associated remote device.
+     * @param status The result of the write operation {@link BluetoothGatt#GATT_SUCCESS} if the
+     * operation succeeds.
+     */
+    public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
+            int status) {
+    }
+
+    /**
+     * Callback invoked when a reliable write transaction has been completed.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#executeReliableWrite}
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the reliable write transaction was
+     * executed successfully
+     */
+    public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
+    }
+
+    /**
+     * Callback reporting the RSSI for a remote device connection.
+     *
+     * This callback is triggered in response to the
+     * {@link BluetoothGatt#readRemoteRssi} function.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#readRemoteRssi}
+     * @param rssi The RSSI value for the remote device
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the RSSI was read successfully
+     */
+    public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
+    }
+
+    /**
+     * Callback indicating the MTU for a given device connection has changed.
+     *
+     * This callback is triggered in response to the
+     * {@link BluetoothGatt#requestMtu} function, or in response to a connection
+     * event.
+     *
+     * @param gatt GATT client invoked {@link BluetoothGatt#requestMtu}
+     * @param mtu The new MTU size
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the MTU has been changed successfully
+     */
+    public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
+    }
+
+    /**
+     * Callback indicating the connection parameters were updated.
+     *
+     * @param gatt GATT client involved
+     * @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from
+     * 6 (7.5ms) to 3200 (4000ms).
+     * @param latency Slave latency for the connection in number of connection events. Valid range
+     * is from 0 to 499
+     * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is from 10
+     * (0.1s) to 3200 (32s)
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated
+     * successfully
+     * @hide
+     */
+    public void onConnectionUpdated(BluetoothGatt gatt, int interval, int latency, int timeout,
+            int status) {
+    }
+}
diff --git a/android/bluetooth/BluetoothGattCharacteristic.java b/android/bluetooth/BluetoothGattCharacteristic.java
new file mode 100644
index 0000000..7066f47
--- /dev/null
+++ b/android/bluetooth/BluetoothGattCharacteristic.java
@@ -0,0 +1,777 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth GATT Characteristic
+ *
+ * <p>A GATT characteristic is a basic data element used to construct a GATT service,
+ * {@link BluetoothGattService}. The characteristic contains a value as well as
+ * additional information and optional GATT descriptors, {@link BluetoothGattDescriptor}.
+ */
+public class BluetoothGattCharacteristic implements Parcelable {
+
+    /**
+     * Characteristic proprty: Characteristic is broadcastable.
+     */
+    public static final int PROPERTY_BROADCAST = 0x01;
+
+    /**
+     * Characteristic property: Characteristic is readable.
+     */
+    public static final int PROPERTY_READ = 0x02;
+
+    /**
+     * Characteristic property: Characteristic can be written without response.
+     */
+    public static final int PROPERTY_WRITE_NO_RESPONSE = 0x04;
+
+    /**
+     * Characteristic property: Characteristic can be written.
+     */
+    public static final int PROPERTY_WRITE = 0x08;
+
+    /**
+     * Characteristic property: Characteristic supports notification
+     */
+    public static final int PROPERTY_NOTIFY = 0x10;
+
+    /**
+     * Characteristic property: Characteristic supports indication
+     */
+    public static final int PROPERTY_INDICATE = 0x20;
+
+    /**
+     * Characteristic property: Characteristic supports write with signature
+     */
+    public static final int PROPERTY_SIGNED_WRITE = 0x40;
+
+    /**
+     * Characteristic property: Characteristic has extended properties
+     */
+    public static final int PROPERTY_EXTENDED_PROPS = 0x80;
+
+    /**
+     * Characteristic read permission
+     */
+    public static final int PERMISSION_READ = 0x01;
+
+    /**
+     * Characteristic permission: Allow encrypted read operations
+     */
+    public static final int PERMISSION_READ_ENCRYPTED = 0x02;
+
+    /**
+     * Characteristic permission: Allow reading with man-in-the-middle protection
+     */
+    public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04;
+
+    /**
+     * Characteristic write permission
+     */
+    public static final int PERMISSION_WRITE = 0x10;
+
+    /**
+     * Characteristic permission: Allow encrypted writes
+     */
+    public static final int PERMISSION_WRITE_ENCRYPTED = 0x20;
+
+    /**
+     * Characteristic permission: Allow encrypted writes with man-in-the-middle
+     * protection
+     */
+    public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40;
+
+    /**
+     * Characteristic permission: Allow signed write operations
+     */
+    public static final int PERMISSION_WRITE_SIGNED = 0x80;
+
+    /**
+     * Characteristic permission: Allow signed write operations with
+     * man-in-the-middle protection
+     */
+    public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100;
+
+    /**
+     * Write characteristic, requesting acknoledgement by the remote device
+     */
+    public static final int WRITE_TYPE_DEFAULT = 0x02;
+
+    /**
+     * Write characteristic without requiring a response by the remote device
+     */
+    public static final int WRITE_TYPE_NO_RESPONSE = 0x01;
+
+    /**
+     * Write characteristic including authentication signature
+     */
+    public static final int WRITE_TYPE_SIGNED = 0x04;
+
+    /**
+     * Characteristic value format type uint8
+     */
+    public static final int FORMAT_UINT8 = 0x11;
+
+    /**
+     * Characteristic value format type uint16
+     */
+    public static final int FORMAT_UINT16 = 0x12;
+
+    /**
+     * Characteristic value format type uint32
+     */
+    public static final int FORMAT_UINT32 = 0x14;
+
+    /**
+     * Characteristic value format type sint8
+     */
+    public static final int FORMAT_SINT8 = 0x21;
+
+    /**
+     * Characteristic value format type sint16
+     */
+    public static final int FORMAT_SINT16 = 0x22;
+
+    /**
+     * Characteristic value format type sint32
+     */
+    public static final int FORMAT_SINT32 = 0x24;
+
+    /**
+     * Characteristic value format type sfloat (16-bit float)
+     */
+    public static final int FORMAT_SFLOAT = 0x32;
+
+    /**
+     * Characteristic value format type float (32-bit float)
+     */
+    public static final int FORMAT_FLOAT = 0x34;
+
+
+    /**
+     * The UUID of this characteristic.
+     *
+     * @hide
+     */
+    protected UUID mUuid;
+
+    /**
+     * Instance ID for this characteristic.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected int mInstance;
+
+    /**
+     * Characteristic properties.
+     *
+     * @hide
+     */
+    protected int mProperties;
+
+    /**
+     * Characteristic permissions.
+     *
+     * @hide
+     */
+    protected int mPermissions;
+
+    /**
+     * Key size (default = 16).
+     *
+     * @hide
+     */
+    protected int mKeySize = 16;
+
+    /**
+     * Write type for this characteristic.
+     * See WRITE_TYPE_* constants.
+     *
+     * @hide
+     */
+    protected int mWriteType;
+
+    /**
+     * Back-reference to the service this characteristic belongs to.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected BluetoothGattService mService;
+
+    /**
+     * The cached value of this characteristic.
+     *
+     * @hide
+     */
+    protected byte[] mValue;
+
+    /**
+     * List of descriptors included in this characteristic.
+     */
+    protected List<BluetoothGattDescriptor> mDescriptors;
+
+    /**
+     * Create a new BluetoothGattCharacteristic.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param uuid The UUID for this characteristic
+     * @param properties Properties of this characteristic
+     * @param permissions Permissions for this characteristic
+     */
+    public BluetoothGattCharacteristic(UUID uuid, int properties, int permissions) {
+        initCharacteristic(null, uuid, 0, properties, permissions);
+    }
+
+    /**
+     * Create a new BluetoothGattCharacteristic
+     *
+     * @hide
+     */
+    /*package*/ BluetoothGattCharacteristic(BluetoothGattService service,
+            UUID uuid, int instanceId,
+            int properties, int permissions) {
+        initCharacteristic(service, uuid, instanceId, properties, permissions);
+    }
+
+    /**
+     * Create a new BluetoothGattCharacteristic
+     *
+     * @hide
+     */
+    public BluetoothGattCharacteristic(UUID uuid, int instanceId,
+            int properties, int permissions) {
+        initCharacteristic(null, uuid, instanceId, properties, permissions);
+    }
+
+    private void initCharacteristic(BluetoothGattService service,
+            UUID uuid, int instanceId,
+            int properties, int permissions) {
+        mUuid = uuid;
+        mInstance = instanceId;
+        mProperties = properties;
+        mPermissions = permissions;
+        mService = service;
+        mValue = null;
+        mDescriptors = new ArrayList<BluetoothGattDescriptor>();
+
+        if ((mProperties & PROPERTY_WRITE_NO_RESPONSE) != 0) {
+            mWriteType = WRITE_TYPE_NO_RESPONSE;
+        } else {
+            mWriteType = WRITE_TYPE_DEFAULT;
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(new ParcelUuid(mUuid), 0);
+        out.writeInt(mInstance);
+        out.writeInt(mProperties);
+        out.writeInt(mPermissions);
+        out.writeInt(mKeySize);
+        out.writeInt(mWriteType);
+        out.writeTypedList(mDescriptors);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattCharacteristic> CREATOR =
+            new Parcelable.Creator<BluetoothGattCharacteristic>() {
+        public BluetoothGattCharacteristic createFromParcel(Parcel in) {
+            return new BluetoothGattCharacteristic(in);
+        }
+
+        public BluetoothGattCharacteristic[] newArray(int size) {
+            return new BluetoothGattCharacteristic[size];
+        }
+    };
+
+    private BluetoothGattCharacteristic(Parcel in) {
+        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
+        mInstance = in.readInt();
+        mProperties = in.readInt();
+        mPermissions = in.readInt();
+        mKeySize = in.readInt();
+        mWriteType = in.readInt();
+
+        mDescriptors = new ArrayList<BluetoothGattDescriptor>();
+
+        ArrayList<BluetoothGattDescriptor> descs =
+                in.createTypedArrayList(BluetoothGattDescriptor.CREATOR);
+        if (descs != null) {
+            for (BluetoothGattDescriptor desc : descs) {
+                desc.setCharacteristic(this);
+                mDescriptors.add(desc);
+            }
+        }
+    }
+
+    /**
+     * Returns the desired key size.
+     *
+     * @hide
+     */
+    public int getKeySize() {
+        return mKeySize;
+    }
+
+    /**
+     * Adds a descriptor to this characteristic.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param descriptor Descriptor to be added to this characteristic.
+     * @return true, if the descriptor was added to the characteristic
+     */
+    public boolean addDescriptor(BluetoothGattDescriptor descriptor) {
+        mDescriptors.add(descriptor);
+        descriptor.setCharacteristic(this);
+        return true;
+    }
+
+    /**
+     * Get a descriptor by UUID and isntance id.
+     *
+     * @hide
+     */
+    /*package*/  BluetoothGattDescriptor getDescriptor(UUID uuid, int instanceId) {
+        for (BluetoothGattDescriptor descriptor : mDescriptors) {
+            if (descriptor.getUuid().equals(uuid)
+                    && descriptor.getInstanceId() == instanceId) {
+                return descriptor;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the service this characteristic belongs to.
+     *
+     * @return The asscociated service
+     */
+    public BluetoothGattService getService() {
+        return mService;
+    }
+
+    /**
+     * Sets the service associated with this device.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    /*package*/ void setService(BluetoothGattService service) {
+        mService = service;
+    }
+
+    /**
+     * Returns the UUID of this characteristic
+     *
+     * @return UUID of this characteristic
+     */
+    public UUID getUuid() {
+        return mUuid;
+    }
+
+    /**
+     * Returns the instance ID for this characteristic.
+     *
+     * <p>If a remote device offers multiple characteristics with the same UUID,
+     * the instance ID is used to distuinguish between characteristics.
+     *
+     * @return Instance ID of this characteristic
+     */
+    public int getInstanceId() {
+        return mInstance;
+    }
+
+    /**
+     * Force the instance ID.
+     *
+     * @hide
+     */
+    public void setInstanceId(int instanceId) {
+        mInstance = instanceId;
+    }
+
+    /**
+     * Returns the properties of this characteristic.
+     *
+     * <p>The properties contain a bit mask of property flags indicating
+     * the features of this characteristic.
+     *
+     * @return Properties of this characteristic
+     */
+    public int getProperties() {
+        return mProperties;
+    }
+
+    /**
+     * Returns the permissions for this characteristic.
+     *
+     * @return Permissions of this characteristic
+     */
+    public int getPermissions() {
+        return mPermissions;
+    }
+
+    /**
+     * Gets the write type for this characteristic.
+     *
+     * @return Write type for this characteristic
+     */
+    public int getWriteType() {
+        return mWriteType;
+    }
+
+    /**
+     * Set the write type for this characteristic
+     *
+     * <p>Setting the write type of a characteristic determines how the
+     * {@link BluetoothGatt#writeCharacteristic} function write this
+     * characteristic.
+     *
+     * @param writeType The write type to for this characteristic. Can be one of: {@link
+     * #WRITE_TYPE_DEFAULT}, {@link #WRITE_TYPE_NO_RESPONSE} or {@link #WRITE_TYPE_SIGNED}.
+     */
+    public void setWriteType(int writeType) {
+        mWriteType = writeType;
+    }
+
+    /**
+     * Set the desired key size.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void setKeySize(int keySize) {
+        mKeySize = keySize;
+    }
+
+    /**
+     * Returns a list of descriptors for this characteristic.
+     *
+     * @return Descriptors for this characteristic
+     */
+    public List<BluetoothGattDescriptor> getDescriptors() {
+        return mDescriptors;
+    }
+
+    /**
+     * Returns a descriptor with a given UUID out of the list of
+     * descriptors for this characteristic.
+     *
+     * @return GATT descriptor object or null if no descriptor with the given UUID was found.
+     */
+    public BluetoothGattDescriptor getDescriptor(UUID uuid) {
+        for (BluetoothGattDescriptor descriptor : mDescriptors) {
+            if (descriptor.getUuid().equals(uuid)) {
+                return descriptor;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the stored value for this characteristic.
+     *
+     * <p>This function returns the stored value for this characteristic as
+     * retrieved by calling {@link BluetoothGatt#readCharacteristic}. The cached
+     * value of the characteristic is updated as a result of a read characteristic
+     * operation or if a characteristic update notification has been received.
+     *
+     * @return Cached value of the characteristic
+     */
+    public byte[] getValue() {
+        return mValue;
+    }
+
+    /**
+     * Return the stored value of this characteristic.
+     *
+     * <p>The formatType parameter determines how the characteristic value
+     * is to be interpreted. For example, settting formatType to
+     * {@link #FORMAT_UINT16} specifies that the first two bytes of the
+     * characteristic value at the given offset are interpreted to generate the
+     * return value.
+     *
+     * @param formatType The format type used to interpret the characteristic value.
+     * @param offset Offset at which the integer value can be found.
+     * @return Cached value of the characteristic or null of offset exceeds value size.
+     */
+    public Integer getIntValue(int formatType, int offset) {
+        if ((offset + getTypeLen(formatType)) > mValue.length) return null;
+
+        switch (formatType) {
+            case FORMAT_UINT8:
+                return unsignedByteToInt(mValue[offset]);
+
+            case FORMAT_UINT16:
+                return unsignedBytesToInt(mValue[offset], mValue[offset + 1]);
+
+            case FORMAT_UINT32:
+                return unsignedBytesToInt(mValue[offset], mValue[offset + 1],
+                        mValue[offset + 2], mValue[offset + 3]);
+            case FORMAT_SINT8:
+                return unsignedToSigned(unsignedByteToInt(mValue[offset]), 8);
+
+            case FORMAT_SINT16:
+                return unsignedToSigned(unsignedBytesToInt(mValue[offset],
+                        mValue[offset + 1]), 16);
+
+            case FORMAT_SINT32:
+                return unsignedToSigned(unsignedBytesToInt(mValue[offset],
+                        mValue[offset + 1], mValue[offset + 2], mValue[offset + 3]), 32);
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the stored value of this characteristic.
+     * <p>See {@link #getValue} for details.
+     *
+     * @param formatType The format type used to interpret the characteristic value.
+     * @param offset Offset at which the float value can be found.
+     * @return Cached value of the characteristic at a given offset or null if the requested offset
+     * exceeds the value size.
+     */
+    public Float getFloatValue(int formatType, int offset) {
+        if ((offset + getTypeLen(formatType)) > mValue.length) return null;
+
+        switch (formatType) {
+            case FORMAT_SFLOAT:
+                return bytesToFloat(mValue[offset], mValue[offset + 1]);
+
+            case FORMAT_FLOAT:
+                return bytesToFloat(mValue[offset], mValue[offset + 1],
+                        mValue[offset + 2], mValue[offset + 3]);
+        }
+
+        return null;
+    }
+
+    /**
+     * Return the stored value of this characteristic.
+     * <p>See {@link #getValue} for details.
+     *
+     * @param offset Offset at which the string value can be found.
+     * @return Cached value of the characteristic
+     */
+    public String getStringValue(int offset) {
+        if (mValue == null || offset > mValue.length) return null;
+        byte[] strBytes = new byte[mValue.length - offset];
+        for (int i = 0; i != (mValue.length - offset); ++i) strBytes[i] = mValue[offset + i];
+        return new String(strBytes);
+    }
+
+    /**
+     * Updates the locally stored value of this characteristic.
+     *
+     * <p>This function modifies the locally stored cached value of this
+     * characteristic. To send the value to the remote device, call
+     * {@link BluetoothGatt#writeCharacteristic} to send the value to the
+     * remote device.
+     *
+     * @param value New value for this characteristic
+     * @return true if the locally stored value has been set, false if the requested value could not
+     * be stored locally.
+     */
+    public boolean setValue(byte[] value) {
+        mValue = value;
+        return true;
+    }
+
+    /**
+     * Set the locally stored value of this characteristic.
+     * <p>See {@link #setValue(byte[])} for details.
+     *
+     * @param value New value for this characteristic
+     * @param formatType Integer format type used to transform the value parameter
+     * @param offset Offset at which the value should be placed
+     * @return true if the locally stored value has been set
+     */
+    public boolean setValue(int value, int formatType, int offset) {
+        int len = offset + getTypeLen(formatType);
+        if (mValue == null) mValue = new byte[len];
+        if (len > mValue.length) return false;
+
+        switch (formatType) {
+            case FORMAT_SINT8:
+                value = intToSignedBits(value, 8);
+                // Fall-through intended
+            case FORMAT_UINT8:
+                mValue[offset] = (byte) (value & 0xFF);
+                break;
+
+            case FORMAT_SINT16:
+                value = intToSignedBits(value, 16);
+                // Fall-through intended
+            case FORMAT_UINT16:
+                mValue[offset++] = (byte) (value & 0xFF);
+                mValue[offset] = (byte) ((value >> 8) & 0xFF);
+                break;
+
+            case FORMAT_SINT32:
+                value = intToSignedBits(value, 32);
+                // Fall-through intended
+            case FORMAT_UINT32:
+                mValue[offset++] = (byte) (value & 0xFF);
+                mValue[offset++] = (byte) ((value >> 8) & 0xFF);
+                mValue[offset++] = (byte) ((value >> 16) & 0xFF);
+                mValue[offset] = (byte) ((value >> 24) & 0xFF);
+                break;
+
+            default:
+                return false;
+        }
+        return true;
+    }
+
+    /**
+     * Set the locally stored value of this characteristic.
+     * <p>See {@link #setValue(byte[])} for details.
+     *
+     * @param mantissa Mantissa for this characteristic
+     * @param exponent exponent value for this characteristic
+     * @param formatType Float format type used to transform the value parameter
+     * @param offset Offset at which the value should be placed
+     * @return true if the locally stored value has been set
+     */
+    public boolean setValue(int mantissa, int exponent, int formatType, int offset) {
+        int len = offset + getTypeLen(formatType);
+        if (mValue == null) mValue = new byte[len];
+        if (len > mValue.length) return false;
+
+        switch (formatType) {
+            case FORMAT_SFLOAT:
+                mantissa = intToSignedBits(mantissa, 12);
+                exponent = intToSignedBits(exponent, 4);
+                mValue[offset++] = (byte) (mantissa & 0xFF);
+                mValue[offset] = (byte) ((mantissa >> 8) & 0x0F);
+                mValue[offset] += (byte) ((exponent & 0x0F) << 4);
+                break;
+
+            case FORMAT_FLOAT:
+                mantissa = intToSignedBits(mantissa, 24);
+                exponent = intToSignedBits(exponent, 8);
+                mValue[offset++] = (byte) (mantissa & 0xFF);
+                mValue[offset++] = (byte) ((mantissa >> 8) & 0xFF);
+                mValue[offset++] = (byte) ((mantissa >> 16) & 0xFF);
+                mValue[offset] += (byte) (exponent & 0xFF);
+                break;
+
+            default:
+                return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Set the locally stored value of this characteristic.
+     * <p>See {@link #setValue(byte[])} for details.
+     *
+     * @param value New value for this characteristic
+     * @return true if the locally stored value has been set
+     */
+    public boolean setValue(String value) {
+        mValue = value.getBytes();
+        return true;
+    }
+
+    /**
+     * Returns the size of a give value type.
+     */
+    private int getTypeLen(int formatType) {
+        return formatType & 0xF;
+    }
+
+    /**
+     * Convert a signed byte to an unsigned int.
+     */
+    private int unsignedByteToInt(byte b) {
+        return b & 0xFF;
+    }
+
+    /**
+     * Convert signed bytes to a 16-bit unsigned int.
+     */
+    private int unsignedBytesToInt(byte b0, byte b1) {
+        return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8));
+    }
+
+    /**
+     * Convert signed bytes to a 32-bit unsigned int.
+     */
+    private int unsignedBytesToInt(byte b0, byte b1, byte b2, byte b3) {
+        return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8))
+                + (unsignedByteToInt(b2) << 16) + (unsignedByteToInt(b3) << 24);
+    }
+
+    /**
+     * Convert signed bytes to a 16-bit short float value.
+     */
+    private float bytesToFloat(byte b0, byte b1) {
+        int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+                + ((unsignedByteToInt(b1) & 0x0F) << 8), 12);
+        int exponent = unsignedToSigned(unsignedByteToInt(b1) >> 4, 4);
+        return (float) (mantissa * Math.pow(10, exponent));
+    }
+
+    /**
+     * Convert signed bytes to a 32-bit short float value.
+     */
+    private float bytesToFloat(byte b0, byte b1, byte b2, byte b3) {
+        int mantissa = unsignedToSigned(unsignedByteToInt(b0)
+                + (unsignedByteToInt(b1) << 8)
+                + (unsignedByteToInt(b2) << 16), 24);
+        return (float) (mantissa * Math.pow(10, b3));
+    }
+
+    /**
+     * Convert an unsigned integer value to a two's-complement encoded
+     * signed value.
+     */
+    private int unsignedToSigned(int unsigned, int size) {
+        if ((unsigned & (1 << size - 1)) != 0) {
+            unsigned = -1 * ((1 << size - 1) - (unsigned & ((1 << size - 1) - 1)));
+        }
+        return unsigned;
+    }
+
+    /**
+     * Convert an integer into the signed bits of a given length.
+     */
+    private int intToSignedBits(int i, int size) {
+        if (i < 0) {
+            i = (1 << size - 1) + (i & ((1 << size - 1) - 1));
+        }
+        return i;
+    }
+}
diff --git a/android/bluetooth/BluetoothGattDescriptor.java b/android/bluetooth/BluetoothGattDescriptor.java
new file mode 100644
index 0000000..7cc2d6b
--- /dev/null
+++ b/android/bluetooth/BluetoothGattDescriptor.java
@@ -0,0 +1,288 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth GATT Descriptor
+ *
+ * <p> GATT Descriptors contain additional information and attributes of a GATT
+ * characteristic, {@link BluetoothGattCharacteristic}. They can be used to describe
+ * the characteristic's features or to control certain behaviours of the characteristic.
+ */
+public class BluetoothGattDescriptor implements Parcelable {
+
+    /**
+     * Value used to enable notification for a client configuration descriptor
+     */
+    public static final byte[] ENABLE_NOTIFICATION_VALUE = {0x01, 0x00};
+
+    /**
+     * Value used to enable indication for a client configuration descriptor
+     */
+    public static final byte[] ENABLE_INDICATION_VALUE = {0x02, 0x00};
+
+    /**
+     * Value used to disable notifications or indicatinos
+     */
+    public static final byte[] DISABLE_NOTIFICATION_VALUE = {0x00, 0x00};
+
+    /**
+     * Descriptor read permission
+     */
+    public static final int PERMISSION_READ = 0x01;
+
+    /**
+     * Descriptor permission: Allow encrypted read operations
+     */
+    public static final int PERMISSION_READ_ENCRYPTED = 0x02;
+
+    /**
+     * Descriptor permission: Allow reading with man-in-the-middle protection
+     */
+    public static final int PERMISSION_READ_ENCRYPTED_MITM = 0x04;
+
+    /**
+     * Descriptor write permission
+     */
+    public static final int PERMISSION_WRITE = 0x10;
+
+    /**
+     * Descriptor permission: Allow encrypted writes
+     */
+    public static final int PERMISSION_WRITE_ENCRYPTED = 0x20;
+
+    /**
+     * Descriptor permission: Allow encrypted writes with man-in-the-middle
+     * protection
+     */
+    public static final int PERMISSION_WRITE_ENCRYPTED_MITM = 0x40;
+
+    /**
+     * Descriptor permission: Allow signed write operations
+     */
+    public static final int PERMISSION_WRITE_SIGNED = 0x80;
+
+    /**
+     * Descriptor permission: Allow signed write operations with
+     * man-in-the-middle protection
+     */
+    public static final int PERMISSION_WRITE_SIGNED_MITM = 0x100;
+
+    /**
+     * The UUID of this descriptor.
+     *
+     * @hide
+     */
+    protected UUID mUuid;
+
+    /**
+     * Instance ID for this descriptor.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected int mInstance;
+
+    /**
+     * Permissions for this descriptor
+     *
+     * @hide
+     */
+    protected int mPermissions;
+
+    /**
+     * Back-reference to the characteristic this descriptor belongs to.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected BluetoothGattCharacteristic mCharacteristic;
+
+    /**
+     * The value for this descriptor.
+     *
+     * @hide
+     */
+    protected byte[] mValue;
+
+    /**
+     * Create a new BluetoothGattDescriptor.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param uuid The UUID for this descriptor
+     * @param permissions Permissions for this descriptor
+     */
+    public BluetoothGattDescriptor(UUID uuid, int permissions) {
+        initDescriptor(null, uuid, 0, permissions);
+    }
+
+    /**
+     * Create a new BluetoothGattDescriptor.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param characteristic The characteristic this descriptor belongs to
+     * @param uuid The UUID for this descriptor
+     * @param permissions Permissions for this descriptor
+     */
+    /*package*/ BluetoothGattDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid,
+            int instance, int permissions) {
+        initDescriptor(characteristic, uuid, instance, permissions);
+    }
+
+    /**
+     * @hide
+     */
+    public BluetoothGattDescriptor(UUID uuid, int instance, int permissions) {
+        initDescriptor(null, uuid, instance, permissions);
+    }
+
+    private void initDescriptor(BluetoothGattCharacteristic characteristic, UUID uuid,
+            int instance, int permissions) {
+        mCharacteristic = characteristic;
+        mUuid = uuid;
+        mInstance = instance;
+        mPermissions = permissions;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(new ParcelUuid(mUuid), 0);
+        out.writeInt(mInstance);
+        out.writeInt(mPermissions);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattDescriptor> CREATOR =
+            new Parcelable.Creator<BluetoothGattDescriptor>() {
+        public BluetoothGattDescriptor createFromParcel(Parcel in) {
+            return new BluetoothGattDescriptor(in);
+        }
+
+        public BluetoothGattDescriptor[] newArray(int size) {
+            return new BluetoothGattDescriptor[size];
+        }
+    };
+
+    private BluetoothGattDescriptor(Parcel in) {
+        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
+        mInstance = in.readInt();
+        mPermissions = in.readInt();
+    }
+
+    /**
+     * Returns the characteristic this descriptor belongs to.
+     *
+     * @return The characteristic.
+     */
+    public BluetoothGattCharacteristic getCharacteristic() {
+        return mCharacteristic;
+    }
+
+    /**
+     * Set the back-reference to the associated characteristic
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    /*package*/ void setCharacteristic(BluetoothGattCharacteristic characteristic) {
+        mCharacteristic = characteristic;
+    }
+
+    /**
+     * Returns the UUID of this descriptor.
+     *
+     * @return UUID of this descriptor
+     */
+    public UUID getUuid() {
+        return mUuid;
+    }
+
+    /**
+     * Returns the instance ID for this descriptor.
+     *
+     * <p>If a remote device offers multiple descriptors with the same UUID,
+     * the instance ID is used to distuinguish between descriptors.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return Instance ID of this descriptor
+     * @hide
+     */
+    public int getInstanceId() {
+        return mInstance;
+    }
+
+    /**
+     * Force the instance ID.
+     *
+     * @hide
+     */
+    public void setInstanceId(int instanceId) {
+        mInstance = instanceId;
+    }
+
+    /**
+     * Returns the permissions for this descriptor.
+     *
+     * @return Permissions of this descriptor
+     */
+    public int getPermissions() {
+        return mPermissions;
+    }
+
+    /**
+     * Returns the stored value for this descriptor
+     *
+     * <p>This function returns the stored value for this descriptor as
+     * retrieved by calling {@link BluetoothGatt#readDescriptor}. The cached
+     * value of the descriptor is updated as a result of a descriptor read
+     * operation.
+     *
+     * @return Cached value of the descriptor
+     */
+    public byte[] getValue() {
+        return mValue;
+    }
+
+    /**
+     * Updates the locally stored value of this descriptor.
+     *
+     * <p>This function modifies the locally stored cached value of this
+     * descriptor. To send the value to the remote device, call
+     * {@link BluetoothGatt#writeDescriptor} to send the value to the
+     * remote device.
+     *
+     * @param value New value for this descriptor
+     * @return true if the locally stored value has been set, false if the requested value could not
+     * be stored locally.
+     */
+    public boolean setValue(byte[] value) {
+        mValue = value;
+        return true;
+    }
+}
diff --git a/android/bluetooth/BluetoothGattIncludedService.java b/android/bluetooth/BluetoothGattIncludedService.java
new file mode 100644
index 0000000..5580619
--- /dev/null
+++ b/android/bluetooth/BluetoothGattIncludedService.java
@@ -0,0 +1,112 @@
+/*
+ * 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.ParcelUuid;
+import android.os.Parcelable;
+
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth GATT Included Service
+ *
+ * @hide
+ */
+public class BluetoothGattIncludedService implements Parcelable {
+
+    /**
+     * The UUID of this service.
+     */
+    protected UUID mUuid;
+
+    /**
+     * Instance ID for this service.
+     */
+    protected int mInstanceId;
+
+    /**
+     * Service type (Primary/Secondary).
+     */
+    protected int mServiceType;
+
+    /**
+     * Create a new BluetoothGattIncludedService
+     */
+    public BluetoothGattIncludedService(UUID uuid, int instanceId, int serviceType) {
+        mUuid = uuid;
+        mInstanceId = instanceId;
+        mServiceType = serviceType;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(new ParcelUuid(mUuid), 0);
+        out.writeInt(mInstanceId);
+        out.writeInt(mServiceType);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattIncludedService> CREATOR =
+            new Parcelable.Creator<BluetoothGattIncludedService>() {
+        public BluetoothGattIncludedService createFromParcel(Parcel in) {
+            return new BluetoothGattIncludedService(in);
+        }
+
+        public BluetoothGattIncludedService[] newArray(int size) {
+            return new BluetoothGattIncludedService[size];
+        }
+    };
+
+    private BluetoothGattIncludedService(Parcel in) {
+        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
+        mInstanceId = in.readInt();
+        mServiceType = in.readInt();
+    }
+
+    /**
+     * Returns the UUID of this service
+     *
+     * @return UUID of this service
+     */
+    public UUID getUuid() {
+        return mUuid;
+    }
+
+    /**
+     * Returns the instance ID for this service
+     *
+     * <p>If a remote device offers multiple services with the same UUID
+     * (ex. multiple battery services for different batteries), the instance
+     * ID is used to distuinguish services.
+     *
+     * @return Instance ID of this service
+     */
+    public int getInstanceId() {
+        return mInstanceId;
+    }
+
+    /**
+     * Get the type of this service (primary/secondary)
+     */
+    public int getType() {
+        return mServiceType;
+    }
+}
diff --git a/android/bluetooth/BluetoothGattServer.java b/android/bluetooth/BluetoothGattServer.java
new file mode 100644
index 0000000..13b1b4f
--- /dev/null
+++ b/android/bluetooth/BluetoothGattServer.java
@@ -0,0 +1,845 @@
+/*
+ * 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.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Public API for the Bluetooth GATT Profile server role.
+ *
+ * <p>This class provides Bluetooth GATT server role functionality,
+ * allowing applications to create Bluetooth Smart services and
+ * characteristics.
+ *
+ * <p>BluetoothGattServer is a proxy object for controlling the Bluetooth Service
+ * via IPC.  Use {@link BluetoothManager#openGattServer} to get an instance
+ * of this class.
+ */
+public final class BluetoothGattServer implements BluetoothProfile {
+    private static final String TAG = "BluetoothGattServer";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    private BluetoothAdapter mAdapter;
+    private IBluetoothGatt mService;
+    private BluetoothGattServerCallback mCallback;
+
+    private Object mServerIfLock = new Object();
+    private int mServerIf;
+    private int mTransport;
+    private BluetoothGattService mPendingService;
+    private List<BluetoothGattService> mServices;
+
+    private static final int CALLBACK_REG_TIMEOUT = 10000;
+
+    /**
+     * Bluetooth GATT interface callbacks
+     */
+    private final IBluetoothGattServerCallback mBluetoothGattServerCallback =
+            new IBluetoothGattServerCallback.Stub() {
+                /**
+                 * Application interface registered - app is ready to go
+                 * @hide
+                 */
+                @Override
+                public void onServerRegistered(int status, int serverIf) {
+                    if (DBG) {
+                        Log.d(TAG, "onServerRegistered() - status=" + status
+                                + " serverIf=" + serverIf);
+                    }
+                    synchronized (mServerIfLock) {
+                        if (mCallback != null) {
+                            mServerIf = serverIf;
+                            mServerIfLock.notify();
+                        } else {
+                            // registration timeout
+                            Log.e(TAG, "onServerRegistered: mCallback is null");
+                        }
+                    }
+                }
+
+                /**
+                 * Server connection state changed
+                 * @hide
+                 */
+                @Override
+                public void onServerConnectionState(int status, int serverIf,
+                        boolean connected, String address) {
+                    if (DBG) {
+                        Log.d(TAG, "onServerConnectionState() - status=" + status
+                                + " serverIf=" + serverIf + " device=" + address);
+                    }
+                    try {
+                        mCallback.onConnectionStateChange(mAdapter.getRemoteDevice(address), status,
+                                connected ? BluetoothProfile.STATE_CONNECTED :
+                                        BluetoothProfile.STATE_DISCONNECTED);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception in callback", ex);
+                    }
+                }
+
+                /**
+                 * Service has been added
+                 * @hide
+                 */
+                @Override
+                public void onServiceAdded(int status, BluetoothGattService service) {
+                    if (DBG) {
+                        Log.d(TAG, "onServiceAdded() - handle=" + service.getInstanceId()
+                                + " uuid=" + service.getUuid() + " status=" + status);
+                    }
+
+                    if (mPendingService == null) {
+                        return;
+                    }
+
+                    BluetoothGattService tmp = mPendingService;
+                    mPendingService = null;
+
+                    // Rewrite newly assigned handles to existing service.
+                    tmp.setInstanceId(service.getInstanceId());
+                    List<BluetoothGattCharacteristic> temp_chars = tmp.getCharacteristics();
+                    List<BluetoothGattCharacteristic> svc_chars = service.getCharacteristics();
+                    for (int i = 0; i < svc_chars.size(); i++) {
+                        BluetoothGattCharacteristic temp_char = temp_chars.get(i);
+                        BluetoothGattCharacteristic svc_char = svc_chars.get(i);
+
+                        temp_char.setInstanceId(svc_char.getInstanceId());
+
+                        List<BluetoothGattDescriptor> temp_descs = temp_char.getDescriptors();
+                        List<BluetoothGattDescriptor> svc_descs = svc_char.getDescriptors();
+                        for (int j = 0; j < svc_descs.size(); j++) {
+                            temp_descs.get(j).setInstanceId(svc_descs.get(j).getInstanceId());
+                        }
+                    }
+
+                    mServices.add(tmp);
+
+                    try {
+                        mCallback.onServiceAdded((int) status, tmp);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception in callback", ex);
+                    }
+                }
+
+                /**
+                 * Remote client characteristic read request.
+                 * @hide
+                 */
+                @Override
+                public void onCharacteristicReadRequest(String address, int transId,
+                        int offset, boolean isLong, int handle) {
+                    if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle);
+
+                    BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                    BluetoothGattCharacteristic characteristic = getCharacteristicByHandle(handle);
+                    if (characteristic == null) {
+                        Log.w(TAG, "onCharacteristicReadRequest() no char for handle " + handle);
+                        return;
+                    }
+
+                    try {
+                        mCallback.onCharacteristicReadRequest(device, transId, offset,
+                                characteristic);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception in callback", ex);
+                    }
+                }
+
+                /**
+                 * Remote client descriptor read request.
+                 * @hide
+                 */
+                @Override
+                public void onDescriptorReadRequest(String address, int transId,
+                        int offset, boolean isLong, int handle) {
+                    if (VDBG) Log.d(TAG, "onCharacteristicReadRequest() - handle=" + handle);
+
+                    BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                    BluetoothGattDescriptor descriptor = getDescriptorByHandle(handle);
+                    if (descriptor == null) {
+                        Log.w(TAG, "onDescriptorReadRequest() no desc for handle " + handle);
+                        return;
+                    }
+
+                    try {
+                        mCallback.onDescriptorReadRequest(device, transId, offset, descriptor);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception in callback", ex);
+                    }
+                }
+
+                /**
+                 * Remote client characteristic write request.
+                 * @hide
+                 */
+                @Override
+                public void onCharacteristicWriteRequest(String address, int transId,
+                        int offset, int length, boolean isPrep, boolean needRsp,
+                        int handle, byte[] value) {
+                    if (VDBG) Log.d(TAG, "onCharacteristicWriteRequest() - handle=" + handle);
+
+                    BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                    BluetoothGattCharacteristic characteristic = getCharacteristicByHandle(handle);
+                    if (characteristic == null) {
+                        Log.w(TAG, "onCharacteristicWriteRequest() no char for handle " + handle);
+                        return;
+                    }
+
+                    try {
+                        mCallback.onCharacteristicWriteRequest(device, transId, characteristic,
+                                isPrep, needRsp, offset, value);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception in callback", ex);
+                    }
+
+                }
+
+                /**
+                 * Remote client descriptor write request.
+                 * @hide
+                 */
+                @Override
+                public void onDescriptorWriteRequest(String address, int transId, int offset,
+                        int length, boolean isPrep, boolean needRsp, int handle, byte[] value) {
+                    if (VDBG) Log.d(TAG, "onDescriptorWriteRequest() - handle=" + handle);
+
+                    BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                    BluetoothGattDescriptor descriptor = getDescriptorByHandle(handle);
+                    if (descriptor == null) {
+                        Log.w(TAG, "onDescriptorWriteRequest() no desc for handle " + handle);
+                        return;
+                    }
+
+                    try {
+                        mCallback.onDescriptorWriteRequest(device, transId, descriptor,
+                                isPrep, needRsp, offset, value);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception in callback", ex);
+                    }
+                }
+
+                /**
+                 * Execute pending writes.
+                 * @hide
+                 */
+                @Override
+                public void onExecuteWrite(String address, int transId,
+                        boolean execWrite) {
+                    if (DBG) {
+                        Log.d(TAG, "onExecuteWrite() - "
+                                + "device=" + address + ", transId=" + transId
+                                + "execWrite=" + execWrite);
+                    }
+
+                    BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                    if (device == null) return;
+
+                    try {
+                        mCallback.onExecuteWrite(device, transId, execWrite);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception in callback", ex);
+                    }
+                }
+
+                /**
+                 * A notification/indication has been sent.
+                 * @hide
+                 */
+                @Override
+                public void onNotificationSent(String address, int status) {
+                    if (VDBG) {
+                        Log.d(TAG, "onNotificationSent() - "
+                                + "device=" + address + ", status=" + status);
+                    }
+
+                    BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                    if (device == null) return;
+
+                    try {
+                        mCallback.onNotificationSent(device, status);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception: " + ex);
+                    }
+                }
+
+                /**
+                 * The MTU for a connection has changed
+                 * @hide
+                 */
+                @Override
+                public void onMtuChanged(String address, int mtu) {
+                    if (DBG) {
+                        Log.d(TAG, "onMtuChanged() - "
+                                + "device=" + address + ", mtu=" + mtu);
+                    }
+
+                    BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                    if (device == null) return;
+
+                    try {
+                        mCallback.onMtuChanged(device, mtu);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception: " + ex);
+                    }
+                }
+
+                /**
+                 * The PHY for a connection was updated
+                 * @hide
+                 */
+                @Override
+                public void onPhyUpdate(String address, int txPhy, int rxPhy, int status) {
+                    if (DBG) {
+                        Log.d(TAG,
+                                "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
+                                        + ", rxPHy=" + rxPhy);
+                    }
+
+                    BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                    if (device == null) return;
+
+                    try {
+                        mCallback.onPhyUpdate(device, txPhy, rxPhy, status);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception: " + ex);
+                    }
+                }
+
+                /**
+                 * The PHY for a connection was read
+                 * @hide
+                 */
+                @Override
+                public void onPhyRead(String address, int txPhy, int rxPhy, int status) {
+                    if (DBG) {
+                        Log.d(TAG,
+                                "onPhyUpdate() - " + "device=" + address + ", txPHy=" + txPhy
+                                        + ", rxPHy=" + rxPhy);
+                    }
+
+                    BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                    if (device == null) return;
+
+                    try {
+                        mCallback.onPhyRead(device, txPhy, rxPhy, status);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception: " + ex);
+                    }
+                }
+
+                /**
+                 * Callback invoked when the given connection is updated
+                 * @hide
+                 */
+                @Override
+                public void onConnectionUpdated(String address, int interval, int latency,
+                        int timeout, int status) {
+                    if (DBG) {
+                        Log.d(TAG, "onConnectionUpdated() - Device=" + address
+                                + " interval=" + interval + " latency=" + latency
+                                + " timeout=" + timeout + " status=" + status);
+                    }
+                    BluetoothDevice device = mAdapter.getRemoteDevice(address);
+                    if (device == null) return;
+
+                    try {
+                        mCallback.onConnectionUpdated(device, interval, latency,
+                                timeout, status);
+                    } catch (Exception ex) {
+                        Log.w(TAG, "Unhandled exception: " + ex);
+                    }
+                }
+
+            };
+
+    /**
+     * Create a BluetoothGattServer proxy object.
+     */
+    /*package*/ BluetoothGattServer(IBluetoothGatt iGatt, int transport) {
+        mService = iGatt;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mCallback = null;
+        mServerIf = 0;
+        mTransport = transport;
+        mServices = new ArrayList<BluetoothGattService>();
+    }
+
+    /**
+     * Returns a characteristic with given handle.
+     *
+     * @hide
+     */
+    /*package*/ BluetoothGattCharacteristic getCharacteristicByHandle(int handle) {
+        for (BluetoothGattService svc : mServices) {
+            for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
+                if (charac.getInstanceId() == handle) {
+                    return charac;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns a descriptor with given handle.
+     *
+     * @hide
+     */
+    /*package*/ BluetoothGattDescriptor getDescriptorByHandle(int handle) {
+        for (BluetoothGattService svc : mServices) {
+            for (BluetoothGattCharacteristic charac : svc.getCharacteristics()) {
+                for (BluetoothGattDescriptor desc : charac.getDescriptors()) {
+                    if (desc.getInstanceId() == handle) {
+                        return desc;
+                    }
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Close this GATT server instance.
+     *
+     * Application should call this method as early as possible after it is done with
+     * this GATT server.
+     */
+    public void close() {
+        if (DBG) Log.d(TAG, "close()");
+        unregisterCallback();
+    }
+
+    /**
+     * Register an application callback to start using GattServer.
+     *
+     * <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, the callback will be called to notify success or failure, false on immediate
+     * error
+     */
+    /*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
+        if (DBG) Log.d(TAG, "registerCallback()");
+        if (mService == null) {
+            Log.e(TAG, "GATT service not available");
+            return false;
+        }
+        UUID uuid = UUID.randomUUID();
+        if (DBG) Log.d(TAG, "registerCallback() - UUID=" + uuid);
+
+        synchronized (mServerIfLock) {
+            if (mCallback != null) {
+                Log.e(TAG, "App can register callback only once");
+                return false;
+            }
+
+            mCallback = callback;
+            try {
+                mService.registerServer(new ParcelUuid(uuid), mBluetoothGattServerCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+                mCallback = null;
+                return false;
+            }
+
+            try {
+                mServerIfLock.wait(CALLBACK_REG_TIMEOUT);
+            } catch (InterruptedException e) {
+                Log.e(TAG, "" + e);
+                mCallback = null;
+            }
+
+            if (mServerIf == 0) {
+                mCallback = null;
+                return false;
+            } else {
+                return true;
+            }
+        }
+    }
+
+    /**
+     * Unregister the current application and callbacks.
+     */
+    private void unregisterCallback() {
+        if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf);
+        if (mService == null || mServerIf == 0) return;
+
+        try {
+            mCallback = null;
+            mService.unregisterServer(mServerIf);
+            mServerIf = 0;
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    /**
+     * Returns a service by UUID, instance and type.
+     *
+     * @hide
+     */
+    /*package*/ BluetoothGattService getService(UUID uuid, int instanceId, int type) {
+        for (BluetoothGattService svc : mServices) {
+            if (svc.getType() == type
+                    && svc.getInstanceId() == instanceId
+                    && svc.getUuid().equals(uuid)) {
+                return svc;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 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 BluetoothGattServerCallback#onConnectionStateChange} callback will be
+     * invoked when the connection state changes as a result of this function.
+     *
+     * <p>The autoConnect parameter 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 true.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @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 || mServerIf == 0) return false;
+
+        try {
+            // autoConnect is inverse of "isDirect"
+            mService.serverConnect(mServerIf, device.getAddress(), !autoConnect, mTransport);
+        } 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, "cancelConnection() - device: " + device.getAddress());
+        if (mService == null || mServerIf == 0) return;
+
+        try {
+            mService.serverDisconnect(mServerIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    /**
+     * Set the preferred connection PHY for this app. Please note that this is just a
+     * recommendation, whether the PHY change will happen depends on other applications peferences,
+     * local and remote controller capabilities. Controller can override these settings. <p> {@link
+     * BluetoothGattServerCallback#onPhyUpdate} will be triggered as a result of this call, even if
+     * no PHY change happens. It is also triggered when remote device updates the PHY.
+     *
+     * @param device The remote device to send this response to
+     * @param txPhy preferred transmitter PHY. Bitwise OR of any of {@link
+     * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
+     * BluetoothDevice#PHY_LE_CODED_MASK}.
+     * @param rxPhy preferred receiver PHY. Bitwise OR of any of {@link
+     * BluetoothDevice#PHY_LE_1M_MASK}, {@link BluetoothDevice#PHY_LE_2M_MASK}, and {@link
+     * BluetoothDevice#PHY_LE_CODED_MASK}.
+     * @param phyOptions preferred coding to use when transmitting on the LE Coded PHY. Can be one
+     * of {@link BluetoothDevice#PHY_OPTION_NO_PREFERRED}, {@link BluetoothDevice#PHY_OPTION_S2} or
+     * {@link BluetoothDevice#PHY_OPTION_S8}
+     */
+    public void setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions) {
+        try {
+            mService.serverSetPreferredPhy(mServerIf, device.getAddress(), txPhy, rxPhy,
+                    phyOptions);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    /**
+     * Read the current transmitter PHY and receiver PHY of the connection. The values are returned
+     * in {@link BluetoothGattServerCallback#onPhyRead}
+     *
+     * @param device The remote device to send this response to
+     */
+    public void readPhy(BluetoothDevice device) {
+        try {
+            mService.serverReadPhy(mServerIf, device.getAddress());
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    /**
+     * Send a response to a read or write request to a remote device.
+     *
+     * <p>This function must be invoked in when a remote read/write request
+     * is received by one of these callback methods:
+     *
+     * <ul>
+     * <li>{@link BluetoothGattServerCallback#onCharacteristicReadRequest}
+     * <li>{@link BluetoothGattServerCallback#onCharacteristicWriteRequest}
+     * <li>{@link BluetoothGattServerCallback#onDescriptorReadRequest}
+     * <li>{@link BluetoothGattServerCallback#onDescriptorWriteRequest}
+     * </ul>
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device The remote device to send this response to
+     * @param requestId The ID of the request that was received with the callback
+     * @param status The status of the request to be sent to the remote devices
+     * @param offset Value offset for partial read/write response
+     * @param value The value of the attribute that was read/written (optional)
+     */
+    public boolean sendResponse(BluetoothDevice device, int requestId,
+            int status, int offset, byte[] value) {
+        if (VDBG) Log.d(TAG, "sendResponse() - device: " + device.getAddress());
+        if (mService == null || mServerIf == 0) return false;
+
+        try {
+            mService.sendResponse(mServerIf, device.getAddress(), requestId,
+                    status, offset, value);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * Send a notification or indication that a local characteristic has been
+     * updated.
+     *
+     * <p>A notification or indication is sent to the remote device to signal
+     * that the characteristic has been updated. This function should be invoked
+     * for every client that requests notifications/indications by writing
+     * to the "Client Configuration" descriptor for the given characteristic.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device The remote device to receive the notification/indication
+     * @param characteristic The local characteristic that has been updated
+     * @param confirm true to request confirmation from the client (indication), false to send a
+     * notification
+     * @return true, if the notification has been triggered successfully
+     * @throws IllegalArgumentException
+     */
+    public boolean notifyCharacteristicChanged(BluetoothDevice device,
+            BluetoothGattCharacteristic characteristic, boolean confirm) {
+        if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
+        if (mService == null || mServerIf == 0) return false;
+
+        BluetoothGattService service = characteristic.getService();
+        if (service == null) return false;
+
+        if (characteristic.getValue() == null) {
+            throw new IllegalArgumentException("Chracteristic value is empty. Use "
+                    + "BluetoothGattCharacteristic#setvalue to update");
+        }
+
+        try {
+            mService.sendNotification(mServerIf, device.getAddress(),
+                    characteristic.getInstanceId(), confirm,
+                    characteristic.getValue());
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Add a service to the list of services to be hosted.
+     *
+     * <p>Once a service has been addded to the list, the service and its
+     * included characteristics will be provided by the local device.
+     *
+     * <p>If the local device has already exposed services when this function
+     * is called, a service update notification will be sent to all clients.
+     *
+     * <p>The {@link BluetoothGattServerCallback#onServiceAdded} callback will indicate
+     * whether this service has been added successfully. Do not add another service
+     * before this callback.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param service Service to be added to the list of services provided by this device.
+     * @return true, if the request to add service has been initiated
+     */
+    public boolean addService(BluetoothGattService service) {
+        if (DBG) Log.d(TAG, "addService() - service: " + service.getUuid());
+        if (mService == null || mServerIf == 0) return false;
+
+        mPendingService = service;
+
+        try {
+            mService.addService(mServerIf, service);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Removes a service from the list of services to be provided.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param service Service to be removed.
+     * @return true, if the service has been removed
+     */
+    public boolean removeService(BluetoothGattService service) {
+        if (DBG) Log.d(TAG, "removeService() - service: " + service.getUuid());
+        if (mService == null || mServerIf == 0) return false;
+
+        BluetoothGattService intService = getService(service.getUuid(),
+                service.getInstanceId(), service.getType());
+        if (intService == null) return false;
+
+        try {
+            mService.removeService(mServerIf, service.getInstanceId());
+            mServices.remove(intService);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Remove all services from the list of provided services.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     */
+    public void clearServices() {
+        if (DBG) Log.d(TAG, "clearServices()");
+        if (mService == null || mServerIf == 0) return;
+
+        try {
+            mService.clearServices(mServerIf);
+            mServices.clear();
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+    }
+
+    /**
+     * Returns a list of GATT services offered by this device.
+     *
+     * <p>An application must call {@link #addService} to add a serice to the
+     * list of services offered by this device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return List of services. Returns an empty list if no services have been added yet.
+     */
+    public List<BluetoothGattService> getServices() {
+        return mServices;
+    }
+
+    /**
+     * Returns a {@link BluetoothGattService} from the list of services offered
+     * by this 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 uuid UUID of the requested service
+     * @return BluetoothGattService if supported, or null if the requested service is not offered by
+     * this device.
+     */
+    public BluetoothGattService getService(UUID uuid) {
+        for (BluetoothGattService service : mServices) {
+            if (service.getUuid().equals(uuid)) {
+                return service;
+            }
+        }
+
+        return null;
+    }
+
+
+    /**
+     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+     * with {@link BluetoothProfile#GATT} as argument
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        throw new UnsupportedOperationException("Use BluetoothManager#getConnectionState instead.");
+    }
+
+    /**
+     * Not supported - please use {@link BluetoothManager#getConnectedDevices(int)}
+     * with {@link BluetoothProfile#GATT} as argument
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        throw new UnsupportedOperationException(
+                "Use BluetoothManager#getConnectedDevices instead.");
+    }
+
+    /**
+     * Not supported - please use
+     * {@link BluetoothManager#getDevicesMatchingConnectionStates(int, int[])}
+     * with {@link BluetoothProfile#GATT} as first argument
+     *
+     * @throws UnsupportedOperationException
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        throw new UnsupportedOperationException(
+                "Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
+    }
+}
diff --git a/android/bluetooth/BluetoothGattServerCallback.java b/android/bluetooth/BluetoothGattServerCallback.java
new file mode 100644
index 0000000..2c8114b
--- /dev/null
+++ b/android/bluetooth/BluetoothGattServerCallback.java
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2017 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;
+
+/**
+ * This abstract class is used to implement {@link BluetoothGattServer} callbacks.
+ */
+public abstract class BluetoothGattServerCallback {
+
+    /**
+     * Callback indicating when a remote device has been connected or disconnected.
+     *
+     * @param device Remote device that has been connected or disconnected.
+     * @param status Status of the connect or disconnect operation.
+     * @param newState Returns the new connection state. Can be one of {@link
+     * BluetoothProfile#STATE_DISCONNECTED} or {@link BluetoothProfile#STATE_CONNECTED}
+     */
+    public void onConnectionStateChange(BluetoothDevice device, int status,
+            int newState) {
+    }
+
+    /**
+     * Indicates whether a local service has been added successfully.
+     *
+     * @param status Returns {@link BluetoothGatt#GATT_SUCCESS} if the service was added
+     * successfully.
+     * @param service The service that has been added
+     */
+    public void onServiceAdded(int status, BluetoothGattService service) {
+    }
+
+    /**
+     * A remote client has requested to read a local characteristic.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the read operation
+     * @param requestId The Id of the request
+     * @param offset Offset into the value of the characteristic
+     * @param characteristic Characteristic to be read
+     */
+    public void onCharacteristicReadRequest(BluetoothDevice device, int requestId,
+            int offset, BluetoothGattCharacteristic characteristic) {
+    }
+
+    /**
+     * A remote client has requested to write to a local characteristic.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the write operation
+     * @param requestId The Id of the request
+     * @param characteristic Characteristic to be written to.
+     * @param preparedWrite true, if this write operation should be queued for later execution.
+     * @param responseNeeded true, if the remote device requires a response
+     * @param offset The offset given for the value
+     * @param value The value the client wants to assign to the characteristic
+     */
+    public void onCharacteristicWriteRequest(BluetoothDevice device, int requestId,
+            BluetoothGattCharacteristic characteristic,
+            boolean preparedWrite, boolean responseNeeded,
+            int offset, byte[] value) {
+    }
+
+    /**
+     * A remote client has requested to read a local descriptor.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the read operation
+     * @param requestId The Id of the request
+     * @param offset Offset into the value of the characteristic
+     * @param descriptor Descriptor to be read
+     */
+    public void onDescriptorReadRequest(BluetoothDevice device, int requestId,
+            int offset, BluetoothGattDescriptor descriptor) {
+    }
+
+    /**
+     * A remote client has requested to write to a local descriptor.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the write operation
+     * @param requestId The Id of the request
+     * @param descriptor Descriptor to be written to.
+     * @param preparedWrite true, if this write operation should be queued for later execution.
+     * @param responseNeeded true, if the remote device requires a response
+     * @param offset The offset given for the value
+     * @param value The value the client wants to assign to the descriptor
+     */
+    public void onDescriptorWriteRequest(BluetoothDevice device, int requestId,
+            BluetoothGattDescriptor descriptor,
+            boolean preparedWrite, boolean responseNeeded,
+            int offset, byte[] value) {
+    }
+
+    /**
+     * Execute all pending write operations for this device.
+     *
+     * <p>An application must call {@link BluetoothGattServer#sendResponse}
+     * to complete the request.
+     *
+     * @param device The remote device that has requested the write operations
+     * @param requestId The Id of the request
+     * @param execute Whether the pending writes should be executed (true) or cancelled (false)
+     */
+    public void onExecuteWrite(BluetoothDevice device, int requestId, boolean execute) {
+    }
+
+    /**
+     * Callback invoked when a notification or indication has been sent to
+     * a remote device.
+     *
+     * <p>When multiple notifications are to be sent, an application must
+     * wait for this callback to be received before sending additional
+     * notifications.
+     *
+     * @param device The remote device the notification has been sent to
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the operation was successful
+     */
+    public void onNotificationSent(BluetoothDevice device, int status) {
+    }
+
+    /**
+     * Callback indicating the MTU for a given device connection has changed.
+     *
+     * <p>This callback will be invoked if a remote client has requested to change
+     * the MTU for a given connection.
+     *
+     * @param device The remote device that requested the MTU change
+     * @param mtu The new MTU size
+     */
+    public void onMtuChanged(BluetoothDevice device, int mtu) {
+    }
+
+    /**
+     * Callback triggered as result of {@link BluetoothGattServer#setPreferredPhy}, or as a result
+     * of remote device changing the PHY.
+     *
+     * @param device The remote device
+     * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link
+     * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}
+     * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link
+     * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}
+     * @param status Status of the PHY update operation. {@link BluetoothGatt#GATT_SUCCESS} if the
+     * operation succeeds.
+     */
+    public void onPhyUpdate(BluetoothDevice device, int txPhy, int rxPhy, int status) {
+    }
+
+    /**
+     * Callback triggered as result of {@link BluetoothGattServer#readPhy}
+     *
+     * @param device The remote device that requested the PHY read
+     * @param txPhy the transmitter PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link
+     * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}
+     * @param rxPhy the receiver PHY in use. One of {@link BluetoothDevice#PHY_LE_1M}, {@link
+     * BluetoothDevice#PHY_LE_2M}, and {@link BluetoothDevice#PHY_LE_CODED}
+     * @param status Status of the PHY read operation. {@link BluetoothGatt#GATT_SUCCESS} if the
+     * operation succeeds.
+     */
+    public void onPhyRead(BluetoothDevice device, int txPhy, int rxPhy, int status) {
+    }
+
+    /**
+     * Callback indicating the connection parameters were updated.
+     *
+     * @param device The remote device involved
+     * @param interval Connection interval used on this connection, 1.25ms unit. Valid range is from
+     * 6 (7.5ms) to 3200 (4000ms).
+     * @param latency Slave latency for the connection in number of connection events. Valid range
+     * is from 0 to 499
+     * @param timeout Supervision timeout for this connection, in 10ms unit. Valid range is from 10
+     * (0.1s) to 3200 (32s)
+     * @param status {@link BluetoothGatt#GATT_SUCCESS} if the connection has been updated
+     * successfully
+     * @hide
+     */
+    public void onConnectionUpdated(BluetoothDevice device, int interval, int latency, int timeout,
+            int status) {
+    }
+
+}
diff --git a/android/bluetooth/BluetoothGattService.java b/android/bluetooth/BluetoothGattService.java
new file mode 100644
index 0000000..13d6d70
--- /dev/null
+++ b/android/bluetooth/BluetoothGattService.java
@@ -0,0 +1,392 @@
+/*
+ * 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Represents a Bluetooth GATT Service
+ *
+ * <p> Gatt Service contains a collection of {@link BluetoothGattCharacteristic},
+ * as well as referenced services.
+ */
+public class BluetoothGattService implements Parcelable {
+
+    /**
+     * Primary service
+     */
+    public static final int SERVICE_TYPE_PRIMARY = 0;
+
+    /**
+     * Secondary service (included by primary services)
+     */
+    public static final int SERVICE_TYPE_SECONDARY = 1;
+
+
+    /**
+     * The remote device his service is associated with.
+     * This applies to client applications only.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    protected BluetoothDevice mDevice;
+
+    /**
+     * The UUID of this service.
+     *
+     * @hide
+     */
+    protected UUID mUuid;
+
+    /**
+     * Instance ID for this service.
+     *
+     * @hide
+     */
+    protected int mInstanceId;
+
+    /**
+     * Handle counter override (for conformance testing).
+     *
+     * @hide
+     */
+    protected int mHandles = 0;
+
+    /**
+     * Service type (Primary/Secondary).
+     *
+     * @hide
+     */
+    protected int mServiceType;
+
+    /**
+     * List of characteristics included in this service.
+     */
+    protected List<BluetoothGattCharacteristic> mCharacteristics;
+
+    /**
+     * List of included services for this service.
+     */
+    protected List<BluetoothGattService> mIncludedServices;
+
+    /**
+     * Whether the service uuid should be advertised.
+     */
+    private boolean mAdvertisePreferred;
+
+    /**
+     * Create a new BluetoothGattService.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param uuid The UUID for this service
+     * @param serviceType The type of this service,
+     * {@link BluetoothGattService#SERVICE_TYPE_PRIMARY}
+     * or {@link BluetoothGattService#SERVICE_TYPE_SECONDARY}
+     */
+    public BluetoothGattService(UUID uuid, int serviceType) {
+        mDevice = null;
+        mUuid = uuid;
+        mInstanceId = 0;
+        mServiceType = serviceType;
+        mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+        mIncludedServices = new ArrayList<BluetoothGattService>();
+    }
+
+    /**
+     * Create a new BluetoothGattService
+     *
+     * @hide
+     */
+    /*package*/ BluetoothGattService(BluetoothDevice device, UUID uuid,
+            int instanceId, int serviceType) {
+        mDevice = device;
+        mUuid = uuid;
+        mInstanceId = instanceId;
+        mServiceType = serviceType;
+        mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+        mIncludedServices = new ArrayList<BluetoothGattService>();
+    }
+
+    /**
+     * Create a new BluetoothGattService
+     *
+     * @hide
+     */
+    public BluetoothGattService(UUID uuid, int instanceId, int serviceType) {
+        mDevice = null;
+        mUuid = uuid;
+        mInstanceId = instanceId;
+        mServiceType = serviceType;
+        mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+        mIncludedServices = new ArrayList<BluetoothGattService>();
+    }
+
+    /**
+     * @hide
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(new ParcelUuid(mUuid), 0);
+        out.writeInt(mInstanceId);
+        out.writeInt(mServiceType);
+        out.writeTypedList(mCharacteristics);
+
+        ArrayList<BluetoothGattIncludedService> includedServices =
+                new ArrayList<BluetoothGattIncludedService>(mIncludedServices.size());
+        for (BluetoothGattService s : mIncludedServices) {
+            includedServices.add(new BluetoothGattIncludedService(s.getUuid(),
+                    s.getInstanceId(), s.getType()));
+        }
+        out.writeTypedList(includedServices);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothGattService> CREATOR =
+            new Parcelable.Creator<BluetoothGattService>() {
+        public BluetoothGattService createFromParcel(Parcel in) {
+            return new BluetoothGattService(in);
+        }
+
+        public BluetoothGattService[] newArray(int size) {
+            return new BluetoothGattService[size];
+        }
+    };
+
+    private BluetoothGattService(Parcel in) {
+        mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
+        mInstanceId = in.readInt();
+        mServiceType = in.readInt();
+
+        mCharacteristics = new ArrayList<BluetoothGattCharacteristic>();
+
+        ArrayList<BluetoothGattCharacteristic> chrcs =
+                in.createTypedArrayList(BluetoothGattCharacteristic.CREATOR);
+        if (chrcs != null) {
+            for (BluetoothGattCharacteristic chrc : chrcs) {
+                chrc.setService(this);
+                mCharacteristics.add(chrc);
+            }
+        }
+
+        mIncludedServices = new ArrayList<BluetoothGattService>();
+
+        ArrayList<BluetoothGattIncludedService> inclSvcs =
+                in.createTypedArrayList(BluetoothGattIncludedService.CREATOR);
+        if (chrcs != null) {
+            for (BluetoothGattIncludedService isvc : inclSvcs) {
+                mIncludedServices.add(new BluetoothGattService(null, isvc.getUuid(),
+                        isvc.getInstanceId(), isvc.getType()));
+            }
+        }
+    }
+
+    /**
+     * Returns the device associated with this service.
+     *
+     * @hide
+     */
+    /*package*/ BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * Returns the device associated with this service.
+     *
+     * @hide
+     */
+    /*package*/ void setDevice(BluetoothDevice device) {
+        mDevice = device;
+    }
+
+    /**
+     * Add an included service to this service.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param service The service to be added
+     * @return true, if the included service was added to the service
+     */
+    public boolean addService(BluetoothGattService service) {
+        mIncludedServices.add(service);
+        return true;
+    }
+
+    /**
+     * Add a characteristic to this service.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param characteristic The characteristics to be added
+     * @return true, if the characteristic was added to the service
+     */
+    public boolean addCharacteristic(BluetoothGattCharacteristic characteristic) {
+        mCharacteristics.add(characteristic);
+        characteristic.setService(this);
+        return true;
+    }
+
+    /**
+     * Get characteristic by UUID and instanceId.
+     *
+     * @hide
+     */
+    /*package*/ BluetoothGattCharacteristic getCharacteristic(UUID uuid, int instanceId) {
+        for (BluetoothGattCharacteristic characteristic : mCharacteristics) {
+            if (uuid.equals(characteristic.getUuid())
+                    && characteristic.getInstanceId() == instanceId) {
+                return characteristic;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Force the instance ID.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void setInstanceId(int instanceId) {
+        mInstanceId = instanceId;
+    }
+
+    /**
+     * Get the handle count override (conformance testing.
+     *
+     * @hide
+     */
+    /*package*/ int getHandles() {
+        return mHandles;
+    }
+
+    /**
+     * Force the number of handles to reserve for this service.
+     * This is needed for conformance testing only.
+     *
+     * @hide
+     */
+    public void setHandles(int handles) {
+        mHandles = handles;
+    }
+
+    /**
+     * Add an included service to the internal map.
+     *
+     * @hide
+     */
+    public void addIncludedService(BluetoothGattService includedService) {
+        mIncludedServices.add(includedService);
+    }
+
+    /**
+     * Returns the UUID of this service
+     *
+     * @return UUID of this service
+     */
+    public UUID getUuid() {
+        return mUuid;
+    }
+
+    /**
+     * Returns the instance ID for this service
+     *
+     * <p>If a remote device offers multiple services with the same UUID
+     * (ex. multiple battery services for different batteries), the instance
+     * ID is used to distuinguish services.
+     *
+     * @return Instance ID of this service
+     */
+    public int getInstanceId() {
+        return mInstanceId;
+    }
+
+    /**
+     * Get the type of this service (primary/secondary)
+     */
+    public int getType() {
+        return mServiceType;
+    }
+
+    /**
+     * Get the list of included GATT services for this service.
+     *
+     * @return List of included services or empty list if no included services were discovered.
+     */
+    public List<BluetoothGattService> getIncludedServices() {
+        return mIncludedServices;
+    }
+
+    /**
+     * Returns a list of characteristics included in this service.
+     *
+     * @return Characteristics included in this service
+     */
+    public List<BluetoothGattCharacteristic> getCharacteristics() {
+        return mCharacteristics;
+    }
+
+    /**
+     * Returns a characteristic with a given UUID out of the list of
+     * characteristics offered by this service.
+     *
+     * <p>This is a convenience function to allow access to a given characteristic
+     * without enumerating over the list returned by {@link #getCharacteristics}
+     * manually.
+     *
+     * <p>If a remote service offers multiple characteristics with the same
+     * UUID, the first instance of a characteristic with the given UUID
+     * is returned.
+     *
+     * @return GATT characteristic object or null if no characteristic with the given UUID was
+     * found.
+     */
+    public BluetoothGattCharacteristic getCharacteristic(UUID uuid) {
+        for (BluetoothGattCharacteristic characteristic : mCharacteristics) {
+            if (uuid.equals(characteristic.getUuid())) {
+                return characteristic;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns whether the uuid of the service should be advertised.
+     *
+     * @hide
+     */
+    public boolean isAdvertisePreferred() {
+        return mAdvertisePreferred;
+    }
+
+    /**
+     * Set whether the service uuid should be advertised.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void setAdvertisePreferred(boolean advertisePreferred) {
+        mAdvertisePreferred = advertisePreferred;
+    }
+}
diff --git a/android/bluetooth/BluetoothHeadset.java b/android/bluetooth/BluetoothHeadset.java
new file mode 100644
index 0000000..6ce05f9
--- /dev/null
+++ b/android/bluetooth/BluetoothHeadset.java
@@ -0,0 +1,1272 @@
+/*
+ * Copyright (C) 2008 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Public API for controlling the Bluetooth Headset Service. This includes both
+ * Bluetooth Headset and Handsfree (v1.5) profiles.
+ *
+ * <p>BluetoothHeadset is a proxy object for controlling the Bluetooth Headset
+ * Service via IPC.
+ *
+ * <p> Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHeadset proxy object. Use
+ * {@link BluetoothAdapter#closeProfileProxy} to close the service connection.
+ *
+ * <p> Android only supports one connected Bluetooth Headset at a time.
+ * Each method is protected with its appropriate permission.
+ */
+public final class BluetoothHeadset implements BluetoothProfile {
+    private static final String TAG = "BluetoothHeadset";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the Headset
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.headset.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Intent used to broadcast the change in the Audio Connection state of the
+     * A2DP profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_AUDIO_CONNECTED}, {@link #STATE_AUDIO_DISCONNECTED},
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
+     * to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_AUDIO_STATE_CHANGED =
+            "android.bluetooth.headset.profile.action.AUDIO_STATE_CHANGED";
+
+    /**
+     * Intent used to broadcast the selection of a connected device as active.
+     *
+     * <p>This intent will have one extra:
+     * <ul>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
+     * be null if no device is active. </li>
+     * </ul>
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     *
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    @UnsupportedAppUsage
+    public static final String ACTION_ACTIVE_DEVICE_CHANGED =
+            "android.bluetooth.headset.profile.action.ACTIVE_DEVICE_CHANGED";
+
+    /**
+     * Intent used to broadcast that the headset has posted a
+     * vendor-specific event.
+     *
+     * <p>This intent will have 4 extras and 1 category.
+     * <ul>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Bluetooth Device
+     * </li>
+     * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD} - The vendor
+     * specific command </li>
+     * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE} - The AT
+     * command type which can be one of  {@link #AT_CMD_TYPE_READ},
+     * {@link #AT_CMD_TYPE_TEST}, or {@link #AT_CMD_TYPE_SET},
+     * {@link #AT_CMD_TYPE_BASIC},{@link #AT_CMD_TYPE_ACTION}. </li>
+     * <li> {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS} - Command
+     * arguments. </li>
+     * </ul>
+     *
+     * <p> The category is the Company ID of the vendor defining the
+     * vendor-specific command. {@link BluetoothAssignedNumbers}
+     *
+     * For example, for Plantronics specific events
+     * Category will be {@link #VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY}.55
+     *
+     * <p> For example, an AT+XEVENT=foo,3 will get translated into
+     * <ul>
+     * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD = +XEVENT </li>
+     * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE = AT_CMD_TYPE_SET </li>
+     * <li> EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS = foo, 3 </li>
+     * </ul>
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission
+     * to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_VENDOR_SPECIFIC_HEADSET_EVENT =
+            "android.bluetooth.headset.action.VENDOR_SPECIFIC_HEADSET_EVENT";
+
+    /**
+     * A String extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
+     * intents that contains the name of the vendor-specific command.
+     */
+    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD =
+            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD";
+
+    /**
+     * An int extra field in {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
+     * intents that contains the AT command type of the vendor-specific command.
+     */
+    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE =
+            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE";
+
+    /**
+     * AT command type READ used with
+     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
+     * For example, AT+VGM?. There are no arguments for this command type.
+     */
+    public static final int AT_CMD_TYPE_READ = 0;
+
+    /**
+     * AT command type TEST used with
+     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
+     * For example, AT+VGM=?. There are no arguments for this command type.
+     */
+    public static final int AT_CMD_TYPE_TEST = 1;
+
+    /**
+     * AT command type SET used with
+     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
+     * For example, AT+VGM=<args>.
+     */
+    public static final int AT_CMD_TYPE_SET = 2;
+
+    /**
+     * AT command type BASIC used with
+     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
+     * For example, ATD. Single character commands and everything following the
+     * character are arguments.
+     */
+    public static final int AT_CMD_TYPE_BASIC = 3;
+
+    /**
+     * AT command type ACTION used with
+     * {@link #EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_CMD_TYPE}
+     * For example, AT+CHUP. There are no arguments for action commands.
+     */
+    public static final int AT_CMD_TYPE_ACTION = 4;
+
+    /**
+     * A Parcelable String array extra field in
+     * {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT} intents that contains
+     * the arguments to the vendor-specific command.
+     */
+    public static final String EXTRA_VENDOR_SPECIFIC_HEADSET_EVENT_ARGS =
+            "android.bluetooth.headset.extra.VENDOR_SPECIFIC_HEADSET_EVENT_ARGS";
+
+    /**
+     * The intent category to be used with {@link #ACTION_VENDOR_SPECIFIC_HEADSET_EVENT}
+     * for the companyId
+     */
+    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_COMPANY_ID_CATEGORY =
+            "android.bluetooth.headset.intent.category.companyid";
+
+    /**
+     * A vendor-specific command for unsolicited result code.
+     */
+    public static final String VENDOR_RESULT_CODE_COMMAND_ANDROID = "+ANDROID";
+
+    /**
+     * A vendor-specific AT command
+     *
+     * @hide
+     */
+    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XAPL = "+XAPL";
+
+    /**
+     * A vendor-specific AT command
+     *
+     * @hide
+     */
+    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV = "+IPHONEACCEV";
+
+    /**
+     * Battery level indicator associated with
+     * {@link #VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV}
+     *
+     * @hide
+     */
+    public static final int VENDOR_SPECIFIC_HEADSET_EVENT_IPHONEACCEV_BATTERY_LEVEL = 1;
+
+    /**
+     * A vendor-specific AT command
+     *
+     * @hide
+     */
+    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT = "+XEVENT";
+
+    /**
+     * Battery level indicator associated with {@link #VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT}
+     *
+     * @hide
+     */
+    public static final String VENDOR_SPECIFIC_HEADSET_EVENT_XEVENT_BATTERY_LEVEL = "BATTERY";
+
+    /**
+     * Headset state when SCO audio is not connected.
+     * This state can be one of
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
+     */
+    public static final int STATE_AUDIO_DISCONNECTED = 10;
+
+    /**
+     * Headset state when SCO audio is connecting.
+     * This state can be one of
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
+     */
+    public static final int STATE_AUDIO_CONNECTING = 11;
+
+    /**
+     * Headset state when SCO audio is connected.
+     * This state can be one of
+     * {@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} of
+     * {@link #ACTION_AUDIO_STATE_CHANGED} intent.
+     */
+    public static final int STATE_AUDIO_CONNECTED = 12;
+
+    /**
+     * Intent used to broadcast the headset's indicator status
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_HF_INDICATORS_IND_ID} - The Assigned number of headset Indicator which
+     * is supported by the headset ( as indicated by AT+BIND command in the SLC
+     * sequence) or whose value is changed (indicated by AT+BIEV command) </li>
+     * <li> {@link #EXTRA_HF_INDICATORS_IND_VALUE} - Updated value of headset indicator. </li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - Remote device. </li>
+     * </ul>
+     * <p>{@link #EXTRA_HF_INDICATORS_IND_ID} is defined by Bluetooth SIG and each of the indicators
+     * are given an assigned number. Below shows the assigned number of Indicator added so far
+     * - Enhanced Safety - 1, Valid Values: 0 - Disabled, 1 - Enabled
+     * - Battery Level - 2, Valid Values: 0~100 - Remaining level of Battery
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
+     *
+     * @hide
+     */
+    public static final String ACTION_HF_INDICATORS_VALUE_CHANGED =
+            "android.bluetooth.headset.action.HF_INDICATORS_VALUE_CHANGED";
+
+    /**
+     * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
+     * intents that contains the assigned number of the headset indicator as defined by
+     * Bluetooth SIG that is being sent. Value range is 0-65535 as defined in HFP 1.7
+     *
+     * @hide
+     */
+    public static final String EXTRA_HF_INDICATORS_IND_ID =
+            "android.bluetooth.headset.extra.HF_INDICATORS_IND_ID";
+
+    /**
+     * A int extra field in {@link #ACTION_HF_INDICATORS_VALUE_CHANGED}
+     * intents that contains the value of the Headset indicator that is being sent.
+     *
+     * @hide
+     */
+    public static final String EXTRA_HF_INDICATORS_IND_VALUE =
+            "android.bluetooth.headset.extra.HF_INDICATORS_IND_VALUE";
+
+    private static final int MESSAGE_HEADSET_SERVICE_CONNECTED = 100;
+    private static final int MESSAGE_HEADSET_SERVICE_DISCONNECTED = 101;
+
+    private Context mContext;
+    private ServiceListener mServiceListener;
+    private volatile IBluetoothHeadset mService;
+    private BluetoothAdapter mAdapter;
+
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+                public void onBluetoothStateChange(boolean up) {
+                    if (DBG) Log.d(TAG, "onBluetoothStateChange: up=" + up);
+                    if (!up) {
+                        doUnbind();
+                    } else {
+                        doBind();
+                    }
+                }
+            };
+
+    /**
+     * Create a BluetoothHeadset proxy object.
+     */
+    /*package*/ BluetoothHeadset(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException e) {
+                Log.e(TAG, "", e);
+            }
+        }
+
+        doBind();
+    }
+
+    private boolean doBind() {
+        synchronized (mConnection) {
+            if (mService == null) {
+                if (VDBG) Log.d(TAG, "Binding service...");
+                try {
+                    return mAdapter.getBluetoothManager().bindBluetoothProfileService(
+                            BluetoothProfile.HEADSET, mConnection);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to bind HeadsetService", e);
+                }
+            }
+        }
+        return false;
+    }
+
+    private void doUnbind() {
+        synchronized (mConnection) {
+            if (mService != null) {
+                if (VDBG) Log.d(TAG, "Unbinding service...");
+                try {
+                    mAdapter.getBluetoothManager().unbindBluetoothProfileService(
+                            BluetoothProfile.HEADSET, mConnection);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Unable to unbind HeadsetService", e);
+                } finally {
+                    mService = null;
+                }
+            }
+        }
+    }
+
+    /**
+     * Close the connection to the backing service.
+     * Other public functions of BluetoothHeadset will return default error
+     * results once close() has been called. Multiple invocations of close()
+     * are ok.
+     */
+    @UnsupportedAppUsage
+    /*package*/ void close() {
+        if (VDBG) log("close()");
+
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "", re);
+            }
+        }
+        mServiceListener = null;
+        doUnbind();
+    }
+
+    /**
+     * Initiate connection to a profile of the remote bluetooth device.
+     *
+     * <p> Currently, the system supports only 1 connection to the
+     * headset/handsfree profile. The API will automatically disconnect connected
+     * devices before connecting.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is already connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that
+     * connection state intent for the profile will be broadcasted with
+     * the state. Users can get the connection state of the profile
+     * from this intent.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Initiate disconnection from a profile
+     *
+     * <p> This API will return false in scenarios like the profile on the
+     * Bluetooth device is not in connected state etc. When this API returns,
+     * true, it is guaranteed that the connection state change
+     * intent will be broadcasted with the state. Users can get the
+     * disconnection state of the profile from this intent.
+     *
+     * <p> If the disconnection is initiated by a remote device, the state
+     * will transition from {@link #STATE_CONNECTED} to
+     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
+     * host (local) device the state will transition from
+     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
+     * state {@link #STATE_DISCONNECTED}. The transition to
+     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
+     * two scenarios.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (VDBG) log("getConnectionState(" + device + ")");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link BluetoothProfile#PRIORITY_ON} or
+     * {@link BluetoothProfile#PRIORITY_OFF}
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     * @deprecated Replaced with {@link #setConnectionPolicy(BluetoothDevice, int)}
+     */
+    @Deprecated
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (priority != BluetoothProfile.PRIORITY_OFF
+                    && priority != BluetoothProfile.PRIORITY_ON) {
+                return false;
+            }
+            try {
+                return service.setPriority(
+                        device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                return false;
+            }
+            try {
+                return service.setConnectionPolicy(device, connectionPolicy);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_AUTO_CONNECT}, {@link #PRIORITY_OFF},
+     * {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return BluetoothAdapter.connectionPolicyToPriority(service.getPriority(device));
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.PRIORITY_OFF;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.PRIORITY_OFF;
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
+        if (VDBG) log("getConnectionPolicy(" + device + ")");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionPolicy(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    /**
+     * Start Bluetooth voice recognition. This methods sends the voice
+     * recognition AT command to the headset and establishes the
+     * audio connection.
+     *
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * If this function returns true, this intent will be broadcasted with
+     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
+     *
+     * <p> {@link #EXTRA_STATE} will transition from
+     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
+     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
+     * in case of failure to establish the audio connection.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Bluetooth headset
+     * @return false if there is no headset connected, or the connected headset doesn't support
+     * voice recognition, or voice recognition is already started, or audio channel is occupied,
+     * or on error, true otherwise
+     */
+    public boolean startVoiceRecognition(BluetoothDevice device) {
+        if (DBG) log("startVoiceRecognition()");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.startVoiceRecognition(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Stop Bluetooth Voice Recognition mode, and shut down the
+     * Bluetooth audio path.
+     *
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * If this function returns true, this intent will be broadcasted with
+     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Bluetooth headset
+     * @return false if there is no headset connected, or voice recognition has not started,
+     * or voice recognition has ended on this headset, or on error, true otherwise
+     */
+    public boolean stopVoiceRecognition(BluetoothDevice device) {
+        if (DBG) log("stopVoiceRecognition()");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.stopVoiceRecognition(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Check if Bluetooth SCO audio is connected.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Bluetooth headset
+     * @return true if SCO is connected, false otherwise or on error
+     */
+    public boolean isAudioConnected(BluetoothDevice device) {
+        if (VDBG) log("isAudioConnected()");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.isAudioConnected(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Indicates if current platform supports voice dialing over bluetooth SCO.
+     *
+     * @return true if voice dialing over bluetooth is supported, false otherwise.
+     * @hide
+     */
+    public static boolean isBluetoothVoiceDialingEnabled(Context context) {
+        return context.getResources().getBoolean(
+                com.android.internal.R.bool.config_bluetooth_sco_off_call);
+    }
+
+    /**
+     * Get the current audio state of the Headset.
+     * Note: This is an internal function and shouldn't be exposed
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int getAudioState(BluetoothDevice device) {
+        if (VDBG) log("getAudioState");
+        final IBluetoothHeadset service = mService;
+        if (service != null && !isDisabled()) {
+            try {
+                return service.getAudioState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+    }
+
+    /**
+     * Sets whether audio routing is allowed. When set to {@code false}, the AG will not route any
+     * audio to the HF unless explicitly told to.
+     * This method should be used in cases where the SCO channel is shared between multiple profiles
+     * and must be delegated by a source knowledgeable
+     * Note: This is an internal function and shouldn't be exposed
+     *
+     * @param allowed {@code true} if the profile can reroute audio, {@code false} otherwise.
+     * @hide
+     */
+    public void setAudioRouteAllowed(boolean allowed) {
+        if (VDBG) log("setAudioRouteAllowed");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                service.setAudioRouteAllowed(allowed);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+    }
+
+    /**
+     * Returns whether audio routing is allowed. see {@link #setAudioRouteAllowed(boolean)}.
+     * Note: This is an internal function and shouldn't be exposed
+     *
+     * @hide
+     */
+    public boolean getAudioRouteAllowed() {
+        if (VDBG) log("getAudioRouteAllowed");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.getAudioRouteAllowed();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Force SCO audio to be opened regardless any other restrictions
+     *
+     * @param forced Whether or not SCO audio connection should be forced: True to force SCO audio
+     * False to use SCO audio in normal manner
+     * @hide
+     */
+    public void setForceScoAudio(boolean forced) {
+        if (VDBG) log("setForceScoAudio " + String.valueOf(forced));
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                service.setForceScoAudio(forced);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+    }
+
+    /**
+     * Check if at least one headset's SCO audio is connected or connecting
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @return true if at least one device's SCO audio is connected or connecting, false otherwise
+     * or on error
+     * @hide
+     */
+    public boolean isAudioOn() {
+        if (VDBG) log("isAudioOn()");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.isAudioOn();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+
+    }
+
+    /**
+     * Initiates a connection of headset audio to the current active device
+     *
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * If this function returns true, this intent will be broadcasted with
+     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
+     *
+     * <p> {@link #EXTRA_STATE} will transition from
+     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
+     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
+     * in case of failure to establish the audio connection.
+     *
+     * Note that this intent will not be sent if {@link BluetoothHeadset#isAudioOn()} is true
+     * before calling this method
+     *
+     * @return false if there was some error such as there is no active headset
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean connectAudio() {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.connectAudio();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Initiates a disconnection of HFP SCO audio.
+     * Tear down voice recognition or virtual voice call if any.
+     *
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * If this function returns true, this intent will be broadcasted with
+     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
+     *
+     * @return false if audio is not connected, or on error, true otherwise
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean disconnectAudio() {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.disconnectAudio();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Initiates a SCO channel connection as a virtual voice call to the current active device
+     * Active handsfree device will be notified of incoming call and connected call.
+     *
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * If this function returns true, this intent will be broadcasted with
+     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_CONNECTING}.
+     *
+     * <p> {@link #EXTRA_STATE} will transition from
+     * {@link #STATE_AUDIO_CONNECTING} to {@link #STATE_AUDIO_CONNECTED} when
+     * audio connection is established and to {@link #STATE_AUDIO_DISCONNECTED}
+     * in case of failure to establish the audio connection.
+     *
+     * @return true if successful, false if one of the following case applies
+     *  - SCO audio is not idle (connecting or connected)
+     *  - virtual call has already started
+     *  - there is no active device
+     *  - a Telecom managed call is going on
+     *  - binder is dead or Bluetooth is disabled or other error
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @UnsupportedAppUsage
+    public boolean startScoUsingVirtualVoiceCall() {
+        if (DBG) log("startScoUsingVirtualVoiceCall()");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.startScoUsingVirtualVoiceCall();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Terminates an ongoing SCO connection and the associated virtual call.
+     *
+     * <p> Users can listen to {@link #ACTION_AUDIO_STATE_CHANGED}.
+     * If this function returns true, this intent will be broadcasted with
+     * {@link #EXTRA_STATE} set to {@link #STATE_AUDIO_DISCONNECTED}.
+     *
+     * @return true if successful, false if one of the following case applies
+     *  - virtual voice call is not started or has ended
+     *  - binder is dead or Bluetooth is disabled or other error
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    @UnsupportedAppUsage
+    public boolean stopScoUsingVirtualVoiceCall() {
+        if (DBG) log("stopScoUsingVirtualVoiceCall()");
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.stopScoUsingVirtualVoiceCall();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Notify Headset of phone state change.
+     * This is a backdoor for phone app to call BluetoothHeadset since
+     * there is currently not a good way to get precise call state change outside
+     * of phone app.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void phoneStateChanged(int numActive, int numHeld, int callState, String number,
+            int type, String name) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                service.phoneStateChanged(numActive, numHeld, callState, number, type, name);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+    }
+
+    /**
+     * Send Headset of CLCC response
+     *
+     * @hide
+     */
+    public void clccResponse(int index, int direction, int status, int mode, boolean mpty,
+            String number, int type) {
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                service.clccResponse(index, direction, status, mode, mpty, number, type);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+    }
+
+    /**
+     * Sends a vendor-specific unsolicited result code to the headset.
+     *
+     * <p>The actual string to be sent is <code>command + ": " + arg</code>. For example, if {@code
+     * command} is {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} and {@code arg} is {@code "0"}, the
+     * string <code>"+ANDROID: 0"</code> will be sent.
+     *
+     * <p>Currently only {@link #VENDOR_RESULT_CODE_COMMAND_ANDROID} is allowed as {@code command}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device Bluetooth headset.
+     * @param command A vendor-specific command.
+     * @param arg The argument that will be attached to the command.
+     * @return {@code false} if there is no headset connected, or if the command is not an allowed
+     * vendor-specific unsolicited result code, or on error. {@code true} otherwise.
+     * @throws IllegalArgumentException if {@code command} is {@code null}.
+     */
+    public boolean sendVendorSpecificResultCode(BluetoothDevice device, String command,
+            String arg) {
+        if (DBG) {
+            log("sendVendorSpecificResultCode()");
+        }
+        if (command == null) {
+            throw new IllegalArgumentException("command is null");
+        }
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.sendVendorSpecificResultCode(device, command, arg);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return false;
+    }
+
+    /**
+     * Select a connected device as active.
+     *
+     * The active device selection is per profile. An active device's
+     * purpose is profile-specific. For example, in HFP and HSP profiles,
+     * it is the device used for phone call audio. If a remote device is not
+     * connected, it cannot be selected as active.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is not connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that the
+     * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
+     * with the active device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device Remote Bluetooth Device, could be null if phone call audio should not be
+     * streamed to a headset
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH_ADMIN)
+    @UnsupportedAppUsage
+    public boolean setActiveDevice(@Nullable BluetoothDevice device) {
+        if (DBG) {
+            Log.d(TAG, "setActiveDevice: " + device);
+        }
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled() && (device == null || isValidDevice(device))) {
+            try {
+                return service.setActiveDevice(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return false;
+    }
+
+    /**
+     * Get the connected device that is active.
+     *
+     * @return the connected device that is active or null if no device
+     * is active.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @Nullable
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public BluetoothDevice getActiveDevice() {
+        if (VDBG) {
+            Log.d(TAG, "getActiveDevice");
+        }
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.getActiveDevice();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return null;
+    }
+
+    /**
+     * Check if in-band ringing is currently enabled. In-band ringing could be disabled during an
+     * active connection.
+     *
+     * @return true if in-band ringing is enabled, false if in-band ringing is disabled
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.BLUETOOTH)
+    public boolean isInbandRingingEnabled() {
+        if (DBG) {
+            log("isInbandRingingEnabled()");
+        }
+        final IBluetoothHeadset service = mService;
+        if (service != null && isEnabled()) {
+            try {
+                return service.isInbandRingingEnabled();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return false;
+    }
+
+    /**
+     * Check if in-band ringing is supported for this platform.
+     *
+     * @return true if in-band ringing is supported, false if in-band ringing is not supported
+     * @hide
+     */
+    public static boolean isInbandRingingSupported(Context context) {
+        return context.getResources().getBoolean(
+                com.android.internal.R.bool.config_bluetooth_hfp_inband_ringing_support);
+    }
+
+    private final IBluetoothProfileServiceConnection mConnection =
+            new IBluetoothProfileServiceConnection.Stub() {
+        @Override
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) Log.d(TAG, "Proxy object connected");
+            mService = IBluetoothHeadset.Stub.asInterface(Binder.allowBlocking(service));
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    MESSAGE_HEADSET_SERVICE_CONNECTED));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName className) {
+            if (DBG) Log.d(TAG, "Proxy object disconnected");
+            doUnbind();
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    MESSAGE_HEADSET_SERVICE_DISCONNECTED));
+        }
+    };
+
+    @UnsupportedAppUsage
+    private boolean isEnabled() {
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
+    }
+
+    private boolean isDisabled() {
+        return mAdapter.getState() == BluetoothAdapter.STATE_OFF;
+    }
+
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    private final Handler mHandler = new Handler(Looper.getMainLooper()) {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_HEADSET_SERVICE_CONNECTED: {
+                    if (mServiceListener != null) {
+                        mServiceListener.onServiceConnected(BluetoothProfile.HEADSET,
+                                BluetoothHeadset.this);
+                    }
+                    break;
+                }
+                case MESSAGE_HEADSET_SERVICE_DISCONNECTED: {
+                    if (mServiceListener != null) {
+                        mServiceListener.onServiceDisconnected(BluetoothProfile.HEADSET);
+                    }
+                    break;
+                }
+            }
+        }
+    };
+}
diff --git a/android/bluetooth/BluetoothHeadsetClient.java b/android/bluetooth/BluetoothHeadsetClient.java
new file mode 100644
index 0000000..85e0e08
--- /dev/null
+++ b/android/bluetooth/BluetoothHeadsetClient.java
@@ -0,0 +1,1168 @@
+/*
+ * Copyright (C) 2014 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Public API to control Hands Free Profile (HFP role only).
+ * <p>
+ * This class defines methods that shall be used by application to manage profile
+ * connection, calls states and calls actions.
+ * <p>
+ *
+ * @hide
+ */
+public final class BluetoothHeadsetClient implements BluetoothProfile {
+    private static final String TAG = "BluetoothHeadsetClient";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent sent whenever connection to remote changes.
+     *
+     * <p>It includes two extras:
+     * <code>BluetoothProfile.EXTRA_PREVIOUS_STATE</code>
+     * and <code>BluetoothProfile.EXTRA_STATE</code>, which
+     * are mandatory.
+     * <p>There are also non mandatory feature extras:
+     * {@link #EXTRA_AG_FEATURE_3WAY_CALLING},
+     * {@link #EXTRA_AG_FEATURE_VOICE_RECOGNITION},
+     * {@link #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT},
+     * {@link #EXTRA_AG_FEATURE_REJECT_CALL},
+     * {@link #EXTRA_AG_FEATURE_ECC},
+     * {@link #EXTRA_AG_FEATURE_RESPONSE_AND_HOLD},
+     * {@link #EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL},
+     * {@link #EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL},
+     * {@link #EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT},
+     * {@link #EXTRA_AG_FEATURE_MERGE},
+     * {@link #EXTRA_AG_FEATURE_MERGE_AND_DETACH},
+     * sent as boolean values only when <code>EXTRA_STATE</code>
+     * is set to <code>STATE_CONNECTED</code>.</p>
+     *
+     * <p>Note that features supported by AG are being sent as
+     * booleans with value <code>true</code>,
+     * and not supported ones are <strong>not</strong> being sent at all.</p>
+     */
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.headsetclient.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Intent sent whenever audio state changes.
+     *
+     * <p>It includes two mandatory extras:
+     * {@link BluetoothProfile#EXTRA_STATE},
+     * {@link BluetoothProfile#EXTRA_PREVIOUS_STATE},
+     * with possible values:
+     * {@link #STATE_AUDIO_CONNECTING},
+     * {@link #STATE_AUDIO_CONNECTED},
+     * {@link #STATE_AUDIO_DISCONNECTED}</p>
+     * <p>When <code>EXTRA_STATE</code> is set
+     * to </code>STATE_AUDIO_CONNECTED</code>,
+     * it also includes {@link #EXTRA_AUDIO_WBS}
+     * indicating wide band speech support.</p>
+     */
+    public static final String ACTION_AUDIO_STATE_CHANGED =
+            "android.bluetooth.headsetclient.profile.action.AUDIO_STATE_CHANGED";
+
+    /**
+     * Intent sending updates of the Audio Gateway state.
+     * Each extra is being sent only when value it
+     * represents has been changed recently on AG.
+     * <p>It can contain one or more of the following extras:
+     * {@link #EXTRA_NETWORK_STATUS},
+     * {@link #EXTRA_NETWORK_SIGNAL_STRENGTH},
+     * {@link #EXTRA_NETWORK_ROAMING},
+     * {@link #EXTRA_BATTERY_LEVEL},
+     * {@link #EXTRA_OPERATOR_NAME},
+     * {@link #EXTRA_VOICE_RECOGNITION},
+     * {@link #EXTRA_IN_BAND_RING}</p>
+     */
+    public static final String ACTION_AG_EVENT =
+            "android.bluetooth.headsetclient.profile.action.AG_EVENT";
+
+    /**
+     * Intent sent whenever state of a call changes.
+     *
+     * <p>It includes:
+     * {@link #EXTRA_CALL},
+     * with value of {@link BluetoothHeadsetClientCall} instance,
+     * representing actual call state.</p>
+     */
+    public static final String ACTION_CALL_CHANGED =
+            "android.bluetooth.headsetclient.profile.action.AG_CALL_CHANGED";
+
+    /**
+     * Intent that notifies about the result of the last issued action.
+     * Please note that not every action results in explicit action result code being sent.
+     * Instead other notifications about new Audio Gateway state might be sent,
+     * like <code>ACTION_AG_EVENT</code> with <code>EXTRA_VOICE_RECOGNITION</code> value
+     * when for example user started voice recognition from HF unit.
+     */
+    public static final String ACTION_RESULT =
+            "android.bluetooth.headsetclient.profile.action.RESULT";
+
+    /**
+     * Intent that notifies about vendor specific event arrival. Events not defined in
+     * HFP spec will be matched with supported vendor event list and this intent will
+     * be broadcasted upon a match. Supported vendor events are of format of
+     * of "+eventCode" or "+eventCode=xxxx" or "+eventCode:=xxxx".
+     * Vendor event can be a response to an vendor specific command or unsolicited.
+     *
+     */
+    public static final String ACTION_VENDOR_SPECIFIC_HEADSETCLIENT_EVENT =
+            "android.bluetooth.headsetclient.profile.action.VENDOR_SPECIFIC_EVENT";
+
+    /**
+     * Intent that notifies about the number attached to the last voice tag
+     * recorded on AG.
+     *
+     * <p>It contains:
+     * {@link #EXTRA_NUMBER},
+     * with a <code>String</code> value representing phone number.</p>
+     */
+    public static final String ACTION_LAST_VTAG =
+            "android.bluetooth.headsetclient.profile.action.LAST_VTAG";
+
+    public static final int STATE_AUDIO_DISCONNECTED = 0;
+    public static final int STATE_AUDIO_CONNECTING = 1;
+    public static final int STATE_AUDIO_CONNECTED = 2;
+
+    /**
+     * Extra with information if connected audio is WBS.
+     * <p>Possible values: <code>true</code>,
+     * <code>false</code>.</p>
+     */
+    public static final String EXTRA_AUDIO_WBS =
+            "android.bluetooth.headsetclient.extra.AUDIO_WBS";
+
+    /**
+     * Extra for AG_EVENT indicates network status.
+     * <p>Value: 0 - network unavailable,
+     * 1 - network available </p>
+     */
+    public static final String EXTRA_NETWORK_STATUS =
+            "android.bluetooth.headsetclient.extra.NETWORK_STATUS";
+    /**
+     * Extra for AG_EVENT intent indicates network signal strength.
+     * <p>Value: <code>Integer</code> representing signal strength.</p>
+     */
+    public static final String EXTRA_NETWORK_SIGNAL_STRENGTH =
+            "android.bluetooth.headsetclient.extra.NETWORK_SIGNAL_STRENGTH";
+    /**
+     * Extra for AG_EVENT intent indicates roaming state.
+     * <p>Value: 0 - no roaming
+     * 1 - active roaming</p>
+     */
+    public static final String EXTRA_NETWORK_ROAMING =
+            "android.bluetooth.headsetclient.extra.NETWORK_ROAMING";
+    /**
+     * Extra for AG_EVENT intent indicates the battery level.
+     * <p>Value: <code>Integer</code> representing signal strength.</p>
+     */
+    public static final String EXTRA_BATTERY_LEVEL =
+            "android.bluetooth.headsetclient.extra.BATTERY_LEVEL";
+    /**
+     * Extra for AG_EVENT intent indicates operator name.
+     * <p>Value: <code>String</code> representing operator name.</p>
+     */
+    public static final String EXTRA_OPERATOR_NAME =
+            "android.bluetooth.headsetclient.extra.OPERATOR_NAME";
+    /**
+     * Extra for AG_EVENT intent indicates voice recognition state.
+     * <p>Value:
+     * 0 - voice recognition stopped,
+     * 1 - voice recognition started.</p>
+     */
+    public static final String EXTRA_VOICE_RECOGNITION =
+            "android.bluetooth.headsetclient.extra.VOICE_RECOGNITION";
+    /**
+     * Extra for AG_EVENT intent indicates in band ring state.
+     * <p>Value:
+     * 0 - in band ring tone not supported, or
+     * 1 - in band ring tone supported.</p>
+     */
+    public static final String EXTRA_IN_BAND_RING =
+            "android.bluetooth.headsetclient.extra.IN_BAND_RING";
+
+    /**
+     * Extra for AG_EVENT intent indicates subscriber info.
+     * <p>Value: <code>String</code> containing subscriber information.</p>
+     */
+    public static final String EXTRA_SUBSCRIBER_INFO =
+            "android.bluetooth.headsetclient.extra.SUBSCRIBER_INFO";
+
+    /**
+     * Extra for AG_CALL_CHANGED intent indicates the
+     * {@link BluetoothHeadsetClientCall} object that has changed.
+     */
+    public static final String EXTRA_CALL =
+            "android.bluetooth.headsetclient.extra.CALL";
+
+    /**
+     * Extra for ACTION_LAST_VTAG intent.
+     * <p>Value: <code>String</code> representing phone number
+     * corresponding to last voice tag recorded on AG</p>
+     */
+    public static final String EXTRA_NUMBER =
+            "android.bluetooth.headsetclient.extra.NUMBER";
+
+    /**
+     * Extra for ACTION_RESULT intent that shows the result code of
+     * last issued action.
+     * <p>Possible results:
+     * {@link #ACTION_RESULT_OK},
+     * {@link #ACTION_RESULT_ERROR},
+     * {@link #ACTION_RESULT_ERROR_NO_CARRIER},
+     * {@link #ACTION_RESULT_ERROR_BUSY},
+     * {@link #ACTION_RESULT_ERROR_NO_ANSWER},
+     * {@link #ACTION_RESULT_ERROR_DELAYED},
+     * {@link #ACTION_RESULT_ERROR_BLACKLISTED},
+     * {@link #ACTION_RESULT_ERROR_CME}</p>
+     */
+    public static final String EXTRA_RESULT_CODE =
+            "android.bluetooth.headsetclient.extra.RESULT_CODE";
+
+    /**
+     * Extra for ACTION_RESULT intent that shows the extended result code of
+     * last issued action.
+     * <p>Value: <code>Integer</code> - error code.</p>
+     */
+    public static final String EXTRA_CME_CODE =
+            "android.bluetooth.headsetclient.extra.CME_CODE";
+
+    /**
+     * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
+     * indicates vendor ID.
+     */
+    public static final String EXTRA_VENDOR_ID =
+            "android.bluetooth.headsetclient.extra.VENDOR_ID";
+
+     /**
+     * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
+     * indicates vendor event code.
+     */
+    public static final String EXTRA_VENDOR_EVENT_CODE =
+            "android.bluetooth.headsetclient.extra.VENDOR_EVENT_CODE";
+
+     /**
+     * Extra for VENDOR_SPECIFIC_HEADSETCLIENT_EVENT intent that
+     * contains full vendor event including event code and full arguments.
+     */
+    public static final String EXTRA_VENDOR_EVENT_FULL_ARGS =
+            "android.bluetooth.headsetclient.extra.VENDOR_EVENT_FULL_ARGS";
+
+
+    /* Extras for AG_FEATURES, extras type is boolean */
+    // TODO verify if all of those are actually useful
+    /**
+     * AG feature: three way calling.
+     */
+    public static final String EXTRA_AG_FEATURE_3WAY_CALLING =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_3WAY_CALLING";
+    /**
+     * AG feature: voice recognition.
+     */
+    public static final String EXTRA_AG_FEATURE_VOICE_RECOGNITION =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_VOICE_RECOGNITION";
+    /**
+     * AG feature: fetching phone number for voice tagging procedure.
+     */
+    public static final String EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT";
+    /**
+     * AG feature: ability to reject incoming call.
+     */
+    public static final String EXTRA_AG_FEATURE_REJECT_CALL =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_REJECT_CALL";
+    /**
+     * AG feature: enhanced call handling (terminate specific call, private consultation).
+     */
+    public static final String EXTRA_AG_FEATURE_ECC =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ECC";
+    /**
+     * AG feature: response and hold.
+     */
+    public static final String EXTRA_AG_FEATURE_RESPONSE_AND_HOLD =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RESPONSE_AND_HOLD";
+    /**
+     * AG call handling feature: accept held or waiting call in three way calling scenarios.
+     */
+    public static final String EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_ACCEPT_HELD_OR_WAITING_CALL";
+    /**
+     * AG call handling feature: release held or waiting call in three way calling scenarios.
+     */
+    public static final String EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_HELD_OR_WAITING_CALL";
+    /**
+     * AG call handling feature: release active call and accept held or waiting call in three way
+     * calling scenarios.
+     */
+    public static final String EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_RELEASE_AND_ACCEPT";
+    /**
+     * AG call handling feature: merge two calls, held and active - multi party conference mode.
+     */
+    public static final String EXTRA_AG_FEATURE_MERGE =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE";
+    /**
+     * AG call handling feature: merge calls and disconnect from multi party
+     * conversation leaving peers connected to each other.
+     * Note that this feature needs to be supported by mobile network operator
+     * as it requires connection and billing transfer.
+     */
+    public static final String EXTRA_AG_FEATURE_MERGE_AND_DETACH =
+            "android.bluetooth.headsetclient.extra.EXTRA_AG_FEATURE_MERGE_AND_DETACH";
+
+    /* Action result codes */
+    public static final int ACTION_RESULT_OK = 0;
+    public static final int ACTION_RESULT_ERROR = 1;
+    public static final int ACTION_RESULT_ERROR_NO_CARRIER = 2;
+    public static final int ACTION_RESULT_ERROR_BUSY = 3;
+    public static final int ACTION_RESULT_ERROR_NO_ANSWER = 4;
+    public static final int ACTION_RESULT_ERROR_DELAYED = 5;
+    public static final int ACTION_RESULT_ERROR_BLACKLISTED = 6;
+    public static final int ACTION_RESULT_ERROR_CME = 7;
+
+    /* Detailed CME error codes */
+    public static final int CME_PHONE_FAILURE = 0;
+    public static final int CME_NO_CONNECTION_TO_PHONE = 1;
+    public static final int CME_OPERATION_NOT_ALLOWED = 3;
+    public static final int CME_OPERATION_NOT_SUPPORTED = 4;
+    public static final int CME_PHSIM_PIN_REQUIRED = 5;
+    public static final int CME_PHFSIM_PIN_REQUIRED = 6;
+    public static final int CME_PHFSIM_PUK_REQUIRED = 7;
+    public static final int CME_SIM_NOT_INSERTED = 10;
+    public static final int CME_SIM_PIN_REQUIRED = 11;
+    public static final int CME_SIM_PUK_REQUIRED = 12;
+    public static final int CME_SIM_FAILURE = 13;
+    public static final int CME_SIM_BUSY = 14;
+    public static final int CME_SIM_WRONG = 15;
+    public static final int CME_INCORRECT_PASSWORD = 16;
+    public static final int CME_SIM_PIN2_REQUIRED = 17;
+    public static final int CME_SIM_PUK2_REQUIRED = 18;
+    public static final int CME_MEMORY_FULL = 20;
+    public static final int CME_INVALID_INDEX = 21;
+    public static final int CME_NOT_FOUND = 22;
+    public static final int CME_MEMORY_FAILURE = 23;
+    public static final int CME_TEXT_STRING_TOO_LONG = 24;
+    public static final int CME_INVALID_CHARACTER_IN_TEXT_STRING = 25;
+    public static final int CME_DIAL_STRING_TOO_LONG = 26;
+    public static final int CME_INVALID_CHARACTER_IN_DIAL_STRING = 27;
+    public static final int CME_NO_NETWORK_SERVICE = 30;
+    public static final int CME_NETWORK_TIMEOUT = 31;
+    public static final int CME_EMERGENCY_SERVICE_ONLY = 32;
+    public static final int CME_NO_SIMULTANOUS_VOIP_CS_CALLS = 33;
+    public static final int CME_NOT_SUPPORTED_FOR_VOIP = 34;
+    public static final int CME_SIP_RESPONSE_CODE = 35;
+    public static final int CME_NETWORK_PERSONALIZATION_PIN_REQUIRED = 40;
+    public static final int CME_NETWORK_PERSONALIZATION_PUK_REQUIRED = 41;
+    public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PIN_REQUIRED = 42;
+    public static final int CME_NETWORK_SUBSET_PERSONALIZATION_PUK_REQUIRED = 43;
+    public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PIN_REQUIRED = 44;
+    public static final int CME_SERVICE_PROVIDER_PERSONALIZATION_PUK_REQUIRED = 45;
+    public static final int CME_CORPORATE_PERSONALIZATION_PIN_REQUIRED = 46;
+    public static final int CME_CORPORATE_PERSONALIZATION_PUK_REQUIRED = 47;
+    public static final int CME_HIDDEN_KEY_REQUIRED = 48;
+    public static final int CME_EAP_NOT_SUPPORTED = 49;
+    public static final int CME_INCORRECT_PARAMETERS = 50;
+
+    /* Action policy for other calls when accepting call */
+    public static final int CALL_ACCEPT_NONE = 0;
+    public static final int CALL_ACCEPT_HOLD = 1;
+    public static final int CALL_ACCEPT_TERMINATE = 2;
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothHeadsetClient> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.HEADSET_CLIENT,
+                    "BluetoothHeadsetClient", IBluetoothHeadsetClient.class.getName()) {
+                @Override
+                public IBluetoothHeadsetClient getServiceInterface(IBinder service) {
+                    return IBluetoothHeadsetClient.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
+    /**
+     * Create a BluetoothHeadsetClient proxy object.
+     */
+    /*package*/ BluetoothHeadsetClient(Context context, ServiceListener listener) {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+    }
+
+    /**
+     * Close the connection to the backing service.
+     * Other public functions of BluetoothHeadsetClient will return default error
+     * results once close() has been called. Multiple invocations of close()
+     * are ok.
+     */
+    /*package*/ void close() {
+        if (VDBG) log("close()");
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothHeadsetClient getService() {
+        return mProfileConnector.getService();
+    }
+
+    /**
+     * Connects to remote device.
+     *
+     * Currently, the system supports only 1 connection. So, in case of the
+     * second connection, this implementation will disconnect already connected
+     * device automatically and will process the new one.
+     *
+     * @param device a remote device we want connect to
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Disconnects remote device
+     *
+     * @param device a remote device we want disconnect
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_CONNECTION_STATE_CHANGED} intent.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Return the list of connected remote devices
+     *
+     * @return list of connected devices; empty list if nothing is connected.
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Returns list of remote devices in a particular state
+     *
+     * @param states collection of states
+     * @return list of devices that state matches the states listed in <code>states</code>; empty
+     * list if nothing matches the <code>states</code>
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Returns state of the <code>device</code>
+     *
+     * @param device a remote device
+     * @return the state of connection of the device
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (VDBG) log("getConnectionState(" + device + ")");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF}
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                return false;
+            }
+            try {
+                return service.setConnectionPolicy(device, connectionPolicy);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
+        if (VDBG) log("getConnectionPolicy(" + device + ")");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionPolicy(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    /**
+     * Starts voice recognition.
+     *
+     * @param device remote device
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent.
+     *
+     * <p>Feature required for successful execution is being reported by: {@link
+     * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
+     * is not supported.</p>
+     */
+    public boolean startVoiceRecognition(BluetoothDevice device) {
+        if (DBG) log("startVoiceRecognition()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.startVoiceRecognition(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send vendor specific AT command.
+     *
+     * @param device remote device
+     * @param vendorId vendor number by Bluetooth SIG
+     * @param atCommand command to be sent. It start with + prefix and only one command at one time.
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise.
+     */
+    public boolean sendVendorAtCommand(BluetoothDevice device, int vendorId,
+                                             String atCommand) {
+        if (DBG) log("sendVendorSpecificCommand()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.sendVendorAtCommand(device, vendorId, atCommand);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Stops voice recognition.
+     *
+     * @param device remote device
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_AG_EVENT} intent.
+     *
+     * <p>Feature required for successful execution is being reported by: {@link
+     * #EXTRA_AG_FEATURE_VOICE_RECOGNITION}. This method invocation will fail silently when feature
+     * is not supported.</p>
+     */
+    public boolean stopVoiceRecognition(BluetoothDevice device) {
+        if (DBG) log("stopVoiceRecognition()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.stopVoiceRecognition(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Returns list of all calls in any state.
+     *
+     * @param device remote device
+     * @return list of calls; empty list if none call exists
+     */
+    public List<BluetoothHeadsetClientCall> getCurrentCalls(BluetoothDevice device) {
+        if (DBG) log("getCurrentCalls()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getCurrentCalls(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return null;
+    }
+
+    /**
+     * Returns list of current values of AG indicators.
+     *
+     * @param device remote device
+     * @return bundle of AG  indicators; null if device is not in CONNECTED state
+     */
+    public Bundle getCurrentAgEvents(BluetoothDevice device) {
+        if (DBG) log("getCurrentCalls()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getCurrentAgEvents(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return null;
+    }
+
+    /**
+     * Accepts a call
+     *
+     * @param device remote device
+     * @param flag action policy while accepting a call. Possible values {@link #CALL_ACCEPT_NONE},
+     * {@link #CALL_ACCEPT_HOLD}, {@link #CALL_ACCEPT_TERMINATE}
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
+     */
+    @UnsupportedAppUsage
+    public boolean acceptCall(BluetoothDevice device, int flag) {
+        if (DBG) log("acceptCall()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.acceptCall(device, flag);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Holds a call.
+     *
+     * @param device remote device
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
+     */
+    public boolean holdCall(BluetoothDevice device) {
+        if (DBG) log("holdCall()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.holdCall(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Rejects a call.
+     *
+     * @param device remote device
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
+     *
+     * <p>Feature required for successful execution is being reported by: {@link
+     * #EXTRA_AG_FEATURE_REJECT_CALL}. This method invocation will fail silently when feature is not
+     * supported.</p>
+     */
+    @UnsupportedAppUsage
+    public boolean rejectCall(BluetoothDevice device) {
+        if (DBG) log("rejectCall()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.rejectCall(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Terminates a specified call.
+     *
+     * Works only when Extended Call Control is supported by Audio Gateway.
+     *
+     * @param device remote device
+     * @param call Handle of call obtained in {@link #dial(BluetoothDevice, String)} or obtained via
+     * {@link #ACTION_CALL_CHANGED}. {@code call} may be null in which case we will hangup all active
+     * calls.
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
+     *
+     * <p>Feature required for successful execution is being reported by: {@link
+     * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
+     * supported.</p>
+     */
+    public boolean terminateCall(BluetoothDevice device, BluetoothHeadsetClientCall call) {
+        if (DBG) log("terminateCall()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.terminateCall(device, call);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Enters private mode with a specified call.
+     *
+     * Works only when Extended Call Control is supported by Audio Gateway.
+     *
+     * @param device remote device
+     * @param index index of the call to connect in private mode
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
+     *
+     * <p>Feature required for successful execution is being reported by: {@link
+     * #EXTRA_AG_FEATURE_ECC}. This method invocation will fail silently when feature is not
+     * supported.</p>
+     */
+    public boolean enterPrivateMode(BluetoothDevice device, int index) {
+        if (DBG) log("enterPrivateMode()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.enterPrivateMode(device, index);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Performs explicit call transfer.
+     *
+     * That means connect other calls and disconnect.
+     *
+     * @param device remote device
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_CALL_CHANGED} intent.
+     *
+     * <p>Feature required for successful execution is being reported by: {@link
+     * #EXTRA_AG_FEATURE_MERGE_AND_DETACH}. This method invocation will fail silently when feature
+     * is not supported.</p>
+     */
+    public boolean explicitCallTransfer(BluetoothDevice device) {
+        if (DBG) log("explicitCallTransfer()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.explicitCallTransfer(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Places a call with specified number.
+     *
+     * @param device remote device
+     * @param number valid phone number
+     * @return <code>{@link BluetoothHeadsetClientCall} call</code> if command has been issued
+     * successfully; <code>{@link null}</code> otherwise; upon completion HFP sends {@link
+     * #ACTION_CALL_CHANGED} intent in case of success; {@link #ACTION_RESULT} is sent otherwise;
+     */
+    public BluetoothHeadsetClientCall dial(BluetoothDevice device, String number) {
+        if (DBG) log("dial()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.dial(device, number);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return null;
+    }
+
+    /**
+     * Sends DTMF code.
+     *
+     * Possible code values : 0,1,2,3,4,5,6,7,8,9,A,B,C,D,*,#
+     *
+     * @param device remote device
+     * @param code ASCII code
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_RESULT} intent;
+     */
+    public boolean sendDTMF(BluetoothDevice device, byte code) {
+        if (DBG) log("sendDTMF()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.sendDTMF(device, code);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get a number corresponding to last voice tag recorded on AG.
+     *
+     * @param device remote device
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_LAST_VTAG} or {@link #ACTION_RESULT}
+     * intent;
+     *
+     * <p>Feature required for successful execution is being reported by: {@link
+     * #EXTRA_AG_FEATURE_ATTACH_NUMBER_TO_VT}. This method invocation will fail silently when
+     * feature is not supported.</p>
+     */
+    public boolean getLastVoiceTagNumber(BluetoothDevice device) {
+        if (DBG) log("getLastVoiceTagNumber()");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getLastVoiceTagNumber(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Returns current audio state of Audio Gateway.
+     *
+     * Note: This is an internal function and shouldn't be exposed
+     */
+    @UnsupportedAppUsage
+    public int getAudioState(BluetoothDevice device) {
+        if (VDBG) log("getAudioState");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getAudioState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return BluetoothHeadsetClient.STATE_AUDIO_DISCONNECTED;
+    }
+
+    /**
+     * Sets whether audio routing is allowed.
+     *
+     * @param device remote device
+     * @param allowed if routing is allowed to the device Note: This is an internal function and
+     * shouldn't be exposed
+     */
+    public void setAudioRouteAllowed(BluetoothDevice device, boolean allowed) {
+        if (VDBG) log("setAudioRouteAllowed");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                service.setAudioRouteAllowed(device, allowed);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+    }
+
+    /**
+     * Returns whether audio routing is allowed.
+     *
+     * @param device remote device
+     * @return whether the command succeeded Note: This is an internal function and shouldn't be
+     * exposed
+     */
+    public boolean getAudioRouteAllowed(BluetoothDevice device) {
+        if (VDBG) log("getAudioRouteAllowed");
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getAudioRouteAllowed(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Initiates a connection of audio channel.
+     *
+     * It setup SCO channel with remote connected Handsfree AG device.
+     *
+     * @param device remote device
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
+     */
+    public boolean connectAudio(BluetoothDevice device) {
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.connectAudio(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Disconnects audio channel.
+     *
+     * It tears down the SCO channel from remote AG device.
+     *
+     * @param device remote device
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise; upon completion HFP sends {@link #ACTION_AUDIO_STATE_CHANGED} intent;
+     */
+    public boolean disconnectAudio(BluetoothDevice device) {
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.disconnectAudio(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Get Audio Gateway features
+     *
+     * @param device remote device
+     * @return bundle of AG features; null if no service or AG not connected
+     */
+    public Bundle getCurrentAgFeatures(BluetoothDevice device) {
+        final IBluetoothHeadsetClient service =
+                getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getCurrentAgFeatures(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return null;
+    }
+
+    private boolean isEnabled() {
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
+    }
+
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/android/bluetooth/BluetoothHeadsetClientCall.java b/android/bluetooth/BluetoothHeadsetClientCall.java
new file mode 100644
index 0000000..d1a096e
--- /dev/null
+++ b/android/bluetooth/BluetoothHeadsetClientCall.java
@@ -0,0 +1,315 @@
+/*
+ * Copyright (C) 2014 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemClock;
+
+import java.util.UUID;
+
+/**
+ * This class represents a single call, its state and properties.
+ * It implements {@link Parcelable} for inter-process message passing.
+ *
+ * @hide
+ */
+public final class BluetoothHeadsetClientCall implements Parcelable {
+
+    /* Call state */
+    /**
+     * Call is active.
+     */
+    public static final int CALL_STATE_ACTIVE = 0;
+    /**
+     * Call is in held state.
+     */
+    public static final int CALL_STATE_HELD = 1;
+    /**
+     * Outgoing call that is being dialed right now.
+     */
+    public static final int CALL_STATE_DIALING = 2;
+    /**
+     * Outgoing call that remote party has already been alerted about.
+     */
+    public static final int CALL_STATE_ALERTING = 3;
+    /**
+     * Incoming call that can be accepted or rejected.
+     */
+    public static final int CALL_STATE_INCOMING = 4;
+    /**
+     * Waiting call state when there is already an active call.
+     */
+    public static final int CALL_STATE_WAITING = 5;
+    /**
+     * Call that has been held by response and hold
+     * (see Bluetooth specification for further references).
+     */
+    public static final int CALL_STATE_HELD_BY_RESPONSE_AND_HOLD = 6;
+    /**
+     * Call that has been already terminated and should not be referenced as a valid call.
+     */
+    public static final int CALL_STATE_TERMINATED = 7;
+
+    private final BluetoothDevice mDevice;
+    private final int mId;
+    private int mState;
+    private String mNumber;
+    private boolean mMultiParty;
+    private final boolean mOutgoing;
+    private final UUID mUUID;
+    private final long mCreationElapsedMilli;
+    private final boolean mInBandRing;
+
+    /**
+     * Creates BluetoothHeadsetClientCall instance.
+     */
+    public BluetoothHeadsetClientCall(BluetoothDevice device, int id, int state, String number,
+            boolean multiParty, boolean outgoing, boolean inBandRing) {
+        this(device, id, UUID.randomUUID(), state, number, multiParty, outgoing, inBandRing);
+    }
+
+    public BluetoothHeadsetClientCall(BluetoothDevice device, int id, UUID uuid, int state,
+            String number, boolean multiParty, boolean outgoing, boolean inBandRing) {
+        mDevice = device;
+        mId = id;
+        mUUID = uuid;
+        mState = state;
+        mNumber = number != null ? number : "";
+        mMultiParty = multiParty;
+        mOutgoing = outgoing;
+        mInBandRing = inBandRing;
+        mCreationElapsedMilli = SystemClock.elapsedRealtime();
+    }
+
+    /**
+     * Sets call's state.
+     *
+     * <p>Note: This is an internal function and shouldn't be exposed</p>
+     *
+     * @param state new call state.
+     */
+    public void setState(int state) {
+        mState = state;
+    }
+
+    /**
+     * Sets call's number.
+     *
+     * <p>Note: This is an internal function and shouldn't be exposed</p>
+     *
+     * @param number String representing phone number.
+     */
+    public void setNumber(String number) {
+        mNumber = number;
+    }
+
+    /**
+     * Sets this call as multi party call.
+     *
+     * <p>Note: This is an internal function and shouldn't be exposed</p>
+     *
+     * @param multiParty if <code>true</code> sets this call as a part of multi party conference.
+     */
+    public void setMultiParty(boolean multiParty) {
+        mMultiParty = multiParty;
+    }
+
+    /**
+     * Gets call's device.
+     *
+     * @return call device.
+     */
+    public BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * Gets call's Id.
+     *
+     * @return call id.
+     */
+    @UnsupportedAppUsage
+    public int getId() {
+        return mId;
+    }
+
+    /**
+     * Gets call's UUID.
+     *
+     * @return call uuid
+     * @hide
+     */
+    public UUID getUUID() {
+        return mUUID;
+    }
+
+    /**
+     * Gets call's current state.
+     *
+     * @return state of this particular phone call.
+     */
+    @UnsupportedAppUsage
+    public int getState() {
+        return mState;
+    }
+
+    /**
+     * Gets call's number.
+     *
+     * @return string representing phone number.
+     */
+    @UnsupportedAppUsage
+    public String getNumber() {
+        return mNumber;
+    }
+
+    /**
+     * Gets call's creation time in millis since epoch.
+     *
+     * @return long representing the creation time.
+     */
+    public long getCreationElapsedMilli() {
+        return mCreationElapsedMilli;
+    }
+
+    /**
+     * Checks if call is an active call in a conference mode (aka multi party).
+     *
+     * @return <code>true</code> if call is a multi party call, <code>false</code> otherwise.
+     */
+    @UnsupportedAppUsage
+    public boolean isMultiParty() {
+        return mMultiParty;
+    }
+
+    /**
+     * Checks if this call is an outgoing call.
+     *
+     * @return <code>true</code> if its outgoing call, <code>false</code> otherwise.
+     */
+    @UnsupportedAppUsage
+    public boolean isOutgoing() {
+        return mOutgoing;
+    }
+
+    /**
+     * Checks if the ringtone will be generated by the connected phone
+     *
+     * @return <code>true</code> if in band ring is enabled, <code>false</code> otherwise.
+     */
+    public boolean isInBandRing() {
+        return mInBandRing;
+    }
+
+
+    @Override
+    public String toString() {
+        return toString(false);
+    }
+
+    /**
+     * Generate a log string for this call
+     * @param loggable whether device address should be logged
+     * @return log string
+     */
+    public String toString(boolean loggable) {
+        StringBuilder builder = new StringBuilder("BluetoothHeadsetClientCall{mDevice: ");
+        builder.append(loggable ? mDevice : mDevice.hashCode());
+        builder.append(", mId: ");
+        builder.append(mId);
+        builder.append(", mUUID: ");
+        builder.append(mUUID);
+        builder.append(", mState: ");
+        switch (mState) {
+            case CALL_STATE_ACTIVE:
+                builder.append("ACTIVE");
+                break;
+            case CALL_STATE_HELD:
+                builder.append("HELD");
+                break;
+            case CALL_STATE_DIALING:
+                builder.append("DIALING");
+                break;
+            case CALL_STATE_ALERTING:
+                builder.append("ALERTING");
+                break;
+            case CALL_STATE_INCOMING:
+                builder.append("INCOMING");
+                break;
+            case CALL_STATE_WAITING:
+                builder.append("WAITING");
+                break;
+            case CALL_STATE_HELD_BY_RESPONSE_AND_HOLD:
+                builder.append("HELD_BY_RESPONSE_AND_HOLD");
+                break;
+            case CALL_STATE_TERMINATED:
+                builder.append("TERMINATED");
+                break;
+            default:
+                builder.append(mState);
+                break;
+        }
+        builder.append(", mNumber: ");
+        builder.append(loggable ? mNumber : mNumber.hashCode());
+        builder.append(", mMultiParty: ");
+        builder.append(mMultiParty);
+        builder.append(", mOutgoing: ");
+        builder.append(mOutgoing);
+        builder.append(", mInBandRing: ");
+        builder.append(mInBandRing);
+        builder.append("}");
+        return builder.toString();
+    }
+
+    /**
+     * {@link Parcelable.Creator} interface implementation.
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothHeadsetClientCall> CREATOR =
+            new Parcelable.Creator<BluetoothHeadsetClientCall>() {
+                @Override
+                public BluetoothHeadsetClientCall createFromParcel(Parcel in) {
+                    return new BluetoothHeadsetClientCall((BluetoothDevice) in.readParcelable(null),
+                            in.readInt(), UUID.fromString(in.readString()), in.readInt(),
+                            in.readString(), in.readInt() == 1, in.readInt() == 1,
+                            in.readInt() == 1);
+                }
+
+                @Override
+                public BluetoothHeadsetClientCall[] newArray(int size) {
+                    return new BluetoothHeadsetClientCall[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeParcelable(mDevice, 0);
+        out.writeInt(mId);
+        out.writeString(mUUID.toString());
+        out.writeInt(mState);
+        out.writeString(mNumber);
+        out.writeInt(mMultiParty ? 1 : 0);
+        out.writeInt(mOutgoing ? 1 : 0);
+        out.writeInt(mInBandRing ? 1 : 0);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/android/bluetooth/BluetoothHealth.java b/android/bluetooth/BluetoothHealth.java
new file mode 100644
index 0000000..5fd60e0
--- /dev/null
+++ b/android/bluetooth/BluetoothHealth.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2011 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.ParcelFileDescriptor;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Public API for Bluetooth Health Profile.
+ *
+ * <p>BluetoothHealth is a proxy object for controlling the Bluetooth
+ * Service via IPC.
+ *
+ * <p> How to connect to a health device which is acting in the source role.
+ * <li> Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHealth proxy object. </li>
+ * <li> Create an {@link BluetoothHealth} callback and call
+ * {@link #registerSinkAppConfiguration} to register an application
+ * configuration </li>
+ * <li> Pair with the remote device. This currently needs to be done manually
+ * from Bluetooth Settings </li>
+ * <li> Connect to a health device using {@link #connectChannelToSource}. Some
+ * devices will connect the channel automatically. The {@link BluetoothHealth}
+ * callback will inform the application of channel state change. </li>
+ * <li> Use the file descriptor provided with a connected channel to read and
+ * write data to the health channel. </li>
+ * <li> The received data needs to be interpreted using a health manager which
+ * implements the IEEE 11073-xxxxx specifications.
+ * <li> When done, close the health channel by calling {@link #disconnectChannel}
+ * and unregister the application configuration calling
+ * {@link #unregisterAppConfiguration}
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New apps
+ * should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
+ */
+@Deprecated
+public final class BluetoothHealth implements BluetoothProfile {
+    private static final String TAG = "BluetoothHealth";
+    /**
+     * Health Profile Source Role - the health device.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int SOURCE_ROLE = 1 << 0;
+
+    /**
+     * Health Profile Sink Role the device talking to the health device.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int SINK_ROLE = 1 << 1;
+
+    /**
+     * Health Profile - Channel Type used - Reliable
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int CHANNEL_TYPE_RELIABLE = 10;
+
+    /**
+     * Health Profile - Channel Type used - Streaming
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int CHANNEL_TYPE_STREAMING = 11;
+
+    /**
+     * Hide auto-created default constructor
+     * @hide
+     */
+    BluetoothHealth() {}
+
+    /**
+     * Register an application configuration that acts as a Health SINK.
+     * This is the configuration that will be used to communicate with health devices
+     * which will act as the {@link #SOURCE_ROLE}. This is an asynchronous call and so
+     * the callback is used to notify success or failure if the function returns true.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param name The friendly name associated with the application or configuration.
+     * @param dataType The dataType of the Source role of Health Profile to which the sink wants to
+     * connect to.
+     * @param callback A callback to indicate success or failure of the registration and all
+     * operations done on this application configuration.
+     * @return If true, callback will be called.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public boolean registerSinkAppConfiguration(String name, int dataType,
+            BluetoothHealthCallback callback) {
+        Log.e(TAG, "registerSinkAppConfiguration(): BluetoothHealth is deprecated");
+        return false;
+    }
+
+    /**
+     * Unregister an application configuration that has been registered using
+     * {@link #registerSinkAppConfiguration}
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param config The health app configuration
+     * @return Success or failure.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public boolean unregisterAppConfiguration(BluetoothHealthAppConfiguration config) {
+        Log.e(TAG, "unregisterAppConfiguration(): BluetoothHealth is deprecated");
+        return false;
+    }
+
+    /**
+     * Connect to a health device which has the {@link #SOURCE_ROLE}.
+     * This is an asynchronous call. If this function returns true, the callback
+     * associated with the application configuration will be called.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device The remote Bluetooth device.
+     * @param config The application configuration which has been registered using {@link
+     * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
+     * @return If true, the callback associated with the application config will be called.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public boolean connectChannelToSource(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config) {
+        Log.e(TAG, "connectChannelToSource(): BluetoothHealth is deprecated");
+        return false;
+    }
+
+    /**
+     * Disconnect a connected health channel.
+     * This is an asynchronous call. If this function returns true, the callback
+     * associated with the application configuration will be called.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * @param device The remote Bluetooth device.
+     * @param config The application configuration which has been registered using {@link
+     * #registerSinkAppConfiguration(String, int, BluetoothHealthCallback) }
+     * @param channelId The channel id associated with the channel
+     * @return If true, the callback associated with the application config will be called.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public boolean disconnectChannel(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config, int channelId) {
+        Log.e(TAG, "disconnectChannel(): BluetoothHealth is deprecated");
+        return false;
+    }
+
+    /**
+     * Get the file descriptor of the main channel associated with the remote device
+     * and application configuration.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * <p> Its the responsibility of the caller to close the ParcelFileDescriptor
+     * when done.
+     *
+     * @param device The remote Bluetooth health device
+     * @param config The application configuration
+     * @return null on failure, ParcelFileDescriptor on success.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
+            BluetoothHealthAppConfiguration config) {
+        Log.e(TAG, "getMainChannelFd(): BluetoothHealth is deprecated");
+        return null;
+    }
+
+    /**
+     * Get the current connection state of the profile.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * This is not specific to any application configuration but represents the connection
+     * state of the local Bluetooth adapter with the remote device. This can be used
+     * by applications like status bar which would just like to know the state of the
+     * local adapter.
+     *
+     * @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) {
+        Log.e(TAG, "getConnectionState(): BluetoothHealth is deprecated");
+        return STATE_DISCONNECTED;
+    }
+
+    /**
+     * Get connected devices for the health profile.
+     *
+     * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     *
+     * 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.
+     *
+     * @return List of devices. The list will be empty on error.
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        Log.e(TAG, "getConnectedDevices(): BluetoothHealth is deprecated");
+        return new ArrayList<>();
+    }
+
+    /**
+     * 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>Requires {@link android.Manifest.permission#BLUETOOTH} permission.
+     * 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.
+     *
+     * @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) {
+        Log.e(TAG, "getDevicesMatchingConnectionStates(): BluetoothHealth is deprecated");
+        return new ArrayList<>();
+    }
+
+    /** Health Channel Connection State - Disconnected
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int STATE_CHANNEL_DISCONNECTED = 0;
+    /** Health Channel Connection State - Connecting
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int STATE_CHANNEL_CONNECTING = 1;
+    /** Health Channel Connection State - Connected
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int STATE_CHANNEL_CONNECTED = 2;
+    /** Health Channel Connection State - Disconnecting
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int STATE_CHANNEL_DISCONNECTING = 3;
+
+    /** Health App Configuration registration success
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int APP_CONFIG_REGISTRATION_SUCCESS = 0;
+    /** Health App Configuration registration failure
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int APP_CONFIG_REGISTRATION_FAILURE = 1;
+    /** Health App Configuration un-registration success
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int APP_CONFIG_UNREGISTRATION_SUCCESS = 2;
+    /** Health App Configuration un-registration failure
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final int APP_CONFIG_UNREGISTRATION_FAILURE = 3;
+}
diff --git a/android/bluetooth/BluetoothHealthAppConfiguration.java b/android/bluetooth/BluetoothHealthAppConfiguration.java
new file mode 100644
index 0000000..2f66df2
--- /dev/null
+++ b/android/bluetooth/BluetoothHealthAppConfiguration.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (C) 2011 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;
+
+/**
+ * The Bluetooth Health Application Configuration that is used in conjunction with
+ * the {@link BluetoothHealth} class. This class represents an application configuration
+ * that the Bluetooth Health third party application will register to communicate with the
+ * remote Bluetooth health device.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
+ */
+@Deprecated
+public final class BluetoothHealthAppConfiguration implements Parcelable {
+
+    /**
+     * Hide auto-created default constructor
+     * @hide
+     */
+    BluetoothHealthAppConfiguration() {}
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Return the data type associated with this application configuration.
+     *
+     * @return dataType
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public int getDataType() {
+        return 0;
+    }
+
+    /**
+     * Return the name of the application configuration.
+     *
+     * @return String name
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public String getName() {
+        return null;
+    }
+
+    /**
+     * Return the role associated with this application configuration.
+     *
+     * @return One of {@link BluetoothHealth#SOURCE_ROLE} or {@link BluetoothHealth#SINK_ROLE}
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public int getRole() {
+        return 0;
+    }
+
+    /**
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothHealthAppConfiguration> CREATOR =
+            new Parcelable.Creator<BluetoothHealthAppConfiguration>() {
+                @Override
+                public BluetoothHealthAppConfiguration createFromParcel(Parcel in) {
+                    return new BluetoothHealthAppConfiguration();
+                }
+
+                @Override
+                public BluetoothHealthAppConfiguration[] newArray(int size) {
+                    return new BluetoothHealthAppConfiguration[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {}
+}
diff --git a/android/bluetooth/BluetoothHealthCallback.java b/android/bluetooth/BluetoothHealthCallback.java
new file mode 100644
index 0000000..4769212
--- /dev/null
+++ b/android/bluetooth/BluetoothHealthCallback.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2011 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.annotation.BinderThread;
+import android.os.ParcelFileDescriptor;
+import android.util.Log;
+
+/**
+ * This abstract class is used to implement {@link BluetoothHealth} callbacks.
+ *
+ * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+ * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+ * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+ * {@link BluetoothDevice#createL2capChannel(int)}
+ */
+@Deprecated
+public abstract class BluetoothHealthCallback {
+    private static final String TAG = "BluetoothHealthCallback";
+
+    /**
+     * Callback to inform change in registration state of the health
+     * application.
+     * <p> This callback is called on the binder thread (not on the UI thread)
+     *
+     * @param config Bluetooth Health app configuration
+     * @param status Success or failure of the registration or unregistration calls. Can be one of
+     * {@link BluetoothHealth#APP_CONFIG_REGISTRATION_SUCCESS} or {@link
+     * BluetoothHealth#APP_CONFIG_REGISTRATION_FAILURE} or
+     * {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_SUCCESS}
+     * or {@link BluetoothHealth#APP_CONFIG_UNREGISTRATION_FAILURE}
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @BinderThread
+    @Deprecated
+    public void onHealthAppConfigurationStatusChange(BluetoothHealthAppConfiguration config,
+            int status) {
+        Log.d(TAG, "onHealthAppConfigurationStatusChange: " + config + "Status: " + status);
+    }
+
+    /**
+     * Callback to inform change in channel state.
+     * <p> Its the responsibility of the implementor of this callback to close the
+     * parcel file descriptor when done. This callback is called on the Binder
+     * thread (not the UI thread)
+     *
+     * @param config The Health app configutation
+     * @param device The Bluetooth Device
+     * @param prevState The previous state of the channel
+     * @param newState The new state of the channel.
+     * @param fd The Parcel File Descriptor when the channel state is connected.
+     * @param channelId The id associated with the channel. This id will be used in future calls
+     * like when disconnecting the channel.
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()(int)}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @BinderThread
+    @Deprecated
+    public void onHealthChannelStateChange(BluetoothHealthAppConfiguration config,
+            BluetoothDevice device, int prevState, int newState, ParcelFileDescriptor fd,
+            int channelId) {
+        Log.d(TAG, "onHealthChannelStateChange: " + config + "Device: " + device
+                + "prevState:" + prevState + "newState:" + newState + "ParcelFd:" + fd
+                + "ChannelId:" + channelId);
+    }
+}
diff --git a/android/bluetooth/BluetoothHearingAid.java b/android/bluetooth/BluetoothHearingAid.java
new file mode 100644
index 0000000..fa62a02
--- /dev/null
+++ b/android/bluetooth/BluetoothHearingAid.java
@@ -0,0 +1,600 @@
+/*
+ * Copyright 2018 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the public APIs to control the Hearing Aid profile.
+ *
+ * <p>BluetoothHearingAid is a proxy object for controlling the Bluetooth Hearing Aid
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHearingAid proxy object.
+ *
+ * <p> Android only supports one set of connected Bluetooth Hearing Aid device at a time. Each
+ * method is protected with its appropriate permission.
+ */
+public final class BluetoothHearingAid implements BluetoothProfile {
+    private static final String TAG = "BluetoothHearingAid";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the Hearing Aid
+     * profile. Please note that in the binaural case, there will be two different LE devices for
+     * the left and right side and each device will have their own connection state changes.S
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.hearingaid.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Intent used to broadcast the selection of a connected device as active.
+     *
+     * <p>This intent will have one extra:
+     * <ul>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
+     * be null if no device is active. </li>
+     * </ul>
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_ACTIVE_DEVICE_CHANGED =
+            "android.bluetooth.hearingaid.profile.action.ACTIVE_DEVICE_CHANGED";
+
+    /**
+     * This device represents Left Hearing Aid.
+     *
+     * @hide
+     */
+    public static final int SIDE_LEFT = IBluetoothHearingAid.SIDE_LEFT;
+
+    /**
+     * This device represents Right Hearing Aid.
+     *
+     * @hide
+     */
+    public static final int SIDE_RIGHT = IBluetoothHearingAid.SIDE_RIGHT;
+
+    /**
+     * This device is Monaural.
+     *
+     * @hide
+     */
+    public static final int MODE_MONAURAL = IBluetoothHearingAid.MODE_MONAURAL;
+
+    /**
+     * This device is Binaural (should receive only left or right audio).
+     *
+     * @hide
+     */
+    public static final int MODE_BINAURAL = IBluetoothHearingAid.MODE_BINAURAL;
+
+    /**
+     * Indicates the HiSyncID could not be read and is unavailable.
+     *
+     * @hide
+     */
+    public static final long HI_SYNC_ID_INVALID = IBluetoothHearingAid.HI_SYNC_ID_INVALID;
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothHearingAid> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.HEARING_AID,
+                    "BluetoothHearingAid", IBluetoothHearingAid.class.getName()) {
+                @Override
+                public IBluetoothHearingAid getServiceInterface(IBinder service) {
+                    return IBluetoothHearingAid.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
+    /**
+     * Create a BluetoothHearingAid proxy object for interacting with the local
+     * Bluetooth Hearing Aid service.
+     */
+    /*package*/ BluetoothHearingAid(Context context, ServiceListener listener) {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+    }
+
+    /*package*/ void close() {
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothHearingAid getService() {
+        return mProfileConnector.getService();
+    }
+
+    /**
+     * Initiate connection to a profile of the remote bluetooth device.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is already connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that
+     * connection state intent for the profile will be broadcasted with
+     * the state. Users can get the connection state of the profile
+     * from this intent.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.connect(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * Initiate disconnection from a profile
+     *
+     * <p> This API will return false in scenarios like the profile on the
+     * Bluetooth device is not in connected state etc. When this API returns,
+     * true, it is guaranteed that the connection state change
+     * intent will be broadcasted with the state. Users can get the
+     * disconnection state of the profile from this intent.
+     *
+     * <p> If the disconnection is initiated by a remote device, the state
+     * will transition from {@link #STATE_CONNECTED} to
+     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
+     * host (local) device the state will transition from
+     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
+     * state {@link #STATE_DISCONNECTED}. The transition to
+     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
+     * two scenarios.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.disconnect(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service != null && isEnabled()) {
+                return service.getConnectedDevices();
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return new ArrayList<BluetoothDevice>();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return new ArrayList<BluetoothDevice>();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
+    @NonNull int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service != null && isEnabled()) {
+                return service.getDevicesMatchingConnectionStates(states);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return new ArrayList<BluetoothDevice>();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return new ArrayList<BluetoothDevice>();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public @BluetoothProfile.BtProfileState int getConnectionState(
+    @NonNull BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                return service.getConnectionState(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return BluetoothProfile.STATE_DISCONNECTED;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+    }
+
+    /**
+     * Select a connected device as active.
+     *
+     * The active device selection is per profile. An active device's
+     * purpose is profile-specific. For example, Hearing Aid audio
+     * streaming is to the active Hearing Aid device. If a remote device
+     * is not connected, it cannot be selected as active.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is not connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that the
+     * {@link #ACTION_ACTIVE_DEVICE_CHANGED} intent will be broadcasted
+     * with the active device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     * permission.
+     *
+     * @param device the remote Bluetooth device. Could be null to clear
+     * the active device and stop streaming audio to a Bluetooth device.
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean setActiveDevice(@Nullable BluetoothDevice device) {
+        if (DBG) log("setActiveDevice(" + device + ")");
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service != null && isEnabled()
+                    && ((device == null) || isValidDevice(device))) {
+                service.setActiveDevice(device);
+                return true;
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * Get the connected physical Hearing Aid devices that are active
+     *
+     * @return the list of active devices. The first element is the left active
+     * device; the second element is the right active device. If either or both side
+     * is not active, it will be null on that position. Returns empty list on error.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public @NonNull List<BluetoothDevice> getActiveDevices() {
+        if (VDBG) log("getActiveDevices()");
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service != null && isEnabled()) {
+                return service.getActiveDevices();
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return new ArrayList<>();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return new ArrayList<>();
+        }
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF},
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        verifyDeviceNotNull(device, "setConnectionPolicy");
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
+        if (VDBG) log("getConnectionPolicy(" + device + ")");
+        verifyDeviceNotNull(device, "getConnectionPolicy");
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                return service.getConnectionPolicy(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+        }
+    }
+
+    /**
+     * Helper for converting a state to a string.
+     *
+     * For debug use only - strings are not internationalized.
+     *
+     * @hide
+     */
+    public static String stateToString(int state) {
+        switch (state) {
+            case STATE_DISCONNECTED:
+                return "disconnected";
+            case STATE_CONNECTING:
+                return "connecting";
+            case STATE_CONNECTED:
+                return "connected";
+            case STATE_DISCONNECTING:
+                return "disconnecting";
+            default:
+                return "<unknown state " + state + ">";
+        }
+    }
+
+    /**
+     * Tells remote device to set an absolute volume.
+     *
+     * @param volume Absolute volume to be set on remote
+     * @hide
+     */
+    public void setVolume(int volume) {
+        if (DBG) Log.d(TAG, "setVolume(" + volume + ")");
+
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service == null) {
+                Log.w(TAG, "Proxy not attached to service");
+                return;
+            }
+
+            if (!isEnabled()) return;
+
+            service.setVolume(volume);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+        }
+    }
+
+    /**
+     * Get the HiSyncId (unique hearing aid device identifier) of the device.
+     *
+     * <a href=https://source.android.com/devices/bluetooth/asha#hisyncid>HiSyncId documentation
+     * can be found here</a>
+     *
+     * @param device Bluetooth device
+     * @return the HiSyncId of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public long getHiSyncId(@NonNull BluetoothDevice device) {
+        if (VDBG) {
+            log("getHiSyncId(" + device + ")");
+        }
+        verifyDeviceNotNull(device, "getConnectionPolicy");
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service == null) {
+                Log.w(TAG, "Proxy not attached to service");
+                return HI_SYNC_ID_INVALID;
+            }
+
+            if (!isEnabled() || !isValidDevice(device)) return HI_SYNC_ID_INVALID;
+
+            return service.getHiSyncId(device);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return HI_SYNC_ID_INVALID;
+        }
+    }
+
+    /**
+     * Get the side of the device.
+     *
+     * @param device Bluetooth device.
+     * @return SIDE_LEFT or SIDE_RIGHT
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public int getDeviceSide(BluetoothDevice device) {
+        if (VDBG) {
+            log("getDeviceSide(" + device + ")");
+        }
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                return service.getDeviceSide(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return SIDE_LEFT;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return SIDE_LEFT;
+        }
+    }
+
+    /**
+     * Get the mode of the device.
+     *
+     * @param device Bluetooth device
+     * @return MODE_MONAURAL or MODE_BINAURAL
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public int getDeviceMode(BluetoothDevice device) {
+        if (VDBG) {
+            log("getDeviceMode(" + device + ")");
+        }
+        final IBluetoothHearingAid service = getService();
+        try {
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                return service.getDeviceMode(device);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return MODE_MONAURAL;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return MODE_MONAURAL;
+        }
+    }
+
+    private boolean isEnabled() {
+        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+        return false;
+    }
+
+    private void verifyDeviceNotNull(BluetoothDevice device, String methodName) {
+        if (device == null) {
+            Log.e(TAG, methodName + ": device param is null");
+            throw new IllegalArgumentException("Device cannot be null");
+        }
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+        if (device == null) return false;
+
+        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+        return false;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/android/bluetooth/BluetoothHidDevice.java b/android/bluetooth/BluetoothHidDevice.java
new file mode 100644
index 0000000..b5959c0
--- /dev/null
+++ b/android/bluetooth/BluetoothHidDevice.java
@@ -0,0 +1,748 @@
+/*
+ * 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Provides the public APIs to control the Bluetooth HID Device profile.
+ *
+ * <p>BluetoothHidDevice is a proxy object for controlling the Bluetooth HID Device Service via IPC.
+ * Use {@link BluetoothAdapter#getProfileProxy} to get the BluetoothHidDevice proxy object.
+ */
+public final class BluetoothHidDevice implements BluetoothProfile {
+    private static final String TAG = BluetoothHidDevice.class.getSimpleName();
+    private static final boolean DBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the Input Host profile.
+     *
+     * <p>This intent will have 3 extras:
+     *
+     * <ul>
+     *   <li>{@link #EXTRA_STATE} - The current state of the profile.
+     *   <li>{@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.
+     *   <li>{@link BluetoothDevice#EXTRA_DEVICE} - The remote device.
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of {@link
+     * #STATE_DISCONNECTED}, {@link #STATE_CONNECTING}, {@link #STATE_CONNECTED}, {@link
+     * #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to receive.
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.hiddevice.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Constant representing unspecified HID device subclass.
+     *
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
+     */
+    public static final byte SUBCLASS1_NONE = (byte) 0x00;
+    /**
+     * Constant representing keyboard subclass.
+     *
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
+     */
+    public static final byte SUBCLASS1_KEYBOARD = (byte) 0x40;
+    /**
+     * Constant representing mouse subclass.
+     *
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
+     */
+    public static final byte SUBCLASS1_MOUSE = (byte) 0x80;
+    /**
+     * Constant representing combo keyboard and mouse subclass.
+     *
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
+     */
+    public static final byte SUBCLASS1_COMBO = (byte) 0xC0;
+
+    /**
+     * Constant representing uncategorized HID device subclass.
+     *
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
+     */
+    public static final byte SUBCLASS2_UNCATEGORIZED = (byte) 0x00;
+    /**
+     * Constant representing joystick subclass.
+     *
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
+     */
+    public static final byte SUBCLASS2_JOYSTICK = (byte) 0x01;
+    /**
+     * Constant representing gamepad subclass.
+     *
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
+     */
+    public static final byte SUBCLASS2_GAMEPAD = (byte) 0x02;
+    /**
+     * Constant representing remote control subclass.
+     *
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
+     */
+    public static final byte SUBCLASS2_REMOTE_CONTROL = (byte) 0x03;
+    /**
+     * Constant representing sensing device subclass.
+     *
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
+     */
+    public static final byte SUBCLASS2_SENSING_DEVICE = (byte) 0x04;
+    /**
+     * Constant representing digitizer tablet subclass.
+     *
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
+     */
+    public static final byte SUBCLASS2_DIGITIZER_TABLET = (byte) 0x05;
+    /**
+     * Constant representing card reader subclass.
+     *
+     * @see #registerApp (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     *     BluetoothHidDeviceAppQosSettings, Executor, Callback)
+     */
+    public static final byte SUBCLASS2_CARD_READER = (byte) 0x06;
+
+    /**
+     * Constant representing HID Input Report type.
+     *
+     * @see Callback#onGetReport(BluetoothDevice, byte, byte, int)
+     * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     * @see Callback#onInterruptData(BluetoothDevice, byte, byte[])
+     */
+    public static final byte REPORT_TYPE_INPUT = (byte) 1;
+    /**
+     * Constant representing HID Output Report type.
+     *
+     * @see Callback#onGetReport(BluetoothDevice, byte, byte, int)
+     * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     * @see Callback#onInterruptData(BluetoothDevice, byte, byte[])
+     */
+    public static final byte REPORT_TYPE_OUTPUT = (byte) 2;
+    /**
+     * Constant representing HID Feature Report type.
+     *
+     * @see Callback#onGetReport(BluetoothDevice, byte, byte, int)
+     * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     * @see Callback#onInterruptData(BluetoothDevice, byte, byte[])
+     */
+    public static final byte REPORT_TYPE_FEATURE = (byte) 3;
+
+    /**
+     * Constant representing success response for Set Report.
+     *
+     * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     */
+    public static final byte ERROR_RSP_SUCCESS = (byte) 0;
+    /**
+     * Constant representing error response for Set Report due to "not ready".
+     *
+     * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     */
+    public static final byte ERROR_RSP_NOT_READY = (byte) 1;
+    /**
+     * Constant representing error response for Set Report due to "invalid report ID".
+     *
+     * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     */
+    public static final byte ERROR_RSP_INVALID_RPT_ID = (byte) 2;
+    /**
+     * Constant representing error response for Set Report due to "unsupported request".
+     *
+     * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     */
+    public static final byte ERROR_RSP_UNSUPPORTED_REQ = (byte) 3;
+    /**
+     * Constant representing error response for Set Report due to "invalid parameter".
+     *
+     * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     */
+    public static final byte ERROR_RSP_INVALID_PARAM = (byte) 4;
+    /**
+     * Constant representing error response for Set Report with unknown reason.
+     *
+     * @see Callback#onSetReport(BluetoothDevice, byte, byte, byte[])
+     */
+    public static final byte ERROR_RSP_UNKNOWN = (byte) 14;
+
+    /**
+     * Constant representing boot protocol mode used set by host. Default is always {@link
+     * #PROTOCOL_REPORT_MODE} unless notified otherwise.
+     *
+     * @see Callback#onSetProtocol(BluetoothDevice, byte)
+     */
+    public static final byte PROTOCOL_BOOT_MODE = (byte) 0;
+    /**
+     * Constant representing report protocol mode used set by host. Default is always {@link
+     * #PROTOCOL_REPORT_MODE} unless notified otherwise.
+     *
+     * @see Callback#onSetProtocol(BluetoothDevice, byte)
+     */
+    public static final byte PROTOCOL_REPORT_MODE = (byte) 1;
+
+    /**
+     * The template class that applications use to call callback functions on events from the HID
+     * host. Callback functions are wrapped in this class and registered to the Android system
+     * during app registration.
+     */
+    public abstract static class Callback {
+
+        private static final String TAG = "BluetoothHidDevCallback";
+
+        /**
+         * Callback called when application registration state changes. Usually it's called due to
+         * either {@link BluetoothHidDevice#registerApp (String, String, String, byte, byte[],
+         * Executor, Callback)} or {@link BluetoothHidDevice#unregisterApp()} , 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 registered <code>true</code> if application is registered, <code>false</code>
+         *     otherwise.
+         */
+        public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
+            Log.d(
+                    TAG,
+                    "onAppStatusChanged: pluggedDevice="
+                            + pluggedDevice
+                            + " 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 + " state=" + state);
+        }
+
+        /**
+         * Callback called when GET_REPORT is received from remote host. Should be replied by
+         * application using {@link BluetoothHidDevice#replyReport(BluetoothDevice, 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(BluetoothDevice device, byte type, byte id, int bufferSize) {
+            Log.d(
+                    TAG,
+                    "onGetReport: device="
+                            + device
+                            + " 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(BluetoothDevice, byte)}.
+         *
+         * @param type Report Type.
+         * @param id Report Id.
+         * @param data Report data.
+         */
+        public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+            Log.d(TAG, "onSetReport: device=" + device + " 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(BluetoothDevice device, byte protocol) {
+            Log.d(TAG, "onSetProtocol: device=" + device + " 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 onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+            Log.d(TAG, "onInterruptData: device=" + device + " reportId=" + reportId);
+        }
+
+        /**
+         * Callback called when Virtual Cable is removed. After this callback is received connection
+         * will be disconnected automatically.
+         */
+        public void onVirtualCableUnplug(BluetoothDevice device) {
+            Log.d(TAG, "onVirtualCableUnplug: device=" + device);
+        }
+    }
+
+    private static class CallbackWrapper extends IBluetoothHidDeviceCallback.Stub {
+
+        private final Executor mExecutor;
+        private final Callback mCallback;
+
+        CallbackWrapper(Executor executor, Callback callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
+            clearCallingIdentity();
+            mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered));
+        }
+
+        @Override
+        public void onConnectionStateChanged(BluetoothDevice device, int state) {
+            clearCallingIdentity();
+            mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state));
+        }
+
+        @Override
+        public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
+            clearCallingIdentity();
+            mExecutor.execute(() -> mCallback.onGetReport(device, type, id, bufferSize));
+        }
+
+        @Override
+        public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+            clearCallingIdentity();
+            mExecutor.execute(() -> mCallback.onSetReport(device, type, id, data));
+        }
+
+        @Override
+        public void onSetProtocol(BluetoothDevice device, byte protocol) {
+            clearCallingIdentity();
+            mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol));
+        }
+
+        @Override
+        public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
+            clearCallingIdentity();
+            mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data));
+        }
+
+        @Override
+        public void onVirtualCableUnplug(BluetoothDevice device) {
+            clearCallingIdentity();
+            mExecutor.execute(() -> mCallback.onVirtualCableUnplug(device));
+        }
+    }
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothHidDevice> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.HID_DEVICE,
+                    "BluetoothHidDevice", IBluetoothHidDevice.class.getName()) {
+                @Override
+                public IBluetoothHidDevice getServiceInterface(IBinder service) {
+                    return IBluetoothHidDevice.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
+    BluetoothHidDevice(Context context, ServiceListener listener) {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+    }
+
+    void close() {
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothHidDevice getService() {
+        return mProfileConnector.getService();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        final IBluetoothHidDevice service = getService();
+        if (service != null) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return new ArrayList<>();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        final IBluetoothHidDevice service = getService();
+        if (service != null) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return new ArrayList<>();
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        final IBluetoothHidDevice service = getService();
+        if (service != null) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        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 one time. When an
+     * application is registered, the HID Host service will be disabled until it is unregistered.
+     * When no longer used, application should be unregistered using {@link #unregisterApp()}. The
+     * app will be automatically unregistered if it is not foreground. The registration status
+     * should be tracked by the application by handling callback from Callback#onAppStatusChanged.
+     * The app registration status is not related to the return value of this method.
+     *
+     * @param sdp {@link BluetoothHidDeviceAppSdpSettings} object of HID Device SDP record. The HID
+     *     Device SDP record is required.
+     * @param inQos {@link BluetoothHidDeviceAppQosSettings} object of Incoming QoS Settings. The
+     *     Incoming QoS Settings is not required. Use null or default
+     *     BluetoothHidDeviceAppQosSettings.Builder for default values.
+     * @param outQos {@link BluetoothHidDeviceAppQosSettings} object of Outgoing QoS Settings. The
+     *     Outgoing QoS Settings is not required. Use null or default
+     *     BluetoothHidDeviceAppQosSettings.Builder for default values.
+     * @param executor {@link Executor} object on which callback will be executed. The Executor
+     *     object is required.
+     * @param callback {@link Callback} object to which callback messages will be sent. The Callback
+     *     object is required.
+     * @return true if the command is successfully sent; otherwise false.
+     */
+    public boolean registerApp(
+            BluetoothHidDeviceAppSdpSettings sdp,
+            BluetoothHidDeviceAppQosSettings inQos,
+            BluetoothHidDeviceAppQosSettings outQos,
+            Executor executor,
+            Callback callback) {
+        boolean result = false;
+
+        if (sdp == null) {
+            throw new IllegalArgumentException("sdp parameter cannot be null");
+        }
+
+        if (executor == null) {
+            throw new IllegalArgumentException("executor parameter cannot be null");
+        }
+
+        if (callback == null) {
+            throw new IllegalArgumentException("callback parameter cannot be null");
+        }
+
+        final IBluetoothHidDevice service = getService();
+        if (service != null) {
+            try {
+                CallbackWrapper cbw = new CallbackWrapper(executor, callback);
+                result = service.registerApp(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
+     * (BluetoothHidDeviceAppQosSettings, BluetoothHidDeviceAppQosSettings,
+     * BluetoothHidDeviceAppQosSettings, Executor, Callback)}. The registration status should be
+     * tracked by the application by handling callback from Callback#onAppStatusChanged. The app
+     * registration status is not related to the return value of this method.
+     *
+     * @return true if the command is successfully sent; otherwise false.
+     */
+    public boolean unregisterApp() {
+        boolean result = false;
+
+        final IBluetoothHidDevice service = getService();
+        if (service != null) {
+            try {
+                result = service.unregisterApp();
+            } 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 true if the command is successfully sent; otherwise false.
+     */
+    public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
+        boolean result = false;
+
+        final IBluetoothHidDevice service = getService();
+        if (service != null) {
+            try {
+                result = service.sendReport(device, 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
+     * Callback#onGetReport(BluetoothDevice, 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 true if the command is successfully sent; otherwise false.
+     */
+    public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
+        boolean result = false;
+
+        final IBluetoothHidDevice service = getService();
+        if (service != null) {
+            try {
+                result = service.replyReport(device, 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
+     * Callback#onSetReport(BluetoothDevice, byte, byte, byte[])}.
+     *
+     * @param error Error to be sent for SET_REPORT via HANDSHAKE.
+     * @return true if the command is successfully sent; otherwise false.
+     */
+    public boolean reportError(BluetoothDevice device, byte error) {
+        boolean result = false;
+
+        final IBluetoothHidDevice service = getService();
+        if (service != null) {
+            try {
+                result = service.reportError(device, error);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Gets the application name of the current HidDeviceService user.
+     *
+     * @return the current user name, or empty string if cannot get the name
+     * {@hide}
+     */
+    public String getUserAppName() {
+        final IBluetoothHidDevice service = getService();
+
+        if (service != null) {
+            try {
+                return service.getUserAppName();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return "";
+    }
+
+    /**
+     * Initiates connection to host which is currently paired with this device. If the application
+     * is not registered, #connect(BluetoothDevice) will fail. The connection state should be
+     * tracked by the application by handling callback from Callback#onConnectionStateChanged. The
+     * connection state is not related to the return value of this method.
+     *
+     * @return true if the command is successfully sent; otherwise false.
+     */
+    public boolean connect(BluetoothDevice device) {
+        boolean result = false;
+
+        final IBluetoothHidDevice service = getService();
+        if (service != null) {
+            try {
+                result = service.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Disconnects from currently connected host. The connection state should be tracked by the
+     * application by handling callback from Callback#onConnectionStateChanged. The connection state
+     * is not related to the return value of this method.
+     *
+     * @return true if the command is successfully sent; otherwise false.
+     */
+    public boolean disconnect(BluetoothDevice device) {
+        boolean result = false;
+
+        final IBluetoothHidDevice service = getService();
+        if (service != null) {
+            try {
+                result = service.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+
+        return result;
+    }
+
+    /**
+     * Connects Hid Device if connectionPolicy is {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED}
+     * and disconnects Hid device if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}.
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy determines whether hid device should be connected or disconnected
+     * @return true if hid device is connected or disconnected, false otherwise
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        try {
+            final IBluetoothHidDevice service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    private boolean isEnabled() {
+        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+        return false;
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+        if (device == null) return false;
+
+        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+        return false;
+    }
+
+    private static void log(String msg) {
+        if (DBG) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/android/bluetooth/BluetoothHidDeviceAppQosSettings.java b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
new file mode 100644
index 0000000..b21ebe5
--- /dev/null
+++ b/android/bluetooth/BluetoothHidDeviceAppQosSettings.java
@@ -0,0 +1,131 @@
+/*
+ * 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;
+
+/**
+ * Represents the Quality of Service (QoS) settings for a Bluetooth HID Device application.
+ *
+ * <p>The BluetoothHidDevice framework will update the L2CAP QoS settings for the app during
+ * registration.
+ *
+ * <p>{@see BluetoothHidDevice}
+ */
+public final class BluetoothHidDeviceAppQosSettings implements Parcelable {
+
+    private final int mServiceType;
+    private final int mTokenRate;
+    private final int mTokenBucketSize;
+    private final int mPeakBandwidth;
+    private final int mLatency;
+    private final int mDelayVariation;
+
+    public static final int SERVICE_NO_TRAFFIC = 0x00;
+    public static final int SERVICE_BEST_EFFORT = 0x01;
+    public static final int SERVICE_GUARANTEED = 0x02;
+
+    public static final int MAX = (int) 0xffffffff;
+
+    /**
+     * Create a BluetoothHidDeviceAppQosSettings object for the Bluetooth L2CAP channel. The QoS
+     * Settings is optional. Please refer to Bluetooth HID Specfication v1.1.1 Section 5.2 and
+     * Appendix D for parameters.
+     *
+     * @param serviceType L2CAP service type, default = SERVICE_BEST_EFFORT
+     * @param tokenRate L2CAP token rate, default = 0
+     * @param tokenBucketSize L2CAP token bucket size, default = 0
+     * @param peakBandwidth L2CAP peak bandwidth, default = 0
+     * @param latency L2CAP latency, default = MAX
+     * @param delayVariation L2CAP delay variation, default = MAX
+     */
+    public BluetoothHidDeviceAppQosSettings(
+            int serviceType,
+            int tokenRate,
+            int tokenBucketSize,
+            int peakBandwidth,
+            int latency,
+            int delayVariation) {
+        mServiceType = serviceType;
+        mTokenRate = tokenRate;
+        mTokenBucketSize = tokenBucketSize;
+        mPeakBandwidth = peakBandwidth;
+        mLatency = latency;
+        mDelayVariation = delayVariation;
+    }
+
+    public int getServiceType() {
+        return mServiceType;
+    }
+
+    public int getTokenRate() {
+        return mTokenRate;
+    }
+
+    public int getTokenBucketSize() {
+        return mTokenBucketSize;
+    }
+
+    public int getPeakBandwidth() {
+        return mPeakBandwidth;
+    }
+
+    public int getLatency() {
+        return mLatency;
+    }
+
+    public int getDelayVariation() {
+        return mDelayVariation;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull 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(mServiceType);
+        out.writeInt(mTokenRate);
+        out.writeInt(mTokenBucketSize);
+        out.writeInt(mPeakBandwidth);
+        out.writeInt(mLatency);
+        out.writeInt(mDelayVariation);
+    }
+}
diff --git a/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
new file mode 100644
index 0000000..4e1a2aa
--- /dev/null
+++ b/android/bluetooth/BluetoothHidDeviceAppSdpSettings.java
@@ -0,0 +1,123 @@
+/*
+ * 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 android.util.EventLog;
+
+
+/**
+ * Represents the Service Discovery Protocol (SDP) settings for a Bluetooth HID Device application.
+ *
+ * <p>The BluetoothHidDevice framework adds the SDP record during app registration, so that the
+ * Android device can be discovered as a Bluetooth HID Device.
+ *
+ * <p>{@see BluetoothHidDevice}
+ */
+public final class BluetoothHidDeviceAppSdpSettings implements Parcelable {
+
+    private static final int MAX_DESCRIPTOR_SIZE = 2048;
+
+    private final String mName;
+    private final String mDescription;
+    private final String mProvider;
+    private final byte mSubclass;
+    private final byte[] mDescriptors;
+
+    /**
+     * Create a BluetoothHidDeviceAppSdpSettings object for the Bluetooth SDP record.
+     *
+     * @param name Name of this Bluetooth HID device. Maximum length is 50 bytes.
+     * @param description Description for this Bluetooth HID device. Maximum length is 50 bytes.
+     * @param provider Provider of this Bluetooth HID device. Maximum length is 50 bytes.
+     * @param subclass Subclass of this Bluetooth HID device. See <a
+     *     href="www.usb.org/developers/hidpage/HID1_11.pdf">
+     *     www.usb.org/developers/hidpage/HID1_11.pdf Section 4.2</a>
+     * @param descriptors Descriptors of this Bluetooth HID device. See <a
+     *     href="www.usb.org/developers/hidpage/HID1_11.pdf">
+     *     www.usb.org/developers/hidpage/HID1_11.pdf Chapter 6</a> Maximum length is 2048 bytes.
+     */
+    public BluetoothHidDeviceAppSdpSettings(
+            String name, String description, String provider, byte subclass, byte[] descriptors) {
+        mName = name;
+        mDescription = description;
+        mProvider = provider;
+        mSubclass = subclass;
+
+        if (descriptors == null || descriptors.length > MAX_DESCRIPTOR_SIZE) {
+            EventLog.writeEvent(0x534e4554, "119819889", -1, "");
+            throw new IllegalArgumentException("descriptors must be not null and shorter than "
+                    + MAX_DESCRIPTOR_SIZE);
+        }
+        mDescriptors = descriptors.clone();
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public String getDescription() {
+        return mDescription;
+    }
+
+    public String getProvider() {
+        return mProvider;
+    }
+
+    public byte getSubclass() {
+        return mSubclass;
+    }
+
+    public byte[] getDescriptors() {
+        return mDescriptors;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull 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(mName);
+        out.writeString(mDescription);
+        out.writeString(mProvider);
+        out.writeByte(mSubclass);
+        out.writeByteArray(mDescriptors);
+    }
+}
diff --git a/android/bluetooth/BluetoothHidHost.java b/android/bluetooth/BluetoothHidHost.java
new file mode 100644
index 0000000..9561d93
--- /dev/null
+++ b/android/bluetooth/BluetoothHidHost.java
@@ -0,0 +1,711 @@
+/*
+ * Copyright (C) 2011 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+
+/**
+ * This class provides the public APIs to control the Bluetooth Input
+ * Device Profile.
+ *
+ * <p>BluetoothHidHost is a proxy object for controlling the Bluetooth
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothHidHost proxy object.
+ *
+ * <p>Each method is protected with its appropriate permission.
+ *
+ * @hide
+ */
+@SystemApi
+public final class BluetoothHidHost implements BluetoothProfile {
+    private static final String TAG = "BluetoothHidHost";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the Input
+     * Device profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    @SuppressLint("ActionValue")
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_PROTOCOL_MODE_CHANGED =
+            "android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED";
+
+    /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_HANDSHAKE =
+            "android.bluetooth.input.profile.action.HANDSHAKE";
+
+    /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_REPORT =
+            "android.bluetooth.input.profile.action.REPORT";
+
+    /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_VIRTUAL_UNPLUG_STATUS =
+            "android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS";
+
+    /**
+     * @hide
+     */
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_IDLE_TIME_CHANGED =
+            "android.bluetooth.input.profile.action.IDLE_TIME_CHANGED";
+
+    /**
+     * Return codes for the connect and disconnect Bluez / Dbus calls.
+     *
+     * @hide
+     */
+    public static final int INPUT_DISCONNECT_FAILED_NOT_CONNECTED = 5000;
+
+    /**
+     * @hide
+     */
+    public static final int INPUT_CONNECT_FAILED_ALREADY_CONNECTED = 5001;
+
+    /**
+     * @hide
+     */
+    public static final int INPUT_CONNECT_FAILED_ATTEMPT_FAILED = 5002;
+
+    /**
+     * @hide
+     */
+    public static final int INPUT_OPERATION_GENERIC_FAILURE = 5003;
+
+    /**
+     * @hide
+     */
+    public static final int INPUT_OPERATION_SUCCESS = 5004;
+
+    /**
+     * @hide
+     */
+    public static final int PROTOCOL_REPORT_MODE = 0;
+
+    /**
+     * @hide
+     */
+    public static final int PROTOCOL_BOOT_MODE = 1;
+
+    /**
+     * @hide
+     */
+    public static final int PROTOCOL_UNSUPPORTED_MODE = 255;
+
+    /*  int reportType, int reportType, int bufferSize */
+    /**
+     * @hide
+     */
+    public static final byte REPORT_TYPE_INPUT = 1;
+
+    /**
+     * @hide
+     */
+    public static final byte REPORT_TYPE_OUTPUT = 2;
+
+    /**
+     * @hide
+     */
+    public static final byte REPORT_TYPE_FEATURE = 3;
+
+    /**
+     * @hide
+     */
+    public static final int VIRTUAL_UNPLUG_STATUS_SUCCESS = 0;
+
+    /**
+     * @hide
+     */
+    public static final int VIRTUAL_UNPLUG_STATUS_FAIL = 1;
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_PROTOCOL_MODE =
+            "android.bluetooth.BluetoothHidHost.extra.PROTOCOL_MODE";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_REPORT_TYPE =
+            "android.bluetooth.BluetoothHidHost.extra.REPORT_TYPE";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_REPORT_ID =
+            "android.bluetooth.BluetoothHidHost.extra.REPORT_ID";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_REPORT_BUFFER_SIZE =
+            "android.bluetooth.BluetoothHidHost.extra.REPORT_BUFFER_SIZE";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_REPORT = "android.bluetooth.BluetoothHidHost.extra.REPORT";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_STATUS = "android.bluetooth.BluetoothHidHost.extra.STATUS";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_VIRTUAL_UNPLUG_STATUS =
+            "android.bluetooth.BluetoothHidHost.extra.VIRTUAL_UNPLUG_STATUS";
+
+    /**
+     * @hide
+     */
+    public static final String EXTRA_IDLE_TIME =
+            "android.bluetooth.BluetoothHidHost.extra.IDLE_TIME";
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothHidHost> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.HID_HOST,
+                    "BluetoothHidHost", IBluetoothHidHost.class.getName()) {
+                @Override
+                public IBluetoothHidHost getServiceInterface(IBinder service) {
+                    return IBluetoothHidHost.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
+    /**
+     * Create a BluetoothHidHost proxy object for interacting with the local
+     * Bluetooth Service which handles the InputDevice profile
+     */
+    /*package*/ BluetoothHidHost(Context context, ServiceListener listener) {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+    }
+
+    /*package*/ void close() {
+        if (VDBG) log("close()");
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothHidHost getService() {
+        return mProfileConnector.getService();
+    }
+
+    /**
+     * Initiate connection to a profile of the remote bluetooth device.
+     *
+     * <p> The system supports connection to multiple input devices.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is already connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that
+     * connection state intent for the profile will be broadcasted with
+     * the state. Users can get the connection state of the profile
+     * from this intent.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Initiate disconnection from a profile
+     *
+     * <p> This API will return false in scenarios like the profile on the
+     * Bluetooth device is not in connected state etc. When this API returns,
+     * true, it is guaranteed that the connection state change
+     * intent will be broadcasted with the state. Users can get the
+     * disconnection state of the profile from this intent.
+     *
+     * <p> If the disconnection is initiated by a remote device, the state
+     * will transition from {@link #STATE_CONNECTED} to
+     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
+     * host (local) device the state will transition from
+     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
+     * state {@link #STATE_DISCONNECTED}. The transition to
+     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
+     * two scenarios.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @SystemApi
+    @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @SystemApi
+    @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getConnectionState(@NonNull BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
+        if (device == null) {
+            throw new IllegalArgumentException("device must not be null");
+        }
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF},
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        if (device == null) {
+            throw new IllegalArgumentException("device must not be null");
+        }
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                return false;
+            }
+            try {
+                return service.setConnectionPolicy(device, connectionPolicy);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
+        if (VDBG) log("getConnectionPolicy(" + device + ")");
+        if (device == null) {
+            throw new IllegalArgumentException("device must not be null");
+        }
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionPolicy(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    private boolean isEnabled() {
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
+    }
+
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    /**
+     * Initiate virtual unplug for a HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean virtualUnplug(BluetoothDevice device) {
+        if (DBG) log("virtualUnplug(" + device + ")");
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.virtualUnplug(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+
+    }
+
+    /**
+     * Send Get_Protocol_Mode command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean getProtocolMode(BluetoothDevice device) {
+        if (VDBG) log("getProtocolMode(" + device + ")");
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getProtocolMode(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Set_Protocol_Mode command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
+        if (DBG) log("setProtocolMode(" + device + ")");
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.setProtocolMode(device, protocolMode);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Get_Report command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @param reportType Report type
+     * @param reportId Report ID
+     * @param bufferSize Report receiving buffer size
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean getReport(BluetoothDevice device, byte reportType, byte reportId,
+            int bufferSize) {
+        if (VDBG) {
+            log("getReport(" + device + "), reportType=" + reportType + " reportId=" + reportId
+                    + "bufferSize=" + bufferSize);
+        }
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getReport(device, reportType, reportId, bufferSize);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Set_Report command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @param reportType Report type
+     * @param report Report receiving buffer size
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean setReport(BluetoothDevice device, byte reportType, String report) {
+        if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.setReport(device, reportType, report);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Send_Data command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @param report Report to send
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean sendData(BluetoothDevice device, String report) {
+        if (DBG) log("sendData(" + device + "), report=" + report);
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.sendData(device, report);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Get_Idle_Time command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean getIdleTime(BluetoothDevice device) {
+        if (DBG) log("getIdletime(" + device + ")");
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getIdleTime(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Send Set_Idle_Time command to the connected HID input device.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param device Remote Bluetooth Device
+     * @param idleTime Idle time to be set on HID Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
+        if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
+        final IBluetoothHidHost service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.setIdleTime(device, idleTime);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/android/bluetooth/BluetoothInputStream.java b/android/bluetooth/BluetoothInputStream.java
new file mode 100644
index 0000000..8eb79b2
--- /dev/null
+++ b/android/bluetooth/BluetoothInputStream.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2009 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 java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * BluetoothInputStream.
+ *
+ * Used to write to a Bluetooth socket.
+ *
+ * @hide
+ */
+/*package*/ final class BluetoothInputStream extends InputStream {
+    private BluetoothSocket mSocket;
+
+    /*package*/ BluetoothInputStream(BluetoothSocket s) {
+        mSocket = s;
+    }
+
+    /**
+     * Return number of bytes available before this stream will block.
+     */
+    public int available() throws IOException {
+        return mSocket.available();
+    }
+
+    public void close() throws IOException {
+        mSocket.close();
+    }
+
+    /**
+     * Reads a single byte from this stream and returns it as an integer in the
+     * range from 0 to 255. Returns -1 if the end of the stream has been
+     * reached. Blocks until one byte has been read, the end of the source
+     * stream is detected or an exception is thrown.
+     *
+     * @return the byte read or -1 if the end of stream has been reached.
+     * @throws IOException if the stream is closed or another IOException occurs.
+     * @since Android 1.5
+     */
+    public int read() throws IOException {
+        byte[] b = new byte[1];
+        int ret = mSocket.read(b, 0, 1);
+        if (ret == 1) {
+            return (int) b[0] & 0xff;
+        } else {
+            return -1;
+        }
+    }
+
+    /**
+     * Reads at most {@code length} bytes from this stream and stores them in
+     * the byte array {@code b} starting at {@code offset}.
+     *
+     * @param b the byte array in which to store the bytes read.
+     * @param offset the initial position in {@code buffer} to store the bytes read from this
+     * stream.
+     * @param length the maximum number of bytes to store in {@code b}.
+     * @return the number of bytes actually read or -1 if the end of the stream has been reached.
+     * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code length < 0}, or if {@code
+     * offset + length} is greater than the length of {@code b}.
+     * @throws IOException if the stream is closed or another IOException occurs.
+     * @since Android 1.5
+     */
+    public int read(byte[] b, int offset, int length) throws IOException {
+        if (b == null) {
+            throw new NullPointerException("byte array is null");
+        }
+        if ((offset | length) < 0 || length > b.length - offset) {
+            throw new ArrayIndexOutOfBoundsException("invalid offset or length");
+        }
+        return mSocket.read(b, offset, length);
+    }
+}
diff --git a/android/bluetooth/BluetoothManager.java b/android/bluetooth/BluetoothManager.java
new file mode 100644
index 0000000..3b4fe0a
--- /dev/null
+++ b/android/bluetooth/BluetoothManager.java
@@ -0,0 +1,258 @@
+/*
+ * 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.Manifest;
+import android.annotation.RequiresFeature;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * High level manager used to obtain an instance of an {@link BluetoothAdapter}
+ * and to conduct overall Bluetooth Management.
+ * <p>
+ * Use {@link android.content.Context#getSystemService(java.lang.String)}
+ * with {@link Context#BLUETOOTH_SERVICE} to create an {@link BluetoothManager},
+ * then call {@link #getAdapter} to obtain the {@link BluetoothAdapter}.
+ * </p>
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>
+ * For more information about using BLUETOOTH, read the <a href=
+ * "{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer
+ * guide.
+ * </p>
+ * </div>
+ *
+ * @see Context#getSystemService
+ * @see BluetoothAdapter#getDefaultAdapter()
+ */
+@SystemService(Context.BLUETOOTH_SERVICE)
+@RequiresFeature(PackageManager.FEATURE_BLUETOOTH)
+public final class BluetoothManager {
+    private static final String TAG = "BluetoothManager";
+    private static final boolean DBG = false;
+
+    private final BluetoothAdapter mAdapter;
+
+    /**
+     * @hide
+     */
+    public BluetoothManager(Context context) {
+        if (context.getAttributionTag() == null) {
+            context = context.getApplicationContext();
+            if (context == null) {
+                throw new IllegalArgumentException(
+                        "context not associated with any application (using a mock context?)");
+            }
+
+            mAdapter = BluetoothAdapter.getDefaultAdapter();
+        } else {
+            IBinder b = ServiceManager.getService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE);
+            if (b != null) {
+                mAdapter = new BluetoothAdapter(IBluetoothManager.Stub.asInterface(b));
+            } else {
+                Log.e(TAG, "Bluetooth binder is null");
+                mAdapter = null;
+            }
+        }
+
+        // Context is not initialized in constructor
+        if (mAdapter != null) {
+            mAdapter.setContext(context);
+        }
+    }
+
+    /**
+     * Get the BLUETOOTH Adapter for this device.
+     *
+     * @return the BLUETOOTH Adapter
+     */
+    public BluetoothAdapter getAdapter() {
+        return mAdapter;
+    }
+
+    /**
+     * Get the current connection state of the profile to the remote device.
+     *
+     * <p>This is not specific to any application configuration but represents
+     * the connection state of the local Bluetooth adapter for certain profile.
+     * This can be used by applications like status bar which would just like
+     * to know the state of Bluetooth.
+     *
+     * @param device Remote bluetooth device.
+     * @param profile GATT or GATT_SERVER
+     * @return State of the profile connection. One of {@link BluetoothProfile#STATE_CONNECTED},
+     * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_DISCONNECTED},
+     * {@link BluetoothProfile#STATE_DISCONNECTING}
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public int getConnectionState(BluetoothDevice device, int profile) {
+        if (DBG) Log.d(TAG, "getConnectionState()");
+
+        List<BluetoothDevice> connectedDevices = getConnectedDevices(profile);
+        for (BluetoothDevice connectedDevice : connectedDevices) {
+            if (device.equals(connectedDevice)) {
+                return BluetoothProfile.STATE_CONNECTED;
+            }
+        }
+
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Get connected devices for the specified profile.
+     *
+     * <p> Return the set of devices which are in state {@link BluetoothProfile#STATE_CONNECTED}
+     *
+     * <p>This is not specific to any application configuration but represents
+     * the connection state of Bluetooth for this profile.
+     * This can be used by applications like status bar which would just like
+     * to know the state of Bluetooth.
+     *
+     * @param profile GATT or GATT_SERVER
+     * @return List of devices. The list will be empty on error.
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public List<BluetoothDevice> getConnectedDevices(int profile) {
+        if (DBG) Log.d(TAG, "getConnectedDevices");
+        if (profile != BluetoothProfile.GATT && profile != BluetoothProfile.GATT_SERVER) {
+            throw new IllegalArgumentException("Profile not supported: " + profile);
+        }
+
+        List<BluetoothDevice> connectedDevices = new ArrayList<BluetoothDevice>();
+
+        try {
+            IBluetoothManager managerService = mAdapter.getBluetoothManager();
+            IBluetoothGatt iGatt = managerService.getBluetoothGatt();
+            if (iGatt == null) return connectedDevices;
+
+            connectedDevices = iGatt.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.
+     *
+     * @param profile GATT or GATT_SERVER
+     * @param states Array of states. States can be one of {@link BluetoothProfile#STATE_CONNECTED},
+     * {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_DISCONNECTED},
+     * {@link BluetoothProfile#STATE_DISCONNECTING},
+     * @return List of devices. The list will be empty on error.
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int profile, int[] states) {
+        if (DBG) Log.d(TAG, "getDevicesMatchingConnectionStates");
+
+        if (profile != BluetoothProfile.GATT && profile != BluetoothProfile.GATT_SERVER) {
+            throw new IllegalArgumentException("Profile not supported: " + profile);
+        }
+
+        List<BluetoothDevice> devices = new ArrayList<BluetoothDevice>();
+
+        try {
+            IBluetoothManager managerService = mAdapter.getBluetoothManager();
+            IBluetoothGatt iGatt = managerService.getBluetoothGatt();
+            if (iGatt == null) return devices;
+            devices = iGatt.getDevicesMatchingConnectionStates(states);
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+        }
+
+        return devices;
+    }
+
+    /**
+     * Open a GATT Server
+     * The callback is used to deliver results to Caller, such as connection status as well
+     * as the results of any other GATT server operations.
+     * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer
+     * to conduct GATT server operations.
+     *
+     * @param context App context
+     * @param callback GATT server callback handler that will receive asynchronous callbacks.
+     * @return BluetoothGattServer instance
+     */
+    public BluetoothGattServer openGattServer(Context context,
+            BluetoothGattServerCallback callback) {
+
+        return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO));
+    }
+
+    /**
+     * Open a GATT Server
+     * The callback is used to deliver results to Caller, such as connection status as well
+     * as the results of any other GATT server operations.
+     * The method returns a BluetoothGattServer instance. You can use BluetoothGattServer
+     * to conduct GATT server operations.
+     *
+     * @param context App context
+     * @param callback GATT server callback handler that will receive asynchronous callbacks.
+     * @param transport preferred transport for GATT connections to remote dual-mode devices {@link
+     * BluetoothDevice#TRANSPORT_AUTO} or {@link BluetoothDevice#TRANSPORT_BREDR} or {@link
+     * BluetoothDevice#TRANSPORT_LE}
+     * @return BluetoothGattServer instance
+     * @hide
+     */
+    public BluetoothGattServer openGattServer(Context context,
+            BluetoothGattServerCallback callback, int transport) {
+        if (context == null || callback == null) {
+            throw new IllegalArgumentException("null parameter: " + context + " " + callback);
+        }
+
+        // TODO(Bluetooth) check whether platform support BLE
+        //     Do the check here or in GattServer?
+
+        try {
+            IBluetoothManager managerService = mAdapter.getBluetoothManager();
+            IBluetoothGatt iGatt = managerService.getBluetoothGatt();
+            if (iGatt == null) {
+                Log.e(TAG, "Fail to get GATT Server connection");
+                return null;
+            }
+            BluetoothGattServer mGattServer = new BluetoothGattServer(iGatt, transport);
+            Boolean regStatus = mGattServer.registerCallback(callback);
+            return regStatus ? mGattServer : null;
+        } catch (RemoteException e) {
+            Log.e(TAG, "", e);
+            return null;
+        }
+    }
+}
diff --git a/android/bluetooth/BluetoothMap.java b/android/bluetooth/BluetoothMap.java
new file mode 100644
index 0000000..14a71c4
--- /dev/null
+++ b/android/bluetooth/BluetoothMap.java
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2008 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.CloseGuard;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the APIs to control the Bluetooth MAP
+ * Profile.
+ *
+ * @hide
+ */
+@SystemApi
+public final class BluetoothMap implements BluetoothProfile, AutoCloseable {
+
+    private static final String TAG = "BluetoothMap";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    private CloseGuard mCloseGuard;
+
+    /** @hide */
+    @SuppressLint("ActionValue")
+    @SystemApi
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.map.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * There was an error trying to obtain the state
+     *
+     * @hide
+     */
+    public static final int STATE_ERROR = -1;
+
+    /** @hide */
+    public static final int RESULT_FAILURE = 0;
+    /** @hide */
+    public static final int RESULT_SUCCESS = 1;
+    /**
+     * Connection canceled before completion.
+     *
+     * @hide
+     */
+    public static final int RESULT_CANCELED = 2;
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothMap> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.MAP,
+                    "BluetoothMap", IBluetoothMap.class.getName()) {
+                @Override
+                public IBluetoothMap getServiceInterface(IBinder service) {
+                    return IBluetoothMap.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
+    /**
+     * Create a BluetoothMap proxy object.
+     */
+    /*package*/ BluetoothMap(Context context, ServiceListener listener) {
+        if (DBG) Log.d(TAG, "Create BluetoothMap proxy object");
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+        mCloseGuard = new CloseGuard();
+        mCloseGuard.open("close");
+    }
+
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    protected void finalize() {
+        if (mCloseGuard != null) {
+            mCloseGuard.warnIfOpen();
+        }
+        close();
+    }
+
+    /**
+     * Close the connection to the backing service.
+     * Other public functions of BluetoothMap will return default error
+     * results once close() has been called. Multiple invocations of close()
+     * are ok.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void close() {
+        if (VDBG) log("close()");
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothMap getService() {
+        return mProfileConnector.getService();
+    }
+
+    /**
+     * Get the current state of the BluetoothMap service.
+     *
+     * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
+     * connected to the Map service.
+     *
+     * @hide
+     */
+    public int getState() {
+        if (VDBG) log("getState()");
+        final IBluetoothMap service = getService();
+        if (service != null) {
+            try {
+                return service.getState();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        }
+        return BluetoothMap.STATE_ERROR;
+    }
+
+    /**
+     * Get the currently connected remote Bluetooth device (PCE).
+     *
+     * @return The remote Bluetooth device, or null if not in connected or connecting state, or if
+     * this proxy object is not connected to the Map service.
+     *
+     * @hide
+     */
+    public BluetoothDevice getClient() {
+        if (VDBG) log("getClient()");
+        final IBluetoothMap service = getService();
+        if (service != null) {
+            try {
+                return service.getClient();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if the specified Bluetooth device is connected.
+     * Returns false if not connected, or if this proxy object is not
+     * currently connected to the Map service.
+     *
+     * @hide
+     */
+    public boolean isConnected(BluetoothDevice device) {
+        if (VDBG) log("isConnected(" + device + ")");
+        final IBluetoothMap service = getService();
+        if (service != null) {
+            try {
+                return service.isConnected(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Initiate connection. Initiation of outgoing connections is not
+     * supported for MAP server.
+     *
+     * @hide
+     */
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")" + "not supported for MAPS");
+        return false;
+    }
+
+    /**
+     * Initiate disconnect.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on error, true otherwise
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        final IBluetoothMap service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Check class bits for possible Map support.
+     * This is a simple heuristic that tries to guess if a device with the
+     * given class bits might support Map. It is not accurate for all
+     * devices. It tries to err on the side of false positives.
+     *
+     * @return True if this device might support Map.
+     *
+     * @hide
+     */
+    public static boolean doesClassMatchSink(BluetoothClass btClass) {
+        // TODO optimize the rule
+        switch (btClass.getDeviceClass()) {
+            case BluetoothClass.Device.COMPUTER_DESKTOP:
+            case BluetoothClass.Device.COMPUTER_LAPTOP:
+            case BluetoothClass.Device.COMPUTER_SERVER:
+            case BluetoothClass.Device.COMPUTER_UNCATEGORIZED:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Get the list of connected devices. Currently at most one.
+     *
+     * @return list of connected devices
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
+        if (DBG) log("getConnectedDevices()");
+        final IBluetoothMap service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Get the list of devices matching specified states. Currently at most one.
+     *
+     * @return list of matching devices
+     *
+     * @hide
+     */
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) log("getDevicesMatchingStates()");
+        final IBluetoothMap service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Get connection state of device
+     *
+     * @return device connection state
+     *
+     * @hide
+     */
+    public int getConnectionState(BluetoothDevice device) {
+        if (DBG) log("getConnectionState(" + device + ")");
+        final IBluetoothMap service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF},
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        final IBluetoothMap service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                return false;
+            }
+            try {
+                return service.setConnectionPolicy(device, connectionPolicy);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
+        if (VDBG) log("getConnectionPolicy(" + device + ")");
+        final IBluetoothMap service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionPolicy(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    private boolean isEnabled() {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
+        log("Bluetooth is Not enabled");
+        return false;
+    }
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+}
diff --git a/android/bluetooth/BluetoothMapClient.java b/android/bluetooth/BluetoothMapClient.java
new file mode 100644
index 0000000..19240dc
--- /dev/null
+++ b/android/bluetooth/BluetoothMapClient.java
@@ -0,0 +1,422 @@
+/*
+ * 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the APIs to control the Bluetooth MAP MCE Profile.
+ *
+ * @hide
+ */
+public final class BluetoothMapClient implements BluetoothProfile {
+
+    private static final String TAG = "BluetoothMapClient";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
+
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED";
+    public static final String ACTION_MESSAGE_RECEIVED =
+            "android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED";
+    /* Actions to be used for pending intents */
+    public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY =
+            "android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY";
+    public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
+            "android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";
+
+    /* Extras used in ACTION_MESSAGE_RECEIVED intent.
+     * NOTE: HANDLE is only valid for a single session with the device. */
+    public static final String EXTRA_MESSAGE_HANDLE =
+            "android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE";
+    public static final String EXTRA_MESSAGE_TIMESTAMP =
+            "android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP";
+    public static final String EXTRA_MESSAGE_READ_STATUS =
+            "android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS";
+    public static final String EXTRA_SENDER_CONTACT_URI =
+            "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
+    public static final String EXTRA_SENDER_CONTACT_NAME =
+            "android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
+
+    /** There was an error trying to obtain the state */
+    public static final int STATE_ERROR = -1;
+
+    public static final int RESULT_FAILURE = 0;
+    public static final int RESULT_SUCCESS = 1;
+    /** Connection canceled before completion. */
+    public static final int RESULT_CANCELED = 2;
+
+    private static final int UPLOADING_FEATURE_BITMASK = 0x08;
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothMapClient> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.MAP_CLIENT,
+                    "BluetoothMapClient", IBluetoothMapClient.class.getName()) {
+                @Override
+                public IBluetoothMapClient getServiceInterface(IBinder service) {
+                    return IBluetoothMapClient.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
+    /**
+     * Create a BluetoothMapClient proxy object.
+     */
+    /*package*/ BluetoothMapClient(Context context, ServiceListener listener) {
+        if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object");
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Close the connection to the backing service.
+     * Other public functions of BluetoothMap will return default error
+     * results once close() has been called. Multiple invocations of close()
+     * are ok.
+     */
+    public void close() {
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothMapClient getService() {
+        return mProfileConnector.getService();
+    }
+
+    /**
+     * Returns true if the specified Bluetooth device is connected.
+     * Returns false if not connected, or if this proxy object is not
+     * currently connected to the Map service.
+     */
+    public boolean isConnected(BluetoothDevice device) {
+        if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
+        final IBluetoothMapClient service = getService();
+        if (service != null) {
+            try {
+                return service.isConnected(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Initiate connection. Initiation of outgoing connections is not
+     * supported for MAP server.
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
+        final IBluetoothMapClient service = getService();
+        if (service != null) {
+            try {
+                return service.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Initiate disconnect.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on error, true otherwise
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "disconnect(" + device + ")");
+        final IBluetoothMapClient service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the list of connected devices. Currently at most one.
+     *
+     * @return list of connected devices
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (DBG) Log.d(TAG, "getConnectedDevices()");
+        final IBluetoothMapClient service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<>();
+    }
+
+    /**
+     * Get the list of devices matching specified states. Currently at most one.
+     *
+     * @return list of matching devices
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
+        final IBluetoothMapClient service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<>();
+    }
+
+    /**
+     * Get connection state of device
+     *
+     * @return device connection state
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
+        final IBluetoothMapClient service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF},
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) Log.d(TAG, "setPriority(" + device + ", " + priority + ")");
+        return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) Log.d(TAG, "setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        final IBluetoothMapClient service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                return false;
+            }
+            try {
+                return service.setConnectionPolicy(device, connectionPolicy);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) Log.d(TAG, "getPriority(" + device + ")");
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
+        if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")");
+        final IBluetoothMapClient service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionPolicy(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    /**
+     * Send a message.
+     *
+     * Send an SMS message to either the contacts primary number or the telephone number specified.
+     *
+     * @param device Bluetooth device
+     * @param contacts Uri[] of the contacts
+     * @param message Message to be sent
+     * @param sentIntent intent issued when message is sent
+     * @param deliveredIntent intent issued when message is delivered
+     * @return true if the message is enqueued, false on error
+     */
+    @UnsupportedAppUsage
+    public boolean sendMessage(BluetoothDevice device, Uri[] contacts, String message,
+            PendingIntent sentIntent, PendingIntent deliveredIntent) {
+        if (DBG) Log.d(TAG, "sendMessage(" + device + ", " + contacts + ", " + message);
+        final IBluetoothMapClient service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.sendMessage(device, contacts, message, sentIntent, deliveredIntent);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Get unread messages.  Unread messages will be published via {@link #ACTION_MESSAGE_RECEIVED}.
+     *
+     * @param device Bluetooth device
+     * @return true if the message is enqueued, false on error
+     */
+    public boolean getUnreadMessages(BluetoothDevice device) {
+        if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
+        final IBluetoothMapClient service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getUnreadMessages(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the "Uploading" feature bit value from the SDP record's
+     * MapSupportedFeatures field (see Bluetooth MAP 1.4 spec, page 114).
+     * @param device The Bluetooth device to get this value for.
+     * @return Returns true if the Uploading bit value in SDP record's
+     *         MapSupportedFeatures field is set. False is returned otherwise.
+     */
+    public boolean isUploadingSupported(BluetoothDevice device) {
+        final IBluetoothMapClient service = getService();
+        try {
+            return (service != null && isEnabled() && isValidDevice(device))
+                && ((service.getSupportedFeatures(device) & UPLOADING_FEATURE_BITMASK) > 0);
+        } catch (RemoteException e) {
+            Log.e(TAG, e.getMessage());
+        }
+        return false;
+    }
+
+    private boolean isEnabled() {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) return true;
+        if (DBG) Log.d(TAG, "Bluetooth is Not enabled");
+        return false;
+    }
+
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+}
diff --git a/android/bluetooth/BluetoothMasInstance.java b/android/bluetooth/BluetoothMasInstance.java
new file mode 100644
index 0000000..b64d049
--- /dev/null
+++ b/android/bluetooth/BluetoothMasInstance.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2014 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;
+
+/** @hide */
+public final class BluetoothMasInstance implements Parcelable {
+    private final int mId;
+    private final String mName;
+    private final int mChannel;
+    private final int mMsgTypes;
+
+    public BluetoothMasInstance(int id, String name, int channel, int msgTypes) {
+        mId = id;
+        mName = name;
+        mChannel = channel;
+        mMsgTypes = msgTypes;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o instanceof BluetoothMasInstance) {
+            return mId == ((BluetoothMasInstance) o).mId;
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return mId + (mChannel << 8) + (mMsgTypes << 16);
+    }
+
+    @Override
+    public String toString() {
+        return Integer.toString(mId) + ":" + mName + ":" + mChannel + ":"
+                + Integer.toHexString(mMsgTypes);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BluetoothMasInstance> CREATOR =
+            new Parcelable.Creator<BluetoothMasInstance>() {
+                public BluetoothMasInstance createFromParcel(Parcel in) {
+                    return new BluetoothMasInstance(in.readInt(), in.readString(),
+                            in.readInt(), in.readInt());
+                }
+
+                public BluetoothMasInstance[] newArray(int size) {
+                    return new BluetoothMasInstance[size];
+                }
+            };
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mId);
+        out.writeString(mName);
+        out.writeInt(mChannel);
+        out.writeInt(mMsgTypes);
+    }
+
+    public static final class MessageType {
+        public static final int EMAIL = 0x01;
+        public static final int SMS_GSM = 0x02;
+        public static final int SMS_CDMA = 0x04;
+        public static final int MMS = 0x08;
+    }
+
+    public int getId() {
+        return mId;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public int getChannel() {
+        return mChannel;
+    }
+
+    public int getMsgTypes() {
+        return mMsgTypes;
+    }
+
+    public boolean msgSupported(int msg) {
+        return (mMsgTypes & msg) != 0;
+    }
+}
diff --git a/android/bluetooth/BluetoothOutputStream.java b/android/bluetooth/BluetoothOutputStream.java
new file mode 100644
index 0000000..a0aa2de
--- /dev/null
+++ b/android/bluetooth/BluetoothOutputStream.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2009 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 java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * BluetoothOutputStream.
+ *
+ * Used to read from a Bluetooth socket.
+ *
+ * @hide
+ */
+/*package*/ final class BluetoothOutputStream extends OutputStream {
+    private BluetoothSocket mSocket;
+
+    /*package*/ BluetoothOutputStream(BluetoothSocket s) {
+        mSocket = s;
+    }
+
+    /**
+     * Close this output stream and the socket associated with it.
+     */
+    public void close() throws IOException {
+        mSocket.close();
+    }
+
+    /**
+     * Writes a single byte to this stream. Only the least significant byte of
+     * the integer {@code oneByte} is written to the stream.
+     *
+     * @param oneByte the byte to be written.
+     * @throws IOException if an error occurs while writing to this stream.
+     * @since Android 1.0
+     */
+    public void write(int oneByte) throws IOException {
+        byte[] b = new byte[1];
+        b[0] = (byte) oneByte;
+        mSocket.write(b, 0, 1);
+    }
+
+    /**
+     * Writes {@code count} bytes from the byte array {@code buffer} starting
+     * at position {@code offset} to this stream.
+     *
+     * @param b the buffer to be written.
+     * @param offset the start position in {@code buffer} from where to get bytes.
+     * @param count the number of bytes from {@code buffer} to write to this stream.
+     * @throws IOException if an error occurs while writing to this stream.
+     * @throws IndexOutOfBoundsException if {@code offset < 0} or {@code count < 0}, or if {@code
+     * offset + count} is bigger than the length of {@code buffer}.
+     * @since Android 1.0
+     */
+    public void write(byte[] b, int offset, int count) throws IOException {
+        if (b == null) {
+            throw new NullPointerException("buffer is null");
+        }
+        if ((offset | count) < 0 || count > b.length - offset) {
+            throw new IndexOutOfBoundsException("invalid offset or length");
+        }
+        mSocket.write(b, offset, count);
+    }
+}
diff --git a/android/bluetooth/BluetoothPan.java b/android/bluetooth/BluetoothPan.java
new file mode 100644
index 0000000..a80f5b7
--- /dev/null
+++ b/android/bluetooth/BluetoothPan.java
@@ -0,0 +1,412 @@
+/*
+ * Copyright (C) 2008 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.Manifest;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the APIs to control the Bluetooth Pan
+ * Profile.
+ *
+ * <p>BluetoothPan is a proxy object for controlling the Bluetooth
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothPan proxy object.
+ *
+ * <p>Each method is protected with its appropriate permission.
+ *
+ * @hide
+ */
+@SystemApi
+public final class BluetoothPan implements BluetoothProfile {
+    private static final String TAG = "BluetoothPan";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the Pan
+     * profile.
+     *
+     * <p>This intent will have 4 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * <li> {@link #EXTRA_LOCAL_ROLE} - Which local role the remote device is
+     * bound to. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p> {@link #EXTRA_LOCAL_ROLE} can be one of {@link #LOCAL_NAP_ROLE} or
+     * {@link #LOCAL_PANU_ROLE}
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     */
+    @SuppressLint("ActionValue")
+    @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.pan.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * Extra for {@link #ACTION_CONNECTION_STATE_CHANGED} intent
+     * The local role of the PAN profile that the remote device is bound to.
+     * It can be one of {@link #LOCAL_NAP_ROLE} or {@link #LOCAL_PANU_ROLE}.
+     */
+    @SuppressLint("ActionValue")
+    public static final String EXTRA_LOCAL_ROLE = "android.bluetooth.pan.extra.LOCAL_ROLE";
+
+    /** @hide */
+    @IntDef({PAN_ROLE_NONE, LOCAL_NAP_ROLE, LOCAL_PANU_ROLE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface LocalPanRole {}
+
+    public static final int PAN_ROLE_NONE = 0;
+    /**
+     * The local device is acting as a Network Access Point.
+     */
+    public static final int LOCAL_NAP_ROLE = 1;
+
+    /**
+     * The local device is acting as a PAN User.
+     */
+    public static final int LOCAL_PANU_ROLE = 2;
+
+    /** @hide */
+    @IntDef({PAN_ROLE_NONE, REMOTE_NAP_ROLE, REMOTE_PANU_ROLE})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RemotePanRole {}
+
+    public static final int REMOTE_NAP_ROLE = 1;
+
+    public static final int REMOTE_PANU_ROLE = 2;
+
+    /**
+     * Return codes for the connect and disconnect Bluez / Dbus calls.
+     *
+     * @hide
+     */
+    public static final int PAN_DISCONNECT_FAILED_NOT_CONNECTED = 1000;
+
+    /**
+     * @hide
+     */
+    public static final int PAN_CONNECT_FAILED_ALREADY_CONNECTED = 1001;
+
+    /**
+     * @hide
+     */
+    public static final int PAN_CONNECT_FAILED_ATTEMPT_FAILED = 1002;
+
+    /**
+     * @hide
+     */
+    public static final int PAN_OPERATION_GENERIC_FAILURE = 1003;
+
+    /**
+     * @hide
+     */
+    public static final int PAN_OPERATION_SUCCESS = 1004;
+
+    private final Context mContext;
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothPan> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.PAN,
+                    "BluetoothPan", IBluetoothPan.class.getName()) {
+                @Override
+                public IBluetoothPan getServiceInterface(IBinder service) {
+                    return IBluetoothPan.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
+
+    /**
+     * Create a BluetoothPan proxy object for interacting with the local
+     * Bluetooth Service which handles the Pan profile
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    /*package*/ BluetoothPan(Context context, ServiceListener listener) {
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mContext = context;
+        mProfileConnector.connect(context, listener);
+    }
+
+    /**
+     * Closes the connection to the service and unregisters callbacks
+     */
+    @UnsupportedAppUsage
+    void close() {
+        if (VDBG) log("close()");
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothPan getService() {
+        return mProfileConnector.getService();
+    }
+
+    /** @hide */
+    protected void finalize() {
+        close();
+    }
+
+    /**
+     * Initiate connection to a profile of the remote bluetooth device.
+     *
+     * <p> This API returns false in scenarios like the profile on the
+     * device is already connected or Bluetooth is not turned on.
+     * When this API returns true, it is guaranteed that
+     * connection state intent for the profile will be broadcasted with
+     * the state. Users can get the connection state of the profile
+     * from this intent.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")");
+        final IBluetoothPan service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Initiate disconnection from a profile
+     *
+     * <p> This API will return false in scenarios like the profile on the
+     * Bluetooth device is not in connected state etc. When this API returns,
+     * true, it is guaranteed that the connection state change
+     * intent will be broadcasted with the state. Users can get the
+     * disconnection state of the profile from this intent.
+     *
+     * <p> If the disconnection is initiated by a remote device, the state
+     * will transition from {@link #STATE_CONNECTED} to
+     * {@link #STATE_DISCONNECTED}. If the disconnect is initiated by the
+     * host (local) device the state will transition from
+     * {@link #STATE_CONNECTED} to state {@link #STATE_DISCONNECTING} to
+     * state {@link #STATE_DISCONNECTED}. The transition to
+     * {@link #STATE_DISCONNECTING} can be used to distinguish between the
+     * two scenarios.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on immediate error, true otherwise
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        final IBluetoothPan service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        try {
+            final IBluetoothPan service = getService();
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    @SystemApi
+    @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @NonNull List<BluetoothDevice> getConnectedDevices() {
+        if (VDBG) log("getConnectedDevices()");
+        final IBluetoothPan service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (VDBG) log("getDevicesMatchingStates()");
+        final IBluetoothPan service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     * @hide
+     */
+    @SystemApi
+    @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getConnectionState(@NonNull BluetoothDevice device) {
+        if (VDBG) log("getState(" + device + ")");
+        final IBluetoothPan service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Turns on/off bluetooth tethering
+     *
+     * @param value is whether to enable or disable bluetooth tethering
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public void setBluetoothTethering(boolean value) {
+        String pkgName = mContext.getOpPackageName();
+        if (DBG) log("setBluetoothTethering(" + value + "), calling package:" + pkgName);
+        final IBluetoothPan service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                service.setBluetoothTethering(value, pkgName);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            }
+        }
+    }
+
+    /**
+     * Determines whether tethering is enabled
+     *
+     * @return true if tethering is on, false if not or some error occurred
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean isTetheringOn() {
+        if (VDBG) log("isTetheringOn()");
+        final IBluetoothPan service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.isTetheringOn();
+            } catch (RemoteException e) {
+                Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            }
+        }
+        return false;
+    }
+
+    @UnsupportedAppUsage
+    private boolean isEnabled() {
+        return mAdapter.getState() == BluetoothAdapter.STATE_ON;
+    }
+
+    @UnsupportedAppUsage
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    @UnsupportedAppUsage
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/android/bluetooth/BluetoothPbap.java b/android/bluetooth/BluetoothPbap.java
new file mode 100644
index 0000000..d58a893
--- /dev/null
+++ b/android/bluetooth/BluetoothPbap.java
@@ -0,0 +1,377 @@
+/*
+ * Copyright (C) 2008 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+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.os.UserHandle;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Public API for controlling the Bluetooth Pbap Service. This includes
+ * Bluetooth Phone book Access profile.
+ * BluetoothPbap is a proxy object for controlling the Bluetooth Pbap
+ * Service via IPC.
+ *
+ * Creating a BluetoothPbap object will create a binding with the
+ * BluetoothPbap service. Users of this object should call close() when they
+ * are finished with the BluetoothPbap, so that this proxy object can unbind
+ * from the service.
+ *
+ * This BluetoothPbap object is not immediately bound to the
+ * BluetoothPbap service. Use the ServiceListener interface to obtain a
+ * notification when it is bound, this is especially important if you wish to
+ * immediately call methods on BluetoothPbap after construction.
+ *
+ * To get an instance of the BluetoothPbap class, you can call
+ * {@link BluetoothAdapter#getProfileProxy(Context, ServiceListener, int)} with the final param
+ * being {@link BluetoothProfile#PBAP}. The ServiceListener should be able to get the instance of
+ * BluetoothPbap in {@link android.bluetooth.BluetoothProfile.ServiceListener#onServiceConnected}.
+ *
+ * Android only supports one connected Bluetooth Pce at a time.
+ *
+ * @hide
+ */
+@SystemApi
+public class BluetoothPbap implements BluetoothProfile {
+
+    private static final String TAG = "BluetoothPbap";
+    private static final boolean DBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the PBAP
+     * profile.
+     *
+     * <p>This intent will have 3 extras:
+     * <ul>
+     * <li> {@link BluetoothProfile#EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}- The previous state of the profile. </li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     * <p>{@link BluetoothProfile#EXTRA_STATE} or {@link BluetoothProfile#EXTRA_PREVIOUS_STATE}
+     *  can be any of {@link BluetoothProfile#STATE_DISCONNECTED},
+     *  {@link BluetoothProfile#STATE_CONNECTING}, {@link BluetoothProfile#STATE_CONNECTED},
+     *  {@link BluetoothProfile#STATE_DISCONNECTING}.
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     *
+     * @hide
+     */
+    @SuppressLint("ActionValue")
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
+
+    private volatile IBluetoothPbap mService;
+    private final Context mContext;
+    private ServiceListener mServiceListener;
+    private BluetoothAdapter mAdapter;
+
+    /** @hide */
+    public static final int RESULT_FAILURE = 0;
+    /** @hide */
+    public static final int RESULT_SUCCESS = 1;
+    /**
+     * Connection canceled before completion.
+     *
+     * @hide
+     */
+    public static final int RESULT_CANCELED = 2;
+
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+                public void onBluetoothStateChange(boolean up) {
+                    log("onBluetoothStateChange: up=" + up);
+                    if (!up) {
+                        doUnbind();
+                    } else {
+                        doBind();
+                    }
+                }
+            };
+
+    /**
+     * Create a BluetoothPbap proxy object.
+     *
+     * @hide
+     */
+    public BluetoothPbap(Context context, ServiceListener l) {
+        mContext = context;
+        mServiceListener = l;
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "", re);
+            }
+        }
+        doBind();
+    }
+
+    boolean doBind() {
+        synchronized (mConnection) {
+            try {
+                if (mService == null) {
+                    log("Binding service...");
+                    Intent intent = new Intent(IBluetoothPbap.class.getName());
+                    ComponentName comp = intent.resolveSystemService(
+                            mContext.getPackageManager(), 0);
+                    intent.setComponent(comp);
+                    if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+                            UserHandle.CURRENT_OR_SELF)) {
+                        Log.e(TAG, "Could not bind to Bluetooth Pbap Service with " + intent);
+                        return false;
+                    }
+                }
+            } catch (SecurityException se) {
+                Log.e(TAG, "", se);
+                return false;
+            }
+        }
+        return true;
+    }
+
+    private void doUnbind() {
+        synchronized (mConnection) {
+            if (mService != null) {
+                log("Unbinding service...");
+                try {
+                    mContext.unbindService(mConnection);
+                } catch (IllegalArgumentException ie) {
+                    Log.e(TAG, "", ie);
+                } finally {
+                    mService = null;
+                }
+            }
+        }
+    }
+
+    /** @hide */
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Close the connection to the backing service.
+     * Other public functions of BluetoothPbap will return default error
+     * results once close() has been called. Multiple invocations of close()
+     * are ok.
+     *
+     * @hide
+     */
+    public synchronized void close() {
+        IBluetoothManager mgr = mAdapter.getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                Log.e(TAG, "", re);
+            }
+        }
+        doUnbind();
+        mServiceListener = null;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        log("getConnectedDevices()");
+        final IBluetoothPbap service = mService;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            return new ArrayList<BluetoothDevice>();
+        }
+        try {
+            return service.getConnectedDevices();
+        } catch (RemoteException e) {
+            Log.e(TAG, e.toString());
+        }
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @SystemApi
+    @Override
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
+        log("getConnectionState: device=" + device);
+        try {
+            final IBluetoothPbap service = mService;
+            if (service != null && isEnabled() && isValidDevice(device)) {
+                return service.getConnectionState(device);
+            }
+            if (service == null) {
+                Log.w(TAG, "Proxy not attached to service");
+            }
+            return BluetoothProfile.STATE_DISCONNECTED;
+        } catch (RemoteException e) {
+            Log.e(TAG, e.toString());
+        }
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states));
+        final IBluetoothPbap service = mService;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            return new ArrayList<BluetoothDevice>();
+        }
+        try {
+            return service.getDevicesMatchingConnectionStates(states);
+        } catch (RemoteException e) {
+            Log.e(TAG, e.toString());
+        }
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Set connection policy of the profile and tries to disconnect it if connectionPolicy is
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN}
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of:
+     * {@link BluetoothProfile#CONNECTION_POLICY_ALLOWED},
+     * {@link BluetoothProfile#CONNECTION_POLICY_FORBIDDEN},
+     * {@link BluetoothProfile#CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        try {
+            final IBluetoothPbap service = mService;
+            if (service != null && isEnabled()
+                    && isValidDevice(device)) {
+                if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                        && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                    return false;
+                }
+                return service.setConnectionPolicy(device, connectionPolicy);
+            }
+            if (service == null) Log.w(TAG, "Proxy not attached to service");
+            return false;
+        } catch (RemoteException e) {
+            Log.e(TAG, "Stack:" + Log.getStackTraceString(new Throwable()));
+            return false;
+        }
+    }
+
+    /**
+     * Disconnects the current Pbap client (PCE). Currently this call blocks,
+     * it may soon be made asynchronous. Returns false if this proxy object is
+     * not currently connected to the Pbap service.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean disconnect(BluetoothDevice device) {
+        log("disconnect()");
+        final IBluetoothPbap service = mService;
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+            return false;
+        }
+        try {
+            service.disconnect(device);
+            return true;
+        } catch (RemoteException e) {
+            Log.e(TAG, e.toString());
+        }
+        return false;
+    }
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            log("Proxy object connected");
+            mService = IBluetoothPbap.Stub.asInterface(service);
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(BluetoothProfile.PBAP, BluetoothPbap.this);
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            log("Proxy object disconnected");
+            doUnbind();
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected(BluetoothProfile.PBAP);
+            }
+        }
+    };
+
+    private boolean isEnabled() {
+        if (mAdapter.getState() == BluetoothAdapter.STATE_ON) return true;
+        return false;
+    }
+
+    private boolean isValidDevice(BluetoothDevice device) {
+        if (device == null) return false;
+
+        if (BluetoothAdapter.checkBluetoothAddress(device.getAddress())) return true;
+        return false;
+    }
+
+    private static void log(String msg) {
+        if (DBG) {
+            Log.d(TAG, msg);
+        }
+    }
+}
diff --git a/android/bluetooth/BluetoothPbapClient.java b/android/bluetooth/BluetoothPbapClient.java
new file mode 100644
index 0000000..d3452ff
--- /dev/null
+++ b/android/bluetooth/BluetoothPbapClient.java
@@ -0,0 +1,352 @@
+/*
+ * 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.Manifest;
+import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the APIs to control the Bluetooth PBAP Client Profile.
+ *
+ * @hide
+ */
+public final class BluetoothPbapClient implements BluetoothProfile {
+
+    private static final String TAG = "BluetoothPbapClient";
+    private static final boolean DBG = false;
+    private static final boolean VDBG = false;
+
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.pbapclient.profile.action.CONNECTION_STATE_CHANGED";
+
+    /** There was an error trying to obtain the state */
+    public static final int STATE_ERROR = -1;
+
+    public static final int RESULT_FAILURE = 0;
+    public static final int RESULT_SUCCESS = 1;
+    /** Connection canceled before completion. */
+    public static final int RESULT_CANCELED = 2;
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothPbapClient> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.PBAP_CLIENT,
+                    "BluetoothPbapClient", IBluetoothPbapClient.class.getName()) {
+                @Override
+                public IBluetoothPbapClient getServiceInterface(IBinder service) {
+                    return IBluetoothPbapClient.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
+    /**
+     * Create a BluetoothPbapClient proxy object.
+     */
+    BluetoothPbapClient(Context context, ServiceListener listener) {
+        if (DBG) {
+            Log.d(TAG, "Create BluetoothPbapClient proxy object");
+        }
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Close the connection to the backing service.
+     * Other public functions of BluetoothPbapClient will return default error
+     * results once close() has been called. Multiple invocations of close()
+     * are ok.
+     */
+    public synchronized void close() {
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothPbapClient getService() {
+        return mProfileConnector.getService();
+    }
+
+    /**
+     * Initiate connection.
+     * Upon successful connection to remote PBAP server the Client will
+     * attempt to automatically download the users phonebook and call log.
+     *
+     * @param device a remote device we want connect to
+     * @return <code>true</code> if command has been issued successfully; <code>false</code>
+     * otherwise;
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) {
+            log("connect(" + device + ") for PBAP Client.");
+        }
+        final IBluetoothPbapClient service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.connect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return false;
+    }
+
+    /**
+     * Initiate disconnect.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on error, true otherwise
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) {
+            log("disconnect(" + device + ")" + new Exception());
+        }
+        final IBluetoothPbapClient service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                service.disconnect(device);
+                return true;
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return false;
+    }
+
+    /**
+     * Get the list of connected devices.
+     * Currently at most one.
+     *
+     * @return list of connected devices
+     */
+    @Override
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (DBG) {
+            log("getConnectedDevices()");
+        }
+        final IBluetoothPbapClient service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Get the list of devices matching specified states. Currently at most one.
+     *
+     * @return list of matching devices
+     */
+    @Override
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) {
+            log("getDevicesMatchingStates()");
+        }
+        final IBluetoothPbapClient service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Get connection state of device
+     *
+     * @return device connection state
+     */
+    @Override
+    public int getConnectionState(BluetoothDevice device) {
+        if (DBG) {
+            log("getConnectionState(" + device + ")");
+        }
+        final IBluetoothPbapClient service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    private boolean isEnabled() {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) {
+            return true;
+        }
+        log("Bluetooth is Not enabled");
+        return false;
+    }
+
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF},
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) {
+            log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        }
+        final IBluetoothPbapClient service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                return false;
+            }
+            try {
+                return service.setConnectionPolicy(device, connectionPolicy);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
+        if (VDBG) {
+            log("getConnectionPolicy(" + device + ")");
+        }
+        final IBluetoothPbapClient service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionPolicy(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            }
+        }
+        if (service == null) {
+            Log.w(TAG, "Proxy not attached to service");
+        }
+        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+    }
+}
diff --git a/android/bluetooth/BluetoothProfile.java b/android/bluetooth/BluetoothProfile.java
new file mode 100644
index 0000000..7538df8
--- /dev/null
+++ b/android/bluetooth/BluetoothProfile.java
@@ -0,0 +1,417 @@
+/*
+ * Copyright (C) 2010-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.Manifest;
+import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * Public APIs for the Bluetooth Profiles.
+ *
+ * <p> Clients should call {@link BluetoothAdapter#getProfileProxy},
+ * to get the Profile Proxy. Each public profile implements this
+ * interface.
+ */
+public interface BluetoothProfile {
+
+    /**
+     * Extra for the connection state intents of the individual profiles.
+     *
+     * This extra represents the current connection state of the profile of the
+     * Bluetooth device.
+     */
+    @SuppressLint("ActionValue")
+    String EXTRA_STATE = "android.bluetooth.profile.extra.STATE";
+
+    /**
+     * Extra for the connection state intents of the individual profiles.
+     *
+     * This extra represents the previous connection state of the profile of the
+     * Bluetooth device.
+     */
+    @SuppressLint("ActionValue")
+    String EXTRA_PREVIOUS_STATE =
+            "android.bluetooth.profile.extra.PREVIOUS_STATE";
+
+    /** The profile is in disconnected state */
+    int STATE_DISCONNECTED = 0;
+    /** The profile is in connecting state */
+    int STATE_CONNECTING = 1;
+    /** The profile is in connected state */
+    int STATE_CONNECTED = 2;
+    /** The profile is in disconnecting state */
+    int STATE_DISCONNECTING = 3;
+
+    /** @hide */
+    @IntDef({
+            STATE_DISCONNECTED,
+            STATE_CONNECTING,
+            STATE_CONNECTED,
+            STATE_DISCONNECTING,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BtProfileState {}
+
+    /**
+     * Headset and Handsfree profile
+     */
+    int HEADSET = 1;
+
+    /**
+     * A2DP profile.
+     */
+    int A2DP = 2;
+
+    /**
+     * Health Profile
+     *
+     * @deprecated Health Device Profile (HDP) and MCAP protocol are no longer used. New
+     * apps should use Bluetooth Low Energy based solutions such as {@link BluetoothGatt},
+     * {@link BluetoothAdapter#listenUsingL2capChannel()}, or
+     * {@link BluetoothDevice#createL2capChannel(int)}
+     */
+    @Deprecated
+    int HEALTH = 3;
+
+    /**
+     * HID Host
+     *
+     * @hide
+     */
+    int HID_HOST = 4;
+
+    /**
+     * PAN Profile
+     *
+     * @hide
+     */
+    @SystemApi
+    int PAN = 5;
+
+    /**
+     * PBAP
+     *
+     * @hide
+     */
+    int PBAP = 6;
+
+    /**
+     * GATT
+     */
+    int GATT = 7;
+
+    /**
+     * GATT_SERVER
+     */
+    int GATT_SERVER = 8;
+
+    /**
+     * MAP Profile
+     *
+     * @hide
+     */
+    int MAP = 9;
+
+    /*
+     * SAP Profile
+     * @hide
+     */
+    int SAP = 10;
+
+    /**
+     * A2DP Sink Profile
+     *
+     * @hide
+     */
+    @SystemApi
+    int A2DP_SINK = 11;
+
+    /**
+     * AVRCP Controller Profile
+     *
+     * @hide
+     */
+    @SystemApi
+    int AVRCP_CONTROLLER = 12;
+
+    /**
+     * AVRCP Target Profile
+     *
+     * @hide
+     */
+    int AVRCP = 13;
+
+    /**
+     * Headset Client - HFP HF Role
+     *
+     * @hide
+     */
+    @SystemApi
+    int HEADSET_CLIENT = 16;
+
+    /**
+     * PBAP Client
+     *
+     * @hide
+     */
+    @SystemApi
+    int PBAP_CLIENT = 17;
+
+    /**
+     * MAP Messaging Client Equipment (MCE)
+     *
+     * @hide
+     */
+    int MAP_CLIENT = 18;
+
+    /**
+     * HID Device
+     */
+    int HID_DEVICE = 19;
+
+    /**
+     * Object Push Profile (OPP)
+     *
+     * @hide
+     */
+    int OPP = 20;
+
+    /**
+     * Hearing Aid Device
+     *
+     */
+    int HEARING_AID = 21;
+
+    /**
+     * Max profile ID. This value should be updated whenever a new profile is added to match
+     * the largest value assigned to a profile.
+     *
+     * @hide
+     */
+    int MAX_PROFILE_ID = 21;
+
+    /**
+     * Default priority for devices that we try to auto-connect to and
+     * and allow incoming connections for the profile
+     *
+     * @hide
+     **/
+    @UnsupportedAppUsage
+    int PRIORITY_AUTO_CONNECT = 1000;
+
+    /**
+     * Default priority for devices that allow incoming
+     * and outgoing connections for the profile
+     *
+     * @hide
+     * @deprecated Replaced with {@link #CONNECTION_POLICY_ALLOWED}
+     **/
+    @Deprecated
+    @SystemApi
+    int PRIORITY_ON = 100;
+
+    /**
+     * Default priority for devices that does not allow incoming
+     * connections and outgoing connections for the profile.
+     *
+     * @hide
+     * @deprecated Replaced with {@link #CONNECTION_POLICY_FORBIDDEN}
+     **/
+    @Deprecated
+    @SystemApi
+    int PRIORITY_OFF = 0;
+
+    /**
+     * Default priority when not set or when the device is unpaired
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    int PRIORITY_UNDEFINED = -1;
+
+    /** @hide */
+    @IntDef(prefix = "CONNECTION_POLICY_", value = {CONNECTION_POLICY_ALLOWED,
+            CONNECTION_POLICY_FORBIDDEN, CONNECTION_POLICY_UNKNOWN})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ConnectionPolicy{}
+
+    /**
+     * Default connection policy for devices that allow incoming and outgoing connections
+     * for the profile
+     *
+     * @hide
+     **/
+    @SystemApi
+    int CONNECTION_POLICY_ALLOWED = 100;
+
+    /**
+     * Default connection policy for devices that do not allow incoming or outgoing connections
+     * for the profile.
+     *
+     * @hide
+     **/
+    @SystemApi
+    int CONNECTION_POLICY_FORBIDDEN = 0;
+
+    /**
+     * Default connection policy when not set or when the device is unpaired
+     *
+     * @hide
+     */
+    @SystemApi
+    int CONNECTION_POLICY_UNKNOWN = -1;
+
+    /**
+     * Get connected devices for this specific profile.
+     *
+     * <p> Return the set of devices which are in state {@link #STATE_CONNECTED}
+     *
+     * @return List of devices. The list will be empty on error.
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public List<BluetoothDevice> getConnectedDevices();
+
+    /**
+     * 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.
+     *
+     * @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.
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states);
+
+    /**
+     * Get the current connection state of the profile
+     *
+     * @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}
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @BtProfileState int getConnectionState(BluetoothDevice device);
+
+    /**
+     * An interface for notifying BluetoothProfile IPC clients when they have
+     * been connected or disconnected to the service.
+     */
+    public interface ServiceListener {
+        /**
+         * Called to notify the client when the proxy object has been
+         * connected to the service.
+         *
+         * @param profile - One of {@link #HEADSET} or {@link #A2DP}
+         * @param proxy - One of {@link BluetoothHeadset} or {@link BluetoothA2dp}
+         */
+        public void onServiceConnected(int profile, BluetoothProfile proxy);
+
+        /**
+         * Called to notify the client that this proxy object has been
+         * disconnected from the service.
+         *
+         * @param profile - One of {@link #HEADSET} or {@link #A2DP}
+         */
+        public void onServiceDisconnected(int profile);
+    }
+
+    /**
+     * Convert an integer value of connection state into human readable string
+     *
+     * @param connectionState - One of {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, or {@link #STATE_DISCONNECTED}
+     * @return a string representation of the connection state, STATE_UNKNOWN if the state
+     * is not defined
+     * @hide
+     */
+    static String getConnectionStateName(int connectionState) {
+        switch (connectionState) {
+            case STATE_DISCONNECTED:
+                return "STATE_DISCONNECTED";
+            case STATE_CONNECTING:
+                return "STATE_CONNECTING";
+            case STATE_CONNECTED:
+                return "STATE_CONNECTED";
+            case STATE_DISCONNECTING:
+                return "STATE_DISCONNECTING";
+            default:
+                return "STATE_UNKNOWN";
+        }
+    }
+
+    /**
+     * Convert an integer value of profile ID into human readable string
+     *
+     * @param profile profile ID
+     * @return profile name as String, UNKOWN_PROFILE if the profile ID is not defined.
+     * @hide
+     */
+    static String getProfileName(int profile) {
+        switch(profile) {
+            case HEADSET:
+                return "HEADSET";
+            case A2DP:
+                return "A2DP";
+            case HID_HOST:
+                return "HID_HOST";
+            case PAN:
+                return "PAN";
+            case PBAP:
+                return "PBAP";
+            case GATT:
+                return "GATT";
+            case GATT_SERVER:
+                return "GATT_SERVER";
+            case MAP:
+                return "MAP";
+            case SAP:
+                return "SAP";
+            case A2DP_SINK:
+                return "A2DP_SINK";
+            case AVRCP_CONTROLLER:
+                return "AVRCP_CONTROLLER";
+            case AVRCP:
+                return "AVRCP";
+            case HEADSET_CLIENT:
+                return "HEADSET_CLIENT";
+            case PBAP_CLIENT:
+                return "PBAP_CLIENT";
+            case MAP_CLIENT:
+                return "MAP_CLIENT";
+            case HID_DEVICE:
+                return "HID_DEVICE";
+            case OPP:
+                return "OPP";
+            case HEARING_AID:
+                return "HEARING_AID";
+            default:
+                return "UNKNOWN_PROFILE";
+        }
+    }
+}
diff --git a/android/bluetooth/BluetoothProfileConnector.java b/android/bluetooth/BluetoothProfileConnector.java
new file mode 100644
index 0000000..863fd36
--- /dev/null
+++ b/android/bluetooth/BluetoothProfileConnector.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2019 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.os.UserHandle;
+import android.util.Log;
+
+/**
+ * Connector for Bluetooth profile proxies to bind manager service and
+ * profile services
+ * @param <T> The Bluetooth profile interface for this connection.
+ * @hide
+ */
+public abstract class BluetoothProfileConnector<T> {
+    private final int mProfileId;
+    private BluetoothProfile.ServiceListener mServiceListener;
+    private final BluetoothProfile mProfileProxy;
+    private Context mContext;
+    private final String mProfileName;
+    private final String mServiceName;
+    private volatile T mService;
+
+    private final IBluetoothStateChangeCallback mBluetoothStateChangeCallback =
+            new IBluetoothStateChangeCallback.Stub() {
+        public void onBluetoothStateChange(boolean up) {
+            if (up) {
+                doBind();
+            } else {
+                doUnbind();
+            }
+        }
+    };
+
+    private final ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            logDebug("Proxy object connected");
+            mService = getServiceInterface(service);
+
+            if (mServiceListener != null) {
+                mServiceListener.onServiceConnected(mProfileId, mProfileProxy);
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            logDebug("Proxy object disconnected");
+            doUnbind();
+            if (mServiceListener != null) {
+                mServiceListener.onServiceDisconnected(mProfileId);
+            }
+        }
+    };
+
+    BluetoothProfileConnector(BluetoothProfile profile, int profileId, String profileName,
+            String serviceName) {
+        mProfileId = profileId;
+        mProfileProxy = profile;
+        mProfileName = profileName;
+        mServiceName = serviceName;
+    }
+
+    private boolean doBind() {
+        synchronized (mConnection) {
+            if (mService == null) {
+                logDebug("Binding service...");
+                try {
+                    Intent intent = new Intent(mServiceName);
+                    ComponentName comp = intent.resolveSystemService(
+                            mContext.getPackageManager(), 0);
+                    intent.setComponent(comp);
+                    if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
+                            UserHandle.CURRENT_OR_SELF)) {
+                        logError("Could not bind to Bluetooth Service with " + intent);
+                        return false;
+                    }
+                } catch (SecurityException se) {
+                    logError("Failed to bind service. " + se);
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private void doUnbind() {
+        synchronized (mConnection) {
+            if (mService != null) {
+                logDebug("Unbinding service...");
+                try {
+                    mContext.unbindService(mConnection);
+                } catch (IllegalArgumentException ie) {
+                    logError("Unable to unbind service: " + ie);
+                } finally {
+                    mService = null;
+                }
+            }
+        }
+    }
+
+    void connect(Context context, BluetoothProfile.ServiceListener listener) {
+        mContext = context;
+        mServiceListener = listener;
+        IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.registerStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                logError("Failed to register state change callback. " + re);
+            }
+        }
+        doBind();
+    }
+
+    void disconnect() {
+        mServiceListener = null;
+        IBluetoothManager mgr = BluetoothAdapter.getDefaultAdapter().getBluetoothManager();
+        if (mgr != null) {
+            try {
+                mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
+            } catch (RemoteException re) {
+                logError("Failed to unregister state change callback" + re);
+            }
+        }
+        doUnbind();
+    }
+
+    T getService() {
+        return mService;
+    }
+
+    /**
+     * This abstract function is used to implement method to get the
+     * connected Bluetooth service interface.
+     * @param service the connected binder service.
+     * @return T the binder interface of {@code service}.
+     * @hide
+     */
+    public abstract T getServiceInterface(IBinder service);
+
+    private void logDebug(String log) {
+        Log.d(mProfileName, log);
+    }
+
+    private void logError(String log) {
+        Log.e(mProfileName, log);
+    }
+}
diff --git a/android/bluetooth/BluetoothSap.java b/android/bluetooth/BluetoothSap.java
new file mode 100644
index 0000000..6e03481
--- /dev/null
+++ b/android/bluetooth/BluetoothSap.java
@@ -0,0 +1,415 @@
+/*
+ * Copyright (C) 2008 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.Manifest;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * This class provides the APIs to control the Bluetooth SIM
+ * Access Profile (SAP).
+ *
+ * <p>BluetoothSap is a proxy object for controlling the Bluetooth
+ * Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
+ * the BluetoothSap proxy object.
+ *
+ * <p>Each method is protected with its appropriate permission.
+ *
+ * @hide
+ */
+public final class BluetoothSap implements BluetoothProfile {
+
+    private static final String TAG = "BluetoothSap";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Intent used to broadcast the change in connection state of the profile.
+     *
+     * <p>This intent will have 4 extras:
+     * <ul>
+     * <li> {@link #EXTRA_STATE} - The current state of the profile. </li>
+     * <li> {@link #EXTRA_PREVIOUS_STATE}- The previous state of the profile.</li>
+     * <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. </li>
+     * </ul>
+     *
+     * <p>{@link #EXTRA_STATE} or {@link #EXTRA_PREVIOUS_STATE} can be any of
+     * {@link #STATE_DISCONNECTED}, {@link #STATE_CONNECTING},
+     * {@link #STATE_CONNECTED}, {@link #STATE_DISCONNECTING}.
+     *
+     * <p>Requires {@link android.Manifest.permission#BLUETOOTH} permission to
+     * receive.
+     *
+     * @hide
+     */
+    public static final String ACTION_CONNECTION_STATE_CHANGED =
+            "android.bluetooth.sap.profile.action.CONNECTION_STATE_CHANGED";
+
+    /**
+     * There was an error trying to obtain the state.
+     *
+     * @hide
+     */
+    public static final int STATE_ERROR = -1;
+
+    /**
+     * Connection state change succceeded.
+     *
+     * @hide
+     */
+    public static final int RESULT_SUCCESS = 1;
+
+    /**
+     * Connection canceled before completion.
+     *
+     * @hide
+     */
+    public static final int RESULT_CANCELED = 2;
+
+    private BluetoothAdapter mAdapter;
+    private final BluetoothProfileConnector<IBluetoothSap> mProfileConnector =
+            new BluetoothProfileConnector(this, BluetoothProfile.SAP,
+                    "BluetoothSap", IBluetoothSap.class.getName()) {
+                @Override
+                public IBluetoothSap getServiceInterface(IBinder service) {
+                    return IBluetoothSap.Stub.asInterface(Binder.allowBlocking(service));
+                }
+    };
+
+    /**
+     * Create a BluetoothSap proxy object.
+     */
+    /*package*/ BluetoothSap(Context context, ServiceListener listener) {
+        if (DBG) Log.d(TAG, "Create BluetoothSap proxy object");
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        mProfileConnector.connect(context, listener);
+    }
+
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Close the connection to the backing service.
+     * Other public functions of BluetoothSap will return default error
+     * results once close() has been called. Multiple invocations of close()
+     * are ok.
+     *
+     * @hide
+     */
+    public synchronized void close() {
+        mProfileConnector.disconnect();
+    }
+
+    private IBluetoothSap getService() {
+        return mProfileConnector.getService();
+    }
+
+    /**
+     * Get the current state of the BluetoothSap service.
+     *
+     * @return One of the STATE_ return codes, or STATE_ERROR if this proxy object is currently not
+     * connected to the Sap service.
+     * @hide
+     */
+    public int getState() {
+        if (VDBG) log("getState()");
+        final IBluetoothSap service = getService();
+        if (service != null) {
+            try {
+                return service.getState();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        }
+        return BluetoothSap.STATE_ERROR;
+    }
+
+    /**
+     * Get the currently connected remote Bluetooth device (PCE).
+     *
+     * @return The remote Bluetooth device, or null if not in connected or connecting state, or if
+     * this proxy object is not connected to the Sap service.
+     * @hide
+     */
+    public BluetoothDevice getClient() {
+        if (VDBG) log("getClient()");
+        final IBluetoothSap service = getService();
+        if (service != null) {
+            try {
+                return service.getClient();
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        }
+        return null;
+    }
+
+    /**
+     * Returns true if the specified Bluetooth device is connected.
+     * Returns false if not connected, or if this proxy object is not
+     * currently connected to the Sap service.
+     *
+     * @hide
+     */
+    public boolean isConnected(BluetoothDevice device) {
+        if (VDBG) log("isConnected(" + device + ")");
+        final IBluetoothSap service = getService();
+        if (service != null) {
+            try {
+                return service.isConnected(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, e.toString());
+            }
+        } else {
+            Log.w(TAG, "Proxy not attached to service");
+            if (DBG) log(Log.getStackTraceString(new Throwable()));
+        }
+        return false;
+    }
+
+    /**
+     * Initiate connection. Initiation of outgoing connections is not
+     * supported for SAP server.
+     *
+     * @hide
+     */
+    public boolean connect(BluetoothDevice device) {
+        if (DBG) log("connect(" + device + ")" + "not supported for SAPS");
+        return false;
+    }
+
+    /**
+     * Initiate disconnect.
+     *
+     * @param device Remote Bluetooth Device
+     * @return false on error, true otherwise
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean disconnect(BluetoothDevice device) {
+        if (DBG) log("disconnect(" + device + ")");
+        final IBluetoothSap service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.disconnect(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the list of connected devices. Currently at most one.
+     *
+     * @return list of connected devices
+     * @hide
+     */
+    public List<BluetoothDevice> getConnectedDevices() {
+        if (DBG) log("getConnectedDevices()");
+        final IBluetoothSap service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getConnectedDevices();
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Get the list of devices matching specified states. Currently at most one.
+     *
+     * @return list of matching devices
+     * @hide
+     */
+    public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
+        if (DBG) log("getDevicesMatchingStates()");
+        final IBluetoothSap service = getService();
+        if (service != null && isEnabled()) {
+            try {
+                return service.getDevicesMatchingConnectionStates(states);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return new ArrayList<BluetoothDevice>();
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return new ArrayList<BluetoothDevice>();
+    }
+
+    /**
+     * Get connection state of device
+     *
+     * @return device connection state
+     * @hide
+     */
+    public int getConnectionState(BluetoothDevice device) {
+        if (DBG) log("getConnectionState(" + device + ")");
+        final IBluetoothSap service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionState(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.STATE_DISCONNECTED;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.STATE_DISCONNECTED;
+    }
+
+    /**
+     * Set priority of the profile
+     *
+     * <p> The device should already be paired.
+     * Priority can be one of {@link #PRIORITY_ON} or {@link #PRIORITY_OFF},
+     *
+     * @param device Paired bluetooth device
+     * @param priority
+     * @return true if priority is set, false on error
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setPriority(BluetoothDevice device, int priority) {
+        if (DBG) log("setPriority(" + device + ", " + priority + ")");
+        return setConnectionPolicy(device, BluetoothAdapter.priorityToConnectionPolicy(priority));
+    }
+
+    /**
+     * Set connection policy of the profile
+     *
+     * <p> The device should already be paired.
+     * Connection policy can be one of {@link #CONNECTION_POLICY_ALLOWED},
+     * {@link #CONNECTION_POLICY_FORBIDDEN}, {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Paired bluetooth device
+     * @param connectionPolicy is the connection policy to set to for this profile
+     * @return true if connectionPolicy is set, false on error
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public boolean setConnectionPolicy(BluetoothDevice device,
+            @ConnectionPolicy int connectionPolicy) {
+        if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
+        final IBluetoothSap service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            if (connectionPolicy != BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
+                    && connectionPolicy != BluetoothProfile.CONNECTION_POLICY_ALLOWED) {
+                return false;
+            }
+            try {
+                return service.setConnectionPolicy(device, connectionPolicy);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return false;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return false;
+    }
+
+    /**
+     * Get the priority of the profile.
+     *
+     * <p> The priority can be any of:
+     * {@link #PRIORITY_OFF}, {@link #PRIORITY_ON}, {@link #PRIORITY_UNDEFINED}
+     *
+     * @param device Bluetooth device
+     * @return priority of the device
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public int getPriority(BluetoothDevice device) {
+        if (VDBG) log("getPriority(" + device + ")");
+        return BluetoothAdapter.connectionPolicyToPriority(getConnectionPolicy(device));
+    }
+
+    /**
+     * Get the connection policy of the profile.
+     *
+     * <p> The connection policy can be any of:
+     * {@link #CONNECTION_POLICY_ALLOWED}, {@link #CONNECTION_POLICY_FORBIDDEN},
+     * {@link #CONNECTION_POLICY_UNKNOWN}
+     *
+     * @param device Bluetooth device
+     * @return connection policy of the device
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
+    public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) {
+        if (VDBG) log("getConnectionPolicy(" + device + ")");
+        final IBluetoothSap service = getService();
+        if (service != null && isEnabled() && isValidDevice(device)) {
+            try {
+                return service.getConnectionPolicy(device);
+            } catch (RemoteException e) {
+                Log.e(TAG, Log.getStackTraceString(new Throwable()));
+                return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+            }
+        }
+        if (service == null) Log.w(TAG, "Proxy not attached to service");
+        return BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+
+    private boolean isEnabled() {
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+
+        if (adapter != null && adapter.getState() == BluetoothAdapter.STATE_ON) {
+            return true;
+        }
+        log("Bluetooth is Not enabled");
+        return false;
+    }
+
+    private static boolean isValidDevice(BluetoothDevice device) {
+        return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
+    }
+
+}
diff --git a/android/bluetooth/BluetoothServerSocket.java b/android/bluetooth/BluetoothServerSocket.java
new file mode 100644
index 0000000..88c186c
--- /dev/null
+++ b/android/bluetooth/BluetoothServerSocket.java
@@ -0,0 +1,267 @@
+/*
+ * Copyright (C) 2009 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.compat.annotation.UnsupportedAppUsage;
+import android.os.Handler;
+import android.os.ParcelUuid;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.IOException;
+
+/**
+ * A listening Bluetooth socket.
+ *
+ * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets:
+ * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server
+ * side, use a {@link BluetoothServerSocket} to create a listening server
+ * socket. When a connection is accepted by the {@link BluetoothServerSocket},
+ * it will return a new {@link BluetoothSocket} to manage the connection.
+ * On the client side, use a single {@link BluetoothSocket} to both initiate
+ * an outgoing connection and to manage the connection.
+ *
+ * <p>For Bluetooth BR/EDR, the most common type of socket is RFCOMM, which is the type supported by
+ * the Android APIs. RFCOMM is a connection-oriented, streaming transport over Bluetooth BR/EDR. It
+ * is also known as the Serial Port Profile (SPP). To create a listening
+ * {@link BluetoothServerSocket} that's ready for incoming Bluetooth BR/EDR connections, use {@link
+ * BluetoothAdapter#listenUsingRfcommWithServiceRecord
+ * BluetoothAdapter.listenUsingRfcommWithServiceRecord()}.
+ *
+ * <p>For Bluetooth LE, the socket uses LE Connection-oriented Channel (CoC). LE CoC is a
+ * connection-oriented, streaming transport over Bluetooth LE and has a credit-based flow control.
+ * Correspondingly, use {@link BluetoothAdapter#listenUsingL2capChannel
+ * BluetoothAdapter.listenUsingL2capChannel()} to create a listening {@link BluetoothServerSocket}
+ * that's ready for incoming Bluetooth LE CoC connections. For LE CoC, you can use {@link #getPsm()}
+ * to get the protocol/service multiplexer (PSM) value that the peer needs to use to connect to your
+ * socket.
+ *
+ * <p> After the listening {@link BluetoothServerSocket} is created, call {@link #accept()} to
+ * listen for incoming connection requests. This call will block until a connection is established,
+ * at which point, it will return a {@link BluetoothSocket} to manage the connection. Once the
+ * {@link BluetoothSocket} is acquired, it's a good idea to call {@link #close()} on the {@link
+ * BluetoothServerSocket} when it's no longer needed for accepting
+ * connections. Closing the {@link BluetoothServerSocket} will <em>not</em> close the returned
+ * {@link BluetoothSocket}.
+ *
+ * <p>{@link BluetoothServerSocket} is thread
+ * safe. In particular, {@link #close} will always immediately abort ongoing
+ * operations and close the server socket.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using Bluetooth, read the
+ * <a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer guide.</p>
+ * </div>
+ *
+ * {@see BluetoothSocket}
+ */
+public final class BluetoothServerSocket implements Closeable {
+
+    private static final String TAG = "BluetoothServerSocket";
+    private static final boolean DBG = false;
+    @UnsupportedAppUsage(publicAlternatives = "Use public {@link BluetoothServerSocket} API "
+            + "instead.")
+    /*package*/ final BluetoothSocket mSocket;
+    private Handler mHandler;
+    private int mMessage;
+    private int mChannel;
+
+    /**
+     * Construct a socket for incoming connections.
+     *
+     * @param type type of socket
+     * @param auth require the remote device to be authenticated
+     * @param encrypt require the connection to be encrypted
+     * @param port remote port
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * privileges
+     */
+    /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port)
+            throws IOException {
+        mChannel = port;
+        mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null);
+        if (port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+            mSocket.setExcludeSdp(true);
+        }
+    }
+
+    /**
+     * Construct a socket for incoming connections.
+     *
+     * @param type type of socket
+     * @param auth require the remote device to be authenticated
+     * @param encrypt require the connection to be encrypted
+     * @param port remote port
+     * @param mitm enforce man-in-the-middle protection for authentication.
+     * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * privileges
+     */
+    /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, int port,
+            boolean mitm, boolean min16DigitPin)
+            throws IOException {
+        mChannel = port;
+        mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, port, null, mitm,
+                min16DigitPin);
+        if (port == BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+            mSocket.setExcludeSdp(true);
+        }
+    }
+
+    /**
+     * Construct a socket for incoming connections.
+     *
+     * @param type type of socket
+     * @param auth require the remote device to be authenticated
+     * @param encrypt require the connection to be encrypted
+     * @param uuid uuid
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * privileges
+     */
+    /*package*/ BluetoothServerSocket(int type, boolean auth, boolean encrypt, ParcelUuid uuid)
+            throws IOException {
+        mSocket = new BluetoothSocket(type, -1, auth, encrypt, null, -1, uuid);
+        // TODO: This is the same as mChannel = -1 - is this intentional?
+        mChannel = mSocket.getPort();
+    }
+
+
+    /**
+     * Block until a connection is established.
+     * <p>Returns a connected {@link BluetoothSocket} on successful connection.
+     * <p>Once this call returns, it can be called again to accept subsequent
+     * incoming connections.
+     * <p>{@link #close} can be used to abort this call from another thread.
+     *
+     * @return a connected {@link BluetoothSocket}
+     * @throws IOException on error, for example this call was aborted, or timeout
+     */
+    public BluetoothSocket accept() throws IOException {
+        return accept(-1);
+    }
+
+    /**
+     * Block until a connection is established, with timeout.
+     * <p>Returns a connected {@link BluetoothSocket} on successful connection.
+     * <p>Once this call returns, it can be called again to accept subsequent
+     * incoming connections.
+     * <p>{@link #close} can be used to abort this call from another thread.
+     *
+     * @return a connected {@link BluetoothSocket}
+     * @throws IOException on error, for example this call was aborted, or timeout
+     */
+    public BluetoothSocket accept(int timeout) throws IOException {
+        return mSocket.accept(timeout);
+    }
+
+    /**
+     * Immediately close this socket, and release all associated resources.
+     * <p>Causes blocked calls on this socket in other threads to immediately
+     * throw an IOException.
+     * <p>Closing the {@link BluetoothServerSocket} will <em>not</em>
+     * close any {@link BluetoothSocket} received from {@link #accept()}.
+     */
+    public void close() throws IOException {
+        if (DBG) Log.d(TAG, "BluetoothServerSocket:close() called. mChannel=" + mChannel);
+        synchronized (this) {
+            if (mHandler != null) {
+                mHandler.obtainMessage(mMessage).sendToTarget();
+            }
+        }
+        mSocket.close();
+    }
+
+    /*package*/
+    synchronized void setCloseHandler(Handler handler, int message) {
+        mHandler = handler;
+        mMessage = message;
+    }
+
+    /*package*/ void setServiceName(String serviceName) {
+        mSocket.setServiceName(serviceName);
+    }
+
+    /**
+     * Returns the channel on which this socket is bound.
+     *
+     * @hide
+     */
+    public int getChannel() {
+        return mChannel;
+    }
+
+    /**
+     * Returns the assigned dynamic protocol/service multiplexer (PSM) value for the listening L2CAP
+     * Connection-oriented Channel (CoC) server socket. This server socket must be returned by the
+     * {@link BluetoothAdapter#listenUsingL2capChannel()} or {@link
+     * BluetoothAdapter#listenUsingInsecureL2capChannel()}. The returned value is undefined if this
+     * method is called on non-L2CAP server sockets.
+     *
+     * @return the assigned PSM or LE_PSM value depending on transport
+     */
+    public int getPsm() {
+        return mChannel;
+    }
+
+    /**
+     * Sets the channel on which future sockets are bound.
+     * Currently used only when a channel is auto generated.
+     */
+    /*package*/ void setChannel(int newChannel) {
+        /* TODO: From a design/architecture perspective this is wrong.
+         *       The bind operation should be conducted through this class
+         *       and the resulting port should be kept in mChannel, and
+         *       not set from BluetoothAdapter. */
+        if (mSocket != null) {
+            if (mSocket.getPort() != newChannel) {
+                Log.w(TAG, "The port set is different that the underlying port. mSocket.getPort(): "
+                        + mSocket.getPort() + " requested newChannel: " + newChannel);
+            }
+        }
+        mChannel = newChannel;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+        sb.append("ServerSocket: Type: ");
+        switch (mSocket.getConnectionType()) {
+            case BluetoothSocket.TYPE_RFCOMM: {
+                sb.append("TYPE_RFCOMM");
+                break;
+            }
+            case BluetoothSocket.TYPE_L2CAP: {
+                sb.append("TYPE_L2CAP");
+                break;
+            }
+            case BluetoothSocket.TYPE_L2CAP_LE: {
+                sb.append("TYPE_L2CAP_LE");
+                break;
+            }
+            case BluetoothSocket.TYPE_SCO: {
+                sb.append("TYPE_SCO");
+                break;
+            }
+        }
+        sb.append(" Channel: ").append(mChannel);
+        return sb.toString();
+    }
+}
diff --git a/android/bluetooth/BluetoothSocket.java b/android/bluetooth/BluetoothSocket.java
new file mode 100644
index 0000000..f774369
--- /dev/null
+++ b/android/bluetooth/BluetoothSocket.java
@@ -0,0 +1,792 @@
+/*
+ * Copyright (C) 2012 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.compat.annotation.UnsupportedAppUsage;
+import android.net.LocalSocket;
+import android.os.ParcelFileDescriptor;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.io.Closeable;
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.Locale;
+import java.util.UUID;
+
+/**
+ * A connected or connecting Bluetooth socket.
+ *
+ * <p>The interface for Bluetooth Sockets is similar to that of TCP sockets:
+ * {@link java.net.Socket} and {@link java.net.ServerSocket}. On the server
+ * side, use a {@link BluetoothServerSocket} to create a listening server
+ * socket. When a connection is accepted by the {@link BluetoothServerSocket},
+ * it will return a new {@link BluetoothSocket} to manage the connection.
+ * On the client side, use a single {@link BluetoothSocket} to both initiate
+ * an outgoing connection and to manage the connection.
+ *
+ * <p>The most common type of Bluetooth socket is RFCOMM, which is the type
+ * supported by the Android APIs. RFCOMM is a connection-oriented, streaming
+ * transport over Bluetooth. It is also known as the Serial Port Profile (SPP).
+ *
+ * <p>To create a {@link BluetoothSocket} for connecting to a known device, use
+ * {@link BluetoothDevice#createRfcommSocketToServiceRecord
+ * BluetoothDevice.createRfcommSocketToServiceRecord()}.
+ * Then call {@link #connect()} to attempt a connection to the remote device.
+ * This call will block until a connection is established or the connection
+ * fails.
+ *
+ * <p>To create a {@link BluetoothSocket} as a server (or "host"), see the
+ * {@link BluetoothServerSocket} documentation.
+ *
+ * <p>Once the socket is connected, whether initiated as a client or accepted
+ * as a server, open the IO streams by calling {@link #getInputStream} and
+ * {@link #getOutputStream} in order to retrieve {@link java.io.InputStream}
+ * and {@link java.io.OutputStream} objects, respectively, which are
+ * automatically connected to the socket.
+ *
+ * <p>{@link BluetoothSocket} is thread
+ * safe. In particular, {@link #close} will always immediately abort ongoing
+ * operations and close the socket.
+ *
+ * <p class="note"><strong>Note:</strong>
+ * Requires the {@link android.Manifest.permission#BLUETOOTH} permission.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about using Bluetooth, read the
+ * <a href="{@docRoot}guide/topics/connectivity/bluetooth.html">Bluetooth</a> developer guide.</p>
+ * </div>
+ *
+ * {@see BluetoothServerSocket}
+ * {@see java.io.InputStream}
+ * {@see java.io.OutputStream}
+ */
+public final class BluetoothSocket implements Closeable {
+    private static final String TAG = "BluetoothSocket";
+    private static final boolean DBG = Log.isLoggable(TAG, Log.DEBUG);
+    private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
+
+    /** @hide */
+    public static final int MAX_RFCOMM_CHANNEL = 30;
+    /*package*/ static final int MAX_L2CAP_PACKAGE_SIZE = 0xFFFF;
+
+    /** RFCOMM socket */
+    public static final int TYPE_RFCOMM = 1;
+
+    /** SCO socket */
+    public static final int TYPE_SCO = 2;
+
+    /** L2CAP socket */
+    public static final int TYPE_L2CAP = 3;
+
+    /** L2CAP socket on BR/EDR transport
+     * @hide
+     */
+    public static final int TYPE_L2CAP_BREDR = TYPE_L2CAP;
+
+    /** L2CAP socket on LE transport
+     * @hide
+     */
+    public static final int TYPE_L2CAP_LE = 4;
+
+    /*package*/ static final int EBADFD = 77;
+    @UnsupportedAppUsage
+    /*package*/ static final int EADDRINUSE = 98;
+
+    /*package*/ static final int SEC_FLAG_ENCRYPT = 1;
+    /*package*/ static final int SEC_FLAG_AUTH = 1 << 1;
+    /*package*/ static final int BTSOCK_FLAG_NO_SDP = 1 << 2;
+    /*package*/ static final int SEC_FLAG_AUTH_MITM = 1 << 3;
+    /*package*/ static final int SEC_FLAG_AUTH_16_DIGIT = 1 << 4;
+
+    private final int mType;  /* one of TYPE_RFCOMM etc */
+    private BluetoothDevice mDevice;    /* remote device */
+    private String mAddress;    /* remote address */
+    private final boolean mAuth;
+    private final boolean mEncrypt;
+    private final BluetoothInputStream mInputStream;
+    private final BluetoothOutputStream mOutputStream;
+    private final ParcelUuid mUuid;
+    private boolean mExcludeSdp = false; /* when true no SPP SDP record will be created */
+    private boolean mAuthMitm = false;   /* when true Man-in-the-middle protection will be enabled*/
+    private boolean mMin16DigitPin = false; /* Minimum 16 digit pin for sec mode 2 connections */
+    @UnsupportedAppUsage(publicAlternatives = "Use {@link BluetoothSocket} public API instead.")
+    private ParcelFileDescriptor mPfd;
+    @UnsupportedAppUsage
+    private LocalSocket mSocket;
+    private InputStream mSocketIS;
+    private OutputStream mSocketOS;
+    @UnsupportedAppUsage
+    private int mPort;  /* RFCOMM channel or L2CAP psm */
+    private int mFd;
+    private String mServiceName;
+    private static final int PROXY_CONNECTION_TIMEOUT = 5000;
+
+    private static final int SOCK_SIGNAL_SIZE = 20;
+
+    private ByteBuffer mL2capBuffer = null;
+    private int mMaxTxPacketSize = 0; // The l2cap maximum packet size supported by the peer.
+    private int mMaxRxPacketSize = 0; // The l2cap maximum packet size that can be received.
+
+    private enum SocketState {
+        INIT,
+        CONNECTED,
+        LISTENING,
+        CLOSED,
+    }
+
+    /** prevents all native calls after destroyNative() */
+    private volatile SocketState mSocketState;
+
+    /** protects mSocketState */
+    //private final ReentrantReadWriteLock mLock;
+
+    /**
+     * Construct a BluetoothSocket.
+     *
+     * @param type type of socket
+     * @param fd fd to use for connected socket, or -1 for a new socket
+     * @param auth require the remote device to be authenticated
+     * @param encrypt require the connection to be encrypted
+     * @param device remote device that this socket can connect to
+     * @param port remote port
+     * @param uuid SDP uuid
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * privileges
+     */
+    /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
+            BluetoothDevice device, int port, ParcelUuid uuid) throws IOException {
+        this(type, fd, auth, encrypt, device, port, uuid, false, false);
+    }
+
+    /**
+     * Construct a BluetoothSocket.
+     *
+     * @param type type of socket
+     * @param fd fd to use for connected socket, or -1 for a new socket
+     * @param auth require the remote device to be authenticated
+     * @param encrypt require the connection to be encrypted
+     * @param device remote device that this socket can connect to
+     * @param port remote port
+     * @param uuid SDP uuid
+     * @param mitm enforce man-in-the-middle protection.
+     * @param min16DigitPin enforce a minimum length of 16 digits for a sec mode 2 connection
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * privileges
+     */
+    /*package*/ BluetoothSocket(int type, int fd, boolean auth, boolean encrypt,
+            BluetoothDevice device, int port, ParcelUuid uuid, boolean mitm, boolean min16DigitPin)
+            throws IOException {
+        if (VDBG) Log.d(TAG, "Creating new BluetoothSocket of type: " + type);
+        if (type == BluetoothSocket.TYPE_RFCOMM && uuid == null && fd == -1
+                && port != BluetoothAdapter.SOCKET_CHANNEL_AUTO_STATIC_NO_SDP) {
+            if (port < 1 || port > MAX_RFCOMM_CHANNEL) {
+                throw new IOException("Invalid RFCOMM channel: " + port);
+            }
+        }
+        if (uuid != null) {
+            mUuid = uuid;
+        } else {
+            mUuid = new ParcelUuid(new UUID(0, 0));
+        }
+        mType = type;
+        mAuth = auth;
+        mAuthMitm = mitm;
+        mMin16DigitPin = min16DigitPin;
+        mEncrypt = encrypt;
+        mDevice = device;
+        mPort = port;
+        mFd = fd;
+
+        mSocketState = SocketState.INIT;
+
+        if (device == null) {
+            // Server socket
+            mAddress = BluetoothAdapter.getDefaultAdapter().getAddress();
+        } else {
+            // Remote socket
+            mAddress = device.getAddress();
+        }
+        mInputStream = new BluetoothInputStream(this);
+        mOutputStream = new BluetoothOutputStream(this);
+    }
+
+    private BluetoothSocket(BluetoothSocket s) {
+        if (VDBG) Log.d(TAG, "Creating new Private BluetoothSocket of type: " + s.mType);
+        mUuid = s.mUuid;
+        mType = s.mType;
+        mAuth = s.mAuth;
+        mEncrypt = s.mEncrypt;
+        mPort = s.mPort;
+        mInputStream = new BluetoothInputStream(this);
+        mOutputStream = new BluetoothOutputStream(this);
+        mMaxRxPacketSize = s.mMaxRxPacketSize;
+        mMaxTxPacketSize = s.mMaxTxPacketSize;
+
+        mServiceName = s.mServiceName;
+        mExcludeSdp = s.mExcludeSdp;
+        mAuthMitm = s.mAuthMitm;
+        mMin16DigitPin = s.mMin16DigitPin;
+    }
+
+    private BluetoothSocket acceptSocket(String remoteAddr) throws IOException {
+        BluetoothSocket as = new BluetoothSocket(this);
+        as.mSocketState = SocketState.CONNECTED;
+        FileDescriptor[] fds = mSocket.getAncillaryFileDescriptors();
+        if (DBG) Log.d(TAG, "socket fd passed by stack fds: " + Arrays.toString(fds));
+        if (fds == null || fds.length != 1) {
+            Log.e(TAG, "socket fd passed from stack failed, fds: " + Arrays.toString(fds));
+            as.close();
+            throw new IOException("bt socket acept failed");
+        }
+
+        as.mPfd = new ParcelFileDescriptor(fds[0]);
+        as.mSocket = LocalSocket.createConnectedLocalSocket(fds[0]);
+        as.mSocketIS = as.mSocket.getInputStream();
+        as.mSocketOS = as.mSocket.getOutputStream();
+        as.mAddress = remoteAddr;
+        as.mDevice = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(remoteAddr);
+        return as;
+    }
+
+    /**
+     * Construct a BluetoothSocket from address. Used by native code.
+     *
+     * @param type type of socket
+     * @param fd fd to use for connected socket, or -1 for a new socket
+     * @param auth require the remote device to be authenticated
+     * @param encrypt require the connection to be encrypted
+     * @param address remote device that this socket can connect to
+     * @param port remote port
+     * @throws IOException On error, for example Bluetooth not available, or insufficient
+     * privileges
+     */
+    private BluetoothSocket(int type, int fd, boolean auth, boolean encrypt, String address,
+            int port) throws IOException {
+        this(type, fd, auth, encrypt, new BluetoothDevice(address), port, null, false, false);
+    }
+
+    /** @hide */
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            close();
+        } finally {
+            super.finalize();
+        }
+    }
+
+    private int getSecurityFlags() {
+        int flags = 0;
+        if (mAuth) {
+            flags |= SEC_FLAG_AUTH;
+        }
+        if (mEncrypt) {
+            flags |= SEC_FLAG_ENCRYPT;
+        }
+        if (mExcludeSdp) {
+            flags |= BTSOCK_FLAG_NO_SDP;
+        }
+        if (mAuthMitm) {
+            flags |= SEC_FLAG_AUTH_MITM;
+        }
+        if (mMin16DigitPin) {
+            flags |= SEC_FLAG_AUTH_16_DIGIT;
+        }
+        return flags;
+    }
+
+    /**
+     * Get the remote device this socket is connecting, or connected, to.
+     *
+     * @return remote device
+     */
+    public BluetoothDevice getRemoteDevice() {
+        return mDevice;
+    }
+
+    /**
+     * Get the input stream associated with this socket.
+     * <p>The input stream will be returned even if the socket is not yet
+     * connected, but operations on that stream will throw IOException until
+     * the associated socket is connected.
+     *
+     * @return InputStream
+     */
+    public InputStream getInputStream() throws IOException {
+        return mInputStream;
+    }
+
+    /**
+     * Get the output stream associated with this socket.
+     * <p>The output stream will be returned even if the socket is not yet
+     * connected, but operations on that stream will throw IOException until
+     * the associated socket is connected.
+     *
+     * @return OutputStream
+     */
+    public OutputStream getOutputStream() throws IOException {
+        return mOutputStream;
+    }
+
+    /**
+     * Get the connection status of this socket, ie, whether there is an active connection with
+     * remote device.
+     *
+     * @return true if connected false if not connected
+     */
+    public boolean isConnected() {
+        return mSocketState == SocketState.CONNECTED;
+    }
+
+    /*package*/ void setServiceName(String name) {
+        mServiceName = name;
+    }
+
+    /**
+     * Attempt to connect to a remote device.
+     * <p>This method will block until a connection is made or the connection
+     * fails. If this method returns without an exception then this socket
+     * is now connected.
+     * <p>Creating new connections to
+     * remote Bluetooth devices should not be attempted while device discovery
+     * is in progress. Device discovery is a heavyweight procedure on the
+     * Bluetooth adapter and will significantly slow a device connection.
+     * Use {@link BluetoothAdapter#cancelDiscovery()} to cancel an ongoing
+     * discovery. Discovery is not managed by the Activity,
+     * but is run as a system service, so an application should always call
+     * {@link BluetoothAdapter#cancelDiscovery()} even if it
+     * did not directly request a discovery, just to be sure.
+     * <p>{@link #close} can be used to abort this call from another thread.
+     *
+     * @throws IOException on error, for example connection failure
+     */
+    public void connect() throws IOException {
+        if (mDevice == null) throw new IOException("Connect is called on null device");
+
+        try {
+            if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
+            IBluetooth bluetoothProxy =
+                    BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
+            if (bluetoothProxy == null) throw new IOException("Bluetooth is off");
+            mPfd = bluetoothProxy.getSocketManager().connectSocket(mDevice, mType,
+                    mUuid, mPort, getSecurityFlags());
+            synchronized (this) {
+                if (DBG) Log.d(TAG, "connect(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
+                if (mSocketState == SocketState.CLOSED) throw new IOException("socket closed");
+                if (mPfd == null) throw new IOException("bt socket connect failed");
+                FileDescriptor fd = mPfd.getFileDescriptor();
+                mSocket = LocalSocket.createConnectedLocalSocket(fd);
+                mSocketIS = mSocket.getInputStream();
+                mSocketOS = mSocket.getOutputStream();
+            }
+            int channel = readInt(mSocketIS);
+            if (channel <= 0) {
+                throw new IOException("bt socket connect failed");
+            }
+            mPort = channel;
+            waitSocketSignal(mSocketIS);
+            synchronized (this) {
+                if (mSocketState == SocketState.CLOSED) {
+                    throw new IOException("bt socket closed");
+                }
+                mSocketState = SocketState.CONNECTED;
+            }
+        } catch (RemoteException e) {
+            Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            throw new IOException("unable to send RPC: " + e.getMessage());
+        }
+    }
+
+    /**
+     * Currently returns unix errno instead of throwing IOException,
+     * so that BluetoothAdapter can check the error code for EADDRINUSE
+     */
+    /*package*/ int bindListen() {
+        int ret;
+        if (mSocketState == SocketState.CLOSED) return EBADFD;
+        IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
+        if (bluetoothProxy == null) {
+            Log.e(TAG, "bindListen fail, reason: bluetooth is off");
+            return -1;
+        }
+        try {
+            if (DBG) Log.d(TAG, "bindListen(): mPort=" + mPort + ", mType=" + mType);
+            mPfd = bluetoothProxy.getSocketManager().createSocketChannel(mType, mServiceName,
+                    mUuid, mPort, getSecurityFlags());
+        } catch (RemoteException e) {
+            Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            return -1;
+        }
+
+        // read out port number
+        try {
+            synchronized (this) {
+                if (DBG) {
+                    Log.d(TAG, "bindListen(), SocketState: " + mSocketState + ", mPfd: " + mPfd);
+                }
+                if (mSocketState != SocketState.INIT) return EBADFD;
+                if (mPfd == null) return -1;
+                FileDescriptor fd = mPfd.getFileDescriptor();
+                if (fd == null) {
+                    Log.e(TAG, "bindListen(), null file descriptor");
+                    return -1;
+                }
+
+                if (DBG) Log.d(TAG, "bindListen(), Create LocalSocket");
+                mSocket = LocalSocket.createConnectedLocalSocket(fd);
+                if (DBG) Log.d(TAG, "bindListen(), new LocalSocket.getInputStream()");
+                mSocketIS = mSocket.getInputStream();
+                mSocketOS = mSocket.getOutputStream();
+            }
+            if (DBG) Log.d(TAG, "bindListen(), readInt mSocketIS: " + mSocketIS);
+            int channel = readInt(mSocketIS);
+            synchronized (this) {
+                if (mSocketState == SocketState.INIT) {
+                    mSocketState = SocketState.LISTENING;
+                }
+            }
+            if (DBG) Log.d(TAG, "bindListen(): channel=" + channel + ", mPort=" + mPort);
+            if (mPort <= -1) {
+                mPort = channel;
+            } // else ASSERT(mPort == channel)
+            ret = 0;
+        } catch (IOException e) {
+            if (mPfd != null) {
+                try {
+                    mPfd.close();
+                } catch (IOException e1) {
+                    Log.e(TAG, "bindListen, close mPfd: " + e1);
+                }
+                mPfd = null;
+            }
+            Log.e(TAG, "bindListen, fail to get port number, exception: " + e);
+            return -1;
+        }
+        return ret;
+    }
+
+    /*package*/ BluetoothSocket accept(int timeout) throws IOException {
+        BluetoothSocket acceptedSocket;
+        if (mSocketState != SocketState.LISTENING) {
+            throw new IOException("bt socket is not in listen state");
+        }
+        if (timeout > 0) {
+            Log.d(TAG, "accept() set timeout (ms):" + timeout);
+            mSocket.setSoTimeout(timeout);
+        }
+        String RemoteAddr = waitSocketSignal(mSocketIS);
+        if (timeout > 0) {
+            mSocket.setSoTimeout(0);
+        }
+        synchronized (this) {
+            if (mSocketState != SocketState.LISTENING) {
+                throw new IOException("bt socket is not in listen state");
+            }
+            acceptedSocket = acceptSocket(RemoteAddr);
+            //quick drop the reference of the file handle
+        }
+        return acceptedSocket;
+    }
+
+    /*package*/ int available() throws IOException {
+        if (VDBG) Log.d(TAG, "available: " + mSocketIS);
+        return mSocketIS.available();
+    }
+
+    /*package*/ int read(byte[] b, int offset, int length) throws IOException {
+        int ret = 0;
+        if (VDBG) Log.d(TAG, "read in:  " + mSocketIS + " len: " + length);
+        if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) {
+            int bytesToRead = length;
+            if (VDBG) {
+                Log.v(TAG, "l2cap: read(): offset: " + offset + " length:" + length
+                        + "mL2capBuffer= " + mL2capBuffer);
+            }
+            if (mL2capBuffer == null) {
+                createL2capRxBuffer();
+            }
+            if (mL2capBuffer.remaining() == 0) {
+                if (VDBG) Log.v(TAG, "l2cap buffer empty, refilling...");
+                if (fillL2capRxBuffer() == -1) {
+                    return -1;
+                }
+            }
+            if (bytesToRead > mL2capBuffer.remaining()) {
+                bytesToRead = mL2capBuffer.remaining();
+            }
+            if (VDBG) {
+                Log.v(TAG, "get(): offset: " + offset
+                        + " bytesToRead: " + bytesToRead);
+            }
+            mL2capBuffer.get(b, offset, bytesToRead);
+            ret = bytesToRead;
+        } else {
+            if (VDBG) Log.v(TAG, "default: read(): offset: " + offset + " length:" + length);
+            ret = mSocketIS.read(b, offset, length);
+        }
+        if (ret < 0) {
+            throw new IOException("bt socket closed, read return: " + ret);
+        }
+        if (VDBG) Log.d(TAG, "read out:  " + mSocketIS + " ret: " + ret);
+        return ret;
+    }
+
+    /*package*/ int write(byte[] b, int offset, int length) throws IOException {
+
+        //TODO: Since bindings can exist between the SDU size and the
+        //      protocol, we might need to throw an exception instead of just
+        //      splitting the write into multiple smaller writes.
+        //      Rfcomm uses dynamic allocation, and should not have any bindings
+        //      to the actual message length.
+        if (VDBG) Log.d(TAG, "write: " + mSocketOS + " length: " + length);
+        if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) {
+            if (length <= mMaxTxPacketSize) {
+                mSocketOS.write(b, offset, length);
+            } else {
+                if (DBG) {
+                    Log.w(TAG, "WARNING: Write buffer larger than L2CAP packet size!\n"
+                            + "Packet will be divided into SDU packets of size "
+                            + mMaxTxPacketSize);
+                }
+                int tmpOffset = offset;
+                int bytesToWrite = length;
+                while (bytesToWrite > 0) {
+                    int tmpLength = (bytesToWrite > mMaxTxPacketSize)
+                            ? mMaxTxPacketSize
+                            : bytesToWrite;
+                    mSocketOS.write(b, tmpOffset, tmpLength);
+                    tmpOffset += tmpLength;
+                    bytesToWrite -= tmpLength;
+                }
+            }
+        } else {
+            mSocketOS.write(b, offset, length);
+        }
+        // There is no good way to confirm since the entire process is asynchronous anyway
+        if (VDBG) Log.d(TAG, "write out: " + mSocketOS + " length: " + length);
+        return length;
+    }
+
+    @Override
+    public void close() throws IOException {
+        Log.d(TAG, "close() this: " + this + ", channel: " + mPort + ", mSocketIS: " + mSocketIS
+                + ", mSocketOS: " + mSocketOS + "mSocket: " + mSocket + ", mSocketState: "
+                + mSocketState);
+        if (mSocketState == SocketState.CLOSED) {
+            return;
+        } else {
+            synchronized (this) {
+                if (mSocketState == SocketState.CLOSED) {
+                    return;
+                }
+                mSocketState = SocketState.CLOSED;
+                if (mSocket != null) {
+                    if (DBG) Log.d(TAG, "Closing mSocket: " + mSocket);
+                    mSocket.shutdownInput();
+                    mSocket.shutdownOutput();
+                    mSocket.close();
+                    mSocket = null;
+                }
+                if (mPfd != null) {
+                    mPfd.close();
+                    mPfd = null;
+                }
+            }
+        }
+    }
+
+    /*package */ void removeChannel() {
+    }
+
+    /*package */ int getPort() {
+        return mPort;
+    }
+
+    /**
+     * Get the maximum supported Transmit packet size for the underlying transport.
+     * Use this to optimize the writes done to the output socket, to avoid sending
+     * half full packets.
+     *
+     * @return the maximum supported Transmit packet size for the underlying transport.
+     */
+    public int getMaxTransmitPacketSize() {
+        return mMaxTxPacketSize;
+    }
+
+    /**
+     * Get the maximum supported Receive packet size for the underlying transport.
+     * Use this to optimize the reads done on the input stream, as any call to read
+     * will return a maximum of this amount of bytes - or for some transports a
+     * multiple of this value.
+     *
+     * @return the maximum supported Receive packet size for the underlying transport.
+     */
+    public int getMaxReceivePacketSize() {
+        return mMaxRxPacketSize;
+    }
+
+    /**
+     * Get the type of the underlying connection.
+     *
+     * @return one of {@link #TYPE_RFCOMM}, {@link #TYPE_SCO} or {@link #TYPE_L2CAP}
+     */
+    public int getConnectionType() {
+        if (mType == TYPE_L2CAP_LE) {
+            // Treat the LE CoC to be the same type as L2CAP.
+            return TYPE_L2CAP;
+        }
+        return mType;
+    }
+
+    /**
+     * Change if a SDP entry should be automatically created.
+     * Must be called before calling .bind, for the call to have any effect.
+     *
+     * @param excludeSdp <li>TRUE - do not auto generate SDP record. <li>FALSE - default - auto
+     * generate SPP SDP record.
+     * @hide
+     */
+    public void setExcludeSdp(boolean excludeSdp) {
+        mExcludeSdp = excludeSdp;
+    }
+
+    /**
+     * Set the LE Transmit Data Length to be the maximum that the BT Controller is capable of. This
+     * parameter is used by the BT Controller to set the maximum transmission packet size on this
+     * connection. This function is currently used for testing only.
+     * @hide
+     */
+    public void requestMaximumTxDataLength() throws IOException {
+        if (mDevice == null) {
+            throw new IOException("requestMaximumTxDataLength is called on null device");
+        }
+
+        try {
+            if (mSocketState == SocketState.CLOSED) {
+                throw new IOException("socket closed");
+            }
+            IBluetooth bluetoothProxy =
+                    BluetoothAdapter.getDefaultAdapter().getBluetoothService(null);
+            if (bluetoothProxy == null) {
+                throw new IOException("Bluetooth is off");
+            }
+
+            if (DBG) Log.d(TAG, "requestMaximumTxDataLength");
+            bluetoothProxy.getSocketManager().requestMaximumTxDataLength(mDevice);
+        } catch (RemoteException e) {
+            Log.e(TAG, Log.getStackTraceString(new Throwable()));
+            throw new IOException("unable to send RPC: " + e.getMessage());
+        }
+    }
+
+    private String convertAddr(final byte[] addr) {
+        return String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X",
+                addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]);
+    }
+
+    private String waitSocketSignal(InputStream is) throws IOException {
+        byte[] sig = new byte[SOCK_SIGNAL_SIZE];
+        int ret = readAll(is, sig);
+        if (VDBG) {
+            Log.d(TAG, "waitSocketSignal read " + SOCK_SIGNAL_SIZE + " bytes signal ret: " + ret);
+        }
+        ByteBuffer bb = ByteBuffer.wrap(sig);
+        /* the struct in native is decorated with __attribute__((packed)), hence this is possible */
+        bb.order(ByteOrder.nativeOrder());
+        int size = bb.getShort();
+        if (size != SOCK_SIGNAL_SIZE) {
+            throw new IOException("Connection failure, wrong signal size: " + size);
+        }
+        byte[] addr = new byte[6];
+        bb.get(addr);
+        int channel = bb.getInt();
+        int status = bb.getInt();
+        mMaxTxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value
+        mMaxRxPacketSize = (bb.getShort() & 0xffff); // Convert to unsigned value
+        String RemoteAddr = convertAddr(addr);
+        if (VDBG) {
+            Log.d(TAG, "waitSocketSignal: sig size: " + size + ", remote addr: "
+                    + RemoteAddr + ", channel: " + channel + ", status: " + status
+                    + " MaxRxPktSize: " + mMaxRxPacketSize + " MaxTxPktSize: " + mMaxTxPacketSize);
+        }
+        if (status != 0) {
+            throw new IOException("Connection failure, status: " + status);
+        }
+        return RemoteAddr;
+    }
+
+    private void createL2capRxBuffer() {
+        if ((mType == TYPE_L2CAP) || (mType == TYPE_L2CAP_LE)) {
+            // Allocate the buffer to use for reads.
+            if (VDBG) Log.v(TAG, "  Creating mL2capBuffer: mMaxPacketSize: " + mMaxRxPacketSize);
+            mL2capBuffer = ByteBuffer.wrap(new byte[mMaxRxPacketSize]);
+            if (VDBG) Log.v(TAG, "mL2capBuffer.remaining()" + mL2capBuffer.remaining());
+            mL2capBuffer.limit(0); // Ensure we do a real read at the first read-request
+            if (VDBG) {
+                Log.v(TAG, "mL2capBuffer.remaining() after limit(0):" + mL2capBuffer.remaining());
+            }
+        }
+    }
+
+    private int readAll(InputStream is, byte[] b) throws IOException {
+        int left = b.length;
+        while (left > 0) {
+            int ret = is.read(b, b.length - left, left);
+            if (ret <= 0) {
+                throw new IOException("read failed, socket might closed or timeout, read ret: "
+                        + ret);
+            }
+            left -= ret;
+            if (left != 0) {
+                Log.w(TAG, "readAll() looping, read partial size: " + (b.length - left)
+                        + ", expect size: " + b.length);
+            }
+        }
+        return b.length;
+    }
+
+    private int readInt(InputStream is) throws IOException {
+        byte[] ibytes = new byte[4];
+        int ret = readAll(is, ibytes);
+        if (VDBG) Log.d(TAG, "inputStream.read ret: " + ret);
+        ByteBuffer bb = ByteBuffer.wrap(ibytes);
+        bb.order(ByteOrder.nativeOrder());
+        return bb.getInt();
+    }
+
+    private int fillL2capRxBuffer() throws IOException {
+        mL2capBuffer.rewind();
+        int ret = mSocketIS.read(mL2capBuffer.array());
+        if (ret == -1) {
+            // reached end of stream - return -1
+            mL2capBuffer.limit(0);
+            return -1;
+        }
+        mL2capBuffer.limit(ret);
+        return ret;
+    }
+
+
+}
diff --git a/android/bluetooth/BluetoothUuid.java b/android/bluetooth/BluetoothUuid.java
new file mode 100644
index 0000000..e274af1
--- /dev/null
+++ b/android/bluetooth/BluetoothUuid.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2009 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.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.ParcelUuid;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.UUID;
+
+/**
+ * Static helper methods and constants to decode the ParcelUuid of remote devices.
+ *
+ * @hide
+ */
+@SystemApi
+public final class BluetoothUuid {
+
+    /* See Bluetooth Assigned Numbers document - SDP section, to get the values of UUIDs
+     * for the various services.
+     *
+     * The following 128 bit values are calculated as:
+     *  uuid * 2^96 + BASE_UUID
+     */
+
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid A2DP_SINK =
+            ParcelUuid.fromString("0000110B-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid A2DP_SOURCE =
+            ParcelUuid.fromString("0000110A-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid ADV_AUDIO_DIST =
+            ParcelUuid.fromString("0000110D-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid HSP =
+            ParcelUuid.fromString("00001108-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid HSP_AG =
+            ParcelUuid.fromString("00001112-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid HFP =
+            ParcelUuid.fromString("0000111E-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid HFP_AG =
+            ParcelUuid.fromString("0000111F-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid AVRCP_CONTROLLER =
+            ParcelUuid.fromString("0000110E-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid AVRCP_TARGET =
+            ParcelUuid.fromString("0000110C-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid OBEX_OBJECT_PUSH =
+            ParcelUuid.fromString("00001105-0000-1000-8000-00805f9b34fb");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid HID =
+            ParcelUuid.fromString("00001124-0000-1000-8000-00805f9b34fb");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid HOGP =
+            ParcelUuid.fromString("00001812-0000-1000-8000-00805f9b34fb");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid PANU =
+            ParcelUuid.fromString("00001115-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid NAP =
+            ParcelUuid.fromString("00001116-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid BNEP =
+            ParcelUuid.fromString("0000000f-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid PBAP_PCE =
+            ParcelUuid.fromString("0000112e-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid PBAP_PSE =
+            ParcelUuid.fromString("0000112f-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid MAP =
+            ParcelUuid.fromString("00001134-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid MNS =
+            ParcelUuid.fromString("00001133-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid MAS =
+            ParcelUuid.fromString("00001132-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid SAP =
+            ParcelUuid.fromString("0000112D-0000-1000-8000-00805F9B34FB");
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid HEARING_AID =
+            ParcelUuid.fromString("0000FDF0-0000-1000-8000-00805f9b34fb");
+
+    /** @hide */
+    @NonNull
+    @SystemApi
+    public static final ParcelUuid BASE_UUID =
+            ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
+
+    /**
+     * Length of bytes for 16 bit UUID
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int UUID_BYTES_16_BIT = 2;
+    /**
+     * Length of bytes for 32 bit UUID
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int UUID_BYTES_32_BIT = 4;
+    /**
+     * Length of bytes for 128 bit UUID
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int UUID_BYTES_128_BIT = 16;
+
+    /**
+     * Returns true if there any common ParcelUuids in uuidA and uuidB.
+     *
+     * @param uuidA - List of ParcelUuids
+     * @param uuidB - List of ParcelUuids
+     *
+     * @hide
+     */
+    @SystemApi
+    public static boolean containsAnyUuid(@Nullable ParcelUuid[] uuidA,
+            @Nullable ParcelUuid[] uuidB) {
+        if (uuidA == null && uuidB == null) return true;
+
+        if (uuidA == null) {
+            return uuidB.length == 0;
+        }
+
+        if (uuidB == null) {
+            return uuidA.length == 0;
+        }
+
+        HashSet<ParcelUuid> uuidSet = new HashSet<ParcelUuid>(Arrays.asList(uuidA));
+        for (ParcelUuid uuid : uuidB) {
+            if (uuidSet.contains(uuid)) return true;
+        }
+        return false;
+    }
+
+    /**
+     * Extract the Service Identifier or the actual uuid from the Parcel Uuid.
+     * For example, if 0000110B-0000-1000-8000-00805F9B34FB is the parcel Uuid,
+     * this function will return 110B
+     *
+     * @param parcelUuid
+     * @return the service identifier.
+     */
+    private static int getServiceIdentifierFromParcelUuid(ParcelUuid parcelUuid) {
+        UUID uuid = parcelUuid.getUuid();
+        long value = (uuid.getMostSignificantBits() & 0xFFFFFFFF00000000L) >>> 32;
+        return (int) value;
+    }
+
+    /**
+     * Parse UUID from bytes. The {@code uuidBytes} can represent a 16-bit, 32-bit or 128-bit UUID,
+     * but the returned UUID is always in 128-bit format.
+     * Note UUID is little endian in Bluetooth.
+     *
+     * @param uuidBytes Byte representation of uuid.
+     * @return {@link ParcelUuid} parsed from bytes.
+     * @throws IllegalArgumentException If the {@code uuidBytes} cannot be parsed.
+     *
+     * @hide
+     */
+    @NonNull
+    @SystemApi
+    public static ParcelUuid parseUuidFrom(@Nullable byte[] uuidBytes) {
+        if (uuidBytes == null) {
+            throw new IllegalArgumentException("uuidBytes cannot be null");
+        }
+        int length = uuidBytes.length;
+        if (length != UUID_BYTES_16_BIT && length != UUID_BYTES_32_BIT
+                && length != UUID_BYTES_128_BIT) {
+            throw new IllegalArgumentException("uuidBytes length invalid - " + length);
+        }
+
+        // Construct a 128 bit UUID.
+        if (length == UUID_BYTES_128_BIT) {
+            ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
+            long msb = buf.getLong(8);
+            long lsb = buf.getLong(0);
+            return new ParcelUuid(new UUID(msb, lsb));
+        }
+
+        // For 16 bit and 32 bit UUID we need to convert them to 128 bit value.
+        // 128_bit_value = uuid * 2^96 + BASE_UUID
+        long shortUuid;
+        if (length == UUID_BYTES_16_BIT) {
+            shortUuid = uuidBytes[0] & 0xFF;
+            shortUuid += (uuidBytes[1] & 0xFF) << 8;
+        } else {
+            shortUuid = uuidBytes[0] & 0xFF;
+            shortUuid += (uuidBytes[1] & 0xFF) << 8;
+            shortUuid += (uuidBytes[2] & 0xFF) << 16;
+            shortUuid += (uuidBytes[3] & 0xFF) << 24;
+        }
+        long msb = BASE_UUID.getUuid().getMostSignificantBits() + (shortUuid << 32);
+        long lsb = BASE_UUID.getUuid().getLeastSignificantBits();
+        return new ParcelUuid(new UUID(msb, lsb));
+    }
+
+    /**
+     * Parse UUID to bytes. The returned value is shortest representation, a 16-bit, 32-bit or
+     * 128-bit UUID, Note returned value is little endian (Bluetooth).
+     *
+     * @param uuid uuid to parse.
+     * @return shortest representation of {@code uuid} as bytes.
+     * @throws IllegalArgumentException If the {@code uuid} is null.
+     *
+     * @hide
+     */
+    public static byte[] uuidToBytes(ParcelUuid uuid) {
+        if (uuid == null) {
+            throw new IllegalArgumentException("uuid cannot be null");
+        }
+
+        if (is16BitUuid(uuid)) {
+            byte[] uuidBytes = new byte[UUID_BYTES_16_BIT];
+            int uuidVal = getServiceIdentifierFromParcelUuid(uuid);
+            uuidBytes[0] = (byte) (uuidVal & 0xFF);
+            uuidBytes[1] = (byte) ((uuidVal & 0xFF00) >> 8);
+            return uuidBytes;
+        }
+
+        if (is32BitUuid(uuid)) {
+            byte[] uuidBytes = new byte[UUID_BYTES_32_BIT];
+            int uuidVal = getServiceIdentifierFromParcelUuid(uuid);
+            uuidBytes[0] = (byte) (uuidVal & 0xFF);
+            uuidBytes[1] = (byte) ((uuidVal & 0xFF00) >> 8);
+            uuidBytes[2] = (byte) ((uuidVal & 0xFF0000) >> 16);
+            uuidBytes[3] = (byte) ((uuidVal & 0xFF000000) >> 24);
+            return uuidBytes;
+        }
+
+        // Construct a 128 bit UUID.
+        long msb = uuid.getUuid().getMostSignificantBits();
+        long lsb = uuid.getUuid().getLeastSignificantBits();
+
+        byte[] uuidBytes = new byte[UUID_BYTES_128_BIT];
+        ByteBuffer buf = ByteBuffer.wrap(uuidBytes).order(ByteOrder.LITTLE_ENDIAN);
+        buf.putLong(8, msb);
+        buf.putLong(0, lsb);
+        return uuidBytes;
+    }
+
+    /**
+     * Check whether the given parcelUuid can be converted to 16 bit bluetooth uuid.
+     *
+     * @param parcelUuid
+     * @return true if the parcelUuid can be converted to 16 bit uuid, false otherwise.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static boolean is16BitUuid(ParcelUuid parcelUuid) {
+        UUID uuid = parcelUuid.getUuid();
+        if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) {
+            return false;
+        }
+        return ((uuid.getMostSignificantBits() & 0xFFFF0000FFFFFFFFL) == 0x1000L);
+    }
+
+
+    /**
+     * Check whether the given parcelUuid can be converted to 32 bit bluetooth uuid.
+     *
+     * @param parcelUuid
+     * @return true if the parcelUuid can be converted to 32 bit uuid, false otherwise.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static boolean is32BitUuid(ParcelUuid parcelUuid) {
+        UUID uuid = parcelUuid.getUuid();
+        if (uuid.getLeastSignificantBits() != BASE_UUID.getUuid().getLeastSignificantBits()) {
+            return false;
+        }
+        if (is16BitUuid(parcelUuid)) {
+            return false;
+        }
+        return ((uuid.getMostSignificantBits() & 0xFFFFFFFFL) == 0x1000L);
+    }
+
+    private BluetoothUuid() {}
+}
diff --git a/android/bluetooth/OobData.java b/android/bluetooth/OobData.java
new file mode 100644
index 0000000..0d0c6ab
--- /dev/null
+++ b/android/bluetooth/OobData.java
@@ -0,0 +1,111 @@
+/*
+ * 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;
+
+/**
+ * Out Of Band Data for Bluetooth device pairing.
+ *
+ * <p>This object represents optional data obtained from a remote device through
+ * an out-of-band channel (eg. NFC).
+ *
+ * @hide
+ */
+public class OobData implements Parcelable {
+    private byte[] mLeBluetoothDeviceAddress;
+    private byte[] mSecurityManagerTk;
+    private byte[] mLeSecureConnectionsConfirmation;
+    private byte[] mLeSecureConnectionsRandom;
+
+    public byte[] getLeBluetoothDeviceAddress() {
+        return mLeBluetoothDeviceAddress;
+    }
+
+    /**
+     * Sets the LE Bluetooth Device Address value to be used during LE pairing.
+     * The value shall be 7 bytes. Please see Bluetooth CSSv6, Part A 1.16 for
+     * a detailed description.
+     */
+    public void setLeBluetoothDeviceAddress(byte[] leBluetoothDeviceAddress) {
+        mLeBluetoothDeviceAddress = leBluetoothDeviceAddress;
+    }
+
+    public byte[] getSecurityManagerTk() {
+        return mSecurityManagerTk;
+    }
+
+    /**
+     * Sets the Temporary Key value to be used by the LE Security Manager during
+     * LE pairing. The value shall be 16 bytes. Please see Bluetooth CSSv6,
+     * Part A 1.8 for a detailed description.
+     */
+    public void setSecurityManagerTk(byte[] securityManagerTk) {
+        mSecurityManagerTk = securityManagerTk;
+    }
+
+    public byte[] getLeSecureConnectionsConfirmation() {
+        return mLeSecureConnectionsConfirmation;
+    }
+
+    public void setLeSecureConnectionsConfirmation(byte[] leSecureConnectionsConfirmation) {
+        mLeSecureConnectionsConfirmation = leSecureConnectionsConfirmation;
+    }
+
+    public byte[] getLeSecureConnectionsRandom() {
+        return mLeSecureConnectionsRandom;
+    }
+
+    public void setLeSecureConnectionsRandom(byte[] leSecureConnectionsRandom) {
+        mLeSecureConnectionsRandom = leSecureConnectionsRandom;
+    }
+
+    public OobData() {
+    }
+
+    private OobData(Parcel in) {
+        mLeBluetoothDeviceAddress = in.createByteArray();
+        mSecurityManagerTk = in.createByteArray();
+        mLeSecureConnectionsConfirmation = in.createByteArray();
+        mLeSecureConnectionsRandom = in.createByteArray();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeByteArray(mLeBluetoothDeviceAddress);
+        out.writeByteArray(mSecurityManagerTk);
+        out.writeByteArray(mLeSecureConnectionsConfirmation);
+        out.writeByteArray(mLeSecureConnectionsRandom);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<OobData> CREATOR =
+            new Parcelable.Creator<OobData>() {
+        public OobData createFromParcel(Parcel in) {
+            return new OobData(in);
+        }
+
+        public OobData[] newArray(int size) {
+            return new OobData[size];
+        }
+    };
+}
diff --git a/android/bluetooth/SdpMasRecord.java b/android/bluetooth/SdpMasRecord.java
new file mode 100644
index 0000000..72d4938
--- /dev/null
+++ b/android/bluetooth/SdpMasRecord.java
@@ -0,0 +1,150 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* 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;
+
+/** @hide */
+public class SdpMasRecord implements Parcelable {
+    private final int mMasInstanceId;
+    private final int mL2capPsm;
+    private final int mRfcommChannelNumber;
+    private final int mProfileVersion;
+    private final int mSupportedFeatures;
+    private final int mSupportedMessageTypes;
+    private final String mServiceName;
+
+    /** Message type */
+    public static final class MessageType {
+        public static final int EMAIL = 0x01;
+        public static final int SMS_GSM = 0x02;
+        public static final int SMS_CDMA = 0x04;
+        public static final int MMS = 0x08;
+    }
+
+    public SdpMasRecord(int masInstanceId,
+            int l2capPsm,
+            int rfcommChannelNumber,
+            int profileVersion,
+            int supportedFeatures,
+            int supportedMessageTypes,
+            String serviceName) {
+        mMasInstanceId = masInstanceId;
+        mL2capPsm = l2capPsm;
+        mRfcommChannelNumber = rfcommChannelNumber;
+        mProfileVersion = profileVersion;
+        mSupportedFeatures = supportedFeatures;
+        mSupportedMessageTypes = supportedMessageTypes;
+        mServiceName = serviceName;
+    }
+
+    public SdpMasRecord(Parcel in) {
+        mMasInstanceId = in.readInt();
+        mL2capPsm = in.readInt();
+        mRfcommChannelNumber = in.readInt();
+        mProfileVersion = in.readInt();
+        mSupportedFeatures = in.readInt();
+        mSupportedMessageTypes = in.readInt();
+        mServiceName = in.readString();
+    }
+
+    @Override
+    public int describeContents() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    public int getMasInstanceId() {
+        return mMasInstanceId;
+    }
+
+    public int getL2capPsm() {
+        return mL2capPsm;
+    }
+
+    public int getRfcommCannelNumber() {
+        return mRfcommChannelNumber;
+    }
+
+    public int getProfileVersion() {
+        return mProfileVersion;
+    }
+
+    public int getSupportedFeatures() {
+        return mSupportedFeatures;
+    }
+
+    public int getSupportedMessageTypes() {
+        return mSupportedMessageTypes;
+    }
+
+    public boolean msgSupported(int msg) {
+        return (mSupportedMessageTypes & msg) != 0;
+    }
+
+    public String getServiceName() {
+        return mServiceName;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mMasInstanceId);
+        dest.writeInt(mL2capPsm);
+        dest.writeInt(mRfcommChannelNumber);
+        dest.writeInt(mProfileVersion);
+        dest.writeInt(mSupportedFeatures);
+        dest.writeInt(mSupportedMessageTypes);
+        dest.writeString(mServiceName);
+    }
+
+    @Override
+    public String toString() {
+        String ret = "Bluetooth MAS SDP Record:\n";
+
+        if (mMasInstanceId != -1) {
+            ret += "Mas Instance Id: " + mMasInstanceId + "\n";
+        }
+        if (mRfcommChannelNumber != -1) {
+            ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
+        }
+        if (mL2capPsm != -1) {
+            ret += "L2CAP PSM: " + mL2capPsm + "\n";
+        }
+        if (mServiceName != null) {
+            ret += "Service Name: " + mServiceName + "\n";
+        }
+        if (mProfileVersion != -1) {
+            ret += "Profile version: " + mProfileVersion + "\n";
+        }
+        if (mSupportedMessageTypes != -1) {
+            ret += "Supported msg types: " + mSupportedMessageTypes + "\n";
+        }
+        if (mSupportedFeatures != -1) {
+            ret += "Supported features: " + mSupportedFeatures + "\n";
+        }
+        return ret;
+    }
+
+    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+        public SdpMasRecord createFromParcel(Parcel in) {
+            return new SdpMasRecord(in);
+        }
+
+        public SdpRecord[] newArray(int size) {
+            return new SdpRecord[size];
+        }
+    };
+}
diff --git a/android/bluetooth/SdpMnsRecord.java b/android/bluetooth/SdpMnsRecord.java
new file mode 100644
index 0000000..a781d5d
--- /dev/null
+++ b/android/bluetooth/SdpMnsRecord.java
@@ -0,0 +1,114 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* 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;
+
+/** @hide */
+public class SdpMnsRecord implements Parcelable {
+    private final int mL2capPsm;
+    private final int mRfcommChannelNumber;
+    private final int mSupportedFeatures;
+    private final int mProfileVersion;
+    private final String mServiceName;
+
+    public SdpMnsRecord(int l2capPsm,
+            int rfcommChannelNumber,
+            int profileVersion,
+            int supportedFeatures,
+            String serviceName) {
+        mL2capPsm = l2capPsm;
+        mRfcommChannelNumber = rfcommChannelNumber;
+        mSupportedFeatures = supportedFeatures;
+        mServiceName = serviceName;
+        mProfileVersion = profileVersion;
+    }
+
+    public SdpMnsRecord(Parcel in) {
+        mRfcommChannelNumber = in.readInt();
+        mL2capPsm = in.readInt();
+        mServiceName = in.readString();
+        mSupportedFeatures = in.readInt();
+        mProfileVersion = in.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+
+    public int getL2capPsm() {
+        return mL2capPsm;
+    }
+
+    public int getRfcommChannelNumber() {
+        return mRfcommChannelNumber;
+    }
+
+    public int getSupportedFeatures() {
+        return mSupportedFeatures;
+    }
+
+    public String getServiceName() {
+        return mServiceName;
+    }
+
+    public int getProfileVersion() {
+        return mProfileVersion;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRfcommChannelNumber);
+        dest.writeInt(mL2capPsm);
+        dest.writeString(mServiceName);
+        dest.writeInt(mSupportedFeatures);
+        dest.writeInt(mProfileVersion);
+    }
+
+    public String toString() {
+        String ret = "Bluetooth MNS SDP Record:\n";
+
+        if (mRfcommChannelNumber != -1) {
+            ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
+        }
+        if (mL2capPsm != -1) {
+            ret += "L2CAP PSM: " + mL2capPsm + "\n";
+        }
+        if (mServiceName != null) {
+            ret += "Service Name: " + mServiceName + "\n";
+        }
+        if (mSupportedFeatures != -1) {
+            ret += "Supported features: " + mSupportedFeatures + "\n";
+        }
+        if (mProfileVersion != -1) {
+            ret += "Profile_version: " + mProfileVersion + "\n";
+        }
+        return ret;
+    }
+
+    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+        public SdpMnsRecord createFromParcel(Parcel in) {
+            return new SdpMnsRecord(in);
+        }
+
+        public SdpMnsRecord[] newArray(int size) {
+            return new SdpMnsRecord[size];
+        }
+    };
+}
diff --git a/android/bluetooth/SdpOppOpsRecord.java b/android/bluetooth/SdpOppOpsRecord.java
new file mode 100644
index 0000000..e30745b
--- /dev/null
+++ b/android/bluetooth/SdpOppOpsRecord.java
@@ -0,0 +1,121 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* 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.Arrays;
+
+/**
+ * Data representation of a Object Push Profile Server side SDP record.
+ */
+
+/** @hide */
+public class SdpOppOpsRecord implements Parcelable {
+
+    private final String mServiceName;
+    private final int mRfcommChannel;
+    private final int mL2capPsm;
+    private final int mProfileVersion;
+    private final byte[] mFormatsList;
+
+    public SdpOppOpsRecord(String serviceName, int rfcommChannel,
+            int l2capPsm, int version, byte[] formatsList) {
+        super();
+        mServiceName = serviceName;
+        mRfcommChannel = rfcommChannel;
+        mL2capPsm = l2capPsm;
+        mProfileVersion = version;
+        mFormatsList = formatsList;
+    }
+
+    public String getServiceName() {
+        return mServiceName;
+    }
+
+    public int getRfcommChannel() {
+        return mRfcommChannel;
+    }
+
+    public int getL2capPsm() {
+        return mL2capPsm;
+    }
+
+    public int getProfileVersion() {
+        return mProfileVersion;
+    }
+
+    public byte[] getFormatsList() {
+        return mFormatsList;
+    }
+
+    @Override
+    public int describeContents() {
+        /* No special objects */
+        return 0;
+    }
+
+    public SdpOppOpsRecord(Parcel in) {
+        mRfcommChannel = in.readInt();
+        mL2capPsm = in.readInt();
+        mProfileVersion = in.readInt();
+        mServiceName = in.readString();
+        int arrayLength = in.readInt();
+        if (arrayLength > 0) {
+            byte[] bytes = new byte[arrayLength];
+            in.readByteArray(bytes);
+            mFormatsList = bytes;
+        } else {
+            mFormatsList = null;
+        }
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRfcommChannel);
+        dest.writeInt(mL2capPsm);
+        dest.writeInt(mProfileVersion);
+        dest.writeString(mServiceName);
+        if (mFormatsList != null && mFormatsList.length > 0) {
+            dest.writeInt(mFormatsList.length);
+            dest.writeByteArray(mFormatsList);
+        } else {
+            dest.writeInt(0);
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder("Bluetooth OPP Server SDP Record:\n");
+        sb.append("  RFCOMM Chan Number: ").append(mRfcommChannel);
+        sb.append("\n  L2CAP PSM: ").append(mL2capPsm);
+        sb.append("\n  Profile version: ").append(mProfileVersion);
+        sb.append("\n  Service Name: ").append(mServiceName);
+        sb.append("\n  Formats List: ").append(Arrays.toString(mFormatsList));
+        return sb.toString();
+    }
+
+    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+        public SdpOppOpsRecord createFromParcel(Parcel in) {
+            return new SdpOppOpsRecord(in);
+        }
+
+        public SdpOppOpsRecord[] newArray(int size) {
+            return new SdpOppOpsRecord[size];
+        }
+    };
+
+}
diff --git a/android/bluetooth/SdpPseRecord.java b/android/bluetooth/SdpPseRecord.java
new file mode 100644
index 0000000..72249d0
--- /dev/null
+++ b/android/bluetooth/SdpPseRecord.java
@@ -0,0 +1,129 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* 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;
+
+/** @hide */
+public class SdpPseRecord implements Parcelable {
+    private final int mL2capPsm;
+    private final int mRfcommChannelNumber;
+    private final int mProfileVersion;
+    private final int mSupportedFeatures;
+    private final int mSupportedRepositories;
+    private final String mServiceName;
+
+    public SdpPseRecord(int l2capPsm,
+            int rfcommChannelNumber,
+            int profileVersion,
+            int supportedFeatures,
+            int supportedRepositories,
+            String serviceName) {
+        mL2capPsm = l2capPsm;
+        mRfcommChannelNumber = rfcommChannelNumber;
+        mProfileVersion = profileVersion;
+        mSupportedFeatures = supportedFeatures;
+        mSupportedRepositories = supportedRepositories;
+        mServiceName = serviceName;
+    }
+
+    public SdpPseRecord(Parcel in) {
+        mRfcommChannelNumber = in.readInt();
+        mL2capPsm = in.readInt();
+        mProfileVersion = in.readInt();
+        mSupportedFeatures = in.readInt();
+        mSupportedRepositories = in.readInt();
+        mServiceName = in.readString();
+    }
+
+    @Override
+    public int describeContents() {
+        // TODO Auto-generated method stub
+        return 0;
+    }
+
+    public int getL2capPsm() {
+        return mL2capPsm;
+    }
+
+    public int getRfcommChannelNumber() {
+        return mRfcommChannelNumber;
+    }
+
+    public int getSupportedFeatures() {
+        return mSupportedFeatures;
+    }
+
+    public String getServiceName() {
+        return mServiceName;
+    }
+
+    public int getProfileVersion() {
+        return mProfileVersion;
+    }
+
+    public int getSupportedRepositories() {
+        return mSupportedRepositories;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRfcommChannelNumber);
+        dest.writeInt(mL2capPsm);
+        dest.writeInt(mProfileVersion);
+        dest.writeInt(mSupportedFeatures);
+        dest.writeInt(mSupportedRepositories);
+        dest.writeString(mServiceName);
+
+    }
+
+    @Override
+    public String toString() {
+        String ret = "Bluetooth MNS SDP Record:\n";
+
+        if (mRfcommChannelNumber != -1) {
+            ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
+        }
+        if (mL2capPsm != -1) {
+            ret += "L2CAP PSM: " + mL2capPsm + "\n";
+        }
+        if (mProfileVersion != -1) {
+            ret += "profile version: " + mProfileVersion + "\n";
+        }
+        if (mServiceName != null) {
+            ret += "Service Name: " + mServiceName + "\n";
+        }
+        if (mSupportedFeatures != -1) {
+            ret += "Supported features: " + mSupportedFeatures + "\n";
+        }
+        if (mSupportedRepositories != -1) {
+            ret += "Supported repositories: " + mSupportedRepositories + "\n";
+        }
+
+        return ret;
+    }
+
+    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+        public SdpPseRecord createFromParcel(Parcel in) {
+            return new SdpPseRecord(in);
+        }
+
+        public SdpPseRecord[] newArray(int size) {
+            return new SdpPseRecord[size];
+        }
+    };
+}
diff --git a/android/bluetooth/SdpRecord.java b/android/bluetooth/SdpRecord.java
new file mode 100644
index 0000000..730862e
--- /dev/null
+++ b/android/bluetooth/SdpRecord.java
@@ -0,0 +1,77 @@
+/*
+* Copyright (C) 2015 Samsung System LSI
+* 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.Arrays;
+
+/** @hide */
+public class SdpRecord implements Parcelable {
+
+    private final byte[] mRawData;
+    private final int mRawSize;
+
+    @Override
+    public String toString() {
+        return "BluetoothSdpRecord [rawData=" + Arrays.toString(mRawData)
+                + ", rawSize=" + mRawSize + "]";
+    }
+
+    public SdpRecord(int sizeRecord, byte[] record) {
+        mRawData = record;
+        mRawSize = sizeRecord;
+    }
+
+    public SdpRecord(Parcel in) {
+        mRawSize = in.readInt();
+        mRawData = new byte[mRawSize];
+        in.readByteArray(mRawData);
+
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRawSize);
+        dest.writeByteArray(mRawData);
+
+
+    }
+
+    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+        public SdpRecord createFromParcel(Parcel in) {
+            return new SdpRecord(in);
+        }
+
+        public SdpRecord[] newArray(int size) {
+            return new SdpRecord[size];
+        }
+    };
+
+    public byte[] getRawData() {
+        return mRawData;
+    }
+
+    public int getRawSize() {
+        return mRawSize;
+    }
+}
diff --git a/android/bluetooth/SdpSapsRecord.java b/android/bluetooth/SdpSapsRecord.java
new file mode 100644
index 0000000..a1e2f7b
--- /dev/null
+++ b/android/bluetooth/SdpSapsRecord.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2015 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;
+
+/** @hide */
+public class SdpSapsRecord implements Parcelable {
+    private final int mRfcommChannelNumber;
+    private final int mProfileVersion;
+    private final String mServiceName;
+
+    public SdpSapsRecord(int rfcommChannelNumber, int profileVersion, String serviceName) {
+        mRfcommChannelNumber = rfcommChannelNumber;
+        mProfileVersion = profileVersion;
+        mServiceName = serviceName;
+    }
+
+    public SdpSapsRecord(Parcel in) {
+        mRfcommChannelNumber = in.readInt();
+        mProfileVersion = in.readInt();
+        mServiceName = in.readString();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public int getRfcommCannelNumber() {
+        return mRfcommChannelNumber;
+    }
+
+    public int getProfileVersion() {
+        return mProfileVersion;
+    }
+
+    public String getServiceName() {
+        return mServiceName;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRfcommChannelNumber);
+        dest.writeInt(mProfileVersion);
+        dest.writeString(mServiceName);
+
+    }
+
+    @Override
+    public String toString() {
+        String ret = "Bluetooth MAS SDP Record:\n";
+
+        if (mRfcommChannelNumber != -1) {
+            ret += "RFCOMM Chan Number: " + mRfcommChannelNumber + "\n";
+        }
+        if (mServiceName != null) {
+            ret += "Service Name: " + mServiceName + "\n";
+        }
+        if (mProfileVersion != -1) {
+            ret += "Profile version: " + mProfileVersion + "\n";
+        }
+        return ret;
+    }
+
+    public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
+        public SdpSapsRecord createFromParcel(Parcel in) {
+            return new SdpSapsRecord(in);
+        }
+
+        public SdpRecord[] newArray(int size) {
+            return new SdpRecord[size];
+        }
+    };
+}
diff --git a/android/bluetooth/UidTraffic.java b/android/bluetooth/UidTraffic.java
new file mode 100644
index 0000000..2ee786a
--- /dev/null
+++ b/android/bluetooth/UidTraffic.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2015 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;
+
+/**
+ * Record of data traffic (in bytes) by an application identified by its UID.
+ *
+ * @hide
+ */
+public class UidTraffic implements Cloneable, Parcelable {
+    private final int mAppUid;
+    private long mRxBytes;
+    private long mTxBytes;
+
+    public UidTraffic(int appUid) {
+        mAppUid = appUid;
+    }
+
+    public UidTraffic(int appUid, long rx, long tx) {
+        mAppUid = appUid;
+        mRxBytes = rx;
+        mTxBytes = tx;
+    }
+
+    UidTraffic(Parcel in) {
+        mAppUid = in.readInt();
+        mRxBytes = in.readLong();
+        mTxBytes = in.readLong();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mAppUid);
+        dest.writeLong(mRxBytes);
+        dest.writeLong(mTxBytes);
+    }
+
+    public void setRxBytes(long bytes) {
+        mRxBytes = bytes;
+    }
+
+    public void setTxBytes(long bytes) {
+        mTxBytes = bytes;
+    }
+
+    public void addRxBytes(long bytes) {
+        mRxBytes += bytes;
+    }
+
+    public void addTxBytes(long bytes) {
+        mTxBytes += bytes;
+    }
+
+    public int getUid() {
+        return mAppUid;
+    }
+
+    public long getRxBytes() {
+        return mRxBytes;
+    }
+
+    public long getTxBytes() {
+        return mTxBytes;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public UidTraffic clone() {
+        return new UidTraffic(mAppUid, mRxBytes, mTxBytes);
+    }
+
+    @Override
+    public String toString() {
+        return "UidTraffic{mAppUid=" + mAppUid + ", mRxBytes=" + mRxBytes + ", mTxBytes="
+                + mTxBytes + '}';
+    }
+
+    public static final @android.annotation.NonNull Creator<UidTraffic> CREATOR = new Creator<UidTraffic>() {
+        @Override
+        public UidTraffic createFromParcel(Parcel source) {
+            return new UidTraffic(source);
+        }
+
+        @Override
+        public UidTraffic[] newArray(int size) {
+            return new UidTraffic[size];
+        }
+    };
+}
diff --git a/android/bluetooth/le/AdvertiseCallback.java b/android/bluetooth/le/AdvertiseCallback.java
new file mode 100644
index 0000000..4fa8c4f
--- /dev/null
+++ b/android/bluetooth/le/AdvertiseCallback.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+/**
+ * Bluetooth LE advertising callbacks, used to deliver advertising operation status.
+ */
+public abstract class AdvertiseCallback {
+
+    /**
+     * The requested operation was successful.
+     *
+     * @hide
+     */
+    public static final int ADVERTISE_SUCCESS = 0;
+
+    /**
+     * Failed to start advertising as the advertise data to be broadcasted is larger than 31 bytes.
+     */
+    public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1;
+
+    /**
+     * Failed to start advertising because no advertising instance is available.
+     */
+    public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2;
+
+    /**
+     * Failed to start advertising as the advertising is already started.
+     */
+    public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3;
+
+    /**
+     * Operation failed due to an internal error.
+     */
+    public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4;
+
+    /**
+     * This feature is not supported on this platform.
+     */
+    public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5;
+
+    /**
+     * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertising} indicating
+     * that the advertising has been started successfully.
+     *
+     * @param settingsInEffect The actual settings used for advertising, which may be different from
+     * what has been requested.
+     */
+    public void onStartSuccess(AdvertiseSettings settingsInEffect) {
+    }
+
+    /**
+     * Callback when advertising could not be started.
+     *
+     * @param errorCode Error code (see ADVERTISE_FAILED_* constants) for advertising start
+     * failures.
+     */
+    public void onStartFailure(int errorCode) {
+    }
+}
diff --git a/android/bluetooth/le/AdvertiseData.java b/android/bluetooth/le/AdvertiseData.java
new file mode 100644
index 0000000..5fd8258
--- /dev/null
+++ b/android/bluetooth/le/AdvertiseData.java
@@ -0,0 +1,286 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+import android.util.ArrayMap;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Advertise data packet container for Bluetooth LE advertising. This represents the data to be
+ * advertised as well as the scan response data for active scans.
+ * <p>
+ * Use {@link AdvertiseData.Builder} to create an instance of {@link AdvertiseData} to be
+ * advertised.
+ *
+ * @see BluetoothLeAdvertiser
+ * @see ScanRecord
+ */
+public final class AdvertiseData implements Parcelable {
+
+    @Nullable
+    private final List<ParcelUuid> mServiceUuids;
+
+    private final SparseArray<byte[]> mManufacturerSpecificData;
+    private final Map<ParcelUuid, byte[]> mServiceData;
+    private final boolean mIncludeTxPowerLevel;
+    private final boolean mIncludeDeviceName;
+
+    private AdvertiseData(List<ParcelUuid> serviceUuids,
+            SparseArray<byte[]> manufacturerData,
+            Map<ParcelUuid, byte[]> serviceData,
+            boolean includeTxPowerLevel,
+            boolean includeDeviceName) {
+        mServiceUuids = serviceUuids;
+        mManufacturerSpecificData = manufacturerData;
+        mServiceData = serviceData;
+        mIncludeTxPowerLevel = includeTxPowerLevel;
+        mIncludeDeviceName = includeDeviceName;
+    }
+
+    /**
+     * Returns a list of service UUIDs within the advertisement that are used to identify the
+     * Bluetooth GATT services.
+     */
+    public List<ParcelUuid> getServiceUuids() {
+        return mServiceUuids;
+    }
+
+    /**
+     * Returns an array of manufacturer Id and the corresponding manufacturer specific data. The
+     * manufacturer id is a non-negative number assigned by Bluetooth SIG.
+     */
+    public SparseArray<byte[]> getManufacturerSpecificData() {
+        return mManufacturerSpecificData;
+    }
+
+    /**
+     * Returns a map of 16-bit UUID and its corresponding service data.
+     */
+    public Map<ParcelUuid, byte[]> getServiceData() {
+        return mServiceData;
+    }
+
+    /**
+     * Whether the transmission power level will be included in the advertisement packet.
+     */
+    public boolean getIncludeTxPowerLevel() {
+        return mIncludeTxPowerLevel;
+    }
+
+    /**
+     * Whether the device name will be included in the advertisement packet.
+     */
+    public boolean getIncludeDeviceName() {
+        return mIncludeDeviceName;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int hashCode() {
+        return Objects.hash(mServiceUuids, mManufacturerSpecificData, mServiceData,
+                mIncludeDeviceName, mIncludeTxPowerLevel);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        AdvertiseData other = (AdvertiseData) obj;
+        return Objects.equals(mServiceUuids, other.mServiceUuids)
+                && BluetoothLeUtils.equals(mManufacturerSpecificData,
+                    other.mManufacturerSpecificData)
+                && BluetoothLeUtils.equals(mServiceData, other.mServiceData)
+                && mIncludeDeviceName == other.mIncludeDeviceName
+                && mIncludeTxPowerLevel == other.mIncludeTxPowerLevel;
+    }
+
+    @Override
+    public String toString() {
+        return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mManufacturerSpecificData="
+                + BluetoothLeUtils.toString(mManufacturerSpecificData) + ", mServiceData="
+                + BluetoothLeUtils.toString(mServiceData)
+                + ", mIncludeTxPowerLevel=" + mIncludeTxPowerLevel + ", mIncludeDeviceName="
+                + mIncludeDeviceName + "]";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeTypedArray(mServiceUuids.toArray(new ParcelUuid[mServiceUuids.size()]), flags);
+
+        // mManufacturerSpecificData could not be null.
+        dest.writeInt(mManufacturerSpecificData.size());
+        for (int i = 0; i < mManufacturerSpecificData.size(); ++i) {
+            dest.writeInt(mManufacturerSpecificData.keyAt(i));
+            dest.writeByteArray(mManufacturerSpecificData.valueAt(i));
+        }
+        dest.writeInt(mServiceData.size());
+        for (ParcelUuid uuid : mServiceData.keySet()) {
+            dest.writeTypedObject(uuid, flags);
+            dest.writeByteArray(mServiceData.get(uuid));
+        }
+        dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0));
+        dest.writeByte((byte) (getIncludeDeviceName() ? 1 : 0));
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<AdvertiseData> CREATOR =
+            new Creator<AdvertiseData>() {
+                @Override
+                public AdvertiseData[] newArray(int size) {
+                    return new AdvertiseData[size];
+                }
+
+                @Override
+                public AdvertiseData createFromParcel(Parcel in) {
+                    Builder builder = new Builder();
+                    ArrayList<ParcelUuid> uuids = in.createTypedArrayList(ParcelUuid.CREATOR);
+                    for (ParcelUuid uuid : uuids) {
+                        builder.addServiceUuid(uuid);
+                    }
+
+                    int manufacturerSize = in.readInt();
+                    for (int i = 0; i < manufacturerSize; ++i) {
+                        int manufacturerId = in.readInt();
+                        byte[] manufacturerData = in.createByteArray();
+                        builder.addManufacturerData(manufacturerId, manufacturerData);
+                    }
+                    int serviceDataSize = in.readInt();
+                    for (int i = 0; i < serviceDataSize; ++i) {
+                        ParcelUuid serviceDataUuid = in.readTypedObject(ParcelUuid.CREATOR);
+                        byte[] serviceData = in.createByteArray();
+                        builder.addServiceData(serviceDataUuid, serviceData);
+                    }
+                    builder.setIncludeTxPowerLevel(in.readByte() == 1);
+                    builder.setIncludeDeviceName(in.readByte() == 1);
+                    return builder.build();
+                }
+            };
+
+    /**
+     * Builder for {@link AdvertiseData}.
+     */
+    public static final class Builder {
+        @Nullable
+        private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>();
+        private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>();
+        private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>();
+        private boolean mIncludeTxPowerLevel;
+        private boolean mIncludeDeviceName;
+
+        /**
+         * Add a service UUID to advertise data.
+         *
+         * @param serviceUuid A service UUID to be advertised.
+         * @throws IllegalArgumentException If the {@code serviceUuids} are null.
+         */
+        public Builder addServiceUuid(ParcelUuid serviceUuid) {
+            if (serviceUuid == null) {
+                throw new IllegalArgumentException("serivceUuids are null");
+            }
+            mServiceUuids.add(serviceUuid);
+            return this;
+        }
+
+        /**
+         * Add service data to advertise data.
+         *
+         * @param serviceDataUuid 16-bit UUID of the service the data is associated with
+         * @param serviceData Service data
+         * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is
+         * empty.
+         */
+        public Builder addServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
+            if (serviceDataUuid == null || serviceData == null) {
+                throw new IllegalArgumentException(
+                        "serviceDataUuid or serviceDataUuid is null");
+            }
+            mServiceData.put(serviceDataUuid, serviceData);
+            return this;
+        }
+
+        /**
+         * Add manufacturer specific data.
+         * <p>
+         * Please refer to the Bluetooth Assigned Numbers document provided by the <a
+         * href="https://www.bluetooth.org">Bluetooth SIG</a> for a list of existing company
+         * identifiers.
+         *
+         * @param manufacturerId Manufacturer ID assigned by Bluetooth SIG.
+         * @param manufacturerSpecificData Manufacturer specific data
+         * @throws IllegalArgumentException If the {@code manufacturerId} is negative or {@code
+         * manufacturerSpecificData} is null.
+         */
+        public Builder addManufacturerData(int manufacturerId, byte[] manufacturerSpecificData) {
+            if (manufacturerId < 0) {
+                throw new IllegalArgumentException(
+                        "invalid manufacturerId - " + manufacturerId);
+            }
+            if (manufacturerSpecificData == null) {
+                throw new IllegalArgumentException("manufacturerSpecificData is null");
+            }
+            mManufacturerSpecificData.put(manufacturerId, manufacturerSpecificData);
+            return this;
+        }
+
+        /**
+         * Whether the transmission power level should be included in the advertise packet. Tx power
+         * level field takes 3 bytes in advertise packet.
+         */
+        public Builder setIncludeTxPowerLevel(boolean includeTxPowerLevel) {
+            mIncludeTxPowerLevel = includeTxPowerLevel;
+            return this;
+        }
+
+        /**
+         * Set whether the device name should be included in advertise packet.
+         */
+        public Builder setIncludeDeviceName(boolean includeDeviceName) {
+            mIncludeDeviceName = includeDeviceName;
+            return this;
+        }
+
+        /**
+         * Build the {@link AdvertiseData}.
+         */
+        public AdvertiseData build() {
+            return new AdvertiseData(mServiceUuids, mManufacturerSpecificData, mServiceData,
+                    mIncludeTxPowerLevel, mIncludeDeviceName);
+        }
+    }
+}
diff --git a/android/bluetooth/le/AdvertiseSettings.java b/android/bluetooth/le/AdvertiseSettings.java
new file mode 100644
index 0000000..7129d76
--- /dev/null
+++ b/android/bluetooth/le/AdvertiseSettings.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The {@link AdvertiseSettings} provide a way to adjust advertising preferences for each
+ * Bluetooth LE advertisement instance. Use {@link AdvertiseSettings.Builder} to create an
+ * instance of this class.
+ */
+public final class AdvertiseSettings implements Parcelable {
+    /**
+     * Perform Bluetooth LE advertising in low power mode. This is the default and preferred
+     * advertising mode as it consumes the least power.
+     */
+    public static final int ADVERTISE_MODE_LOW_POWER = 0;
+
+    /**
+     * Perform Bluetooth LE advertising in balanced power mode. This is balanced between advertising
+     * frequency and power consumption.
+     */
+    public static final int ADVERTISE_MODE_BALANCED = 1;
+
+    /**
+     * Perform Bluetooth LE advertising in low latency, high power mode. This has the highest power
+     * consumption and should not be used for continuous background advertising.
+     */
+    public static final int ADVERTISE_MODE_LOW_LATENCY = 2;
+
+    /**
+     * Advertise using the lowest transmission (TX) power level. Low transmission power can be used
+     * to restrict the visibility range of advertising packets.
+     */
+    public static final int ADVERTISE_TX_POWER_ULTRA_LOW = 0;
+
+    /**
+     * Advertise using low TX power level.
+     */
+    public static final int ADVERTISE_TX_POWER_LOW = 1;
+
+    /**
+     * Advertise using medium TX power level.
+     */
+    public static final int ADVERTISE_TX_POWER_MEDIUM = 2;
+
+    /**
+     * Advertise using high TX power level. This corresponds to largest visibility range of the
+     * advertising packet.
+     */
+    public static final int ADVERTISE_TX_POWER_HIGH = 3;
+
+    /**
+     * The maximum limited advertisement duration as specified by the Bluetooth SIG
+     */
+    private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;
+
+    private final int mAdvertiseMode;
+    private final int mAdvertiseTxPowerLevel;
+    private final int mAdvertiseTimeoutMillis;
+    private final boolean mAdvertiseConnectable;
+
+    private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel,
+            boolean advertiseConnectable, int advertiseTimeout) {
+        mAdvertiseMode = advertiseMode;
+        mAdvertiseTxPowerLevel = advertiseTxPowerLevel;
+        mAdvertiseConnectable = advertiseConnectable;
+        mAdvertiseTimeoutMillis = advertiseTimeout;
+    }
+
+    private AdvertiseSettings(Parcel in) {
+        mAdvertiseMode = in.readInt();
+        mAdvertiseTxPowerLevel = in.readInt();
+        mAdvertiseConnectable = in.readInt() != 0;
+        mAdvertiseTimeoutMillis = in.readInt();
+    }
+
+    /**
+     * Returns the advertise mode.
+     */
+    public int getMode() {
+        return mAdvertiseMode;
+    }
+
+    /**
+     * Returns the TX power level for advertising.
+     */
+    public int getTxPowerLevel() {
+        return mAdvertiseTxPowerLevel;
+    }
+
+    /**
+     * Returns whether the advertisement will indicate connectable.
+     */
+    public boolean isConnectable() {
+        return mAdvertiseConnectable;
+    }
+
+    /**
+     * Returns the advertising time limit in milliseconds.
+     */
+    public int getTimeout() {
+        return mAdvertiseTimeoutMillis;
+    }
+
+    @Override
+    public String toString() {
+        return "Settings [mAdvertiseMode=" + mAdvertiseMode
+                + ", mAdvertiseTxPowerLevel=" + mAdvertiseTxPowerLevel
+                + ", mAdvertiseConnectable=" + mAdvertiseConnectable
+                + ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis + "]";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mAdvertiseMode);
+        dest.writeInt(mAdvertiseTxPowerLevel);
+        dest.writeInt(mAdvertiseConnectable ? 1 : 0);
+        dest.writeInt(mAdvertiseTimeoutMillis);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<AdvertiseSettings> CREATOR =
+            new Creator<AdvertiseSettings>() {
+                @Override
+                public AdvertiseSettings[] newArray(int size) {
+                    return new AdvertiseSettings[size];
+                }
+
+                @Override
+                public AdvertiseSettings createFromParcel(Parcel in) {
+                    return new AdvertiseSettings(in);
+                }
+            };
+
+    /**
+     * Builder class for {@link AdvertiseSettings}.
+     */
+    public static final class Builder {
+        private int mMode = ADVERTISE_MODE_LOW_POWER;
+        private int mTxPowerLevel = ADVERTISE_TX_POWER_MEDIUM;
+        private int mTimeoutMillis = 0;
+        private boolean mConnectable = true;
+
+        /**
+         * Set advertise mode to control the advertising power and latency.
+         *
+         * @param advertiseMode Bluetooth LE Advertising mode, can only be one of {@link
+         * AdvertiseSettings#ADVERTISE_MODE_LOW_POWER},
+         * {@link AdvertiseSettings#ADVERTISE_MODE_BALANCED},
+         * or {@link AdvertiseSettings#ADVERTISE_MODE_LOW_LATENCY}.
+         * @throws IllegalArgumentException If the advertiseMode is invalid.
+         */
+        public Builder setAdvertiseMode(int advertiseMode) {
+            if (advertiseMode < ADVERTISE_MODE_LOW_POWER
+                    || advertiseMode > ADVERTISE_MODE_LOW_LATENCY) {
+                throw new IllegalArgumentException("unknown mode " + advertiseMode);
+            }
+            mMode = advertiseMode;
+            return this;
+        }
+
+        /**
+         * Set advertise TX power level to control the transmission power level for the advertising.
+         *
+         * @param txPowerLevel Transmission power of Bluetooth LE Advertising, can only be one of
+         * {@link AdvertiseSettings#ADVERTISE_TX_POWER_ULTRA_LOW}, {@link
+         * AdvertiseSettings#ADVERTISE_TX_POWER_LOW},
+         * {@link AdvertiseSettings#ADVERTISE_TX_POWER_MEDIUM}
+         * or {@link AdvertiseSettings#ADVERTISE_TX_POWER_HIGH}.
+         * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid.
+         */
+        public Builder setTxPowerLevel(int txPowerLevel) {
+            if (txPowerLevel < ADVERTISE_TX_POWER_ULTRA_LOW
+                    || txPowerLevel > ADVERTISE_TX_POWER_HIGH) {
+                throw new IllegalArgumentException("unknown tx power level " + txPowerLevel);
+            }
+            mTxPowerLevel = txPowerLevel;
+            return this;
+        }
+
+        /**
+         * Set whether the advertisement type should be connectable or non-connectable.
+         *
+         * @param connectable Controls whether the advertisment type will be connectable (true) or
+         * non-connectable (false).
+         */
+        public Builder setConnectable(boolean connectable) {
+            mConnectable = connectable;
+            return this;
+        }
+
+        /**
+         * Limit advertising to a given amount of time.
+         *
+         * @param timeoutMillis Advertising time limit. May not exceed 180000 milliseconds. A value
+         * of 0 will disable the time limit.
+         * @throws IllegalArgumentException If the provided timeout is over 180000 ms.
+         */
+        public Builder setTimeout(int timeoutMillis) {
+            if (timeoutMillis < 0 || timeoutMillis > LIMITED_ADVERTISING_MAX_MILLIS) {
+                throw new IllegalArgumentException("timeoutMillis invalid (must be 0-"
+                        + LIMITED_ADVERTISING_MAX_MILLIS + " milliseconds)");
+            }
+            mTimeoutMillis = timeoutMillis;
+            return this;
+        }
+
+        /**
+         * Build the {@link AdvertiseSettings} object.
+         */
+        public AdvertiseSettings build() {
+            return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis);
+        }
+    }
+}
diff --git a/android/bluetooth/le/AdvertisingSet.java b/android/bluetooth/le/AdvertisingSet.java
new file mode 100644
index 0000000..1df35e1
--- /dev/null
+++ b/android/bluetooth/le/AdvertisingSet.java
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2017 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.le;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothManager;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * This class provides a way to control single Bluetooth LE advertising instance.
+ * <p>
+ * To get an instance of {@link AdvertisingSet}, call the
+ * {@link BluetoothLeAdvertiser#startAdvertisingSet} method.
+ * <p>
+ * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * permission.
+ *
+ * @see AdvertiseData
+ */
+public final class AdvertisingSet {
+    private static final String TAG = "AdvertisingSet";
+
+    private final IBluetoothGatt mGatt;
+    private int mAdvertiserId;
+
+    /* package */ AdvertisingSet(int advertiserId,
+            IBluetoothManager bluetoothManager) {
+        mAdvertiserId = advertiserId;
+
+        try {
+            mGatt = bluetoothManager.getBluetoothGatt();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
+            throw new IllegalStateException("Failed to get Bluetooth");
+        }
+    }
+
+    /* package */ void setAdvertiserId(int advertiserId) {
+        mAdvertiserId = advertiserId;
+    }
+
+    /**
+     * Enables Advertising. This method returns immediately, the operation status is
+     * delivered through {@code callback.onAdvertisingEnabled()}.
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     *
+     * @param enable whether the advertising should be enabled (true), or disabled (false)
+     * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535
+     * (655,350 ms)
+     * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the
+     * controller shall attempt to send prior to terminating the extended advertising, even if the
+     * duration has not expired. Valid range is from 1 to 255.
+     */
+    public void enableAdvertising(boolean enable, int duration,
+            int maxExtendedAdvertisingEvents) {
+        try {
+            mGatt.enableAdvertisingSet(mAdvertiserId, enable, duration,
+                    maxExtendedAdvertisingEvents);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Set/update data being Advertised. Make sure that data doesn't exceed the size limit for
+     * specified AdvertisingSetParameters. This method returns immediately, the operation status is
+     * delivered through {@code callback.onAdvertisingDataSet()}.
+     * <p>
+     * Advertising data must be empty if non-legacy scannable advertising is used.
+     *
+     * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link
+     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable,
+     * three bytes will be added for flags. If the update takes place when the advertising set is
+     * enabled, the data can be maximum 251 bytes long.
+     */
+    public void setAdvertisingData(AdvertiseData advertiseData) {
+        try {
+            mGatt.setAdvertisingData(mAdvertiserId, advertiseData);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Set/update scan response data. Make sure that data doesn't exceed the size limit for
+     * specified AdvertisingSetParameters. This method returns immediately, the operation status
+     * is delivered through {@code callback.onScanResponseDataSet()}.
+     *
+     * @param scanResponse Scan response associated with the advertisement data. Size must not
+     * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place
+     * when the advertising set is enabled, the data can be maximum 251 bytes long.
+     */
+    public void setScanResponseData(AdvertiseData scanResponse) {
+        try {
+            mGatt.setScanResponseData(mAdvertiserId, scanResponse);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Update advertising parameters associated with this AdvertisingSet. Must be called when
+     * advertising is not active. This method returns immediately, the operation status is delivered
+     * through {@code callback.onAdvertisingParametersUpdated}.
+     *
+     * @param parameters advertising set parameters.
+     */
+    public void setAdvertisingParameters(AdvertisingSetParameters parameters) {
+        try {
+            mGatt.setAdvertisingParameters(mAdvertiserId, parameters);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Update periodic advertising parameters associated with this set. Must be called when
+     * periodic advertising is not enabled. This method returns immediately, the operation
+     * status is delivered through {@code callback.onPeriodicAdvertisingParametersUpdated()}.
+     */
+    public void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) {
+        try {
+            mGatt.setPeriodicAdvertisingParameters(mAdvertiserId, parameters);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Used to set periodic advertising data, must be called after setPeriodicAdvertisingParameters,
+     * or after advertising was started with periodic advertising data set. This method returns
+     * immediately, the operation status is delivered through
+     * {@code callback.onPeriodicAdvertisingDataSet()}.
+     *
+     * @param periodicData Periodic advertising data. Size must not exceed {@link
+     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the update takes place when the
+     * periodic advertising is enabled for this set, the data can be maximum 251 bytes long.
+     */
+    public void setPeriodicAdvertisingData(AdvertiseData periodicData) {
+        try {
+            mGatt.setPeriodicAdvertisingData(mAdvertiserId, periodicData);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Used to enable/disable periodic advertising. This method returns immediately, the operation
+     * status is delivered through {@code callback.onPeriodicAdvertisingEnable()}.
+     *
+     * @param enable whether the periodic advertising should be enabled (true), or disabled
+     * (false).
+     */
+    public void setPeriodicAdvertisingEnabled(boolean enable) {
+        try {
+            mGatt.setPeriodicAdvertisingEnable(mAdvertiserId, enable);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Returns address associated with this advertising set.
+     * This method is exposed only for Bluetooth PTS tests, no app or system service
+     * should ever use it.
+     *
+     * This method requires {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission.
+     *
+     * @hide
+     */
+    public void getOwnAddress() {
+        try {
+            mGatt.getOwnAddress(mAdvertiserId);
+        } catch (RemoteException e) {
+            Log.e(TAG, "remote exception - ", e);
+        }
+    }
+
+    /**
+     * Returns advertiserId associated with this advertising set.
+     *
+     * @hide
+     */
+    public int getAdvertiserId() {
+        return mAdvertiserId;
+    }
+}
diff --git a/android/bluetooth/le/AdvertisingSetCallback.java b/android/bluetooth/le/AdvertisingSetCallback.java
new file mode 100644
index 0000000..51324fd
--- /dev/null
+++ b/android/bluetooth/le/AdvertisingSetCallback.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2017 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.le;
+
+/**
+ * Bluetooth LE advertising set callbacks, used to deliver advertising operation
+ * status.
+ */
+public abstract class AdvertisingSetCallback {
+
+    /**
+     * The requested operation was successful.
+     */
+    public static final int ADVERTISE_SUCCESS = 0;
+
+    /**
+     * Failed to start advertising as the advertise data to be broadcasted is too
+     * large.
+     */
+    public static final int ADVERTISE_FAILED_DATA_TOO_LARGE = 1;
+
+    /**
+     * Failed to start advertising because no advertising instance is available.
+     */
+    public static final int ADVERTISE_FAILED_TOO_MANY_ADVERTISERS = 2;
+
+    /**
+     * Failed to start advertising as the advertising is already started.
+     */
+    public static final int ADVERTISE_FAILED_ALREADY_STARTED = 3;
+
+    /**
+     * Operation failed due to an internal error.
+     */
+    public static final int ADVERTISE_FAILED_INTERNAL_ERROR = 4;
+
+    /**
+     * This feature is not supported on this platform.
+     */
+    public static final int ADVERTISE_FAILED_FEATURE_UNSUPPORTED = 5;
+
+    /**
+     * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertisingSet}
+     * indicating result of the operation. If status is ADVERTISE_SUCCESS, then advertisingSet
+     * contains the started set and it is advertising. If error occurred, advertisingSet is
+     * null, and status will be set to proper error code.
+     *
+     * @param advertisingSet The advertising set that was started or null if error.
+     * @param txPower tx power that will be used for this set.
+     * @param status Status of the operation.
+     */
+    public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower, int status) {
+    }
+
+    /**
+     * Callback triggered in response to {@link BluetoothLeAdvertiser#stopAdvertisingSet}
+     * indicating advertising set is stopped.
+     *
+     * @param advertisingSet The advertising set.
+     */
+    public void onAdvertisingSetStopped(AdvertisingSet advertisingSet) {
+    }
+
+    /**
+     * Callback triggered in response to {@link BluetoothLeAdvertiser#startAdvertisingSet}
+     * indicating result of the operation. If status is ADVERTISE_SUCCESS, then advertising set is
+     * advertising.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable, int status) {
+    }
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#setAdvertisingData} indicating
+     * result of the operation. If status is ADVERTISE_SUCCESS, then data was changed.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onAdvertisingDataSet(AdvertisingSet advertisingSet, int status) {
+    }
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#setAdvertisingData} indicating
+     * result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onScanResponseDataSet(AdvertisingSet advertisingSet, int status) {
+    }
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#setAdvertisingParameters}
+     * indicating result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param txPower tx power that will be used for this set.
+     * @param status Status of the operation.
+     */
+    public void onAdvertisingParametersUpdated(AdvertisingSet advertisingSet,
+            int txPower, int status) {
+    }
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingParameters}
+     * indicating result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onPeriodicAdvertisingParametersUpdated(AdvertisingSet advertisingSet, int status) {
+    }
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingData}
+     * indicating result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onPeriodicAdvertisingDataSet(AdvertisingSet advertisingSet,
+            int status) {
+    }
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#setPeriodicAdvertisingEnabled}
+     * indicating result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param status Status of the operation.
+     */
+    public void onPeriodicAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enable,
+            int status) {
+    }
+
+    /**
+     * Callback triggered in response to {@link AdvertisingSet#getOwnAddress()}
+     * indicating result of the operation.
+     *
+     * @param advertisingSet The advertising set.
+     * @param addressType type of address.
+     * @param address advertising set bluetooth address.
+     * @hide
+     */
+    public void onOwnAddressRead(AdvertisingSet advertisingSet, int addressType, String address) {
+    }
+}
diff --git a/android/bluetooth/le/AdvertisingSetParameters.java b/android/bluetooth/le/AdvertisingSetParameters.java
new file mode 100644
index 0000000..e39b198
--- /dev/null
+++ b/android/bluetooth/le/AdvertisingSetParameters.java
@@ -0,0 +1,437 @@
+/*
+ * Copyright (C) 2017 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.le;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The {@link AdvertisingSetParameters} provide a way to adjust advertising
+ * preferences for each
+ * Bluetooth LE advertising set. Use {@link AdvertisingSetParameters.Builder} to
+ * create an
+ * instance of this class.
+ */
+public final class AdvertisingSetParameters implements Parcelable {
+
+    /**
+     * Advertise on low frequency, around every 1000ms. This is the default and
+     * preferred advertising mode as it consumes the least power.
+     */
+    public static final int INTERVAL_HIGH = 1600;
+
+    /**
+     * Advertise on medium frequency, around every 250ms. This is balanced
+     * between advertising frequency and power consumption.
+     */
+    public static final int INTERVAL_MEDIUM = 400;
+
+    /**
+     * Perform high frequency, low latency advertising, around every 100ms. This
+     * has the highest power consumption and should not be used for continuous
+     * background advertising.
+     */
+    public static final int INTERVAL_LOW = 160;
+
+    /**
+     * Minimum value for advertising interval.
+     */
+    public static final int INTERVAL_MIN = 160;
+
+    /**
+     * Maximum value for advertising interval.
+     */
+    public static final int INTERVAL_MAX = 16777215;
+
+    /**
+     * Advertise using the lowest transmission (TX) power level. Low transmission
+     * power can be used to restrict the visibility range of advertising packets.
+     */
+    public static final int TX_POWER_ULTRA_LOW = -21;
+
+    /**
+     * Advertise using low TX power level.
+     */
+    public static final int TX_POWER_LOW = -15;
+
+    /**
+     * Advertise using medium TX power level.
+     */
+    public static final int TX_POWER_MEDIUM = -7;
+
+    /**
+     * Advertise using high TX power level. This corresponds to largest visibility
+     * range of the advertising packet.
+     */
+    public static final int TX_POWER_HIGH = 1;
+
+    /**
+     * Minimum value for TX power.
+     */
+    public static final int TX_POWER_MIN = -127;
+
+    /**
+     * Maximum value for TX power.
+     */
+    public static final int TX_POWER_MAX = 1;
+
+    /**
+     * The maximum limited advertisement duration as specified by the Bluetooth
+     * SIG
+     */
+    private static final int LIMITED_ADVERTISING_MAX_MILLIS = 180 * 1000;
+
+    private final boolean mIsLegacy;
+    private final boolean mIsAnonymous;
+    private final boolean mIncludeTxPower;
+    private final int mPrimaryPhy;
+    private final int mSecondaryPhy;
+    private final boolean mConnectable;
+    private final boolean mScannable;
+    private final int mInterval;
+    private final int mTxPowerLevel;
+
+    private AdvertisingSetParameters(boolean connectable, boolean scannable, boolean isLegacy,
+            boolean isAnonymous, boolean includeTxPower,
+            int primaryPhy, int secondaryPhy,
+            int interval, int txPowerLevel) {
+        mConnectable = connectable;
+        mScannable = scannable;
+        mIsLegacy = isLegacy;
+        mIsAnonymous = isAnonymous;
+        mIncludeTxPower = includeTxPower;
+        mPrimaryPhy = primaryPhy;
+        mSecondaryPhy = secondaryPhy;
+        mInterval = interval;
+        mTxPowerLevel = txPowerLevel;
+    }
+
+    private AdvertisingSetParameters(Parcel in) {
+        mConnectable = in.readInt() != 0;
+        mScannable = in.readInt() != 0;
+        mIsLegacy = in.readInt() != 0;
+        mIsAnonymous = in.readInt() != 0;
+        mIncludeTxPower = in.readInt() != 0;
+        mPrimaryPhy = in.readInt();
+        mSecondaryPhy = in.readInt();
+        mInterval = in.readInt();
+        mTxPowerLevel = in.readInt();
+    }
+
+    /**
+     * Returns whether the advertisement will be connectable.
+     */
+    public boolean isConnectable() {
+        return mConnectable;
+    }
+
+    /**
+     * Returns whether the advertisement will be scannable.
+     */
+    public boolean isScannable() {
+        return mScannable;
+    }
+
+    /**
+     * Returns whether the legacy advertisement will be used.
+     */
+    public boolean isLegacy() {
+        return mIsLegacy;
+    }
+
+    /**
+     * Returns whether the advertisement will be anonymous.
+     */
+    public boolean isAnonymous() {
+        return mIsAnonymous;
+    }
+
+    /**
+     * Returns whether the TX Power will be included.
+     */
+    public boolean includeTxPower() {
+        return mIncludeTxPower;
+    }
+
+    /**
+     * Returns the primary advertising phy.
+     */
+    public int getPrimaryPhy() {
+        return mPrimaryPhy;
+    }
+
+    /**
+     * Returns the secondary advertising phy.
+     */
+    public int getSecondaryPhy() {
+        return mSecondaryPhy;
+    }
+
+    /**
+     * Returns the advertising interval.
+     */
+    public int getInterval() {
+        return mInterval;
+    }
+
+    /**
+     * Returns the TX power level for advertising.
+     */
+    public int getTxPowerLevel() {
+        return mTxPowerLevel;
+    }
+
+    @Override
+    public String toString() {
+        return "AdvertisingSetParameters [connectable=" + mConnectable
+                + ", isLegacy=" + mIsLegacy
+                + ", isAnonymous=" + mIsAnonymous
+                + ", includeTxPower=" + mIncludeTxPower
+                + ", primaryPhy=" + mPrimaryPhy
+                + ", secondaryPhy=" + mSecondaryPhy
+                + ", interval=" + mInterval
+                + ", txPowerLevel=" + mTxPowerLevel + "]";
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mConnectable ? 1 : 0);
+        dest.writeInt(mScannable ? 1 : 0);
+        dest.writeInt(mIsLegacy ? 1 : 0);
+        dest.writeInt(mIsAnonymous ? 1 : 0);
+        dest.writeInt(mIncludeTxPower ? 1 : 0);
+        dest.writeInt(mPrimaryPhy);
+        dest.writeInt(mSecondaryPhy);
+        dest.writeInt(mInterval);
+        dest.writeInt(mTxPowerLevel);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<AdvertisingSetParameters> CREATOR =
+            new Creator<AdvertisingSetParameters>() {
+                @Override
+                public AdvertisingSetParameters[] newArray(int size) {
+                    return new AdvertisingSetParameters[size];
+                }
+
+                @Override
+                public AdvertisingSetParameters createFromParcel(Parcel in) {
+                    return new AdvertisingSetParameters(in);
+                }
+            };
+
+    /**
+     * Builder class for {@link AdvertisingSetParameters}.
+     */
+    public static final class Builder {
+        private boolean mConnectable = false;
+        private boolean mScannable = false;
+        private boolean mIsLegacy = false;
+        private boolean mIsAnonymous = false;
+        private boolean mIncludeTxPower = false;
+        private int mPrimaryPhy = BluetoothDevice.PHY_LE_1M;
+        private int mSecondaryPhy = BluetoothDevice.PHY_LE_1M;
+        private int mInterval = INTERVAL_LOW;
+        private int mTxPowerLevel = TX_POWER_MEDIUM;
+
+        /**
+         * Set whether the advertisement type should be connectable or
+         * non-connectable.
+         * Legacy advertisements can be both connectable and scannable. Non-legacy
+         * advertisements can be only scannable or only connectable.
+         *
+         * @param connectable Controls whether the advertisement type will be connectable (true) or
+         * non-connectable (false).
+         */
+        public Builder setConnectable(boolean connectable) {
+            mConnectable = connectable;
+            return this;
+        }
+
+        /**
+         * Set whether the advertisement type should be scannable.
+         * Legacy advertisements can be both connectable and scannable. Non-legacy
+         * advertisements can be only scannable or only connectable.
+         *
+         * @param scannable Controls whether the advertisement type will be scannable (true) or
+         * non-scannable (false).
+         */
+        public Builder setScannable(boolean scannable) {
+            mScannable = scannable;
+            return this;
+        }
+
+        /**
+         * When set to true, advertising set will advertise 4.x Spec compliant
+         * advertisements.
+         *
+         * @param isLegacy whether legacy advertising mode should be used.
+         */
+        public Builder setLegacyMode(boolean isLegacy) {
+            mIsLegacy = isLegacy;
+            return this;
+        }
+
+        /**
+         * Set whether advertiser address should be ommited from all packets. If this
+         * mode is used, periodic advertising can't be enabled for this set.
+         *
+         * This is used only if legacy mode is not used.
+         *
+         * @param isAnonymous whether anonymous advertising should be used.
+         */
+        public Builder setAnonymous(boolean isAnonymous) {
+            mIsAnonymous = isAnonymous;
+            return this;
+        }
+
+        /**
+         * Set whether TX power should be included in the extended header.
+         *
+         * This is used only if legacy mode is not used.
+         *
+         * @param includeTxPower whether TX power should be included in extended header
+         */
+        public Builder setIncludeTxPower(boolean includeTxPower) {
+            mIncludeTxPower = includeTxPower;
+            return this;
+        }
+
+        /**
+         * Set the primary physical channel used for this advertising set.
+         *
+         * This is used only if legacy mode is not used.
+         *
+         * Use {@link BluetoothAdapter#isLeCodedPhySupported} to determine if LE Coded PHY is
+         * supported on this device.
+         *
+         * @param primaryPhy Primary advertising physical channel, can only be {@link
+         * BluetoothDevice#PHY_LE_1M} or {@link BluetoothDevice#PHY_LE_CODED}.
+         * @throws IllegalArgumentException If the primaryPhy is invalid.
+         */
+        public Builder setPrimaryPhy(int primaryPhy) {
+            if (primaryPhy != BluetoothDevice.PHY_LE_1M
+                    && primaryPhy != BluetoothDevice.PHY_LE_CODED) {
+                throw new IllegalArgumentException("bad primaryPhy " + primaryPhy);
+            }
+            mPrimaryPhy = primaryPhy;
+            return this;
+        }
+
+        /**
+         * Set the secondary physical channel used for this advertising set.
+         *
+         * This is used only if legacy mode is not used.
+         *
+         * Use {@link BluetoothAdapter#isLeCodedPhySupported} and
+         * {@link BluetoothAdapter#isLe2MPhySupported} to determine if LE Coded PHY or 2M PHY is
+         * supported on this device.
+         *
+         * @param secondaryPhy Secondary advertising physical channel, can only be one of {@link
+         * BluetoothDevice#PHY_LE_1M}, {@link BluetoothDevice#PHY_LE_2M} or {@link
+         * BluetoothDevice#PHY_LE_CODED}.
+         * @throws IllegalArgumentException If the secondaryPhy is invalid.
+         */
+        public Builder setSecondaryPhy(int secondaryPhy) {
+            if (secondaryPhy != BluetoothDevice.PHY_LE_1M
+                    && secondaryPhy != BluetoothDevice.PHY_LE_2M
+                    && secondaryPhy != BluetoothDevice.PHY_LE_CODED) {
+                throw new IllegalArgumentException("bad secondaryPhy " + secondaryPhy);
+            }
+            mSecondaryPhy = secondaryPhy;
+            return this;
+        }
+
+        /**
+         * Set advertising interval.
+         *
+         * @param interval Bluetooth LE Advertising interval, in 0.625ms unit. Valid range is from
+         * 160 (100ms) to 16777215 (10,485.759375 s). Recommended values are: {@link
+         * AdvertisingSetParameters#INTERVAL_LOW}, {@link AdvertisingSetParameters#INTERVAL_MEDIUM},
+         * or {@link AdvertisingSetParameters#INTERVAL_HIGH}.
+         * @throws IllegalArgumentException If the interval is invalid.
+         */
+        public Builder setInterval(int interval) {
+            if (interval < INTERVAL_MIN || interval > INTERVAL_MAX) {
+                throw new IllegalArgumentException("unknown interval " + interval);
+            }
+            mInterval = interval;
+            return this;
+        }
+
+        /**
+         * Set the transmission power level for the advertising.
+         *
+         * @param txPowerLevel Transmission power of Bluetooth LE Advertising, in dBm. The valid
+         * range is [-127, 1] Recommended values are:
+         * {@link AdvertisingSetParameters#TX_POWER_ULTRA_LOW},
+         * {@link AdvertisingSetParameters#TX_POWER_LOW},
+         * {@link AdvertisingSetParameters#TX_POWER_MEDIUM},
+         * or {@link AdvertisingSetParameters#TX_POWER_HIGH}.
+         * @throws IllegalArgumentException If the {@code txPowerLevel} is invalid.
+         */
+        public Builder setTxPowerLevel(int txPowerLevel) {
+            if (txPowerLevel < TX_POWER_MIN || txPowerLevel > TX_POWER_MAX) {
+                throw new IllegalArgumentException("unknown txPowerLevel " + txPowerLevel);
+            }
+            mTxPowerLevel = txPowerLevel;
+            return this;
+        }
+
+        /**
+         * Build the {@link AdvertisingSetParameters} object.
+         *
+         * @throws IllegalStateException if invalid combination of parameters is used.
+         */
+        public AdvertisingSetParameters build() {
+            if (mIsLegacy) {
+                if (mIsAnonymous) {
+                    throw new IllegalArgumentException("Legacy advertising can't be anonymous");
+                }
+
+                if (mConnectable && !mScannable) {
+                    throw new IllegalStateException(
+                            "Legacy advertisement can't be connectable and non-scannable");
+                }
+
+                if (mIncludeTxPower) {
+                    throw new IllegalStateException(
+                            "Legacy advertising can't include TX power level in header");
+                }
+            } else {
+                if (mConnectable && mScannable) {
+                    throw new IllegalStateException(
+                            "Advertising can't be both connectable and scannable");
+                }
+
+                if (mIsAnonymous && mConnectable) {
+                    throw new IllegalStateException(
+                            "Advertising can't be both connectable and anonymous");
+                }
+            }
+
+            return new AdvertisingSetParameters(mConnectable, mScannable, mIsLegacy, mIsAnonymous,
+                    mIncludeTxPower, mPrimaryPhy, mSecondaryPhy, mInterval, mTxPowerLevel);
+        }
+    }
+}
diff --git a/android/bluetooth/le/BluetoothLeAdvertiser.java b/android/bluetooth/le/BluetoothLeAdvertiser.java
new file mode 100644
index 0000000..13c5ff6
--- /dev/null
+++ b/android/bluetooth/le/BluetoothLeAdvertiser.java
@@ -0,0 +1,685 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothUuid;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ParcelUuid;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class provides a way to perform Bluetooth LE advertise operations, such as starting and
+ * stopping advertising. An advertiser can broadcast up to 31 bytes of advertisement data
+ * represented by {@link AdvertiseData}.
+ * <p>
+ * To get an instance of {@link BluetoothLeAdvertiser}, call the
+ * {@link BluetoothAdapter#getBluetoothLeAdvertiser()} method.
+ * <p>
+ * <b>Note:</b> Most of the methods here require {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+ * permission.
+ *
+ * @see AdvertiseData
+ */
+public final class BluetoothLeAdvertiser {
+
+    private static final String TAG = "BluetoothLeAdvertiser";
+
+    private static final int MAX_ADVERTISING_DATA_BYTES = 1650;
+    private static final int MAX_LEGACY_ADVERTISING_DATA_BYTES = 31;
+    // Each fields need one byte for field length and another byte for field type.
+    private static final int OVERHEAD_BYTES_PER_FIELD = 2;
+    // Flags field will be set by system.
+    private static final int FLAGS_FIELD_BYTES = 3;
+    private static final int MANUFACTURER_SPECIFIC_DATA_LENGTH = 2;
+
+    private final IBluetoothManager mBluetoothManager;
+    private final Handler mHandler;
+    private BluetoothAdapter mBluetoothAdapter;
+    private final Map<AdvertiseCallback, AdvertisingSetCallback>
+            mLegacyAdvertisers = new HashMap<>();
+    private final Map<AdvertisingSetCallback, IAdvertisingSetCallback>
+            mCallbackWrappers = Collections.synchronizedMap(new HashMap<>());
+    private final Map<Integer, AdvertisingSet>
+            mAdvertisingSets = Collections.synchronizedMap(new HashMap<>());
+
+    /**
+     * Use BluetoothAdapter.getLeAdvertiser() instead.
+     *
+     * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management
+     * @hide
+     */
+    public BluetoothLeAdvertiser(IBluetoothManager bluetoothManager) {
+        mBluetoothManager = bluetoothManager;
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        mHandler = new Handler(Looper.getMainLooper());
+    }
+
+    /**
+     * Start Bluetooth LE Advertising. On success, the {@code advertiseData} will be broadcasted.
+     * Returns immediately, the operation status is delivered through {@code callback}.
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param settings Settings for Bluetooth LE advertising.
+     * @param advertiseData Advertisement data to be broadcasted.
+     * @param callback Callback for advertising status.
+     */
+    public void startAdvertising(AdvertiseSettings settings,
+            AdvertiseData advertiseData, final AdvertiseCallback callback) {
+        startAdvertising(settings, advertiseData, null, callback);
+    }
+
+    /**
+     * Start Bluetooth LE Advertising. The {@code advertiseData} will be broadcasted if the
+     * operation succeeds. The {@code scanResponse} is returned when a scanning device sends an
+     * active scan request. This method returns immediately, the operation status is delivered
+     * through {@code callback}.
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN}
+     *
+     * @param settings Settings for Bluetooth LE advertising.
+     * @param advertiseData Advertisement data to be advertised in advertisement packet.
+     * @param scanResponse Scan response associated with the advertisement data.
+     * @param callback Callback for advertising status.
+     */
+    public void startAdvertising(AdvertiseSettings settings,
+            AdvertiseData advertiseData, AdvertiseData scanResponse,
+            final AdvertiseCallback callback) {
+        synchronized (mLegacyAdvertisers) {
+            BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
+            if (callback == null) {
+                throw new IllegalArgumentException("callback cannot be null");
+            }
+            boolean isConnectable = settings.isConnectable();
+            if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES
+                    || totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
+                postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_DATA_TOO_LARGE);
+                return;
+            }
+            if (mLegacyAdvertisers.containsKey(callback)) {
+                postStartFailure(callback, AdvertiseCallback.ADVERTISE_FAILED_ALREADY_STARTED);
+                return;
+            }
+
+            AdvertisingSetParameters.Builder parameters = new AdvertisingSetParameters.Builder();
+            parameters.setLegacyMode(true);
+            parameters.setConnectable(isConnectable);
+            parameters.setScannable(true); // legacy advertisements we support are always scannable
+            if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_POWER) {
+                parameters.setInterval(1600); // 1s
+            } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_BALANCED) {
+                parameters.setInterval(400); // 250ms
+            } else if (settings.getMode() == AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY) {
+                parameters.setInterval(160); // 100ms
+            }
+
+            if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW) {
+                parameters.setTxPowerLevel(-21);
+            } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_LOW) {
+                parameters.setTxPowerLevel(-15);
+            } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM) {
+                parameters.setTxPowerLevel(-7);
+            } else if (settings.getTxPowerLevel() == AdvertiseSettings.ADVERTISE_TX_POWER_HIGH) {
+                parameters.setTxPowerLevel(1);
+            }
+
+            int duration = 0;
+            int timeoutMillis = settings.getTimeout();
+            if (timeoutMillis > 0) {
+                duration = (timeoutMillis < 10) ? 1 : timeoutMillis / 10;
+            }
+
+            AdvertisingSetCallback wrapped = wrapOldCallback(callback, settings);
+            mLegacyAdvertisers.put(callback, wrapped);
+            startAdvertisingSet(parameters.build(), advertiseData, scanResponse, null, null,
+                    duration, 0, wrapped);
+        }
+    }
+
+    AdvertisingSetCallback wrapOldCallback(AdvertiseCallback callback, AdvertiseSettings settings) {
+        return new AdvertisingSetCallback() {
+            @Override
+            public void onAdvertisingSetStarted(AdvertisingSet advertisingSet, int txPower,
+                    int status) {
+                if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
+                    postStartFailure(callback, status);
+                    return;
+                }
+
+                postStartSuccess(callback, settings);
+            }
+
+            /* Legacy advertiser is disabled on timeout */
+            @Override
+            public void onAdvertisingEnabled(AdvertisingSet advertisingSet, boolean enabled,
+                    int status) {
+                if (enabled) {
+                    Log.e(TAG, "Legacy advertiser should be only disabled on timeout,"
+                            + " but was enabled!");
+                    return;
+                }
+
+                stopAdvertising(callback);
+            }
+
+        };
+    }
+
+    /**
+     * Stop Bluetooth LE advertising. The {@code callback} must be the same one use in
+     * {@link BluetoothLeAdvertiser#startAdvertising}.
+     * <p>
+     * Requires {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+     *
+     * @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
+     */
+    public void stopAdvertising(final AdvertiseCallback callback) {
+        synchronized (mLegacyAdvertisers) {
+            if (callback == null) {
+                throw new IllegalArgumentException("callback cannot be null");
+            }
+            AdvertisingSetCallback wrapper = mLegacyAdvertisers.get(callback);
+            if (wrapper == null) return;
+
+            stopAdvertisingSet(wrapper);
+
+            mLegacyAdvertisers.remove(callback);
+        }
+    }
+
+    /**
+     * Creates a new advertising set. If operation succeed, device will start advertising. This
+     * method returns immediately, the operation status is delivered through
+     * {@code callback.onAdvertisingSetStarted()}.
+     * <p>
+     *
+     * @param parameters advertising set parameters.
+     * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link
+     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable,
+     * three bytes will be added for flags.
+     * @param scanResponse Scan response associated with the advertisement data. Size must not
+     * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
+     * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will
+     * not be started.
+     * @param periodicData Periodic advertising data. Size must not exceed {@link
+     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
+     * @param callback Callback for advertising set.
+     * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable
+     * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
+     * feature is made when it's not supported by the controller.
+     */
+    public void startAdvertisingSet(AdvertisingSetParameters parameters,
+            AdvertiseData advertiseData, AdvertiseData scanResponse,
+            PeriodicAdvertisingParameters periodicParameters,
+            AdvertiseData periodicData, AdvertisingSetCallback callback) {
+        startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
+                periodicData, 0, 0, callback, new Handler(Looper.getMainLooper()));
+    }
+
+    /**
+     * Creates a new advertising set. If operation succeed, device will start advertising. This
+     * method returns immediately, the operation status is delivered through
+     * {@code callback.onAdvertisingSetStarted()}.
+     * <p>
+     *
+     * @param parameters advertising set parameters.
+     * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link
+     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable,
+     * three bytes will be added for flags.
+     * @param scanResponse Scan response associated with the advertisement data. Size must not
+     * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
+     * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will
+     * not be started.
+     * @param periodicData Periodic advertising data. Size must not exceed {@link
+     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
+     * @param callback Callback for advertising set.
+     * @param handler thread upon which the callbacks will be invoked.
+     * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable
+     * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
+     * feature is made when it's not supported by the controller.
+     */
+    public void startAdvertisingSet(AdvertisingSetParameters parameters,
+            AdvertiseData advertiseData, AdvertiseData scanResponse,
+            PeriodicAdvertisingParameters periodicParameters,
+            AdvertiseData periodicData, AdvertisingSetCallback callback,
+            Handler handler) {
+        startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
+                periodicData, 0, 0, callback, handler);
+    }
+
+    /**
+     * Creates a new advertising set. If operation succeed, device will start advertising. This
+     * method returns immediately, the operation status is delivered through
+     * {@code callback.onAdvertisingSetStarted()}.
+     * <p>
+     *
+     * @param parameters advertising set parameters.
+     * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link
+     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable,
+     * three bytes will be added for flags.
+     * @param scanResponse Scan response associated with the advertisement data. Size must not
+     * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
+     * @param periodicParameters periodic advertisng parameters. If null, periodic advertising will
+     * not be started.
+     * @param periodicData Periodic advertising data. Size must not exceed {@link
+     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}.
+     * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535
+     * (655,350 ms). 0 means advertising should continue until stopped.
+     * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the
+     * controller shall attempt to send prior to terminating the extended advertising, even if the
+     * duration has not expired. Valid range is from 1 to 255. 0 means no maximum.
+     * @param callback Callback for advertising set.
+     * @throws IllegalArgumentException when any of the data parameter exceed the maximum allowable
+     * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
+     * feature is made when it's not supported by the controller.
+     */
+    public void startAdvertisingSet(AdvertisingSetParameters parameters,
+            AdvertiseData advertiseData, AdvertiseData scanResponse,
+            PeriodicAdvertisingParameters periodicParameters,
+            AdvertiseData periodicData, int duration,
+            int maxExtendedAdvertisingEvents,
+            AdvertisingSetCallback callback) {
+        startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
+                periodicData, duration, maxExtendedAdvertisingEvents, callback,
+                new Handler(Looper.getMainLooper()));
+    }
+
+    /**
+     * Creates a new advertising set. If operation succeed, device will start advertising. This
+     * method returns immediately, the operation status is delivered through
+     * {@code callback.onAdvertisingSetStarted()}.
+     * <p>
+     *
+     * @param parameters Advertising set parameters.
+     * @param advertiseData Advertisement data to be broadcasted. Size must not exceed {@link
+     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}. If the advertisement is connectable,
+     * three bytes will be added for flags.
+     * @param scanResponse Scan response associated with the advertisement data. Size must not
+     * exceed {@link BluetoothAdapter#getLeMaximumAdvertisingDataLength}
+     * @param periodicParameters Periodic advertisng parameters. If null, periodic advertising will
+     * not be started.
+     * @param periodicData Periodic advertising data. Size must not exceed {@link
+     * BluetoothAdapter#getLeMaximumAdvertisingDataLength}
+     * @param duration advertising duration, in 10ms unit. Valid range is from 1 (10ms) to 65535
+     * (655,350 ms). 0 means advertising should continue until stopped.
+     * @param maxExtendedAdvertisingEvents maximum number of extended advertising events the
+     * controller shall attempt to send prior to terminating the extended advertising, even if the
+     * duration has not expired. Valid range is from 1 to 255. 0 means no maximum.
+     * @param callback Callback for advertising set.
+     * @param handler Thread upon which the callbacks will be invoked.
+     * @throws IllegalArgumentException When any of the data parameter exceed the maximum allowable
+     * size, or unsupported advertising PHY is selected, or when attempt to use Periodic Advertising
+     * feature is made when it's not supported by the controller, or when
+     * maxExtendedAdvertisingEvents is used on a controller that doesn't support the LE Extended
+     * Advertising
+     */
+    public void startAdvertisingSet(AdvertisingSetParameters parameters,
+            AdvertiseData advertiseData, AdvertiseData scanResponse,
+            PeriodicAdvertisingParameters periodicParameters,
+            AdvertiseData periodicData, int duration,
+            int maxExtendedAdvertisingEvents, AdvertisingSetCallback callback,
+            Handler handler) {
+        BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
+        if (callback == null) {
+            throw new IllegalArgumentException("callback cannot be null");
+        }
+
+        boolean isConnectable = parameters.isConnectable();
+        if (parameters.isLegacy()) {
+            if (totalBytes(advertiseData, isConnectable) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
+                throw new IllegalArgumentException("Legacy advertising data too big");
+            }
+
+            if (totalBytes(scanResponse, false) > MAX_LEGACY_ADVERTISING_DATA_BYTES) {
+                throw new IllegalArgumentException("Legacy scan response data too big");
+            }
+        } else {
+            boolean supportCodedPhy = mBluetoothAdapter.isLeCodedPhySupported();
+            boolean support2MPhy = mBluetoothAdapter.isLe2MPhySupported();
+            int pphy = parameters.getPrimaryPhy();
+            int sphy = parameters.getSecondaryPhy();
+            if (pphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy) {
+                throw new IllegalArgumentException("Unsupported primary PHY selected");
+            }
+
+            if ((sphy == BluetoothDevice.PHY_LE_CODED && !supportCodedPhy)
+                    || (sphy == BluetoothDevice.PHY_LE_2M && !support2MPhy)) {
+                throw new IllegalArgumentException("Unsupported secondary PHY selected");
+            }
+
+            int maxData = mBluetoothAdapter.getLeMaximumAdvertisingDataLength();
+            if (totalBytes(advertiseData, isConnectable) > maxData) {
+                throw new IllegalArgumentException("Advertising data too big");
+            }
+
+            if (totalBytes(scanResponse, false) > maxData) {
+                throw new IllegalArgumentException("Scan response data too big");
+            }
+
+            if (totalBytes(periodicData, false) > maxData) {
+                throw new IllegalArgumentException("Periodic advertising data too big");
+            }
+
+            boolean supportPeriodic = mBluetoothAdapter.isLePeriodicAdvertisingSupported();
+            if (periodicParameters != null && !supportPeriodic) {
+                throw new IllegalArgumentException(
+                        "Controller does not support LE Periodic Advertising");
+            }
+        }
+
+        if (maxExtendedAdvertisingEvents < 0 || maxExtendedAdvertisingEvents > 255) {
+            throw new IllegalArgumentException(
+                    "maxExtendedAdvertisingEvents out of range: " + maxExtendedAdvertisingEvents);
+        }
+
+        if (maxExtendedAdvertisingEvents != 0
+                && !mBluetoothAdapter.isLePeriodicAdvertisingSupported()) {
+            throw new IllegalArgumentException(
+                    "Can't use maxExtendedAdvertisingEvents with controller that don't support "
+                            + "LE Extended Advertising");
+        }
+
+        if (duration < 0 || duration > 65535) {
+            throw new IllegalArgumentException("duration out of range: " + duration);
+        }
+
+        IBluetoothGatt gatt;
+        try {
+            gatt = mBluetoothManager.getBluetoothGatt();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get Bluetooth GATT - ", e);
+            postStartSetFailure(handler, callback,
+                    AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
+            return;
+        }
+
+        if (gatt == null) {
+            Log.e(TAG, "Bluetooth GATT is null");
+            postStartSetFailure(handler, callback,
+                    AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
+            return;
+        }
+
+        IAdvertisingSetCallback wrapped = wrap(callback, handler);
+        if (mCallbackWrappers.putIfAbsent(callback, wrapped) != null) {
+            throw new IllegalArgumentException(
+                    "callback instance already associated with advertising");
+        }
+
+        try {
+            gatt.startAdvertisingSet(parameters, advertiseData, scanResponse, periodicParameters,
+                    periodicData, duration, maxExtendedAdvertisingEvents, wrapped);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to start advertising set - ", e);
+            postStartSetFailure(handler, callback,
+                    AdvertiseCallback.ADVERTISE_FAILED_INTERNAL_ERROR);
+            return;
+        }
+    }
+
+    /**
+     * Used to dispose of a {@link AdvertisingSet} object, obtained with {@link
+     * BluetoothLeAdvertiser#startAdvertisingSet}.
+     */
+    public void stopAdvertisingSet(AdvertisingSetCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback cannot be null");
+        }
+
+        IAdvertisingSetCallback wrapped = mCallbackWrappers.remove(callback);
+        if (wrapped == null) {
+            return;
+        }
+
+        IBluetoothGatt gatt;
+        try {
+            gatt = mBluetoothManager.getBluetoothGatt();
+            gatt.stopAdvertisingSet(wrapped);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to stop advertising - ", e);
+        }
+    }
+
+    /**
+     * Cleans up advertisers. Should be called when bluetooth is down.
+     *
+     * @hide
+     */
+    public void cleanup() {
+        mLegacyAdvertisers.clear();
+        mCallbackWrappers.clear();
+        mAdvertisingSets.clear();
+    }
+
+    // Compute the size of advertisement data or scan resp
+    private int totalBytes(AdvertiseData data, boolean isFlagsIncluded) {
+        if (data == null) return 0;
+        // Flags field is omitted if the advertising is not connectable.
+        int size = (isFlagsIncluded) ? FLAGS_FIELD_BYTES : 0;
+        if (data.getServiceUuids() != null) {
+            int num16BitUuids = 0;
+            int num32BitUuids = 0;
+            int num128BitUuids = 0;
+            for (ParcelUuid uuid : data.getServiceUuids()) {
+                if (BluetoothUuid.is16BitUuid(uuid)) {
+                    ++num16BitUuids;
+                } else if (BluetoothUuid.is32BitUuid(uuid)) {
+                    ++num32BitUuids;
+                } else {
+                    ++num128BitUuids;
+                }
+            }
+            // 16 bit service uuids are grouped into one field when doing advertising.
+            if (num16BitUuids != 0) {
+                size += OVERHEAD_BYTES_PER_FIELD + num16BitUuids * BluetoothUuid.UUID_BYTES_16_BIT;
+            }
+            // 32 bit service uuids are grouped into one field when doing advertising.
+            if (num32BitUuids != 0) {
+                size += OVERHEAD_BYTES_PER_FIELD + num32BitUuids * BluetoothUuid.UUID_BYTES_32_BIT;
+            }
+            // 128 bit service uuids are grouped into one field when doing advertising.
+            if (num128BitUuids != 0) {
+                size += OVERHEAD_BYTES_PER_FIELD
+                        + num128BitUuids * BluetoothUuid.UUID_BYTES_128_BIT;
+            }
+        }
+        for (ParcelUuid uuid : data.getServiceData().keySet()) {
+            int uuidLen = BluetoothUuid.uuidToBytes(uuid).length;
+            size += OVERHEAD_BYTES_PER_FIELD + uuidLen
+                    + byteLength(data.getServiceData().get(uuid));
+        }
+        for (int i = 0; i < data.getManufacturerSpecificData().size(); ++i) {
+            size += OVERHEAD_BYTES_PER_FIELD + MANUFACTURER_SPECIFIC_DATA_LENGTH
+                    + byteLength(data.getManufacturerSpecificData().valueAt(i));
+        }
+        if (data.getIncludeTxPowerLevel()) {
+            size += OVERHEAD_BYTES_PER_FIELD + 1; // tx power level value is one byte.
+        }
+        if (data.getIncludeDeviceName() && mBluetoothAdapter.getName() != null) {
+            size += OVERHEAD_BYTES_PER_FIELD + mBluetoothAdapter.getName().length();
+        }
+        return size;
+    }
+
+    private int byteLength(byte[] array) {
+        return array == null ? 0 : array.length;
+    }
+
+    IAdvertisingSetCallback wrap(AdvertisingSetCallback callback, Handler handler) {
+        return new IAdvertisingSetCallback.Stub() {
+            @Override
+            public void onAdvertisingSetStarted(int advertiserId, int txPower, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (status != AdvertisingSetCallback.ADVERTISE_SUCCESS) {
+                            callback.onAdvertisingSetStarted(null, 0, status);
+                            mCallbackWrappers.remove(callback);
+                            return;
+                        }
+
+                        AdvertisingSet advertisingSet =
+                                new AdvertisingSet(advertiserId, mBluetoothManager);
+                        mAdvertisingSets.put(advertiserId, advertisingSet);
+                        callback.onAdvertisingSetStarted(advertisingSet, txPower, status);
+                    }
+                });
+            }
+
+            @Override
+            public void onOwnAddressRead(int advertiserId, int addressType, String address) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onOwnAddressRead(advertisingSet, addressType, address);
+                    }
+                });
+            }
+
+            @Override
+            public void onAdvertisingSetStopped(int advertiserId) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onAdvertisingSetStopped(advertisingSet);
+                        mAdvertisingSets.remove(advertiserId);
+                        mCallbackWrappers.remove(callback);
+                    }
+                });
+            }
+
+            @Override
+            public void onAdvertisingEnabled(int advertiserId, boolean enabled, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onAdvertisingEnabled(advertisingSet, enabled, status);
+                    }
+                });
+            }
+
+            @Override
+            public void onAdvertisingDataSet(int advertiserId, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onAdvertisingDataSet(advertisingSet, status);
+                    }
+                });
+            }
+
+            @Override
+            public void onScanResponseDataSet(int advertiserId, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onScanResponseDataSet(advertisingSet, status);
+                    }
+                });
+            }
+
+            @Override
+            public void onAdvertisingParametersUpdated(int advertiserId, int txPower, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onAdvertisingParametersUpdated(advertisingSet, txPower, status);
+                    }
+                });
+            }
+
+            @Override
+            public void onPeriodicAdvertisingParametersUpdated(int advertiserId, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onPeriodicAdvertisingParametersUpdated(advertisingSet, status);
+                    }
+                });
+            }
+
+            @Override
+            public void onPeriodicAdvertisingDataSet(int advertiserId, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onPeriodicAdvertisingDataSet(advertisingSet, status);
+                    }
+                });
+            }
+
+            @Override
+            public void onPeriodicAdvertisingEnabled(int advertiserId, boolean enable, int status) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        AdvertisingSet advertisingSet = mAdvertisingSets.get(advertiserId);
+                        callback.onPeriodicAdvertisingEnabled(advertisingSet, enable, status);
+                    }
+                });
+            }
+        };
+    }
+
+    private void postStartSetFailure(Handler handler, final AdvertisingSetCallback callback,
+            final int error) {
+        handler.post(new Runnable() {
+            @Override
+            public void run() {
+                callback.onAdvertisingSetStarted(null, 0, error);
+            }
+        });
+    }
+
+    private void postStartFailure(final AdvertiseCallback callback, final int error) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                callback.onStartFailure(error);
+            }
+        });
+    }
+
+    private void postStartSuccess(final AdvertiseCallback callback,
+            final AdvertiseSettings settings) {
+        mHandler.post(new Runnable() {
+
+            @Override
+            public void run() {
+                callback.onStartSuccess(settings);
+            }
+        });
+    }
+}
diff --git a/android/bluetooth/le/BluetoothLeScanner.java b/android/bluetooth/le/BluetoothLeScanner.java
new file mode 100644
index 0000000..9a17346
--- /dev/null
+++ b/android/bluetooth/le/BluetoothLeScanner.java
@@ -0,0 +1,608 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import android.Manifest;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.app.PendingIntent;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothGatt;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.os.WorkSource;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * This class provides methods to perform scan related operations for Bluetooth LE devices. An
+ * application can scan for a particular type of Bluetooth LE devices using {@link ScanFilter}. It
+ * can also request different types of callbacks for delivering the result.
+ * <p>
+ * Use {@link BluetoothAdapter#getBluetoothLeScanner()} to get an instance of
+ * {@link BluetoothLeScanner}.
+ * <p>
+ * <b>Note:</b> Most of the scan methods here require
+ * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @see ScanFilter
+ */
+public final class BluetoothLeScanner {
+
+    private static final String TAG = "BluetoothLeScanner";
+    private static final boolean DBG = true;
+    private static final boolean VDBG = false;
+
+    /**
+     * Extra containing a list of ScanResults. It can have one or more results if there was no
+     * error. In case of error, {@link #EXTRA_ERROR_CODE} will contain the error code and this
+     * extra will not be available.
+     */
+    public static final String EXTRA_LIST_SCAN_RESULT =
+            "android.bluetooth.le.extra.LIST_SCAN_RESULT";
+
+    /**
+     * Optional extra indicating the error code, if any. The error code will be one of the
+     * SCAN_FAILED_* codes in {@link ScanCallback}.
+     */
+    public static final String EXTRA_ERROR_CODE = "android.bluetooth.le.extra.ERROR_CODE";
+
+    /**
+     * Optional extra indicating the callback type, which will be one of
+     * CALLBACK_TYPE_* constants in {@link ScanSettings}.
+     *
+     * @see ScanCallback#onScanResult(int, ScanResult)
+     */
+    public static final String EXTRA_CALLBACK_TYPE = "android.bluetooth.le.extra.CALLBACK_TYPE";
+
+    private final IBluetoothManager mBluetoothManager;
+    private final Handler mHandler;
+    private BluetoothAdapter mBluetoothAdapter;
+    private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients;
+
+    private final String mOpPackageName;
+    private final String mFeatureId;
+
+    /**
+     * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
+     *
+     * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management.
+     * @param opPackageName The opPackageName of the context this object was created from
+     * @param featureId  The featureId of the context this object was created from
+     * @hide
+     */
+    public BluetoothLeScanner(IBluetoothManager bluetoothManager,
+            @NonNull String opPackageName, @Nullable String featureId) {
+        mBluetoothManager = bluetoothManager;
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        mHandler = new Handler(Looper.getMainLooper());
+        mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>();
+        mOpPackageName = opPackageName;
+        mFeatureId = featureId;
+    }
+
+    /**
+     * Start Bluetooth LE scan with default parameters and no filters. The scan results will be
+     * delivered through {@code callback}. For unfiltered scans, scanning is stopped on screen
+     * off to save power. Scanning is resumed when screen is turned on again. To avoid this, use
+     * {@link #startScan(List, ScanSettings, ScanCallback)} with desired {@link ScanFilter}.
+     * <p>
+     * An app must hold
+     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
+     * in order to get results.
+     *
+     * @param callback Callback used to deliver scan results.
+     * @throws IllegalArgumentException If {@code callback} is null.
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public void startScan(final ScanCallback callback) {
+        startScan(null, new ScanSettings.Builder().build(), callback);
+    }
+
+    /**
+     * Start Bluetooth LE scan. The scan results will be delivered through {@code callback}.
+     * For unfiltered scans, scanning is stopped on screen off to save power. Scanning is
+     * resumed when screen is turned on again. To avoid this, do filetered scanning by
+     * using proper {@link ScanFilter}.
+     * <p>
+     * An app must hold
+     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
+     * in order to get results.
+     *
+     * @param filters {@link ScanFilter}s for finding exact BLE devices.
+     * @param settings Settings for the scan.
+     * @param callback Callback used to deliver scan results.
+     * @throws IllegalArgumentException If {@code settings} or {@code callback} is null.
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public void startScan(List<ScanFilter> filters, ScanSettings settings,
+            final ScanCallback callback) {
+        startScan(filters, settings, null, callback, /*callbackIntent=*/ null, null);
+    }
+
+    /**
+     * Start Bluetooth LE scan using a {@link PendingIntent}. The scan results will be delivered via
+     * the PendingIntent. Use this method of scanning if your process is not always running and it
+     * should be started when scan results are available.
+     * <p>
+     * An app must hold
+     * {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} or
+     * {@link android.Manifest.permission#ACCESS_FINE_LOCATION ACCESS_FINE_LOCATION} permission
+     * in order to get results.
+     * <p>
+     * When the PendingIntent is delivered, the Intent passed to the receiver or activity
+     * will contain one or more of the extras {@link #EXTRA_CALLBACK_TYPE},
+     * {@link #EXTRA_ERROR_CODE} and {@link #EXTRA_LIST_SCAN_RESULT} to indicate the result of
+     * the scan.
+     *
+     * @param filters Optional list of ScanFilters for finding exact BLE devices.
+     * @param settings Optional settings for the scan.
+     * @param callbackIntent The PendingIntent to deliver the result to.
+     * @return Returns 0 for success or an error code from {@link ScanCallback} if the scan request
+     * could not be sent.
+     * @see #stopScan(PendingIntent)
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public int startScan(@Nullable List<ScanFilter> filters, @Nullable ScanSettings settings,
+            @NonNull PendingIntent callbackIntent) {
+        return startScan(filters,
+                settings != null ? settings : new ScanSettings.Builder().build(),
+                null, null, callbackIntent, null);
+    }
+
+    /**
+     * Start Bluetooth LE scan. Same as {@link #startScan(ScanCallback)} but allows the caller to
+     * specify on behalf of which application(s) the work is being done.
+     *
+     * @param workSource {@link WorkSource} identifying the application(s) for which to blame for
+     * the scan.
+     * @param callback Callback used to deliver scan results.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS})
+    public void startScanFromSource(final WorkSource workSource, final ScanCallback callback) {
+        startScanFromSource(null, new ScanSettings.Builder().build(), workSource, callback);
+    }
+
+    /**
+     * Start Bluetooth LE scan. Same as {@link #startScan(List, ScanSettings, ScanCallback)} but
+     * allows the caller to specify on behalf of which application(s) the work is being done.
+     *
+     * @param filters {@link ScanFilter}s for finding exact BLE devices.
+     * @param settings Settings for the scan.
+     * @param workSource {@link WorkSource} identifying the application(s) for which to blame for
+     * the scan.
+     * @param callback Callback used to deliver scan results.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            Manifest.permission.BLUETOOTH_ADMIN, Manifest.permission.UPDATE_DEVICE_STATS})
+    public void startScanFromSource(List<ScanFilter> filters, ScanSettings settings,
+            final WorkSource workSource, final ScanCallback callback) {
+        startScan(filters, settings, workSource, callback, null, null);
+    }
+
+    private int startScan(List<ScanFilter> filters, ScanSettings settings,
+            final WorkSource workSource, final ScanCallback callback,
+            final PendingIntent callbackIntent,
+            List<List<ResultStorageDescriptor>> resultStorages) {
+        BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
+        if (callback == null && callbackIntent == null) {
+            throw new IllegalArgumentException("callback is null");
+        }
+        if (settings == null) {
+            throw new IllegalArgumentException("settings is null");
+        }
+        synchronized (mLeScanClients) {
+            if (callback != null && mLeScanClients.containsKey(callback)) {
+                return postCallbackErrorOrReturn(callback,
+                            ScanCallback.SCAN_FAILED_ALREADY_STARTED);
+            }
+            IBluetoothGatt gatt;
+            try {
+                gatt = mBluetoothManager.getBluetoothGatt();
+            } catch (RemoteException e) {
+                gatt = null;
+            }
+            if (gatt == null) {
+                return postCallbackErrorOrReturn(callback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
+            }
+            if (!isSettingsConfigAllowedForScan(settings)) {
+                return postCallbackErrorOrReturn(callback,
+                        ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
+            }
+            if (!isHardwareResourcesAvailableForScan(settings)) {
+                return postCallbackErrorOrReturn(callback,
+                        ScanCallback.SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES);
+            }
+            if (!isSettingsAndFilterComboAllowed(settings, filters)) {
+                return postCallbackErrorOrReturn(callback,
+                        ScanCallback.SCAN_FAILED_FEATURE_UNSUPPORTED);
+            }
+            if (callback != null) {
+                BleScanCallbackWrapper wrapper = new BleScanCallbackWrapper(gatt, filters,
+                        settings, workSource, callback, resultStorages);
+                wrapper.startRegistration();
+            } else {
+                try {
+                    gatt.startScanForIntent(callbackIntent, settings, filters, mOpPackageName,
+                            mFeatureId);
+                } catch (RemoteException e) {
+                    return ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
+                }
+            }
+        }
+        return ScanCallback.NO_ERROR;
+    }
+
+    /**
+     * Stops an ongoing Bluetooth LE scan.
+     *
+     * @param callback
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public void stopScan(ScanCallback callback) {
+        BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
+        synchronized (mLeScanClients) {
+            BleScanCallbackWrapper wrapper = mLeScanClients.remove(callback);
+            if (wrapper == null) {
+                if (DBG) Log.d(TAG, "could not find callback wrapper");
+                return;
+            }
+            wrapper.stopLeScan();
+        }
+    }
+
+    /**
+     * Stops an ongoing Bluetooth LE scan started using a PendingIntent. When creating the
+     * PendingIntent parameter, please do not use the FLAG_CANCEL_CURRENT flag. Otherwise, the stop
+     * scan may have no effect.
+     *
+     * @param callbackIntent The PendingIntent that was used to start the scan.
+     * @see #startScan(List, ScanSettings, PendingIntent)
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH_ADMIN)
+    public void stopScan(PendingIntent callbackIntent) {
+        BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
+        IBluetoothGatt gatt;
+        try {
+            gatt = mBluetoothManager.getBluetoothGatt();
+            gatt.stopScanForIntent(callbackIntent, mOpPackageName);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Flush pending batch scan results stored in Bluetooth controller. This will return Bluetooth
+     * LE scan results batched on bluetooth controller. Returns immediately, batch scan results data
+     * will be delivered through the {@code callback}.
+     *
+     * @param callback Callback of the Bluetooth LE Scan, it has to be the same instance as the one
+     * used to start scan.
+     */
+    public void flushPendingScanResults(ScanCallback callback) {
+        BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
+        if (callback == null) {
+            throw new IllegalArgumentException("callback cannot be null!");
+        }
+        synchronized (mLeScanClients) {
+            BleScanCallbackWrapper wrapper = mLeScanClients.get(callback);
+            if (wrapper == null) {
+                return;
+            }
+            wrapper.flushPendingBatchResults();
+        }
+    }
+
+    /**
+     * Start truncated scan.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings,
+            final ScanCallback callback) {
+        int filterSize = truncatedFilters.size();
+        List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize);
+        List<List<ResultStorageDescriptor>> scanStorages =
+                new ArrayList<List<ResultStorageDescriptor>>(filterSize);
+        for (TruncatedFilter filter : truncatedFilters) {
+            scanFilters.add(filter.getFilter());
+            scanStorages.add(filter.getStorageDescriptors());
+        }
+        startScan(scanFilters, settings, null, callback, null, scanStorages);
+    }
+
+    /**
+     * Cleans up scan clients. Should be called when bluetooth is down.
+     *
+     * @hide
+     */
+    public void cleanup() {
+        mLeScanClients.clear();
+    }
+
+    /**
+     * Bluetooth GATT interface callbacks
+     */
+    private class BleScanCallbackWrapper extends IScannerCallback.Stub {
+        private static final int REGISTRATION_CALLBACK_TIMEOUT_MILLIS = 2000;
+
+        private final ScanCallback mScanCallback;
+        private final List<ScanFilter> mFilters;
+        private final WorkSource mWorkSource;
+        private ScanSettings mSettings;
+        private IBluetoothGatt mBluetoothGatt;
+        private List<List<ResultStorageDescriptor>> mResultStorages;
+
+        // mLeHandle 0: not registered
+        // -2: registration failed because app is scanning to frequently
+        // -1: scan stopped or registration failed
+        // > 0: registered and scan started
+        private int mScannerId;
+
+        public BleScanCallbackWrapper(IBluetoothGatt bluetoothGatt,
+                List<ScanFilter> filters, ScanSettings settings,
+                WorkSource workSource, ScanCallback scanCallback,
+                List<List<ResultStorageDescriptor>> resultStorages) {
+            mBluetoothGatt = bluetoothGatt;
+            mFilters = filters;
+            mSettings = settings;
+            mWorkSource = workSource;
+            mScanCallback = scanCallback;
+            mScannerId = 0;
+            mResultStorages = resultStorages;
+        }
+
+        public void startRegistration() {
+            synchronized (this) {
+                // Scan stopped.
+                if (mScannerId == -1 || mScannerId == -2) return;
+                try {
+                    mBluetoothGatt.registerScanner(this, mWorkSource);
+                    wait(REGISTRATION_CALLBACK_TIMEOUT_MILLIS);
+                } catch (InterruptedException | RemoteException e) {
+                    Log.e(TAG, "application registeration exception", e);
+                    postCallbackError(mScanCallback, ScanCallback.SCAN_FAILED_INTERNAL_ERROR);
+                }
+                if (mScannerId > 0) {
+                    mLeScanClients.put(mScanCallback, this);
+                } else {
+                    // Registration timed out or got exception, reset RscannerId to -1 so no
+                    // subsequent operations can proceed.
+                    if (mScannerId == 0) mScannerId = -1;
+
+                    // If scanning too frequently, don't report anything to the app.
+                    if (mScannerId == -2) return;
+
+                    postCallbackError(mScanCallback,
+                            ScanCallback.SCAN_FAILED_APPLICATION_REGISTRATION_FAILED);
+                }
+            }
+        }
+
+        public void stopLeScan() {
+            synchronized (this) {
+                if (mScannerId <= 0) {
+                    Log.e(TAG, "Error state, mLeHandle: " + mScannerId);
+                    return;
+                }
+                try {
+                    mBluetoothGatt.stopScan(mScannerId);
+                    mBluetoothGatt.unregisterScanner(mScannerId);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to stop scan and unregister", e);
+                }
+                mScannerId = -1;
+            }
+        }
+
+        void flushPendingBatchResults() {
+            synchronized (this) {
+                if (mScannerId <= 0) {
+                    Log.e(TAG, "Error state, mLeHandle: " + mScannerId);
+                    return;
+                }
+                try {
+                    mBluetoothGatt.flushPendingBatchResults(mScannerId);
+                } catch (RemoteException e) {
+                    Log.e(TAG, "Failed to get pending scan results", e);
+                }
+            }
+        }
+
+        /**
+         * Application interface registered - app is ready to go
+         */
+        @Override
+        public void onScannerRegistered(int status, int scannerId) {
+            Log.d(TAG, "onScannerRegistered() - status=" + status
+                    + " scannerId=" + scannerId + " mScannerId=" + mScannerId);
+            synchronized (this) {
+                if (status == BluetoothGatt.GATT_SUCCESS) {
+                    try {
+                        if (mScannerId == -1) {
+                            // Registration succeeds after timeout, unregister scanner.
+                            mBluetoothGatt.unregisterScanner(scannerId);
+                        } else {
+                            mScannerId = scannerId;
+                            mBluetoothGatt.startScan(mScannerId, mSettings, mFilters,
+                                    mResultStorages, mOpPackageName, mFeatureId);
+                        }
+                    } catch (RemoteException e) {
+                        Log.e(TAG, "fail to start le scan: " + e);
+                        mScannerId = -1;
+                    }
+                } else if (status == ScanCallback.SCAN_FAILED_SCANNING_TOO_FREQUENTLY) {
+                    // applicaiton was scanning too frequently
+                    mScannerId = -2;
+                } else {
+                    // registration failed
+                    mScannerId = -1;
+                }
+                notifyAll();
+            }
+        }
+
+        /**
+         * Callback reporting an LE scan result.
+         *
+         * @hide
+         */
+        @Override
+        public void onScanResult(final ScanResult scanResult) {
+            if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString());
+
+            // Check null in case the scan has been stopped
+            synchronized (this) {
+                if (mScannerId <= 0) return;
+            }
+            Handler handler = new Handler(Looper.getMainLooper());
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult);
+                }
+            });
+        }
+
+        @Override
+        public void onBatchScanResults(final List<ScanResult> results) {
+            Handler handler = new Handler(Looper.getMainLooper());
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mScanCallback.onBatchScanResults(results);
+                }
+            });
+        }
+
+        @Override
+        public void onFoundOrLost(final boolean onFound, final ScanResult scanResult) {
+            if (VDBG) {
+                Log.d(TAG, "onFoundOrLost() - onFound = " + onFound + " " + scanResult.toString());
+            }
+
+            // Check null in case the scan has been stopped
+            synchronized (this) {
+                if (mScannerId <= 0) {
+                    return;
+                }
+            }
+            Handler handler = new Handler(Looper.getMainLooper());
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    if (onFound) {
+                        mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_FIRST_MATCH,
+                                scanResult);
+                    } else {
+                        mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_MATCH_LOST,
+                                scanResult);
+                    }
+                }
+            });
+        }
+
+        @Override
+        public void onScanManagerErrorCallback(final int errorCode) {
+            if (VDBG) {
+                Log.d(TAG, "onScanManagerErrorCallback() - errorCode = " + errorCode);
+            }
+            synchronized (this) {
+                if (mScannerId <= 0) {
+                    return;
+                }
+            }
+            postCallbackError(mScanCallback, errorCode);
+        }
+    }
+
+    private int postCallbackErrorOrReturn(final ScanCallback callback, final int errorCode) {
+        if (callback == null) {
+            return errorCode;
+        } else {
+            postCallbackError(callback, errorCode);
+            return ScanCallback.NO_ERROR;
+        }
+    }
+
+    private void postCallbackError(final ScanCallback callback, final int errorCode) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                callback.onScanFailed(errorCode);
+            }
+        });
+    }
+
+    private boolean isSettingsConfigAllowedForScan(ScanSettings settings) {
+        if (mBluetoothAdapter.isOffloadedFilteringSupported()) {
+            return true;
+        }
+        final int callbackType = settings.getCallbackType();
+        // Only support regular scan if no offloaded filter support.
+        if (callbackType == ScanSettings.CALLBACK_TYPE_ALL_MATCHES
+                && settings.getReportDelayMillis() == 0) {
+            return true;
+        }
+        return false;
+    }
+
+    private boolean isSettingsAndFilterComboAllowed(ScanSettings settings,
+            List<ScanFilter> filterList) {
+        final int callbackType = settings.getCallbackType();
+        // If onlost/onfound is requested, a non-empty filter is expected
+        if ((callbackType & (ScanSettings.CALLBACK_TYPE_FIRST_MATCH
+                | ScanSettings.CALLBACK_TYPE_MATCH_LOST)) != 0) {
+            if (filterList == null) {
+                return false;
+            }
+            for (ScanFilter filter : filterList) {
+                if (filter.isAllFieldsEmpty()) {
+                    return false;
+                }
+            }
+        }
+        return true;
+    }
+
+    private boolean isHardwareResourcesAvailableForScan(ScanSettings settings) {
+        final int callbackType = settings.getCallbackType();
+        if ((callbackType & ScanSettings.CALLBACK_TYPE_FIRST_MATCH) != 0
+                || (callbackType & ScanSettings.CALLBACK_TYPE_MATCH_LOST) != 0) {
+            // For onlost/onfound, we required hw support be available
+            return (mBluetoothAdapter.isOffloadedFilteringSupported()
+                    && mBluetoothAdapter.isHardwareTrackingFiltersAvailable());
+        }
+        return true;
+    }
+}
diff --git a/android/bluetooth/le/BluetoothLeUtils.java b/android/bluetooth/le/BluetoothLeUtils.java
new file mode 100644
index 0000000..6381f55
--- /dev/null
+++ b/android/bluetooth/le/BluetoothLeUtils.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import android.bluetooth.BluetoothAdapter;
+import android.util.SparseArray;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Helper class for Bluetooth LE utils.
+ *
+ * @hide
+ */
+public class BluetoothLeUtils {
+
+    /**
+     * Returns a string composed from a {@link SparseArray}.
+     */
+    static String toString(SparseArray<byte[]> array) {
+        if (array == null) {
+            return "null";
+        }
+        if (array.size() == 0) {
+            return "{}";
+        }
+        StringBuilder buffer = new StringBuilder();
+        buffer.append('{');
+        for (int i = 0; i < array.size(); ++i) {
+            buffer.append(array.keyAt(i)).append("=").append(Arrays.toString(array.valueAt(i)));
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+
+    /**
+     * Returns a string composed from a {@link Map}.
+     */
+    static <T> String toString(Map<T, byte[]> map) {
+        if (map == null) {
+            return "null";
+        }
+        if (map.isEmpty()) {
+            return "{}";
+        }
+        StringBuilder buffer = new StringBuilder();
+        buffer.append('{');
+        Iterator<Map.Entry<T, byte[]>> it = map.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<T, byte[]> entry = it.next();
+            Object key = entry.getKey();
+            buffer.append(key).append("=").append(Arrays.toString(map.get(key)));
+            if (it.hasNext()) {
+                buffer.append(", ");
+            }
+        }
+        buffer.append('}');
+        return buffer.toString();
+    }
+
+    /**
+     * Check whether two {@link SparseArray} equal.
+     */
+    static boolean equals(SparseArray<byte[]> array, SparseArray<byte[]> otherArray) {
+        if (array == otherArray) {
+            return true;
+        }
+        if (array == null || otherArray == null) {
+            return false;
+        }
+        if (array.size() != otherArray.size()) {
+            return false;
+        }
+
+        // Keys are guaranteed in ascending order when indices are in ascending order.
+        for (int i = 0; i < array.size(); ++i) {
+            if (array.keyAt(i) != otherArray.keyAt(i)
+                    || !Arrays.equals(array.valueAt(i), otherArray.valueAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Check whether two {@link Map} equal.
+     */
+    static <T> boolean equals(Map<T, byte[]> map, Map<T, byte[]> otherMap) {
+        if (map == otherMap) {
+            return true;
+        }
+        if (map == null || otherMap == null) {
+            return false;
+        }
+        if (map.size() != otherMap.size()) {
+            return false;
+        }
+        Set<T> keys = map.keySet();
+        if (!keys.equals(otherMap.keySet())) {
+            return false;
+        }
+        for (T key : keys) {
+            if (!Objects.deepEquals(map.get(key), otherMap.get(key))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Ensure Bluetooth is turned on.
+     *
+     * @throws IllegalStateException If {@code adapter} is null or Bluetooth state is not {@link
+     * BluetoothAdapter#STATE_ON}.
+     */
+    static void checkAdapterStateOn(BluetoothAdapter adapter) {
+        if (adapter == null || !adapter.isLeEnabled()) {
+            throw new IllegalStateException("BT Adapter is not turned ON");
+        }
+    }
+
+}
diff --git a/android/bluetooth/le/PeriodicAdvertisingCallback.java b/android/bluetooth/le/PeriodicAdvertisingCallback.java
new file mode 100644
index 0000000..14ac911
--- /dev/null
+++ b/android/bluetooth/le/PeriodicAdvertisingCallback.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2017 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.le;
+
+import android.bluetooth.BluetoothDevice;
+
+/**
+ * Bluetooth LE periodic advertising callbacks, used to deliver periodic
+ * advertising operation status.
+ *
+ * @hide
+ * @see PeriodicAdvertisingManager#createSync
+ */
+public abstract class PeriodicAdvertisingCallback {
+
+    /**
+     * The requested operation was successful.
+     *
+     * @hide
+     */
+    public static final int SYNC_SUCCESS = 0;
+
+    /**
+     * Sync failed to be established because remote device did not respond.
+     */
+    public static final int SYNC_NO_RESPONSE = 1;
+
+    /**
+     * Sync failed to be established because controller can't support more syncs.
+     */
+    public static final int SYNC_NO_RESOURCES = 2;
+
+
+    /**
+     * Callback when synchronization was established.
+     *
+     * @param syncHandle handle used to identify this synchronization.
+     * @param device remote device.
+     * @param advertisingSid synchronized advertising set id.
+     * @param skip The number of periodic advertising packets that can be skipped after a successful
+     * receive in force. @see PeriodicAdvertisingManager#createSync
+     * @param timeout Synchronization timeout for the periodic advertising in force. One unit is
+     * 10ms. @see PeriodicAdvertisingManager#createSync
+     * @param timeout
+     * @param status operation status.
+     */
+    public void onSyncEstablished(int syncHandle, BluetoothDevice device,
+            int advertisingSid, int skip, int timeout,
+            int status) {
+    }
+
+    /**
+     * Callback when periodic advertising report is received.
+     *
+     * @param report periodic advertising report.
+     */
+    public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) {
+    }
+
+    /**
+     * Callback when periodic advertising synchronization was lost.
+     *
+     * @param syncHandle handle used to identify this synchronization.
+     */
+    public void onSyncLost(int syncHandle) {
+    }
+}
diff --git a/android/bluetooth/le/PeriodicAdvertisingManager.java b/android/bluetooth/le/PeriodicAdvertisingManager.java
new file mode 100644
index 0000000..0f1a8e9
--- /dev/null
+++ b/android/bluetooth/le/PeriodicAdvertisingManager.java
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2017 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.le;
+
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.IBluetoothGatt;
+import android.bluetooth.IBluetoothManager;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.RemoteException;
+import android.util.Log;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * This class provides methods to perform periodic advertising related
+ * operations. An application can register for periodic advertisements using
+ * {@link PeriodicAdvertisingManager#registerSync}.
+ * <p>
+ * Use {@link BluetoothAdapter#getPeriodicAdvertisingManager()} to get an
+ * instance of {@link PeriodicAdvertisingManager}.
+ * <p>
+ * <b>Note:</b> Most of the methods here require
+ * {@link android.Manifest.permission#BLUETOOTH_ADMIN} permission.
+ *
+ * @hide
+ */
+public final class PeriodicAdvertisingManager {
+
+    private static final String TAG = "PeriodicAdvertisingManager";
+
+    private static final int SKIP_MIN = 0;
+    private static final int SKIP_MAX = 499;
+    private static final int TIMEOUT_MIN = 10;
+    private static final int TIMEOUT_MAX = 16384;
+
+    private static final int SYNC_STARTING = -1;
+
+    private final IBluetoothManager mBluetoothManager;
+    private BluetoothAdapter mBluetoothAdapter;
+
+    /* maps callback, to callback wrapper and sync handle */
+    Map<PeriodicAdvertisingCallback,
+            IPeriodicAdvertisingCallback /* callbackWrapper */> mCallbackWrappers;
+
+    /**
+     * Use {@link BluetoothAdapter#getBluetoothLeScanner()} instead.
+     *
+     * @param bluetoothManager BluetoothManager that conducts overall Bluetooth Management.
+     * @hide
+     */
+    public PeriodicAdvertisingManager(IBluetoothManager bluetoothManager) {
+        mBluetoothManager = bluetoothManager;
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        mCallbackWrappers = new IdentityHashMap<>();
+    }
+
+    /**
+     * Synchronize with periodic advertising pointed to by the {@code scanResult}.
+     * The {@code scanResult} used must contain a valid advertisingSid. First
+     * call to registerSync will use the {@code skip} and {@code timeout} provided.
+     * Subsequent calls from other apps, trying to sync with same set will reuse
+     * existing sync, thus {@code skip} and {@code timeout} values will not take
+     * effect. The values in effect will be returned in
+     * {@link PeriodicAdvertisingCallback#onSyncEstablished}.
+     *
+     * @param scanResult Scan result containing advertisingSid.
+     * @param skip The number of periodic advertising packets that can be skipped after a successful
+     * receive. Must be between 0 and 499.
+     * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must
+     * be between 10 (100ms) and 16384 (163.84s).
+     * @param callback Callback used to deliver all operations status.
+     * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
+     * {@code timeout} is invalid or {@code callback} is null.
+     */
+    public void registerSync(ScanResult scanResult, int skip, int timeout,
+            PeriodicAdvertisingCallback callback) {
+        registerSync(scanResult, skip, timeout, callback, null);
+    }
+
+    /**
+     * Synchronize with periodic advertising pointed to by the {@code scanResult}.
+     * The {@code scanResult} used must contain a valid advertisingSid. First
+     * call to registerSync will use the {@code skip} and {@code timeout} provided.
+     * Subsequent calls from other apps, trying to sync with same set will reuse
+     * existing sync, thus {@code skip} and {@code timeout} values will not take
+     * effect. The values in effect will be returned in
+     * {@link PeriodicAdvertisingCallback#onSyncEstablished}.
+     *
+     * @param scanResult Scan result containing advertisingSid.
+     * @param skip The number of periodic advertising packets that can be skipped after a successful
+     * receive. Must be between 0 and 499.
+     * @param timeout Synchronization timeout for the periodic advertising. One unit is 10ms. Must
+     * be between 10 (100ms) and 16384 (163.84s).
+     * @param callback Callback used to deliver all operations status.
+     * @param handler thread upon which the callbacks will be invoked.
+     * @throws IllegalArgumentException if {@code scanResult} is null or {@code skip} is invalid or
+     * {@code timeout} is invalid or {@code callback} is null.
+     */
+    public void registerSync(ScanResult scanResult, int skip, int timeout,
+            PeriodicAdvertisingCallback callback, Handler handler) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback can't be null");
+        }
+
+        if (scanResult == null) {
+            throw new IllegalArgumentException("scanResult can't be null");
+        }
+
+        if (scanResult.getAdvertisingSid() == ScanResult.SID_NOT_PRESENT) {
+            throw new IllegalArgumentException("scanResult must contain a valid sid");
+        }
+
+        if (skip < SKIP_MIN || skip > SKIP_MAX) {
+            throw new IllegalArgumentException(
+                    "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
+        }
+
+        if (timeout < TIMEOUT_MIN || timeout > TIMEOUT_MAX) {
+            throw new IllegalArgumentException(
+                    "timeout must be between " + TIMEOUT_MIN + " and " + TIMEOUT_MAX);
+        }
+
+        IBluetoothGatt gatt;
+        try {
+            gatt = mBluetoothManager.getBluetoothGatt();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
+            callback.onSyncEstablished(0, scanResult.getDevice(), scanResult.getAdvertisingSid(),
+                    skip, timeout,
+                    PeriodicAdvertisingCallback.SYNC_NO_RESOURCES);
+            return;
+        }
+
+        if (handler == null) {
+            handler = new Handler(Looper.getMainLooper());
+        }
+
+        IPeriodicAdvertisingCallback wrapped = wrap(callback, handler);
+        mCallbackWrappers.put(callback, wrapped);
+
+        try {
+            gatt.registerSync(scanResult, skip, timeout, wrapped);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register sync - ", e);
+            return;
+        }
+    }
+
+    /**
+     * Cancel pending attempt to create sync, or terminate existing sync.
+     *
+     * @param callback Callback used to deliver all operations status.
+     * @throws IllegalArgumentException if {@code callback} is null, or not a properly registered
+     * callback.
+     */
+    public void unregisterSync(PeriodicAdvertisingCallback callback) {
+        if (callback == null) {
+            throw new IllegalArgumentException("callback can't be null");
+        }
+
+        IBluetoothGatt gatt;
+        try {
+            gatt = mBluetoothManager.getBluetoothGatt();
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to get Bluetooth gatt - ", e);
+            return;
+        }
+
+        IPeriodicAdvertisingCallback wrapper = mCallbackWrappers.remove(callback);
+        if (wrapper == null) {
+            throw new IllegalArgumentException("callback was not properly registered");
+        }
+
+        try {
+            gatt.unregisterSync(wrapper);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to cancel sync creation - ", e);
+            return;
+        }
+    }
+
+    private IPeriodicAdvertisingCallback wrap(PeriodicAdvertisingCallback callback,
+            Handler handler) {
+        return new IPeriodicAdvertisingCallback.Stub() {
+            public void onSyncEstablished(int syncHandle, BluetoothDevice device,
+                    int advertisingSid, int skip, int timeout, int status) {
+
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onSyncEstablished(syncHandle, device, advertisingSid, skip,
+                                timeout,
+                                status);
+
+                        if (status != PeriodicAdvertisingCallback.SYNC_SUCCESS) {
+                            // App can still unregister the sync until notified it failed. Remove
+                            // callback
+                            // after app was notifed.
+                            mCallbackWrappers.remove(callback);
+                        }
+                    }
+                });
+            }
+
+            public void onPeriodicAdvertisingReport(PeriodicAdvertisingReport report) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onPeriodicAdvertisingReport(report);
+                    }
+                });
+            }
+
+            public void onSyncLost(int syncHandle) {
+                handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onSyncLost(syncHandle);
+                        // App can still unregister the sync until notified it's lost.
+                        // Remove callback after app was notifed.
+                        mCallbackWrappers.remove(callback);
+                    }
+                });
+            }
+        };
+    }
+}
diff --git a/android/bluetooth/le/PeriodicAdvertisingParameters.java b/android/bluetooth/le/PeriodicAdvertisingParameters.java
new file mode 100644
index 0000000..e3a130c
--- /dev/null
+++ b/android/bluetooth/le/PeriodicAdvertisingParameters.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2017 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.le;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * The {@link PeriodicAdvertisingParameters} provide a way to adjust periodic
+ * advertising preferences for each Bluetooth LE advertising set. Use {@link
+ * AdvertisingSetParameters.Builder} to create an instance of this class.
+ */
+public final class PeriodicAdvertisingParameters implements Parcelable {
+
+    private static final int INTERVAL_MIN = 80;
+    private static final int INTERVAL_MAX = 65519;
+
+    private final boolean mIncludeTxPower;
+    private final int mInterval;
+
+    private PeriodicAdvertisingParameters(boolean includeTxPower, int interval) {
+        mIncludeTxPower = includeTxPower;
+        mInterval = interval;
+    }
+
+    private PeriodicAdvertisingParameters(Parcel in) {
+        mIncludeTxPower = in.readInt() != 0;
+        mInterval = in.readInt();
+    }
+
+    /**
+     * Returns whether the TX Power will be included.
+     */
+    public boolean getIncludeTxPower() {
+        return mIncludeTxPower;
+    }
+
+    /**
+     * Returns the periodic advertising interval, in 1.25ms unit.
+     * Valid values are from 80 (100ms) to 65519 (81.89875s).
+     */
+    public int getInterval() {
+        return mInterval;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mIncludeTxPower ? 1 : 0);
+        dest.writeInt(mInterval);
+    }
+
+    public static final Parcelable
+            .Creator<PeriodicAdvertisingParameters> CREATOR =
+            new Creator<PeriodicAdvertisingParameters>() {
+                @Override
+                public PeriodicAdvertisingParameters[] newArray(int size) {
+                    return new PeriodicAdvertisingParameters[size];
+                }
+
+                @Override
+                public PeriodicAdvertisingParameters createFromParcel(Parcel in) {
+                    return new PeriodicAdvertisingParameters(in);
+                }
+            };
+
+    public static final class Builder {
+        private boolean mIncludeTxPower = false;
+        private int mInterval = INTERVAL_MAX;
+
+        /**
+         * Whether the transmission power level should be included in the periodic
+         * packet.
+         */
+        public Builder setIncludeTxPower(boolean includeTxPower) {
+            mIncludeTxPower = includeTxPower;
+            return this;
+        }
+
+        /**
+         * Set advertising interval for periodic advertising, in 1.25ms unit.
+         * Valid values are from 80 (100ms) to 65519 (81.89875s).
+         * Value from range [interval, interval+20ms] will be picked as the actual value.
+         *
+         * @throws IllegalArgumentException If the interval is invalid.
+         */
+        public Builder setInterval(int interval) {
+            if (interval < INTERVAL_MIN || interval > INTERVAL_MAX) {
+                throw new IllegalArgumentException("Invalid interval (must be " + INTERVAL_MIN
+                        + "-" + INTERVAL_MAX + ")");
+            }
+            mInterval = interval;
+            return this;
+        }
+
+        /**
+         * Build the {@link AdvertisingSetParameters} object.
+         */
+        public PeriodicAdvertisingParameters build() {
+            return new PeriodicAdvertisingParameters(mIncludeTxPower, mInterval);
+        }
+    }
+}
diff --git a/android/bluetooth/le/PeriodicAdvertisingReport.java b/android/bluetooth/le/PeriodicAdvertisingReport.java
new file mode 100644
index 0000000..7a8c2c6
--- /dev/null
+++ b/android/bluetooth/le/PeriodicAdvertisingReport.java
@@ -0,0 +1,186 @@
+/*
+ * Copyright (C) 2017 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.le;
+
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * PeriodicAdvertisingReport for Bluetooth LE synchronized advertising.
+ *
+ * @hide
+ */
+public final class PeriodicAdvertisingReport implements Parcelable {
+
+    /**
+     * The data returned is complete
+     */
+    public static final int DATA_COMPLETE = 0;
+
+    /**
+     * The data returned is incomplete. The controller was unsuccessfull to
+     * receive all chained packets, returning only partial data.
+     */
+    public static final int DATA_INCOMPLETE_TRUNCATED = 2;
+
+    private int mSyncHandle;
+    private int mTxPower;
+    private int mRssi;
+    private int mDataStatus;
+
+    // periodic advertising data.
+    @Nullable
+    private ScanRecord mData;
+
+    // Device timestamp when the result was last seen.
+    private long mTimestampNanos;
+
+    /**
+     * Constructor of periodic advertising result.
+     */
+    public PeriodicAdvertisingReport(int syncHandle, int txPower, int rssi,
+            int dataStatus, ScanRecord data) {
+        mSyncHandle = syncHandle;
+        mTxPower = txPower;
+        mRssi = rssi;
+        mDataStatus = dataStatus;
+        mData = data;
+    }
+
+    private PeriodicAdvertisingReport(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mSyncHandle);
+        dest.writeInt(mTxPower);
+        dest.writeInt(mRssi);
+        dest.writeInt(mDataStatus);
+        if (mData != null) {
+            dest.writeInt(1);
+            dest.writeByteArray(mData.getBytes());
+        } else {
+            dest.writeInt(0);
+        }
+    }
+
+    private void readFromParcel(Parcel in) {
+        mSyncHandle = in.readInt();
+        mTxPower = in.readInt();
+        mRssi = in.readInt();
+        mDataStatus = in.readInt();
+        if (in.readInt() == 1) {
+            mData = ScanRecord.parseFromBytes(in.createByteArray());
+        }
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the synchronization handle.
+     */
+    public int getSyncHandle() {
+        return mSyncHandle;
+    }
+
+    /**
+     * Returns the transmit power in dBm. The valid range is [-127, 126]. Value
+     * of 127 means information was not available.
+     */
+    public int getTxPower() {
+        return mTxPower;
+    }
+
+    /**
+     * Returns the received signal strength in dBm. The valid range is [-127, 20].
+     */
+    public int getRssi() {
+        return mRssi;
+    }
+
+    /**
+     * Returns the data status. Can be one of {@link PeriodicAdvertisingReport#DATA_COMPLETE}
+     * or {@link PeriodicAdvertisingReport#DATA_INCOMPLETE_TRUNCATED}.
+     */
+    public int getDataStatus() {
+        return mDataStatus;
+    }
+
+    /**
+     * Returns the data contained in this periodic advertising report.
+     */
+    @Nullable
+    public ScanRecord getData() {
+        return mData;
+    }
+
+    /**
+     * Returns timestamp since boot when the scan record was observed.
+     */
+    public long getTimestampNanos() {
+        return mTimestampNanos;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSyncHandle, mTxPower, mRssi, mDataStatus, mData, mTimestampNanos);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        PeriodicAdvertisingReport other = (PeriodicAdvertisingReport) obj;
+        return (mSyncHandle == other.mSyncHandle)
+                && (mTxPower == other.mTxPower)
+                && (mRssi == other.mRssi)
+                && (mDataStatus == other.mDataStatus)
+                && Objects.equals(mData, other.mData)
+                && (mTimestampNanos == other.mTimestampNanos);
+    }
+
+    @Override
+    public String toString() {
+        return "PeriodicAdvertisingReport{syncHandle=" + mSyncHandle
+                + ", txPower=" + mTxPower + ", rssi=" + mRssi + ", dataStatus=" + mDataStatus
+                + ", data=" + Objects.toString(mData) + ", timestampNanos=" + mTimestampNanos + '}';
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<PeriodicAdvertisingReport> CREATOR =
+            new Creator<PeriodicAdvertisingReport>() {
+                @Override
+                public PeriodicAdvertisingReport createFromParcel(Parcel source) {
+                    return new PeriodicAdvertisingReport(source);
+                }
+
+                @Override
+                public PeriodicAdvertisingReport[] newArray(int size) {
+                    return new PeriodicAdvertisingReport[size];
+                }
+            };
+}
diff --git a/android/bluetooth/le/ResultStorageDescriptor.java b/android/bluetooth/le/ResultStorageDescriptor.java
new file mode 100644
index 0000000..796c815
--- /dev/null
+++ b/android/bluetooth/le/ResultStorageDescriptor.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Describes the way to store scan result.
+ *
+ * @hide
+ */
+@SystemApi
+public final class ResultStorageDescriptor implements Parcelable {
+    private int mType;
+    private int mOffset;
+    private int mLength;
+
+    public int getType() {
+        return mType;
+    }
+
+    public int getOffset() {
+        return mOffset;
+    }
+
+    public int getLength() {
+        return mLength;
+    }
+
+    /**
+     * Constructor of {@link ResultStorageDescriptor}
+     *
+     * @param type Type of the data.
+     * @param offset Offset from start of the advertise packet payload.
+     * @param length Byte length of the data
+     */
+    public ResultStorageDescriptor(int type, int offset, int length) {
+        mType = type;
+        mOffset = offset;
+        mLength = length;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mType);
+        dest.writeInt(mOffset);
+        dest.writeInt(mLength);
+    }
+
+    private ResultStorageDescriptor(Parcel in) {
+        ReadFromParcel(in);
+    }
+
+    private void ReadFromParcel(Parcel in) {
+        mType = in.readInt();
+        mOffset = in.readInt();
+        mLength = in.readInt();
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ResultStorageDescriptor> CREATOR =
+            new Creator<ResultStorageDescriptor>() {
+        @Override
+        public ResultStorageDescriptor createFromParcel(Parcel source) {
+            return new ResultStorageDescriptor(source);
+        }
+
+        @Override
+        public ResultStorageDescriptor[] newArray(int size) {
+            return new ResultStorageDescriptor[size];
+        }
+    };
+}
diff --git a/android/bluetooth/le/ScanCallback.java b/android/bluetooth/le/ScanCallback.java
new file mode 100644
index 0000000..53d9310
--- /dev/null
+++ b/android/bluetooth/le/ScanCallback.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import java.util.List;
+
+/**
+ * Bluetooth LE scan callbacks. Scan results are reported using these callbacks.
+ *
+ * @see BluetoothLeScanner#startScan
+ */
+public abstract class ScanCallback {
+    /**
+     * Fails to start scan as BLE scan with the same settings is already started by the app.
+     */
+    public static final int SCAN_FAILED_ALREADY_STARTED = 1;
+
+    /**
+     * Fails to start scan as app cannot be registered.
+     */
+    public static final int SCAN_FAILED_APPLICATION_REGISTRATION_FAILED = 2;
+
+    /**
+     * Fails to start scan due an internal error
+     */
+    public static final int SCAN_FAILED_INTERNAL_ERROR = 3;
+
+    /**
+     * Fails to start power optimized scan as this feature is not supported.
+     */
+    public static final int SCAN_FAILED_FEATURE_UNSUPPORTED = 4;
+
+    /**
+     * Fails to start scan as it is out of hardware resources.
+     *
+     * @hide
+     */
+    public static final int SCAN_FAILED_OUT_OF_HARDWARE_RESOURCES = 5;
+
+    /**
+     * Fails to start scan as application tries to scan too frequently.
+     * @hide
+     */
+    public static final int SCAN_FAILED_SCANNING_TOO_FREQUENTLY = 6;
+
+    static final int NO_ERROR = 0;
+
+    /**
+     * Callback when a BLE advertisement has been found.
+     *
+     * @param callbackType Determines how this callback was triggered. Could be one of {@link
+     * ScanSettings#CALLBACK_TYPE_ALL_MATCHES}, {@link ScanSettings#CALLBACK_TYPE_FIRST_MATCH} or
+     * {@link ScanSettings#CALLBACK_TYPE_MATCH_LOST}
+     * @param result A Bluetooth LE scan result.
+     */
+    public void onScanResult(int callbackType, ScanResult result) {
+    }
+
+    /**
+     * Callback when batch results are delivered.
+     *
+     * @param results List of scan results that are previously scanned.
+     */
+    public void onBatchScanResults(List<ScanResult> results) {
+    }
+
+    /**
+     * Callback when scan could not be started.
+     *
+     * @param errorCode Error code (one of SCAN_FAILED_*) for scan failure.
+     */
+    public void onScanFailed(int errorCode) {
+    }
+}
diff --git a/android/bluetooth/le/ScanFilter.java b/android/bluetooth/le/ScanFilter.java
new file mode 100644
index 0000000..7511fd0
--- /dev/null
+++ b/android/bluetooth/le/ScanFilter.java
@@ -0,0 +1,733 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+import android.os.ParcelUuid;
+import android.os.Parcelable;
+
+import com.android.internal.util.BitUtils;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * Criteria for filtering result from Bluetooth LE scans. A {@link ScanFilter} allows clients to
+ * restrict scan results to only those that are of interest to them.
+ * <p>
+ * Current filtering on the following fields are supported:
+ * <li>Service UUIDs which identify the bluetooth gatt services running on the device.
+ * <li>Name of remote Bluetooth LE device.
+ * <li>Mac address of the remote device.
+ * <li>Service data which is the data associated with a service.
+ * <li>Manufacturer specific data which is the data associated with a particular manufacturer.
+ *
+ * @see ScanResult
+ * @see BluetoothLeScanner
+ */
+public final class ScanFilter implements Parcelable {
+
+    @Nullable
+    private final String mDeviceName;
+
+    @Nullable
+    private final String mDeviceAddress;
+
+    @Nullable
+    private final ParcelUuid mServiceUuid;
+    @Nullable
+    private final ParcelUuid mServiceUuidMask;
+
+    @Nullable
+    private final ParcelUuid mServiceSolicitationUuid;
+    @Nullable
+    private final ParcelUuid mServiceSolicitationUuidMask;
+
+    @Nullable
+    private final ParcelUuid mServiceDataUuid;
+    @Nullable
+    private final byte[] mServiceData;
+    @Nullable
+    private final byte[] mServiceDataMask;
+
+    private final int mManufacturerId;
+    @Nullable
+    private final byte[] mManufacturerData;
+    @Nullable
+    private final byte[] mManufacturerDataMask;
+
+    /** @hide */
+    public static final ScanFilter EMPTY = new ScanFilter.Builder().build();
+
+
+    private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
+            ParcelUuid uuidMask, ParcelUuid solicitationUuid,
+            ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid,
+            byte[] serviceData, byte[] serviceDataMask,
+            int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) {
+        mDeviceName = name;
+        mServiceUuid = uuid;
+        mServiceUuidMask = uuidMask;
+        mServiceSolicitationUuid = solicitationUuid;
+        mServiceSolicitationUuidMask = solicitationUuidMask;
+        mDeviceAddress = deviceAddress;
+        mServiceDataUuid = serviceDataUuid;
+        mServiceData = serviceData;
+        mServiceDataMask = serviceDataMask;
+        mManufacturerId = manufacturerId;
+        mManufacturerData = manufacturerData;
+        mManufacturerDataMask = manufacturerDataMask;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mDeviceName == null ? 0 : 1);
+        if (mDeviceName != null) {
+            dest.writeString(mDeviceName);
+        }
+        dest.writeInt(mDeviceAddress == null ? 0 : 1);
+        if (mDeviceAddress != null) {
+            dest.writeString(mDeviceAddress);
+        }
+        dest.writeInt(mServiceUuid == null ? 0 : 1);
+        if (mServiceUuid != null) {
+            dest.writeParcelable(mServiceUuid, flags);
+            dest.writeInt(mServiceUuidMask == null ? 0 : 1);
+            if (mServiceUuidMask != null) {
+                dest.writeParcelable(mServiceUuidMask, flags);
+            }
+        }
+        dest.writeInt(mServiceSolicitationUuid == null ? 0 : 1);
+        if (mServiceSolicitationUuid != null) {
+            dest.writeParcelable(mServiceSolicitationUuid, flags);
+            dest.writeInt(mServiceSolicitationUuidMask == null ? 0 : 1);
+            if (mServiceSolicitationUuidMask != null) {
+                dest.writeParcelable(mServiceSolicitationUuidMask, flags);
+            }
+        }
+        dest.writeInt(mServiceDataUuid == null ? 0 : 1);
+        if (mServiceDataUuid != null) {
+            dest.writeParcelable(mServiceDataUuid, flags);
+            dest.writeInt(mServiceData == null ? 0 : 1);
+            if (mServiceData != null) {
+                dest.writeInt(mServiceData.length);
+                dest.writeByteArray(mServiceData);
+
+                dest.writeInt(mServiceDataMask == null ? 0 : 1);
+                if (mServiceDataMask != null) {
+                    dest.writeInt(mServiceDataMask.length);
+                    dest.writeByteArray(mServiceDataMask);
+                }
+            }
+        }
+        dest.writeInt(mManufacturerId);
+        dest.writeInt(mManufacturerData == null ? 0 : 1);
+        if (mManufacturerData != null) {
+            dest.writeInt(mManufacturerData.length);
+            dest.writeByteArray(mManufacturerData);
+
+            dest.writeInt(mManufacturerDataMask == null ? 0 : 1);
+            if (mManufacturerDataMask != null) {
+                dest.writeInt(mManufacturerDataMask.length);
+                dest.writeByteArray(mManufacturerDataMask);
+            }
+        }
+    }
+
+    /**
+     * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel.
+     */
+    public static final @android.annotation.NonNull Creator<ScanFilter> CREATOR =
+            new Creator<ScanFilter>() {
+
+        @Override
+        public ScanFilter[] newArray(int size) {
+            return new ScanFilter[size];
+        }
+
+        @Override
+        public ScanFilter createFromParcel(Parcel in) {
+            Builder builder = new Builder();
+            if (in.readInt() == 1) {
+                builder.setDeviceName(in.readString());
+            }
+            if (in.readInt() == 1) {
+                builder.setDeviceAddress(in.readString());
+            }
+            if (in.readInt() == 1) {
+                ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
+                builder.setServiceUuid(uuid);
+                if (in.readInt() == 1) {
+                    ParcelUuid uuidMask = in.readParcelable(
+                            ParcelUuid.class.getClassLoader());
+                    builder.setServiceUuid(uuid, uuidMask);
+                }
+            }
+            if (in.readInt() == 1) {
+                ParcelUuid solicitationUuid = in.readParcelable(
+                        ParcelUuid.class.getClassLoader());
+                builder.setServiceSolicitationUuid(solicitationUuid);
+                if (in.readInt() == 1) {
+                    ParcelUuid solicitationUuidMask = in.readParcelable(
+                            ParcelUuid.class.getClassLoader());
+                    builder.setServiceSolicitationUuid(solicitationUuid,
+                            solicitationUuidMask);
+                }
+            }
+            if (in.readInt() == 1) {
+                ParcelUuid servcieDataUuid =
+                        in.readParcelable(ParcelUuid.class.getClassLoader());
+                if (in.readInt() == 1) {
+                    int serviceDataLength = in.readInt();
+                    byte[] serviceData = new byte[serviceDataLength];
+                    in.readByteArray(serviceData);
+                    if (in.readInt() == 0) {
+                        builder.setServiceData(servcieDataUuid, serviceData);
+                    } else {
+                        int serviceDataMaskLength = in.readInt();
+                        byte[] serviceDataMask = new byte[serviceDataMaskLength];
+                        in.readByteArray(serviceDataMask);
+                        builder.setServiceData(
+                                servcieDataUuid, serviceData, serviceDataMask);
+                    }
+                }
+            }
+
+            int manufacturerId = in.readInt();
+            if (in.readInt() == 1) {
+                int manufacturerDataLength = in.readInt();
+                byte[] manufacturerData = new byte[manufacturerDataLength];
+                in.readByteArray(manufacturerData);
+                if (in.readInt() == 0) {
+                    builder.setManufacturerData(manufacturerId, manufacturerData);
+                } else {
+                    int manufacturerDataMaskLength = in.readInt();
+                    byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength];
+                    in.readByteArray(manufacturerDataMask);
+                    builder.setManufacturerData(manufacturerId, manufacturerData,
+                            manufacturerDataMask);
+                }
+            }
+
+            return builder.build();
+        }
+    };
+
+    /**
+     * Returns the filter set the device name field of Bluetooth advertisement data.
+     */
+    @Nullable
+    public String getDeviceName() {
+        return mDeviceName;
+    }
+
+    /**
+     * Returns the filter set on the service uuid.
+     */
+    @Nullable
+    public ParcelUuid getServiceUuid() {
+        return mServiceUuid;
+    }
+
+    @Nullable
+    public ParcelUuid getServiceUuidMask() {
+        return mServiceUuidMask;
+    }
+
+    /**
+     * Returns the filter set on the service Solicitation uuid.
+     */
+    @Nullable
+    public ParcelUuid getServiceSolicitationUuid() {
+        return mServiceSolicitationUuid;
+    }
+
+    /**
+     * Returns the filter set on the service Solicitation uuid mask.
+     */
+    @Nullable
+    public ParcelUuid getServiceSolicitationUuidMask() {
+        return mServiceSolicitationUuidMask;
+    }
+
+    @Nullable
+    public String getDeviceAddress() {
+        return mDeviceAddress;
+    }
+
+    @Nullable
+    public byte[] getServiceData() {
+        return mServiceData;
+    }
+
+    @Nullable
+    public byte[] getServiceDataMask() {
+        return mServiceDataMask;
+    }
+
+    @Nullable
+    public ParcelUuid getServiceDataUuid() {
+        return mServiceDataUuid;
+    }
+
+    /**
+     * Returns the manufacturer id. -1 if the manufacturer filter is not set.
+     */
+    public int getManufacturerId() {
+        return mManufacturerId;
+    }
+
+    @Nullable
+    public byte[] getManufacturerData() {
+        return mManufacturerData;
+    }
+
+    @Nullable
+    public byte[] getManufacturerDataMask() {
+        return mManufacturerDataMask;
+    }
+
+    /**
+     * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match
+     * if it matches all the field filters.
+     */
+    public boolean matches(ScanResult scanResult) {
+        if (scanResult == null) {
+            return false;
+        }
+        BluetoothDevice device = scanResult.getDevice();
+        // Device match.
+        if (mDeviceAddress != null
+                && (device == null || !mDeviceAddress.equals(device.getAddress()))) {
+            return false;
+        }
+
+        ScanRecord scanRecord = scanResult.getScanRecord();
+
+        // Scan record is null but there exist filters on it.
+        if (scanRecord == null
+                && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null
+                || mServiceData != null || mServiceSolicitationUuid != null)) {
+            return false;
+        }
+
+        // Local name match.
+        if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) {
+            return false;
+        }
+
+        // UUID match.
+        if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask,
+                scanRecord.getServiceUuids())) {
+            return false;
+        }
+
+        // solicitation UUID match.
+        if (mServiceSolicitationUuid != null && !matchesServiceSolicitationUuids(
+                mServiceSolicitationUuid, mServiceSolicitationUuidMask,
+                scanRecord.getServiceSolicitationUuids())) {
+            return false;
+        }
+
+        // Service data match
+        if (mServiceDataUuid != null) {
+            if (!matchesPartialData(mServiceData, mServiceDataMask,
+                    scanRecord.getServiceData(mServiceDataUuid))) {
+                return false;
+            }
+        }
+
+        // Manufacturer data match.
+        if (mManufacturerId >= 0) {
+            if (!matchesPartialData(mManufacturerData, mManufacturerDataMask,
+                    scanRecord.getManufacturerSpecificData(mManufacturerId))) {
+                return false;
+            }
+        }
+        // All filters match.
+        return true;
+    }
+
+    /**
+     * Check if the uuid pattern is contained in a list of parcel uuids.
+     *
+     * @hide
+     */
+    public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask,
+            List<ParcelUuid> uuids) {
+        if (uuid == null) {
+            return true;
+        }
+        if (uuids == null) {
+            return false;
+        }
+
+        for (ParcelUuid parcelUuid : uuids) {
+            UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
+            if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Check if the uuid pattern matches the particular service uuid.
+    private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
+        return BitUtils.maskedEquals(data, uuid, mask);
+    }
+
+    /**
+     * Check if the solicitation uuid pattern is contained in a list of parcel uuids.
+     *
+     */
+    private static boolean matchesServiceSolicitationUuids(ParcelUuid solicitationUuid,
+            ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids) {
+        if (solicitationUuid == null) {
+            return true;
+        }
+        if (solicitationUuids == null) {
+            return false;
+        }
+
+        for (ParcelUuid parcelSolicitationUuid : solicitationUuids) {
+            UUID solicitationUuidMask = parcelSolicitationUuidMask == null
+                    ? null : parcelSolicitationUuidMask.getUuid();
+            if (matchesServiceUuid(solicitationUuid.getUuid(), solicitationUuidMask,
+                    parcelSolicitationUuid.getUuid())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    // Check if the solicitation uuid pattern matches the particular service solicitation uuid.
+    private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid,
+            UUID solicitationUuidMask, UUID data) {
+        return BitUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask);
+    }
+
+    // Check whether the data pattern matches the parsed data.
+    private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) {
+        if (parsedData == null || parsedData.length < data.length) {
+            return false;
+        }
+        if (dataMask == null) {
+            for (int i = 0; i < data.length; ++i) {
+                if (parsedData[i] != data[i]) {
+                    return false;
+                }
+            }
+            return true;
+        }
+        for (int i = 0; i < data.length; ++i) {
+            if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public String toString() {
+        return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress="
+                + mDeviceAddress
+                + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask
+                + ", mServiceSolicitationUuid=" + mServiceSolicitationUuid
+                + ", mServiceSolicitationUuidMask=" + mServiceSolicitationUuidMask
+                + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData="
+                + Arrays.toString(mServiceData) + ", mServiceDataMask="
+                + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
+                + ", mManufacturerData=" + Arrays.toString(mManufacturerData)
+                + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + "]";
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId,
+                Arrays.hashCode(mManufacturerData),
+                Arrays.hashCode(mManufacturerDataMask),
+                mServiceDataUuid,
+                Arrays.hashCode(mServiceData),
+                Arrays.hashCode(mServiceDataMask),
+                mServiceUuid, mServiceUuidMask,
+                mServiceSolicitationUuid, mServiceSolicitationUuidMask);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        ScanFilter other = (ScanFilter) obj;
+        return Objects.equals(mDeviceName, other.mDeviceName)
+                && Objects.equals(mDeviceAddress, other.mDeviceAddress)
+                && mManufacturerId == other.mManufacturerId
+                && Objects.deepEquals(mManufacturerData, other.mManufacturerData)
+                && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask)
+                && Objects.equals(mServiceDataUuid, other.mServiceDataUuid)
+                && Objects.deepEquals(mServiceData, other.mServiceData)
+                && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask)
+                && Objects.equals(mServiceUuid, other.mServiceUuid)
+                && Objects.equals(mServiceUuidMask, other.mServiceUuidMask)
+                && Objects.equals(mServiceSolicitationUuid, other.mServiceSolicitationUuid)
+                && Objects.equals(mServiceSolicitationUuidMask,
+                        other.mServiceSolicitationUuidMask);
+    }
+
+    /**
+     * Checks if the scanfilter is empty
+     *
+     * @hide
+     */
+    public boolean isAllFieldsEmpty() {
+        return EMPTY.equals(this);
+    }
+
+    /**
+     * Builder class for {@link ScanFilter}.
+     */
+    public static final class Builder {
+
+        private String mDeviceName;
+        private String mDeviceAddress;
+
+        private ParcelUuid mServiceUuid;
+        private ParcelUuid mUuidMask;
+
+        private ParcelUuid mServiceSolicitationUuid;
+        private ParcelUuid mServiceSolicitationUuidMask;
+
+        private ParcelUuid mServiceDataUuid;
+        private byte[] mServiceData;
+        private byte[] mServiceDataMask;
+
+        private int mManufacturerId = -1;
+        private byte[] mManufacturerData;
+        private byte[] mManufacturerDataMask;
+
+        /**
+         * Set filter on device name.
+         */
+        public Builder setDeviceName(String deviceName) {
+            mDeviceName = deviceName;
+            return this;
+        }
+
+        /**
+         * Set filter on device address.
+         *
+         * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
+         * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
+         * BluetoothAdapter#checkBluetoothAddress}.
+         * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
+         */
+        public Builder setDeviceAddress(String deviceAddress) {
+            if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
+                throw new IllegalArgumentException("invalid device address " + deviceAddress);
+            }
+            mDeviceAddress = deviceAddress;
+            return this;
+        }
+
+        /**
+         * Set filter on service uuid.
+         */
+        public Builder setServiceUuid(ParcelUuid serviceUuid) {
+            mServiceUuid = serviceUuid;
+            mUuidMask = null; // clear uuid mask
+            return this;
+        }
+
+        /**
+         * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the
+         * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the
+         * bit in {@code serviceUuid}, and 0 to ignore that bit.
+         *
+         * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code
+         * uuidMask} is not {@code null}.
+         */
+        public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) {
+            if (mUuidMask != null && mServiceUuid == null) {
+                throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
+            }
+            mServiceUuid = serviceUuid;
+            mUuidMask = uuidMask;
+            return this;
+        }
+
+
+        /**
+         * Set filter on service solicitation uuid.
+         */
+        public @NonNull Builder setServiceSolicitationUuid(
+                @Nullable ParcelUuid serviceSolicitationUuid) {
+            mServiceSolicitationUuid = serviceSolicitationUuid;
+            if (serviceSolicitationUuid == null) {
+                mServiceSolicitationUuidMask = null;
+            }
+            return this;
+        }
+
+
+        /**
+         * Set filter on partial service Solicitation uuid. The {@code SolicitationUuidMask} is the
+         * bit mask for the {@code serviceSolicitationUuid}. Set any bit in the mask to 1 to
+         * indicate a match is needed for the bit in {@code serviceSolicitationUuid}, and 0 to
+         * ignore that bit.
+         *
+         * @param serviceSolicitationUuid can only be null if solicitationUuidMask is null.
+         * @param solicitationUuidMask can be null or a mask with no restriction.
+         *
+         * @throws IllegalArgumentException If {@code serviceSolicitationUuid} is {@code null} but
+         *             {@code serviceSolicitationUuidMask} is not {@code null}.
+         */
+        public @NonNull Builder setServiceSolicitationUuid(
+                @Nullable ParcelUuid serviceSolicitationUuid,
+                @Nullable ParcelUuid solicitationUuidMask) {
+            if (solicitationUuidMask != null && serviceSolicitationUuid == null) {
+                throw new IllegalArgumentException(
+                        "SolicitationUuid is null while SolicitationUuidMask is not null!");
+            }
+            mServiceSolicitationUuid = serviceSolicitationUuid;
+            mServiceSolicitationUuidMask = solicitationUuidMask;
+            return this;
+        }
+
+        /**
+         * Set filtering on service data.
+         *
+         * @throws IllegalArgumentException If {@code serviceDataUuid} is null.
+         */
+        public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
+            if (serviceDataUuid == null) {
+                throw new IllegalArgumentException("serviceDataUuid is null");
+            }
+            mServiceDataUuid = serviceDataUuid;
+            mServiceData = serviceData;
+            mServiceDataMask = null; // clear service data mask
+            return this;
+        }
+
+        /**
+         * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to
+         * match the one in service data, otherwise set it to 0 to ignore that bit.
+         * <p>
+         * The {@code serviceDataMask} must have the same length of the {@code serviceData}.
+         *
+         * @throws IllegalArgumentException If {@code serviceDataUuid} is null or {@code
+         * serviceDataMask} is {@code null} while {@code serviceData} is not or {@code
+         * serviceDataMask} and {@code serviceData} has different length.
+         */
+        public Builder setServiceData(ParcelUuid serviceDataUuid,
+                byte[] serviceData, byte[] serviceDataMask) {
+            if (serviceDataUuid == null) {
+                throw new IllegalArgumentException("serviceDataUuid is null");
+            }
+            if (mServiceDataMask != null) {
+                if (mServiceData == null) {
+                    throw new IllegalArgumentException(
+                            "serviceData is null while serviceDataMask is not null");
+                }
+                // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two
+                // byte array need to be the same.
+                if (mServiceData.length != mServiceDataMask.length) {
+                    throw new IllegalArgumentException(
+                            "size mismatch for service data and service data mask");
+                }
+            }
+            mServiceDataUuid = serviceDataUuid;
+            mServiceData = serviceData;
+            mServiceDataMask = serviceDataMask;
+            return this;
+        }
+
+        /**
+         * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
+         *
+         * @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
+         */
+        public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) {
+            if (manufacturerData != null && manufacturerId < 0) {
+                throw new IllegalArgumentException("invalid manufacture id");
+            }
+            mManufacturerId = manufacturerId;
+            mManufacturerData = manufacturerData;
+            mManufacturerDataMask = null; // clear manufacturer data mask
+            return this;
+        }
+
+        /**
+         * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs
+         * to match the one in manufacturer data, otherwise set it to 0.
+         * <p>
+         * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}.
+         *
+         * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code
+         * manufacturerData} is null while {@code manufacturerDataMask} is not, or {@code
+         * manufacturerData} and {@code manufacturerDataMask} have different length.
+         */
+        public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData,
+                byte[] manufacturerDataMask) {
+            if (manufacturerData != null && manufacturerId < 0) {
+                throw new IllegalArgumentException("invalid manufacture id");
+            }
+            if (mManufacturerDataMask != null) {
+                if (mManufacturerData == null) {
+                    throw new IllegalArgumentException(
+                            "manufacturerData is null while manufacturerDataMask is not null");
+                }
+                // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths
+                // of the two byte array need to be the same.
+                if (mManufacturerData.length != mManufacturerDataMask.length) {
+                    throw new IllegalArgumentException(
+                            "size mismatch for manufacturerData and manufacturerDataMask");
+                }
+            }
+            mManufacturerId = manufacturerId;
+            mManufacturerData = manufacturerData;
+            mManufacturerDataMask = manufacturerDataMask;
+            return this;
+        }
+
+        /**
+         * Build {@link ScanFilter}.
+         *
+         * @throws IllegalArgumentException If the filter cannot be built.
+         */
+        public ScanFilter build() {
+            return new ScanFilter(mDeviceName, mDeviceAddress,
+                    mServiceUuid, mUuidMask, mServiceSolicitationUuid,
+                    mServiceSolicitationUuidMask,
+                    mServiceDataUuid, mServiceData, mServiceDataMask,
+                    mManufacturerId, mManufacturerData, mManufacturerDataMask);
+        }
+    }
+}
diff --git a/android/bluetooth/le/ScanRecord.java b/android/bluetooth/le/ScanRecord.java
new file mode 100644
index 0000000..c0c1aa1
--- /dev/null
+++ b/android/bluetooth/le/ScanRecord.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothUuid;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.os.ParcelUuid;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.SparseArray;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Represents a scan record from Bluetooth LE scan.
+ */
+public final class ScanRecord {
+
+    private static final String TAG = "ScanRecord";
+
+    // The following data type values are assigned by Bluetooth SIG.
+    // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.
+    private static final int DATA_TYPE_FLAGS = 0x01;
+    private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
+    private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
+    private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
+    private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
+    private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
+    private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
+    private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
+    private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
+    private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
+    private static final int DATA_TYPE_SERVICE_DATA_16_BIT = 0x16;
+    private static final int DATA_TYPE_SERVICE_DATA_32_BIT = 0x20;
+    private static final int DATA_TYPE_SERVICE_DATA_128_BIT = 0x21;
+    private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT = 0x14;
+    private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT = 0x1F;
+    private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT = 0x15;
+    private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
+
+    // Flags of the advertising data.
+    private final int mAdvertiseFlags;
+
+    @Nullable
+    private final List<ParcelUuid> mServiceUuids;
+    @Nullable
+    private final List<ParcelUuid> mServiceSolicitationUuids;
+
+    private final SparseArray<byte[]> mManufacturerSpecificData;
+
+    private final Map<ParcelUuid, byte[]> mServiceData;
+
+    // Transmission power level(in dB).
+    private final int mTxPowerLevel;
+
+    // Local name of the Bluetooth LE device.
+    private final String mDeviceName;
+
+    // Raw bytes of scan record.
+    private final byte[] mBytes;
+
+    /**
+     * Returns the advertising flags indicating the discoverable mode and capability of the device.
+     * Returns -1 if the flag field is not set.
+     */
+    public int getAdvertiseFlags() {
+        return mAdvertiseFlags;
+    }
+
+    /**
+     * Returns a list of service UUIDs within the advertisement that are used to identify the
+     * bluetooth GATT services.
+     */
+    public List<ParcelUuid> getServiceUuids() {
+        return mServiceUuids;
+    }
+
+    /**
+     * Returns a list of service solicitation UUIDs within the advertisement that are used to
+     * identify the Bluetooth GATT services.
+     */
+    @NonNull
+    public List<ParcelUuid> getServiceSolicitationUuids() {
+        return mServiceSolicitationUuids;
+    }
+
+    /**
+     * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific
+     * data.
+     */
+    public SparseArray<byte[]> getManufacturerSpecificData() {
+        return mManufacturerSpecificData;
+    }
+
+    /**
+     * Returns the manufacturer specific data associated with the manufacturer id. Returns
+     * {@code null} if the {@code manufacturerId} is not found.
+     */
+    @Nullable
+    public byte[] getManufacturerSpecificData(int manufacturerId) {
+        if (mManufacturerSpecificData == null) {
+            return null;
+        }
+        return mManufacturerSpecificData.get(manufacturerId);
+    }
+
+    /**
+     * Returns a map of service UUID and its corresponding service data.
+     */
+    public Map<ParcelUuid, byte[]> getServiceData() {
+        return mServiceData;
+    }
+
+    /**
+     * Returns the service data byte array associated with the {@code serviceUuid}. Returns
+     * {@code null} if the {@code serviceDataUuid} is not found.
+     */
+    @Nullable
+    public byte[] getServiceData(ParcelUuid serviceDataUuid) {
+        if (serviceDataUuid == null || mServiceData == null) {
+            return null;
+        }
+        return mServiceData.get(serviceDataUuid);
+    }
+
+    /**
+     * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
+     * if the field is not set. This value can be used to calculate the path loss of a received
+     * packet using the following equation:
+     * <p>
+     * <code>pathloss = txPowerLevel - rssi</code>
+     */
+    public int getTxPowerLevel() {
+        return mTxPowerLevel;
+    }
+
+    /**
+     * Returns the local name of the BLE device. This is a UTF-8 encoded string.
+     */
+    @Nullable
+    public String getDeviceName() {
+        return mDeviceName;
+    }
+
+    /**
+     * Returns raw bytes of scan record.
+     */
+    public byte[] getBytes() {
+        return mBytes;
+    }
+
+    private ScanRecord(List<ParcelUuid> serviceUuids,
+            List<ParcelUuid> serviceSolicitationUuids,
+            SparseArray<byte[]> manufacturerData,
+            Map<ParcelUuid, byte[]> serviceData,
+            int advertiseFlags, int txPowerLevel,
+            String localName, byte[] bytes) {
+        mServiceSolicitationUuids = serviceSolicitationUuids;
+        mServiceUuids = serviceUuids;
+        mManufacturerSpecificData = manufacturerData;
+        mServiceData = serviceData;
+        mDeviceName = localName;
+        mAdvertiseFlags = advertiseFlags;
+        mTxPowerLevel = txPowerLevel;
+        mBytes = bytes;
+    }
+
+    /**
+     * Parse scan record bytes to {@link ScanRecord}.
+     * <p>
+     * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
+     * <p>
+     * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong>
+     * order.
+     *
+     * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static ScanRecord parseFromBytes(byte[] scanRecord) {
+        if (scanRecord == null) {
+            return null;
+        }
+
+        int currentPos = 0;
+        int advertiseFlag = -1;
+        List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
+        List<ParcelUuid> serviceSolicitationUuids = new ArrayList<ParcelUuid>();
+        String localName = null;
+        int txPowerLevel = Integer.MIN_VALUE;
+
+        SparseArray<byte[]> manufacturerData = new SparseArray<byte[]>();
+        Map<ParcelUuid, byte[]> serviceData = new ArrayMap<ParcelUuid, byte[]>();
+
+        try {
+            while (currentPos < scanRecord.length) {
+                // length is unsigned int.
+                int length = scanRecord[currentPos++] & 0xFF;
+                if (length == 0) {
+                    break;
+                }
+                // Note the length includes the length of the field type itself.
+                int dataLength = length - 1;
+                // fieldType is unsigned int.
+                int fieldType = scanRecord[currentPos++] & 0xFF;
+                switch (fieldType) {
+                    case DATA_TYPE_FLAGS:
+                        advertiseFlag = scanRecord[currentPos] & 0xFF;
+                        break;
+                    case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
+                    case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
+                        parseServiceUuid(scanRecord, currentPos,
+                                dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
+                        break;
+                    case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
+                    case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
+                        parseServiceUuid(scanRecord, currentPos, dataLength,
+                                BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
+                        break;
+                    case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
+                    case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
+                        parseServiceUuid(scanRecord, currentPos, dataLength,
+                                BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
+                        break;
+                    case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT:
+                        parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
+                                BluetoothUuid.UUID_BYTES_16_BIT, serviceSolicitationUuids);
+                        break;
+                    case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT:
+                        parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
+                                BluetoothUuid.UUID_BYTES_32_BIT, serviceSolicitationUuids);
+                        break;
+                    case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT:
+                        parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
+                                BluetoothUuid.UUID_BYTES_128_BIT, serviceSolicitationUuids);
+                        break;
+                    case DATA_TYPE_LOCAL_NAME_SHORT:
+                    case DATA_TYPE_LOCAL_NAME_COMPLETE:
+                        localName = new String(
+                                extractBytes(scanRecord, currentPos, dataLength));
+                        break;
+                    case DATA_TYPE_TX_POWER_LEVEL:
+                        txPowerLevel = scanRecord[currentPos];
+                        break;
+                    case DATA_TYPE_SERVICE_DATA_16_BIT:
+                    case DATA_TYPE_SERVICE_DATA_32_BIT:
+                    case DATA_TYPE_SERVICE_DATA_128_BIT:
+                        int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
+                        if (fieldType == DATA_TYPE_SERVICE_DATA_32_BIT) {
+                            serviceUuidLength = BluetoothUuid.UUID_BYTES_32_BIT;
+                        } else if (fieldType == DATA_TYPE_SERVICE_DATA_128_BIT) {
+                            serviceUuidLength = BluetoothUuid.UUID_BYTES_128_BIT;
+                        }
+
+                        byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
+                                serviceUuidLength);
+                        ParcelUuid serviceDataUuid = BluetoothUuid.parseUuidFrom(
+                                serviceDataUuidBytes);
+                        byte[] serviceDataArray = extractBytes(scanRecord,
+                                currentPos + serviceUuidLength, dataLength - serviceUuidLength);
+                        serviceData.put(serviceDataUuid, serviceDataArray);
+                        break;
+                    case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
+                        // The first two bytes of the manufacturer specific data are
+                        // manufacturer ids in little endian.
+                        int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8)
+                                + (scanRecord[currentPos] & 0xFF);
+                        byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
+                                dataLength - 2);
+                        manufacturerData.put(manufacturerId, manufacturerDataBytes);
+                        break;
+                    default:
+                        // Just ignore, we don't handle such data type.
+                        break;
+                }
+                currentPos += dataLength;
+            }
+
+            if (serviceUuids.isEmpty()) {
+                serviceUuids = null;
+            }
+            return new ScanRecord(serviceUuids, serviceSolicitationUuids, manufacturerData,
+                    serviceData, advertiseFlag, txPowerLevel, localName, scanRecord);
+        } catch (Exception e) {
+            Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
+            // As the record is invalid, ignore all the parsed results for this packet
+            // and return an empty record with raw scanRecord bytes in results
+            return new ScanRecord(null, null, null, null, -1, Integer.MIN_VALUE, null, scanRecord);
+        }
+    }
+
+    @Override
+    public String toString() {
+        return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
+                + ", mServiceSolicitationUuids=" + mServiceSolicitationUuids
+                + ", mManufacturerSpecificData=" + BluetoothLeUtils.toString(
+                mManufacturerSpecificData)
+                + ", mServiceData=" + BluetoothLeUtils.toString(mServiceData)
+                + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
+    }
+
+    // Parse service UUIDs.
+    private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
+            int uuidLength, List<ParcelUuid> serviceUuids) {
+        while (dataLength > 0) {
+            byte[] uuidBytes = extractBytes(scanRecord, currentPos,
+                    uuidLength);
+            serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
+            dataLength -= uuidLength;
+            currentPos += uuidLength;
+        }
+        return currentPos;
+    }
+
+    /**
+     * Parse service Solicitation UUIDs.
+     */
+    private static int parseServiceSolicitationUuid(byte[] scanRecord, int currentPos,
+            int dataLength, int uuidLength, List<ParcelUuid> serviceSolicitationUuids) {
+        while (dataLength > 0) {
+            byte[] uuidBytes = extractBytes(scanRecord, currentPos, uuidLength);
+            serviceSolicitationUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
+            dataLength -= uuidLength;
+            currentPos += uuidLength;
+        }
+        return currentPos;
+    }
+
+    // Helper method to extract bytes from byte array.
+    private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
+        byte[] bytes = new byte[length];
+        System.arraycopy(scanRecord, start, bytes, 0, length);
+        return bytes;
+    }
+}
diff --git a/android/bluetooth/le/ScanResult.java b/android/bluetooth/le/ScanResult.java
new file mode 100644
index 0000000..855d345
--- /dev/null
+++ b/android/bluetooth/le/ScanResult.java
@@ -0,0 +1,353 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import android.annotation.Nullable;
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * ScanResult for Bluetooth LE scan.
+ */
+public final class ScanResult implements Parcelable {
+
+    /**
+     * For chained advertisements, inidcates tha the data contained in this
+     * scan result is complete.
+     */
+    public static final int DATA_COMPLETE = 0x00;
+
+    /**
+     * For chained advertisements, indicates that the controller was
+     * unable to receive all chained packets and the scan result contains
+     * incomplete truncated data.
+     */
+    public static final int DATA_TRUNCATED = 0x02;
+
+    /**
+     * Indicates that the secondary physical layer was not used.
+     */
+    public static final int PHY_UNUSED = 0x00;
+
+    /**
+     * Advertising Set ID is not present in the packet.
+     */
+    public static final int SID_NOT_PRESENT = 0xFF;
+
+    /**
+     * TX power is not present in the packet.
+     */
+    public static final int TX_POWER_NOT_PRESENT = 0x7F;
+
+    /**
+     * Periodic advertising interval is not present in the packet.
+     */
+    public static final int PERIODIC_INTERVAL_NOT_PRESENT = 0x00;
+
+    /**
+     * Mask for checking whether event type represents legacy advertisement.
+     */
+    private static final int ET_LEGACY_MASK = 0x10;
+
+    /**
+     * Mask for checking whether event type represents connectable advertisement.
+     */
+    private static final int ET_CONNECTABLE_MASK = 0x01;
+
+    // Remote Bluetooth device.
+    private BluetoothDevice mDevice;
+
+    // Scan record, including advertising data and scan response data.
+    @Nullable
+    private ScanRecord mScanRecord;
+
+    // Received signal strength.
+    private int mRssi;
+
+    // Device timestamp when the result was last seen.
+    private long mTimestampNanos;
+
+    private int mEventType;
+    private int mPrimaryPhy;
+    private int mSecondaryPhy;
+    private int mAdvertisingSid;
+    private int mTxPower;
+    private int mPeriodicAdvertisingInterval;
+
+    /**
+     * Constructs a new ScanResult.
+     *
+     * @param device Remote Bluetooth device found.
+     * @param scanRecord Scan record including both advertising data and scan response data.
+     * @param rssi Received signal strength.
+     * @param timestampNanos Timestamp at which the scan result was observed.
+     * @deprecated use {@link #ScanResult(BluetoothDevice, int, int, int, int, int, int, int,
+     * ScanRecord, long)}
+     */
+    @Deprecated
+    public ScanResult(BluetoothDevice device, ScanRecord scanRecord, int rssi,
+            long timestampNanos) {
+        mDevice = device;
+        mScanRecord = scanRecord;
+        mRssi = rssi;
+        mTimestampNanos = timestampNanos;
+        mEventType = (DATA_COMPLETE << 5) | ET_LEGACY_MASK | ET_CONNECTABLE_MASK;
+        mPrimaryPhy = BluetoothDevice.PHY_LE_1M;
+        mSecondaryPhy = PHY_UNUSED;
+        mAdvertisingSid = SID_NOT_PRESENT;
+        mTxPower = 127;
+        mPeriodicAdvertisingInterval = 0;
+    }
+
+    /**
+     * Constructs a new ScanResult.
+     *
+     * @param device Remote Bluetooth device found.
+     * @param eventType Event type.
+     * @param primaryPhy Primary advertising phy.
+     * @param secondaryPhy Secondary advertising phy.
+     * @param advertisingSid Advertising set ID.
+     * @param txPower Transmit power.
+     * @param rssi Received signal strength.
+     * @param periodicAdvertisingInterval Periodic advertising interval.
+     * @param scanRecord Scan record including both advertising data and scan response data.
+     * @param timestampNanos Timestamp at which the scan result was observed.
+     */
+    public ScanResult(BluetoothDevice device, int eventType, int primaryPhy, int secondaryPhy,
+            int advertisingSid, int txPower, int rssi, int periodicAdvertisingInterval,
+            ScanRecord scanRecord, long timestampNanos) {
+        mDevice = device;
+        mEventType = eventType;
+        mPrimaryPhy = primaryPhy;
+        mSecondaryPhy = secondaryPhy;
+        mAdvertisingSid = advertisingSid;
+        mTxPower = txPower;
+        mRssi = rssi;
+        mPeriodicAdvertisingInterval = periodicAdvertisingInterval;
+        mScanRecord = scanRecord;
+        mTimestampNanos = timestampNanos;
+    }
+
+    private ScanResult(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (mDevice != null) {
+            dest.writeInt(1);
+            mDevice.writeToParcel(dest, flags);
+        } else {
+            dest.writeInt(0);
+        }
+        if (mScanRecord != null) {
+            dest.writeInt(1);
+            dest.writeByteArray(mScanRecord.getBytes());
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeInt(mRssi);
+        dest.writeLong(mTimestampNanos);
+        dest.writeInt(mEventType);
+        dest.writeInt(mPrimaryPhy);
+        dest.writeInt(mSecondaryPhy);
+        dest.writeInt(mAdvertisingSid);
+        dest.writeInt(mTxPower);
+        dest.writeInt(mPeriodicAdvertisingInterval);
+    }
+
+    private void readFromParcel(Parcel in) {
+        if (in.readInt() == 1) {
+            mDevice = BluetoothDevice.CREATOR.createFromParcel(in);
+        }
+        if (in.readInt() == 1) {
+            mScanRecord = ScanRecord.parseFromBytes(in.createByteArray());
+        }
+        mRssi = in.readInt();
+        mTimestampNanos = in.readLong();
+        mEventType = in.readInt();
+        mPrimaryPhy = in.readInt();
+        mSecondaryPhy = in.readInt();
+        mAdvertisingSid = in.readInt();
+        mTxPower = in.readInt();
+        mPeriodicAdvertisingInterval = in.readInt();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Returns the remote Bluetooth device identified by the Bluetooth device address.
+     */
+    public BluetoothDevice getDevice() {
+        return mDevice;
+    }
+
+    /**
+     * Returns the scan record, which is a combination of advertisement and scan response.
+     */
+    @Nullable
+    public ScanRecord getScanRecord() {
+        return mScanRecord;
+    }
+
+    /**
+     * Returns the received signal strength in dBm. The valid range is [-127, 126].
+     */
+    public int getRssi() {
+        return mRssi;
+    }
+
+    /**
+     * Returns timestamp since boot when the scan record was observed.
+     */
+    public long getTimestampNanos() {
+        return mTimestampNanos;
+    }
+
+    /**
+     * Returns true if this object represents legacy scan result.
+     * Legacy scan results do not contain advanced advertising information
+     * as specified in the Bluetooth Core Specification v5.
+     */
+    public boolean isLegacy() {
+        return (mEventType & ET_LEGACY_MASK) != 0;
+    }
+
+    /**
+     * Returns true if this object represents connectable scan result.
+     */
+    public boolean isConnectable() {
+        return (mEventType & ET_CONNECTABLE_MASK) != 0;
+    }
+
+    /**
+     * Returns the data status.
+     * Can be one of {@link ScanResult#DATA_COMPLETE} or
+     * {@link ScanResult#DATA_TRUNCATED}.
+     */
+    public int getDataStatus() {
+        // return bit 5 and 6
+        return (mEventType >> 5) & 0x03;
+    }
+
+    /**
+     * Returns the primary Physical Layer
+     * on which this advertisment was received.
+     * Can be one of {@link BluetoothDevice#PHY_LE_1M} or
+     * {@link BluetoothDevice#PHY_LE_CODED}.
+     */
+    public int getPrimaryPhy() {
+        return mPrimaryPhy;
+    }
+
+    /**
+     * Returns the secondary Physical Layer
+     * on which this advertisment was received.
+     * Can be one of {@link BluetoothDevice#PHY_LE_1M},
+     * {@link BluetoothDevice#PHY_LE_2M}, {@link BluetoothDevice#PHY_LE_CODED}
+     * or {@link ScanResult#PHY_UNUSED} - if the advertisement
+     * was not received on a secondary physical channel.
+     */
+    public int getSecondaryPhy() {
+        return mSecondaryPhy;
+    }
+
+    /**
+     * Returns the advertising set id.
+     * May return {@link ScanResult#SID_NOT_PRESENT} if
+     * no set id was is present.
+     */
+    public int getAdvertisingSid() {
+        return mAdvertisingSid;
+    }
+
+    /**
+     * Returns the transmit power in dBm.
+     * Valid range is [-127, 126]. A value of {@link ScanResult#TX_POWER_NOT_PRESENT}
+     * indicates that the TX power is not present.
+     */
+    public int getTxPower() {
+        return mTxPower;
+    }
+
+    /**
+     * Returns the periodic advertising interval in units of 1.25ms.
+     * Valid range is 6 (7.5ms) to 65536 (81918.75ms). A value of
+     * {@link ScanResult#PERIODIC_INTERVAL_NOT_PRESENT} means periodic
+     * advertising interval is not present.
+     */
+    public int getPeriodicAdvertisingInterval() {
+        return mPeriodicAdvertisingInterval;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mDevice, mRssi, mScanRecord, mTimestampNanos,
+                mEventType, mPrimaryPhy, mSecondaryPhy,
+                mAdvertisingSid, mTxPower,
+                mPeriodicAdvertisingInterval);
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null || getClass() != obj.getClass()) {
+            return false;
+        }
+        ScanResult other = (ScanResult) obj;
+        return Objects.equals(mDevice, other.mDevice) && (mRssi == other.mRssi)
+                && Objects.equals(mScanRecord, other.mScanRecord)
+                && (mTimestampNanos == other.mTimestampNanos)
+                && mEventType == other.mEventType
+                && mPrimaryPhy == other.mPrimaryPhy
+                && mSecondaryPhy == other.mSecondaryPhy
+                && mAdvertisingSid == other.mAdvertisingSid
+                && mTxPower == other.mTxPower
+                && mPeriodicAdvertisingInterval == other.mPeriodicAdvertisingInterval;
+    }
+
+    @Override
+    public String toString() {
+        return "ScanResult{" + "device=" + mDevice + ", scanRecord="
+                + Objects.toString(mScanRecord) + ", rssi=" + mRssi
+                + ", timestampNanos=" + mTimestampNanos + ", eventType=" + mEventType
+                + ", primaryPhy=" + mPrimaryPhy + ", secondaryPhy=" + mSecondaryPhy
+                + ", advertisingSid=" + mAdvertisingSid + ", txPower=" + mTxPower
+                + ", periodicAdvertisingInterval=" + mPeriodicAdvertisingInterval + '}';
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ScanResult> CREATOR = new Creator<ScanResult>() {
+        @Override
+        public ScanResult createFromParcel(Parcel source) {
+            return new ScanResult(source);
+        }
+
+        @Override
+        public ScanResult[] newArray(int size) {
+            return new ScanResult[size];
+        }
+    };
+
+}
diff --git a/android/bluetooth/le/ScanSettings.java b/android/bluetooth/le/ScanSettings.java
new file mode 100644
index 0000000..504118e
--- /dev/null
+++ b/android/bluetooth/le/ScanSettings.java
@@ -0,0 +1,419 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import android.annotation.SystemApi;
+import android.bluetooth.BluetoothDevice;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Bluetooth LE scan settings are passed to {@link BluetoothLeScanner#startScan} to define the
+ * parameters for the scan.
+ */
+public final class ScanSettings implements Parcelable {
+
+    /**
+     * A special Bluetooth LE scan mode. Applications using this scan mode will passively listen for
+     * other scan results without starting BLE scans themselves.
+     */
+    public static final int SCAN_MODE_OPPORTUNISTIC = -1;
+
+    /**
+     * Perform Bluetooth LE scan in low power mode. This is the default scan mode as it consumes the
+     * least power. This mode is enforced if the scanning application is not in foreground.
+     */
+    public static final int SCAN_MODE_LOW_POWER = 0;
+
+    /**
+     * Perform Bluetooth LE scan in balanced power mode. Scan results are returned at a rate that
+     * provides a good trade-off between scan frequency and power consumption.
+     */
+    public static final int SCAN_MODE_BALANCED = 1;
+
+    /**
+     * Scan using highest duty cycle. It's recommended to only use this mode when the application is
+     * running in the foreground.
+     */
+    public static final int SCAN_MODE_LOW_LATENCY = 2;
+
+    /**
+     * Trigger a callback for every Bluetooth advertisement found that matches the filter criteria.
+     * If no filter is active, all advertisement packets are reported.
+     */
+    public static final int CALLBACK_TYPE_ALL_MATCHES = 1;
+
+    /**
+     * A result callback is only triggered for the first advertisement packet received that matches
+     * the filter criteria.
+     */
+    public static final int CALLBACK_TYPE_FIRST_MATCH = 2;
+
+    /**
+     * Receive a callback when advertisements are no longer received from a device that has been
+     * previously reported by a first match callback.
+     */
+    public static final int CALLBACK_TYPE_MATCH_LOST = 4;
+
+
+    /**
+     * Determines how many advertisements to match per filter, as this is scarce hw resource
+     */
+    /**
+     * Match one advertisement per filter
+     */
+    public static final int MATCH_NUM_ONE_ADVERTISEMENT = 1;
+
+    /**
+     * Match few advertisement per filter, depends on current capability and availibility of
+     * the resources in hw
+     */
+    public static final int MATCH_NUM_FEW_ADVERTISEMENT = 2;
+
+    /**
+     * Match as many advertisement per filter as hw could allow, depends on current
+     * capability and availibility of the resources in hw
+     */
+    public static final int MATCH_NUM_MAX_ADVERTISEMENT = 3;
+
+
+    /**
+     * In Aggressive mode, hw will determine a match sooner even with feeble signal strength
+     * and few number of sightings/match in a duration.
+     */
+    public static final int MATCH_MODE_AGGRESSIVE = 1;
+
+    /**
+     * For sticky mode, higher threshold of signal strength and sightings is required
+     * before reporting by hw
+     */
+    public static final int MATCH_MODE_STICKY = 2;
+
+    /**
+     * Request full scan results which contain the device, rssi, advertising data, scan response
+     * as well as the scan timestamp.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int SCAN_RESULT_TYPE_FULL = 0;
+
+    /**
+     * Request abbreviated scan results which contain the device, rssi and scan timestamp.
+     * <p>
+     * <b>Note:</b> It is possible for an application to get more scan results than it asked for, if
+     * there are multiple apps using this type.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int SCAN_RESULT_TYPE_ABBREVIATED = 1;
+
+    /**
+     * Use all supported PHYs for scanning.
+     * This will check the controller capabilities, and start
+     * the scan on 1Mbit and LE Coded PHYs if supported, or on
+     * the 1Mbit PHY only.
+     */
+    public static final int PHY_LE_ALL_SUPPORTED = 255;
+
+    // Bluetooth LE scan mode.
+    private int mScanMode;
+
+    // Bluetooth LE scan callback type
+    private int mCallbackType;
+
+    // Bluetooth LE scan result type
+    private int mScanResultType;
+
+    // Time of delay for reporting the scan result
+    private long mReportDelayMillis;
+
+    private int mMatchMode;
+
+    private int mNumOfMatchesPerFilter;
+
+    // Include only legacy advertising results
+    private boolean mLegacy;
+
+    private int mPhy;
+
+    public int getScanMode() {
+        return mScanMode;
+    }
+
+    public int getCallbackType() {
+        return mCallbackType;
+    }
+
+    public int getScanResultType() {
+        return mScanResultType;
+    }
+
+    /**
+     * @hide
+     */
+    public int getMatchMode() {
+        return mMatchMode;
+    }
+
+    /**
+     * @hide
+     */
+    public int getNumOfMatches() {
+        return mNumOfMatchesPerFilter;
+    }
+
+    /**
+     * Returns whether only legacy advertisements will be returned.
+     * Legacy advertisements include advertisements as specified
+     * by the Bluetooth core specification 4.2 and below.
+     */
+    public boolean getLegacy() {
+        return mLegacy;
+    }
+
+    /**
+     * Returns the physical layer used during a scan.
+     */
+    public int getPhy() {
+        return mPhy;
+    }
+
+    /**
+     * Returns report delay timestamp based on the device clock.
+     */
+    public long getReportDelayMillis() {
+        return mReportDelayMillis;
+    }
+
+    private ScanSettings(int scanMode, int callbackType, int scanResultType,
+            long reportDelayMillis, int matchMode,
+            int numOfMatchesPerFilter, boolean legacy, int phy) {
+        mScanMode = scanMode;
+        mCallbackType = callbackType;
+        mScanResultType = scanResultType;
+        mReportDelayMillis = reportDelayMillis;
+        mNumOfMatchesPerFilter = numOfMatchesPerFilter;
+        mMatchMode = matchMode;
+        mLegacy = legacy;
+        mPhy = phy;
+    }
+
+    private ScanSettings(Parcel in) {
+        mScanMode = in.readInt();
+        mCallbackType = in.readInt();
+        mScanResultType = in.readInt();
+        mReportDelayMillis = in.readLong();
+        mMatchMode = in.readInt();
+        mNumOfMatchesPerFilter = in.readInt();
+        mLegacy = in.readInt() != 0;
+        mPhy = in.readInt();
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mScanMode);
+        dest.writeInt(mCallbackType);
+        dest.writeInt(mScanResultType);
+        dest.writeLong(mReportDelayMillis);
+        dest.writeInt(mMatchMode);
+        dest.writeInt(mNumOfMatchesPerFilter);
+        dest.writeInt(mLegacy ? 1 : 0);
+        dest.writeInt(mPhy);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ScanSettings> CREATOR =
+            new Creator<ScanSettings>() {
+        @Override
+        public ScanSettings[] newArray(int size) {
+            return new ScanSettings[size];
+        }
+
+        @Override
+        public ScanSettings createFromParcel(Parcel in) {
+            return new ScanSettings(in);
+        }
+    };
+
+    /**
+     * Builder for {@link ScanSettings}.
+     */
+    public static final class Builder {
+        private int mScanMode = SCAN_MODE_LOW_POWER;
+        private int mCallbackType = CALLBACK_TYPE_ALL_MATCHES;
+        private int mScanResultType = SCAN_RESULT_TYPE_FULL;
+        private long mReportDelayMillis = 0;
+        private int mMatchMode = MATCH_MODE_AGGRESSIVE;
+        private int mNumOfMatchesPerFilter = MATCH_NUM_MAX_ADVERTISEMENT;
+        private boolean mLegacy = true;
+        private int mPhy = PHY_LE_ALL_SUPPORTED;
+
+        /**
+         * Set scan mode for Bluetooth LE scan.
+         *
+         * @param scanMode The scan mode can be one of {@link ScanSettings#SCAN_MODE_LOW_POWER},
+         * {@link ScanSettings#SCAN_MODE_BALANCED} or {@link ScanSettings#SCAN_MODE_LOW_LATENCY}.
+         * @throws IllegalArgumentException If the {@code scanMode} is invalid.
+         */
+        public Builder setScanMode(int scanMode) {
+            if (scanMode < SCAN_MODE_OPPORTUNISTIC || scanMode > SCAN_MODE_LOW_LATENCY) {
+                throw new IllegalArgumentException("invalid scan mode " + scanMode);
+            }
+            mScanMode = scanMode;
+            return this;
+        }
+
+        /**
+         * Set callback type for Bluetooth LE scan.
+         *
+         * @param callbackType The callback type flags for the scan.
+         * @throws IllegalArgumentException If the {@code callbackType} is invalid.
+         */
+        public Builder setCallbackType(int callbackType) {
+
+            if (!isValidCallbackType(callbackType)) {
+                throw new IllegalArgumentException("invalid callback type - " + callbackType);
+            }
+            mCallbackType = callbackType;
+            return this;
+        }
+
+        // Returns true if the callbackType is valid.
+        private boolean isValidCallbackType(int callbackType) {
+            if (callbackType == CALLBACK_TYPE_ALL_MATCHES
+                    || callbackType == CALLBACK_TYPE_FIRST_MATCH
+                    || callbackType == CALLBACK_TYPE_MATCH_LOST) {
+                return true;
+            }
+            return callbackType == (CALLBACK_TYPE_FIRST_MATCH | CALLBACK_TYPE_MATCH_LOST);
+        }
+
+        /**
+         * Set scan result type for Bluetooth LE scan.
+         *
+         * @param scanResultType Type for scan result, could be either {@link
+         * ScanSettings#SCAN_RESULT_TYPE_FULL} or {@link ScanSettings#SCAN_RESULT_TYPE_ABBREVIATED}.
+         * @throws IllegalArgumentException If the {@code scanResultType} is invalid.
+         * @hide
+         */
+        @SystemApi
+        public Builder setScanResultType(int scanResultType) {
+            if (scanResultType < SCAN_RESULT_TYPE_FULL
+                    || scanResultType > SCAN_RESULT_TYPE_ABBREVIATED) {
+                throw new IllegalArgumentException(
+                        "invalid scanResultType - " + scanResultType);
+            }
+            mScanResultType = scanResultType;
+            return this;
+        }
+
+        /**
+         * Set report delay timestamp for Bluetooth LE scan.
+         *
+         * @param reportDelayMillis Delay of report in milliseconds. Set to 0 to be notified of
+         * results immediately. Values &gt; 0 causes the scan results to be queued up and delivered
+         * after the requested delay or when the internal buffers fill up.
+         * @throws IllegalArgumentException If {@code reportDelayMillis} &lt; 0.
+         */
+        public Builder setReportDelay(long reportDelayMillis) {
+            if (reportDelayMillis < 0) {
+                throw new IllegalArgumentException("reportDelay must be > 0");
+            }
+            mReportDelayMillis = reportDelayMillis;
+            return this;
+        }
+
+        /**
+         * Set the number of matches for Bluetooth LE scan filters hardware match
+         *
+         * @param numOfMatches The num of matches can be one of
+         * {@link ScanSettings#MATCH_NUM_ONE_ADVERTISEMENT}
+         * or {@link ScanSettings#MATCH_NUM_FEW_ADVERTISEMENT} or {@link
+         * ScanSettings#MATCH_NUM_MAX_ADVERTISEMENT}
+         * @throws IllegalArgumentException If the {@code matchMode} is invalid.
+         */
+        public Builder setNumOfMatches(int numOfMatches) {
+            if (numOfMatches < MATCH_NUM_ONE_ADVERTISEMENT
+                    || numOfMatches > MATCH_NUM_MAX_ADVERTISEMENT) {
+                throw new IllegalArgumentException("invalid numOfMatches " + numOfMatches);
+            }
+            mNumOfMatchesPerFilter = numOfMatches;
+            return this;
+        }
+
+        /**
+         * Set match mode for Bluetooth LE scan filters hardware match
+         *
+         * @param matchMode The match mode can be one of {@link ScanSettings#MATCH_MODE_AGGRESSIVE}
+         * or {@link ScanSettings#MATCH_MODE_STICKY}
+         * @throws IllegalArgumentException If the {@code matchMode} is invalid.
+         */
+        public Builder setMatchMode(int matchMode) {
+            if (matchMode < MATCH_MODE_AGGRESSIVE
+                    || matchMode > MATCH_MODE_STICKY) {
+                throw new IllegalArgumentException("invalid matchMode " + matchMode);
+            }
+            mMatchMode = matchMode;
+            return this;
+        }
+
+        /**
+         * Set whether only legacy advertisments should be returned in scan results.
+         * Legacy advertisements include advertisements as specified by the
+         * Bluetooth core specification 4.2 and below. This is true by default
+         * for compatibility with older apps.
+         *
+         * @param legacy true if only legacy advertisements will be returned
+         */
+        public Builder setLegacy(boolean legacy) {
+            mLegacy = legacy;
+            return this;
+        }
+
+        /**
+         * Set the Physical Layer to use during this scan.
+         * This is used only if {@link ScanSettings.Builder#setLegacy}
+         * is set to false.
+         * {@link android.bluetooth.BluetoothAdapter#isLeCodedPhySupported}
+         * may be used to check whether LE Coded phy is supported by calling
+         * {@link android.bluetooth.BluetoothAdapter#isLeCodedPhySupported}.
+         * Selecting an unsupported phy will result in failure to start scan.
+         *
+         * @param phy Can be one of {@link BluetoothDevice#PHY_LE_1M}, {@link
+         * BluetoothDevice#PHY_LE_CODED} or {@link ScanSettings#PHY_LE_ALL_SUPPORTED}
+         */
+        public Builder setPhy(int phy) {
+            mPhy = phy;
+            return this;
+        }
+
+        /**
+         * Build {@link ScanSettings}.
+         */
+        public ScanSettings build() {
+            return new ScanSettings(mScanMode, mCallbackType, mScanResultType,
+                    mReportDelayMillis, mMatchMode,
+                    mNumOfMatchesPerFilter, mLegacy, mPhy);
+        }
+    }
+}
diff --git a/android/bluetooth/le/TruncatedFilter.java b/android/bluetooth/le/TruncatedFilter.java
new file mode 100644
index 0000000..a753aa6
--- /dev/null
+++ b/android/bluetooth/le/TruncatedFilter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2014 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.le;
+
+import android.annotation.SystemApi;
+
+import java.util.List;
+
+/**
+ * A special scan filter that lets the client decide how the scan record should be stored.
+ *
+ * @hide
+ */
+@SystemApi
+public final class TruncatedFilter {
+    private final ScanFilter mFilter;
+    private final List<ResultStorageDescriptor> mStorageDescriptors;
+
+    /**
+     * Constructor for {@link TruncatedFilter}.
+     *
+     * @param filter Scan filter of the truncated filter.
+     * @param storageDescriptors Describes how the scan should be stored.
+     */
+    public TruncatedFilter(ScanFilter filter, List<ResultStorageDescriptor> storageDescriptors) {
+        mFilter = filter;
+        mStorageDescriptors = storageDescriptors;
+    }
+
+    /**
+     * Returns the scan filter.
+     */
+    public ScanFilter getFilter() {
+        return mFilter;
+    }
+
+    /**
+     * Returns a list of descriptor for scan result storage.
+     */
+    public List<ResultStorageDescriptor> getStorageDescriptors() {
+        return mStorageDescriptors;
+    }
+
+
+}