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—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—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 > 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} < 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;
+ }
+
+
+}