Migrating frameworks/base BT files
Bug: 206121418 Test: Compile and Test Merged-In: Idb55371e9d678296fe46e5f4231ec2d12ec8b978 Change-Id: Idb55371e9d678296fe46e5f4231ec2d12ec8b978
This commit is contained in:
@@ -69,6 +69,7 @@ filegroup {
|
||||
// Java/AIDL sources under frameworks/base
|
||||
":framework-annotations",
|
||||
":framework-blobstore-sources",
|
||||
":framework-bluetooth-sources", // TODO(b/214988855) : Remove once framework-bluetooth jar is ready
|
||||
":framework-connectivity-tiramisu-sources",
|
||||
":framework-core-sources",
|
||||
":framework-drm-sources",
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.AttributionSource;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Marker interface for a class which can have an {@link AttributionSource}
|
||||
* assigned to it; these are typically {@link android.os.Parcelable} classes
|
||||
* which need to be updated after crossing Binder transaction boundaries.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public interface Attributable {
|
||||
void setAttributionSource(@NonNull AttributionSource attributionSource);
|
||||
|
||||
static @Nullable <T extends Attributable> T setAttributionSource(
|
||||
@Nullable T attributable,
|
||||
@NonNull AttributionSource attributionSource) {
|
||||
if (attributable != null) {
|
||||
attributable.setAttributionSource(attributionSource);
|
||||
}
|
||||
return attributable;
|
||||
}
|
||||
|
||||
static @Nullable <T extends Attributable> List<T> setAttributionSource(
|
||||
@Nullable List<T> attributableList,
|
||||
@NonNull AttributionSource attributionSource) {
|
||||
if (attributableList != null) {
|
||||
final int size = attributableList.size();
|
||||
for (int i = 0; i < size; i++) {
|
||||
setAttributionSource(attributableList.get(i), attributionSource);
|
||||
}
|
||||
}
|
||||
return attributableList;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,516 +0,0 @@
|
||||
/*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
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.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 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")
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_CONNECTION_STATE_CHANGED =
|
||||
"android.bluetooth.a2dp-sink.profile.action.CONNECTION_STATE_CHANGED";
|
||||
|
||||
private final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
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(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothA2dp proxy object for interacting with the local
|
||||
* Bluetooth A2DP service.
|
||||
*/
|
||||
/* package */ BluetoothA2dpSink(Context context, ServiceListener listener,
|
||||
BluetoothAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean connect(BluetoothDevice device) {
|
||||
if (DBG) log("connect(" + device + ")");
|
||||
final IBluetoothA2dpSink service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.connect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean disconnect(BluetoothDevice device) {
|
||||
if (DBG) log("disconnect(" + device + ")");
|
||||
final IBluetoothA2dpSink service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.disconnect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getConnectedDevices() {
|
||||
if (VDBG) log("getConnectedDevices()");
|
||||
final IBluetoothA2dpSink service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
if (VDBG) log("getDevicesMatchingStates()");
|
||||
final IBluetoothA2dpSink service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getConnectionState(BluetoothDevice device) {
|
||||
if (VDBG) log("getConnectionState(" + device + ")");
|
||||
final IBluetoothA2dpSink service = getService();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current audio configuration for the A2DP source device,
|
||||
* or null if the device has no audio configuration
|
||||
*
|
||||
* @param device Remote bluetooth device.
|
||||
* @return audio configuration for the device, or null
|
||||
*
|
||||
* {@see BluetoothAudioConfig}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public BluetoothAudioConfig getAudioConfig(BluetoothDevice device) {
|
||||
if (VDBG) log("getAudioConfig(" + device + ")");
|
||||
final IBluetoothA2dpSink service = getService();
|
||||
final BluetoothAudioConfig defaultValue = null;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<BluetoothAudioConfig> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getAudioConfig(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED
|
||||
})
|
||||
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
|
||||
@ConnectionPolicy int connectionPolicy) {
|
||||
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
|
||||
final IBluetoothA2dpSink service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("getConnectionPolicy(" + device + ")");
|
||||
final IBluetoothA2dpSink service = getService();
|
||||
final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionPolicy(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean isAudioPlaying(@NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("isAudioPlaying(" + device + ")");
|
||||
final IBluetoothA2dpSink service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.isA2dpPlaying(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,199 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.ElapsedRealtimeLong;
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Record of energy and activity information from controller and
|
||||
* underlying bt stack state.Timestamp the record with system
|
||||
* time.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||||
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 List<UidTraffic> mUidTraffic;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = { "BT_STACK_STATE_" }, value = {
|
||||
BT_STACK_STATE_INVALID,
|
||||
BT_STACK_STATE_STATE_ACTIVE,
|
||||
BT_STACK_STATE_STATE_SCANNING,
|
||||
BT_STACK_STATE_STATE_IDLE
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface BluetoothStackState {}
|
||||
|
||||
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;
|
||||
|
||||
/** @hide */
|
||||
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;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
private BluetoothActivityEnergyInfo(Parcel in) {
|
||||
mTimestamp = in.readLong();
|
||||
mBluetoothStackState = in.readInt();
|
||||
mControllerTxTimeMs = in.readLong();
|
||||
mControllerRxTimeMs = in.readLong();
|
||||
mControllerIdleTimeMs = in.readLong();
|
||||
mControllerEnergyUsed = in.readLong();
|
||||
mUidTraffic = in.createTypedArrayList(UidTraffic.CREATOR);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public String toString() {
|
||||
return "BluetoothActivityEnergyInfo{"
|
||||
+ " mTimestamp=" + mTimestamp
|
||||
+ " mBluetoothStackState=" + mBluetoothStackState
|
||||
+ " mControllerTxTimeMs=" + mControllerTxTimeMs
|
||||
+ " mControllerRxTimeMs=" + mControllerRxTimeMs
|
||||
+ " mControllerIdleTimeMs=" + mControllerIdleTimeMs
|
||||
+ " mControllerEnergyUsed=" + mControllerEnergyUsed
|
||||
+ " mUidTraffic=" + mUidTraffic
|
||||
+ " }";
|
||||
}
|
||||
|
||||
public static final @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];
|
||||
}
|
||||
};
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
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.writeTypedList(mUidTraffic);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Bluetooth stack state associated with the energy info.
|
||||
*
|
||||
* @return one of {@link #BluetoothStackState} states
|
||||
*/
|
||||
@BluetoothStackState
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the 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 @ElapsedRealtimeLong long getTimestampMillis() {
|
||||
return mTimestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link List} of each application {@link android.bluetooth.UidTraffic}.
|
||||
*
|
||||
* @return current {@link List} of {@link android.bluetooth.UidTraffic}
|
||||
*/
|
||||
public @NonNull List<UidTraffic> getUidTraffic() {
|
||||
if (mUidTraffic == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return mUidTraffic;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void setUidTraffic(List<UidTraffic> traffic) {
|
||||
mUidTraffic = traffic;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true if the record Tx time, Rx time, and Idle time are more than 0.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return ((mControllerTxTimeMs >= 0) && (mControllerRxTimeMs >= 0)
|
||||
&& (mControllerIdleTimeMs >= 0));
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,117 +0,0 @@
|
||||
/*
|
||||
* 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.Nullable;
|
||||
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(@Nullable 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;
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
@@ -1,298 +0,0 @@
|
||||
/*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
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>
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
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 final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
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(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothAvrcpController proxy object for interacting with the local
|
||||
* Bluetooth AVRCP service.
|
||||
*/
|
||||
/* package */ BluetoothAvrcpController(Context context, ServiceListener listener,
|
||||
BluetoothAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
mProfileConnector.connect(context, listener);
|
||||
}
|
||||
|
||||
/*package*/ void close() {
|
||||
mProfileConnector.disconnect();
|
||||
}
|
||||
|
||||
private IBluetoothAvrcpController getService() {
|
||||
return mProfileConnector.getService();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finalize() {
|
||||
close();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getConnectedDevices() {
|
||||
if (VDBG) log("getConnectedDevices()");
|
||||
final IBluetoothAvrcpController service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
if (VDBG) log("getDevicesMatchingStates()");
|
||||
final IBluetoothAvrcpController service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getConnectionState(BluetoothDevice device) {
|
||||
if (VDBG) log("getState(" + device + ")");
|
||||
final IBluetoothAvrcpController service = getService();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the player application settings.
|
||||
*
|
||||
* @return the {@link BluetoothAvrcpPlayerSettings} or {@link null} if there is an error.
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public BluetoothAvrcpPlayerSettings getPlayerSettings(BluetoothDevice device) {
|
||||
if (DBG) Log.d(TAG, "getPlayerSettings");
|
||||
BluetoothAvrcpPlayerSettings settings = null;
|
||||
final IBluetoothAvrcpController service = getService();
|
||||
final BluetoothAvrcpPlayerSettings defaultValue = null;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<BluetoothAvrcpPlayerSettings> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getPlayerSettings(device, mAttributionSource, recv);
|
||||
settings = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the player app setting for current player.
|
||||
* returns true in case setting is supported by remote, false otherwise
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean setPlayerApplicationSetting(BluetoothAvrcpPlayerSettings plAppSetting) {
|
||||
if (DBG) Log.d(TAG, "setPlayerApplicationSetting");
|
||||
final IBluetoothAvrcpController service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setPlayerApplicationSetting(plAppSetting, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Group Navigation Command to Remote.
|
||||
* possible keycode values: next_grp, previous_grp defined above
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver recv = new SynchronousResultReceiver();
|
||||
service.sendGroupNavigationCmd(device, keyCode, keyState, mAttributionSource, recv);
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
|
||||
return;
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,441 +0,0 @@
|
||||
/*
|
||||
* 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.Nullable;
|
||||
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(@Nullable 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 LE_AUDIO = 0x004000;
|
||||
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 getMajorDeviceClass() == 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 getMajorDeviceClass() == Device.Major.NETWORKING;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,807 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.Nullable;
|
||||
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.
|
||||
* <p>Contains the source codec type, the codec priority, the codec sample
|
||||
* rate, the codec bits per sample, and the codec channel mode.
|
||||
* <p>The source codec type values are the same as those supported by the
|
||||
* device hardware.
|
||||
*
|
||||
* {@see BluetoothA2dp}
|
||||
*/
|
||||
public final class BluetoothCodecConfig implements Parcelable {
|
||||
/** @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_INVALID
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface SourceCodecType {}
|
||||
|
||||
/**
|
||||
* Source codec type SBC. This is the mandatory source codec
|
||||
* type.
|
||||
*/
|
||||
public static final int SOURCE_CODEC_TYPE_SBC = 0;
|
||||
|
||||
/**
|
||||
* Source codec type AAC.
|
||||
*/
|
||||
public static final int SOURCE_CODEC_TYPE_AAC = 1;
|
||||
|
||||
/**
|
||||
* Source codec type APTX.
|
||||
*/
|
||||
public static final int SOURCE_CODEC_TYPE_APTX = 2;
|
||||
|
||||
/**
|
||||
* Source codec type APTX HD.
|
||||
*/
|
||||
public static final int SOURCE_CODEC_TYPE_APTX_HD = 3;
|
||||
|
||||
/**
|
||||
* Source codec type LDAC.
|
||||
*/
|
||||
public static final int SOURCE_CODEC_TYPE_LDAC = 4;
|
||||
|
||||
/**
|
||||
* Source codec type invalid. This is the default value used for codec
|
||||
* type.
|
||||
*/
|
||||
public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
|
||||
|
||||
/**
|
||||
* Represents the count of valid source codec types. Can be accessed via
|
||||
* {@link #getMaxCodecType}.
|
||||
*/
|
||||
private static final int SOURCE_CODEC_TYPE_MAX = 5;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = "CODEC_PRIORITY_", value = {
|
||||
CODEC_PRIORITY_DISABLED,
|
||||
CODEC_PRIORITY_DEFAULT,
|
||||
CODEC_PRIORITY_HIGHEST
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface CodecPriority {}
|
||||
|
||||
/**
|
||||
* Codec priority disabled.
|
||||
* Used to indicate that this codec is disabled and should not be used.
|
||||
*/
|
||||
public static final int CODEC_PRIORITY_DISABLED = -1;
|
||||
|
||||
/**
|
||||
* Codec priority default.
|
||||
* Default value used for codec priority.
|
||||
*/
|
||||
public static final int CODEC_PRIORITY_DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* Codec priority highest.
|
||||
* Used to indicate the highest priority a codec can have.
|
||||
*/
|
||||
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 {}
|
||||
|
||||
/**
|
||||
* Codec sample rate 0 Hz. Default value used for
|
||||
* codec sample rate.
|
||||
*/
|
||||
public static final int SAMPLE_RATE_NONE = 0;
|
||||
|
||||
/**
|
||||
* Codec sample rate 44100 Hz.
|
||||
*/
|
||||
public static final int SAMPLE_RATE_44100 = 0x1 << 0;
|
||||
|
||||
/**
|
||||
* Codec sample rate 48000 Hz.
|
||||
*/
|
||||
public static final int SAMPLE_RATE_48000 = 0x1 << 1;
|
||||
|
||||
/**
|
||||
* Codec sample rate 88200 Hz.
|
||||
*/
|
||||
public static final int SAMPLE_RATE_88200 = 0x1 << 2;
|
||||
|
||||
/**
|
||||
* Codec sample rate 96000 Hz.
|
||||
*/
|
||||
public static final int SAMPLE_RATE_96000 = 0x1 << 3;
|
||||
|
||||
/**
|
||||
* Codec sample rate 176400 Hz.
|
||||
*/
|
||||
public static final int SAMPLE_RATE_176400 = 0x1 << 4;
|
||||
|
||||
/**
|
||||
* Codec sample rate 192000 Hz.
|
||||
*/
|
||||
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 {}
|
||||
|
||||
/**
|
||||
* Codec bits per sample 0. Default value of the codec
|
||||
* bits per sample.
|
||||
*/
|
||||
public static final int BITS_PER_SAMPLE_NONE = 0;
|
||||
|
||||
/**
|
||||
* Codec bits per sample 16.
|
||||
*/
|
||||
public static final int BITS_PER_SAMPLE_16 = 0x1 << 0;
|
||||
|
||||
/**
|
||||
* Codec bits per sample 24.
|
||||
*/
|
||||
public static final int BITS_PER_SAMPLE_24 = 0x1 << 1;
|
||||
|
||||
/**
|
||||
* Codec bits per sample 32.
|
||||
*/
|
||||
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 {}
|
||||
|
||||
/**
|
||||
* Codec channel mode NONE. Default value of the
|
||||
* codec channel mode.
|
||||
*/
|
||||
public static final int CHANNEL_MODE_NONE = 0;
|
||||
|
||||
/**
|
||||
* Codec channel mode MONO.
|
||||
*/
|
||||
public static final int CHANNEL_MODE_MONO = 0x1 << 0;
|
||||
|
||||
/**
|
||||
* Codec channel mode STEREO.
|
||||
*/
|
||||
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;
|
||||
|
||||
/**
|
||||
* Creates a new BluetoothCodecConfig.
|
||||
*
|
||||
* @param codecType the source codec type
|
||||
* @param codecPriority the priority of this codec
|
||||
* @param sampleRate the codec sample rate
|
||||
* @param bitsPerSample the bits per sample of this codec
|
||||
* @param channelMode the channel mode of this codec
|
||||
* @param codecSpecific1 the specific value 1
|
||||
* @param codecSpecific2 the specific value 2
|
||||
* @param codecSpecific3 the specific value 3
|
||||
* @param codecSpecific4 the specific value 4
|
||||
* values to 0.
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new BluetoothCodecConfig.
|
||||
* <p> By default, the codec priority will be set
|
||||
* to {@link BluetoothCodecConfig#CODEC_PRIORITY_DEFAULT}, the sample rate to
|
||||
* {@link BluetoothCodecConfig#SAMPLE_RATE_NONE}, the bits per sample to
|
||||
* {@link BluetoothCodecConfig#BITS_PER_SAMPLE_NONE}, the channel mode to
|
||||
* {@link BluetoothCodecConfig#CHANNEL_MODE_NONE}, and all the codec specific
|
||||
* values to 0.
|
||||
*
|
||||
* @param codecType the source codec type
|
||||
*/
|
||||
public BluetoothCodecConfig(@SourceCodecType int codecType) {
|
||||
this(codecType, BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT,
|
||||
BluetoothCodecConfig.SAMPLE_RATE_NONE,
|
||||
BluetoothCodecConfig.BITS_PER_SAMPLE_NONE,
|
||||
BluetoothCodecConfig.CHANNEL_MODE_NONE, 0, 0, 0, 0);
|
||||
}
|
||||
|
||||
private BluetoothCodecConfig(Parcel in) {
|
||||
mCodecType = in.readInt();
|
||||
mCodecPriority = in.readInt();
|
||||
mSampleRate = in.readInt();
|
||||
mBitsPerSample = in.readInt();
|
||||
mChannelMode = in.readInt();
|
||||
mCodecSpecific1 = in.readLong();
|
||||
mCodecSpecific2 = in.readLong();
|
||||
mCodecSpecific3 = in.readLong();
|
||||
mCodecSpecific4 = in.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable 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 representation of this BluetoothCodecConfig
|
||||
* based on all the config values.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mCodecType, mCodecPriority, mSampleRate,
|
||||
mBitsPerSample, mChannelMode, mCodecSpecific1,
|
||||
mCodecSpecific2, mCodecSpecific3, mCodecSpecific4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds capability string to an existing string.
|
||||
*
|
||||
* @param prevStr the previous string with the capabilities. Can be a {@code 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(@Nullable String prevStr,
|
||||
@NonNull String capStr) {
|
||||
if (prevStr == null) {
|
||||
return capStr;
|
||||
}
|
||||
return prevStr + "|" + capStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link String} that describes each BluetoothCodecConfig parameter
|
||||
* current value.
|
||||
*/
|
||||
@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 + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
return new BluetoothCodecConfig(in);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec name converted to {@link String}.
|
||||
* @hide
|
||||
*/
|
||||
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 + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the source codec type of this config.
|
||||
*/
|
||||
public @SourceCodecType int getCodecType() {
|
||||
return mCodecType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the valid codec types count.
|
||||
*/
|
||||
public static int getMaxCodecType() {
|
||||
return SOURCE_CODEC_TYPE_MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the codec is mandatory.
|
||||
* <p> The actual mandatory codec type for Android Bluetooth audio is SBC.
|
||||
* See {@link #SOURCE_CODEC_TYPE_SBC}.
|
||||
*
|
||||
* @return {@code true} if the codec is mandatory, {@code false} otherwise
|
||||
* @hide
|
||||
*/
|
||||
public boolean isMandatoryCodec() {
|
||||
return mCodecType == SOURCE_CODEC_TYPE_SBC;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec selection priority.
|
||||
* <p>The codec selection priority is relative to other codecs: larger value
|
||||
* means higher priority.
|
||||
*/
|
||||
public @CodecPriority int getCodecPriority() {
|
||||
return mCodecPriority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the codec selection priority.
|
||||
* <p>The codec selection priority is relative to other codecs: larger value
|
||||
* means higher priority.
|
||||
*
|
||||
* @param codecPriority the priority this codec should have
|
||||
* @hide
|
||||
*/
|
||||
public void setCodecPriority(@CodecPriority int codecPriority) {
|
||||
mCodecPriority = codecPriority;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec sample rate. The value can be a bitmask with all
|
||||
* supported sample rates.
|
||||
*/
|
||||
public @SampleRate int getSampleRate() {
|
||||
return mSampleRate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec bits per sample. The value can be a bitmask with all
|
||||
* bits per sample supported.
|
||||
*/
|
||||
public @BitsPerSample int getBitsPerSample() {
|
||||
return mBitsPerSample;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec channel mode. The value can be a bitmask with all
|
||||
* supported channel modes.
|
||||
*/
|
||||
public @ChannelMode int getChannelMode() {
|
||||
return mChannelMode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec specific value1.
|
||||
*/
|
||||
public long getCodecSpecific1() {
|
||||
return mCodecSpecific1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec specific value2.
|
||||
*/
|
||||
public long getCodecSpecific2() {
|
||||
return mCodecSpecific2;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec specific value3.
|
||||
*/
|
||||
public long getCodecSpecific3() {
|
||||
return mCodecSpecific3;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codec specific value4.
|
||||
*/
|
||||
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 {@code true} if the valueSet contains zero or single bit, {@code false} otherwise
|
||||
* @hide
|
||||
*/
|
||||
private static boolean hasSingleBit(int valueSet) {
|
||||
return (valueSet == 0 || (valueSet & (valueSet - 1)) == 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object contains none or single sample rate.
|
||||
* @hide
|
||||
*/
|
||||
public boolean hasSingleSampleRate() {
|
||||
return hasSingleBit(mSampleRate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object contains none or single bits per sample.
|
||||
* @hide
|
||||
*/
|
||||
public boolean hasSingleBitsPerSample() {
|
||||
return hasSingleBit(mBitsPerSample);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the object contains none or single channel mode.
|
||||
* @hide
|
||||
*/
|
||||
public boolean hasSingleChannelMode() {
|
||||
return hasSingleBit(mChannelMode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the audio feeding parameters are the same.
|
||||
*
|
||||
* @param other the codec config to compare against
|
||||
* @return {@code true} if the audio feeding parameters are same, {@code false} otherwise
|
||||
* @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 {@code true} if the audio feeding parameters are similar, {@code false} otherwise
|
||||
* @hide
|
||||
*/
|
||||
public boolean similarCodecFeedingParameters(BluetoothCodecConfig other) {
|
||||
if (other == null || mCodecType != other.mCodecType) {
|
||||
return false;
|
||||
}
|
||||
int sampleRate = other.mSampleRate;
|
||||
if (mSampleRate == SAMPLE_RATE_NONE
|
||||
|| sampleRate == SAMPLE_RATE_NONE) {
|
||||
sampleRate = mSampleRate;
|
||||
}
|
||||
int bitsPerSample = other.mBitsPerSample;
|
||||
if (mBitsPerSample == BITS_PER_SAMPLE_NONE
|
||||
|| bitsPerSample == BITS_PER_SAMPLE_NONE) {
|
||||
bitsPerSample = mBitsPerSample;
|
||||
}
|
||||
int channelMode = other.mChannelMode;
|
||||
if (mChannelMode == CHANNEL_MODE_NONE
|
||||
|| channelMode == 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.
|
||||
* <p> Currently, only AAC VBR and LDAC Playback Quality on CodecSpecific1
|
||||
* are compared.
|
||||
*
|
||||
* @param other the codec config to compare against
|
||||
* @return {@code true} if the codec specific parameters are the same, {@code false} otherwise
|
||||
* @hide
|
||||
*/
|
||||
public boolean sameCodecSpecificParameters(BluetoothCodecConfig other) {
|
||||
if (other == null && mCodecType != other.mCodecType) {
|
||||
return false;
|
||||
}
|
||||
switch (mCodecType) {
|
||||
case SOURCE_CODEC_TYPE_AAC:
|
||||
case SOURCE_CODEC_TYPE_LDAC:
|
||||
if (mCodecSpecific1 != other.mCodecSpecific1) {
|
||||
return false;
|
||||
}
|
||||
default:
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link BluetoothCodecConfig}.
|
||||
* <p> By default, the codec type will be set to
|
||||
* {@link BluetoothCodecConfig#SOURCE_CODEC_TYPE_INVALID}, the codec priority
|
||||
* to {@link BluetoothCodecConfig#CODEC_PRIORITY_DEFAULT}, the sample rate to
|
||||
* {@link BluetoothCodecConfig#SAMPLE_RATE_NONE}, the bits per sample to
|
||||
* {@link BluetoothCodecConfig#BITS_PER_SAMPLE_NONE}, the channel mode to
|
||||
* {@link BluetoothCodecConfig#CHANNEL_MODE_NONE}, and all the codec specific
|
||||
* values to 0.
|
||||
*/
|
||||
public static final class Builder {
|
||||
private int mCodecType = BluetoothCodecConfig.SOURCE_CODEC_TYPE_INVALID;
|
||||
private int mCodecPriority = BluetoothCodecConfig.CODEC_PRIORITY_DEFAULT;
|
||||
private int mSampleRate = BluetoothCodecConfig.SAMPLE_RATE_NONE;
|
||||
private int mBitsPerSample = BluetoothCodecConfig.BITS_PER_SAMPLE_NONE;
|
||||
private int mChannelMode = BluetoothCodecConfig.CHANNEL_MODE_NONE;
|
||||
private long mCodecSpecific1 = 0;
|
||||
private long mCodecSpecific2 = 0;
|
||||
private long mCodecSpecific3 = 0;
|
||||
private long mCodecSpecific4 = 0;
|
||||
|
||||
/**
|
||||
* Set codec type for Bluetooth codec config.
|
||||
*
|
||||
* @param codecType of this codec
|
||||
* @return the same Builder instance
|
||||
*/
|
||||
public @NonNull Builder setCodecType(@SourceCodecType int codecType) {
|
||||
mCodecType = codecType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set codec priority for Bluetooth codec config.
|
||||
*
|
||||
* @param codecPriority of this codec
|
||||
* @return the same Builder instance
|
||||
*/
|
||||
public @NonNull Builder setCodecPriority(@CodecPriority int codecPriority) {
|
||||
mCodecPriority = codecPriority;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set sample rate for Bluetooth codec config.
|
||||
*
|
||||
* @param sampleRate of this codec
|
||||
* @return the same Builder instance
|
||||
*/
|
||||
public @NonNull Builder setSampleRate(@SampleRate int sampleRate) {
|
||||
mSampleRate = sampleRate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the bits per sample for Bluetooth codec config.
|
||||
*
|
||||
* @param bitsPerSample of this codec
|
||||
* @return the same Builder instance
|
||||
*/
|
||||
public @NonNull Builder setBitsPerSample(@BitsPerSample int bitsPerSample) {
|
||||
mBitsPerSample = bitsPerSample;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the channel mode for Bluetooth codec config.
|
||||
*
|
||||
* @param channelMode of this codec
|
||||
* @return the same Builder instance
|
||||
*/
|
||||
public @NonNull Builder setChannelMode(@ChannelMode int channelMode) {
|
||||
mChannelMode = channelMode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the first codec specific values for Bluetooth codec config.
|
||||
*
|
||||
* @param codecSpecific1 codec specific value or 0 if default
|
||||
* @return the same Builder instance
|
||||
*/
|
||||
public @NonNull Builder setCodecSpecific1(long codecSpecific1) {
|
||||
mCodecSpecific1 = codecSpecific1;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the second codec specific values for Bluetooth codec config.
|
||||
*
|
||||
* @param codecSpecific2 codec specific value or 0 if default
|
||||
* @return the same Builder instance
|
||||
*/
|
||||
public @NonNull Builder setCodecSpecific2(long codecSpecific2) {
|
||||
mCodecSpecific2 = codecSpecific2;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the third codec specific values for Bluetooth codec config.
|
||||
*
|
||||
* @param codecSpecific3 codec specific value or 0 if default
|
||||
* @return the same Builder instance
|
||||
*/
|
||||
public @NonNull Builder setCodecSpecific3(long codecSpecific3) {
|
||||
mCodecSpecific3 = codecSpecific3;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the fourth codec specific values for Bluetooth codec config.
|
||||
*
|
||||
* @param codecSpecific4 codec specific value or 0 if default
|
||||
* @return the same Builder instance
|
||||
*/
|
||||
public @NonNull Builder setCodecSpecific4(long codecSpecific4) {
|
||||
mCodecSpecific4 = codecSpecific4;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build {@link BluetoothCodecConfig}.
|
||||
* @return new BluetoothCodecConfig built
|
||||
*/
|
||||
public @NonNull BluetoothCodecConfig build() {
|
||||
return new BluetoothCodecConfig(mCodecType, mCodecPriority,
|
||||
mSampleRate, mBitsPerSample,
|
||||
mChannelMode, mCodecSpecific1,
|
||||
mCodecSpecific2, mCodecSpecific3,
|
||||
mCodecSpecific4);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,208 +0,0 @@
|
||||
/*
|
||||
* 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.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Represents the codec status (configuration and capability) for a Bluetooth
|
||||
* A2DP source device.
|
||||
*
|
||||
* {@see BluetoothA2dp}
|
||||
*/
|
||||
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 @Nullable List<BluetoothCodecConfig> mCodecsLocalCapabilities;
|
||||
private final @Nullable List<BluetoothCodecConfig> mCodecsSelectableCapabilities;
|
||||
|
||||
public BluetoothCodecStatus(@Nullable BluetoothCodecConfig codecConfig,
|
||||
@Nullable List<BluetoothCodecConfig> codecsLocalCapabilities,
|
||||
@Nullable List<BluetoothCodecConfig> codecsSelectableCapabilities) {
|
||||
mCodecConfig = codecConfig;
|
||||
mCodecsLocalCapabilities = codecsLocalCapabilities;
|
||||
mCodecsSelectableCapabilities = codecsSelectableCapabilities;
|
||||
}
|
||||
|
||||
private BluetoothCodecStatus(Parcel in) {
|
||||
mCodecConfig = in.readTypedObject(BluetoothCodecConfig.CREATOR);
|
||||
mCodecsLocalCapabilities = in.createTypedArrayList(BluetoothCodecConfig.CREATOR);
|
||||
mCodecsSelectableCapabilities = in.createTypedArrayList(BluetoothCodecConfig.CREATOR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(@Nullable 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 lists of capabilities contain same capabilities.
|
||||
* The order of the capabilities in each list is ignored.
|
||||
*
|
||||
* @param c1 the first list of capabilities to compare
|
||||
* @param c2 the second list of capabilities to compare
|
||||
* @return {@code true} if both lists contain same capabilities
|
||||
*/
|
||||
private static boolean sameCapabilities(@Nullable List<BluetoothCodecConfig> c1,
|
||||
@Nullable List<BluetoothCodecConfig> c2) {
|
||||
if (c1 == null) {
|
||||
return (c2 == null);
|
||||
}
|
||||
if (c2 == null) {
|
||||
return false;
|
||||
}
|
||||
if (c1.size() != c2.size()) {
|
||||
return false;
|
||||
}
|
||||
return c1.containsAll(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 {@code true} if the codec config matches, {@code false} otherwise
|
||||
*/
|
||||
public boolean isCodecConfigSelectable(@Nullable 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.
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mCodecConfig, mCodecsLocalCapabilities,
|
||||
mCodecsLocalCapabilities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link String} that describes each BluetoothCodecStatus parameter
|
||||
* current value.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{mCodecConfig:" + mCodecConfig
|
||||
+ ",mCodecsLocalCapabilities:" + mCodecsLocalCapabilities
|
||||
+ ",mCodecsSelectableCapabilities:" + mCodecsSelectableCapabilities
|
||||
+ "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* @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) {
|
||||
return new BluetoothCodecStatus(in);
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel out, int flags) {
|
||||
out.writeTypedObject(mCodecConfig, 0);
|
||||
out.writeTypedList(mCodecsLocalCapabilities);
|
||||
out.writeTypedList(mCodecsSelectableCapabilities);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current codec configuration.
|
||||
*/
|
||||
public @Nullable BluetoothCodecConfig getCodecConfig() {
|
||||
return mCodecConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codecs local capabilities.
|
||||
*/
|
||||
public @NonNull List<BluetoothCodecConfig> getCodecsLocalCapabilities() {
|
||||
return (mCodecsLocalCapabilities == null)
|
||||
? Collections.emptyList() : mCodecsLocalCapabilities;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the codecs selectable capabilities.
|
||||
*/
|
||||
public @NonNull List<BluetoothCodecConfig> getCodecsSelectableCapabilities() {
|
||||
return (mCodecsSelectableCapabilities == null)
|
||||
? Collections.emptyList() : mCodecsSelectableCapabilities;
|
||||
}
|
||||
}
|
||||
@@ -1,555 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 HIMSA II K/S - www.himsa.com.
|
||||
* Represented by EHIMA - www.ehima.com
|
||||
*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.CallbackExecutor;
|
||||
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.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.ParcelUuid;
|
||||
import android.os.RemoteException;
|
||||
import android.util.CloseGuard;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* This class provides the public APIs to control the Bluetooth CSIP set coordinator.
|
||||
*
|
||||
* <p>BluetoothCsipSetCoordinator is a proxy object for controlling the Bluetooth VC
|
||||
* Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
|
||||
* the BluetoothCsipSetCoordinator proxy object.
|
||||
*
|
||||
*/
|
||||
public final class BluetoothCsipSetCoordinator implements BluetoothProfile, AutoCloseable {
|
||||
private static final String TAG = "BluetoothCsipSetCoordinator";
|
||||
private static final boolean DBG = false;
|
||||
private static final boolean VDBG = false;
|
||||
|
||||
private CloseGuard mCloseGuard;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public interface ClientLockCallback {
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi void onGroupLockSet(int groupId, int opStatus, boolean isLocked);
|
||||
}
|
||||
|
||||
private static class BluetoothCsipSetCoordinatorLockCallbackDelegate
|
||||
extends IBluetoothCsipSetCoordinatorLockCallback.Stub {
|
||||
private final ClientLockCallback mCallback;
|
||||
private final Executor mExecutor;
|
||||
|
||||
BluetoothCsipSetCoordinatorLockCallbackDelegate(
|
||||
Executor executor, ClientLockCallback callback) {
|
||||
mExecutor = executor;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGroupLockSet(int groupId, int opStatus, boolean isLocked) {
|
||||
mExecutor.execute(() -> mCallback.onGroupLockSet(groupId, opStatus, isLocked));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Intent used to broadcast the change in connection state of the CSIS
|
||||
* Client.
|
||||
*
|
||||
* <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}.
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_CSIS_CONNECTION_STATE_CHANGED =
|
||||
"android.bluetooth.action.CSIS_CONNECTION_STATE_CHANGED";
|
||||
|
||||
/**
|
||||
* Intent used to expose broadcast receiving device.
|
||||
*
|
||||
* <p>This intent will have 2 extras:
|
||||
* <ul>
|
||||
* <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote Broadcast receiver device. </li>
|
||||
* <li> {@link #EXTRA_CSIS_GROUP_ID} - Group identifier. </li>
|
||||
* <li> {@link #EXTRA_CSIS_GROUP_SIZE} - Group size. </li>
|
||||
* <li> {@link #EXTRA_CSIS_GROUP_TYPE_UUID} - Group type UUID. </li>
|
||||
* </ul>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_CSIS_DEVICE_AVAILABLE =
|
||||
"android.bluetooth.action.CSIS_DEVICE_AVAILABLE";
|
||||
|
||||
/**
|
||||
* Used as an extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent.
|
||||
* Contains the group id.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_CSIS_GROUP_ID = "android.bluetooth.extra.CSIS_GROUP_ID";
|
||||
|
||||
/**
|
||||
* Group size as int extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_CSIS_GROUP_SIZE = "android.bluetooth.extra.CSIS_GROUP_SIZE";
|
||||
|
||||
/**
|
||||
* Group type uuid extra field in {@link #ACTION_CSIS_DEVICE_AVAILABLE} intent.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_CSIS_GROUP_TYPE_UUID =
|
||||
"android.bluetooth.extra.CSIS_GROUP_TYPE_UUID";
|
||||
|
||||
/**
|
||||
* Intent used to broadcast information about identified set member
|
||||
* ready to connect.
|
||||
*
|
||||
* <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>
|
||||
* <li> {@link #EXTRA_CSIS_GROUP_ID} - Group identifier. </li>
|
||||
* </ul>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_CSIS_SET_MEMBER_AVAILABLE =
|
||||
"android.bluetooth.action.CSIS_SET_MEMBER_AVAILABLE";
|
||||
|
||||
/**
|
||||
* This represents an invalid group ID.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_ID_INVALID = IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID;
|
||||
|
||||
/**
|
||||
* Indicating that group was locked with success.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_LOCK_SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* Indicating that group locked failed due to invalid group ID.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_LOCK_FAILED_INVALID_GROUP = 1;
|
||||
|
||||
/**
|
||||
* Indicating that group locked failed due to empty group.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_LOCK_FAILED_GROUP_EMPTY = 2;
|
||||
|
||||
/**
|
||||
* Indicating that group locked failed due to group members being disconnected.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_LOCK_FAILED_GROUP_NOT_CONNECTED = 3;
|
||||
|
||||
/**
|
||||
* Indicating that group locked failed due to group member being already locked.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_LOCK_FAILED_LOCKED_BY_OTHER = 4;
|
||||
|
||||
/**
|
||||
* Indicating that group locked failed due to other reason.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_LOCK_FAILED_OTHER_REASON = 5;
|
||||
|
||||
/**
|
||||
* Indicating that group member in locked state was lost.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LOCKED_GROUP_MEMBER_LOST = 6;
|
||||
|
||||
private final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
private final BluetoothProfileConnector<IBluetoothCsipSetCoordinator> mProfileConnector =
|
||||
new BluetoothProfileConnector(this, BluetoothProfile.CSIP_SET_COORDINATOR, TAG,
|
||||
IBluetoothCsipSetCoordinator.class.getName()) {
|
||||
@Override
|
||||
public IBluetoothCsipSetCoordinator getServiceInterface(IBinder service) {
|
||||
return IBluetoothCsipSetCoordinator.Stub.asInterface(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothCsipSetCoordinator proxy object for interacting with the local
|
||||
* Bluetooth CSIS service.
|
||||
*/
|
||||
/*package*/ BluetoothCsipSetCoordinator(Context context, ServiceListener listener, BluetoothAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
mProfileConnector.connect(context, listener);
|
||||
mCloseGuard = new CloseGuard();
|
||||
mCloseGuard.open("close");
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
protected void finalize() {
|
||||
if (mCloseGuard != null) {
|
||||
mCloseGuard.warnIfOpen();
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void close() {
|
||||
mProfileConnector.disconnect();
|
||||
}
|
||||
|
||||
private IBluetoothCsipSetCoordinator getService() {
|
||||
return mProfileConnector.getService();
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock the set.
|
||||
* @param groupId group ID to lock,
|
||||
* @param executor callback executor,
|
||||
* @param cb callback to report lock and unlock events - stays valid until the app unlocks
|
||||
* using the returned lock identifier or the lock timeouts on the remote side,
|
||||
* as per CSIS specification,
|
||||
* @return unique lock identifier used for unlocking or null if lock has failed.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public
|
||||
@Nullable UUID groupLock(int groupId, @Nullable @CallbackExecutor Executor executor,
|
||||
@Nullable ClientLockCallback cb) {
|
||||
if (VDBG) log("groupLockSet()");
|
||||
final IBluetoothCsipSetCoordinator service = getService();
|
||||
final UUID defaultValue = null;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
IBluetoothCsipSetCoordinatorLockCallback delegate = null;
|
||||
if ((executor != null) && (cb != null)) {
|
||||
delegate = new BluetoothCsipSetCoordinatorLockCallbackDelegate(executor, cb);
|
||||
}
|
||||
try {
|
||||
final SynchronousResultReceiver<ParcelUuid> recv = new SynchronousResultReceiver();
|
||||
service.groupLock(groupId, delegate, mAttributionSource, recv);
|
||||
final ParcelUuid ret = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
|
||||
return ret == null ? defaultValue : ret.getUuid();
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock the set.
|
||||
* @param lockUuid unique lock identifier
|
||||
* @return true if unlocked, false on error
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public boolean groupUnlock(@NonNull UUID lockUuid) {
|
||||
if (VDBG) log("groupLockSet()");
|
||||
if (lockUuid == null) {
|
||||
return false;
|
||||
}
|
||||
final IBluetoothCsipSetCoordinator service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver recv = new SynchronousResultReceiver();
|
||||
service.groupUnlock(new ParcelUuid(lockUuid), mAttributionSource, recv);
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
|
||||
return true;
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get device's groups.
|
||||
* @param device the active device
|
||||
* @return Map of groups ids and related UUIDs
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public @NonNull Map getGroupUuidMapByDevice(@Nullable BluetoothDevice device) {
|
||||
if (VDBG) log("getGroupUuidMapByDevice()");
|
||||
final IBluetoothCsipSetCoordinator service = getService();
|
||||
final Map defaultValue = new HashMap<>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Map> recv = new SynchronousResultReceiver();
|
||||
service.getGroupUuidMapByDevice(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group id for the given UUID
|
||||
* @param uuid
|
||||
* @return list of group IDs
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public @NonNull List<Integer> getAllGroupIds(@Nullable ParcelUuid uuid) {
|
||||
if (VDBG) log("getAllGroupIds()");
|
||||
final IBluetoothCsipSetCoordinator service = getService();
|
||||
final List<Integer> defaultValue = new ArrayList<>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<Integer>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getAllGroupIds(uuid, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public @NonNull List<BluetoothDevice> getConnectedDevices() {
|
||||
if (VDBG) log("getConnectedDevices()");
|
||||
final IBluetoothCsipSetCoordinator service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
@NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(@NonNull int[] states) {
|
||||
if (VDBG) log("getDevicesMatchingStates(states=" + Arrays.toString(states) + ")");
|
||||
final IBluetoothCsipSetCoordinator service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public
|
||||
@BluetoothProfile.BtProfileState int getConnectionState(@Nullable BluetoothDevice device) {
|
||||
if (VDBG) log("getState(" + device + ")");
|
||||
final IBluetoothCsipSetCoordinator service = getService();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(
|
||||
@Nullable BluetoothDevice device, @ConnectionPolicy int connectionPolicy) {
|
||||
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
|
||||
final IBluetoothCsipSetCoordinator service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(@Nullable BluetoothDevice device) {
|
||||
if (VDBG) log("getConnectionPolicy(" + device + ")");
|
||||
final IBluetoothCsipSetCoordinator service = getService();
|
||||
final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionPolicy(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
return mAdapter.getState() == BluetoothAdapter.STATE_ON;
|
||||
}
|
||||
|
||||
private static boolean isValidDevice(@Nullable BluetoothDevice device) {
|
||||
return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
|
||||
}
|
||||
|
||||
private static void log(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* 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.RequiresPermission;
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@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;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,267 +0,0 @@
|
||||
/*
|
||||
* 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.NonNull;
|
||||
|
||||
/**
|
||||
* 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(BluetoothGattCharacteristic)}
|
||||
* @param characteristic Characteristic that was read from the associated remote device.
|
||||
* @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed
|
||||
* successfully.
|
||||
* @deprecated Use {@link BluetoothGattCallback#onCharacteristicRead(BluetoothGatt,
|
||||
* BluetoothGattCharacteristic, byte[], int)} as it is memory safe
|
||||
*/
|
||||
@Deprecated
|
||||
public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic,
|
||||
int status) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback reporting the result of a characteristic read operation.
|
||||
*
|
||||
* @param gatt GATT client invoked
|
||||
* {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)}
|
||||
* @param characteristic Characteristic that was read from the associated remote device.
|
||||
* @param value the value of the characteristic
|
||||
* @param status {@link BluetoothGatt#GATT_SUCCESS} if the read operation was completed
|
||||
* successfully.
|
||||
*/
|
||||
public void onCharacteristicRead(@NonNull BluetoothGatt gatt, @NonNull
|
||||
BluetoothGattCharacteristic characteristic, @NonNull byte[] value, 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 that invoked
|
||||
* {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic,
|
||||
* byte[], int)}
|
||||
* @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.
|
||||
* @deprecated Use {@link BluetoothGattCallback#onCharacteristicChanged(BluetoothGatt,
|
||||
* BluetoothGattCharacteristic, byte[])} as it is memory safe by providing the characteristic
|
||||
* value at the time of notification.
|
||||
*/
|
||||
@Deprecated
|
||||
public void onCharacteristicChanged(BluetoothGatt gatt,
|
||||
BluetoothGattCharacteristic characteristic) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback triggered as a result of a remote characteristic notification. Note that the value
|
||||
* within the characteristic object may have changed since receiving the remote characteristic
|
||||
* notification, so check the parameter value for the value at the time of 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.
|
||||
* @param value notified characteristic value
|
||||
*/
|
||||
public void onCharacteristicChanged(@NonNull BluetoothGatt gatt,
|
||||
@NonNull BluetoothGattCharacteristic characteristic, @NonNull byte[] value) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @deprecated Use {@link BluetoothGattCallback#onDescriptorRead(BluetoothGatt,
|
||||
* BluetoothGattDescriptor, int, byte[])} as it is memory safe by providing the descriptor
|
||||
* value at the time it was read.
|
||||
*/
|
||||
@Deprecated
|
||||
public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor,
|
||||
int status) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param value the descriptor value at the time of the read operation
|
||||
*/
|
||||
public void onDescriptorRead(@NonNull BluetoothGatt gatt,
|
||||
@NonNull BluetoothGattDescriptor descriptor, int status, @NonNull byte[] value) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Worker 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) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Callback indicating service changed event is received
|
||||
*
|
||||
* <p>Receiving this event means that the GATT database is out of sync with
|
||||
* the remote device. {@link BluetoothGatt#discoverServices} should be
|
||||
* called to re-discover the services.
|
||||
*
|
||||
* @param gatt GATT client involved
|
||||
*/
|
||||
public void onServiceChanged(@NonNull BluetoothGatt gatt) {
|
||||
}
|
||||
}
|
||||
@@ -1,806 +0,0 @@
|
||||
/*
|
||||
* 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 person-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 person-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
|
||||
* person-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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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(BluetoothGattCharacteristic, byte[], int)} 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
|
||||
*
|
||||
* @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
*
|
||||
* @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} to get
|
||||
* the characteristic value
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
*
|
||||
* @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} to get
|
||||
* the characteristic value
|
||||
*/
|
||||
@Deprecated
|
||||
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
|
||||
*
|
||||
* @deprecated Use {@link BluetoothGatt#readCharacteristic(BluetoothGattCharacteristic)} to get
|
||||
* the characteristic value
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
*
|
||||
* @deprecated Pass the characteristic value directly into
|
||||
* {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)}
|
||||
*/
|
||||
@Deprecated
|
||||
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
|
||||
*
|
||||
* @deprecated Pass the characteristic value directly into
|
||||
* {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)}
|
||||
*/
|
||||
@Deprecated
|
||||
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
|
||||
*
|
||||
* @deprecated Pass the characteristic value directly into
|
||||
* {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)}
|
||||
*/
|
||||
@Deprecated
|
||||
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
|
||||
*
|
||||
* @deprecated Pass the characteristic value directly into
|
||||
* {@link BluetoothGatt#writeCharacteristic(BluetoothGattCharacteristic, byte[], int)}
|
||||
*/
|
||||
@Deprecated
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,291 +0,0 @@
|
||||
/*
|
||||
* 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 person-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 person-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
|
||||
* person-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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @deprecated Use {@link BluetoothGatt#readDescriptor(BluetoothGattDescriptor)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
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.
|
||||
*
|
||||
* @deprecated Pass the descriptor value directly into
|
||||
* {@link BluetoothGatt#writeDescriptor(BluetoothGattDescriptor, byte[])}
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean setValue(byte[] value) {
|
||||
mValue = value;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,954 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.RequiresNoPermission;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
import android.content.AttributionSource;
|
||||
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;
|
||||
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 final IBluetoothGatt mService;
|
||||
private final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
|
||||
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
|
||||
*/
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
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,
|
||||
BluetoothAdapter adapter) {
|
||||
mService = iGatt;
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
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.
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
/*package*/ boolean registerCallback(BluetoothGattServerCallback callback) {
|
||||
return registerCallback(callback, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param callback GATT callback handler that will receive asynchronous callbacks.
|
||||
* @param eatt_support indicates if server can use eatt
|
||||
* @return true, the callback will be called to notify success or failure, false on immediate
|
||||
* error
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
/*package*/ boolean registerCallback(BluetoothGattServerCallback callback,
|
||||
boolean eatt_support) {
|
||||
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,
|
||||
eatt_support, mAttributionSource);
|
||||
} 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.
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
private void unregisterCallback() {
|
||||
if (DBG) Log.d(TAG, "unregisterCallback() - mServerIf=" + mServerIf);
|
||||
if (mService == null || mServerIf == 0) return;
|
||||
|
||||
try {
|
||||
mCallback = null;
|
||||
mService.unregisterServer(mServerIf, mAttributionSource);
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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, mAttributionSource);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects an established connection, or cancels a connection attempt
|
||||
* currently in progress.
|
||||
*
|
||||
* @param device Remote device
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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(), mAttributionSource);
|
||||
} 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}
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public void setPreferredPhy(BluetoothDevice device, int txPhy, int rxPhy, int phyOptions) {
|
||||
try {
|
||||
mService.serverSetPreferredPhy(mServerIf, device.getAddress(), txPhy, rxPhy,
|
||||
phyOptions, mAttributionSource);
|
||||
} 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public void readPhy(BluetoothDevice device) {
|
||||
try {
|
||||
mService.serverReadPhy(mServerIf, device.getAddress(), mAttributionSource);
|
||||
} 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>
|
||||
*
|
||||
* @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)
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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, mAttributionSource);
|
||||
} 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.
|
||||
*
|
||||
* @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
|
||||
*
|
||||
* @deprecated Use {@link BluetoothGattServer#notifyCharacteristicChanged(BluetoothDevice,
|
||||
* BluetoothGattCharacteristic, boolean, byte[])} as this is not memory safe.
|
||||
*/
|
||||
@Deprecated
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean notifyCharacteristicChanged(BluetoothDevice device,
|
||||
BluetoothGattCharacteristic characteristic, boolean confirm) {
|
||||
return notifyCharacteristicChanged(device, characteristic, confirm,
|
||||
characteristic.getValue()) == BluetoothStatusCodes.SUCCESS;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(value = {
|
||||
BluetoothStatusCodes.SUCCESS,
|
||||
BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION,
|
||||
BluetoothStatusCodes.ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION,
|
||||
BluetoothStatusCodes.ERROR_DEVICE_NOT_CONNECTED,
|
||||
BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND,
|
||||
BluetoothStatusCodes.ERROR_GATT_WRITE_NOT_ALLOWED,
|
||||
BluetoothStatusCodes.ERROR_GATT_WRITE_REQUEST_BUSY,
|
||||
BluetoothStatusCodes.ERROR_UNKNOWN
|
||||
})
|
||||
public @interface NotifyCharacteristicReturnValues{}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @param device the remote device to receive the notification/indication
|
||||
* @param characteristic the local characteristic that has been updated
|
||||
* @param confirm {@code true} to request confirmation from the client (indication) or
|
||||
* {@code false} to send a notification
|
||||
* @param value the characteristic value
|
||||
* @return whether the notification has been triggered successfully
|
||||
* @throws IllegalArgumentException if the characteristic value or service is null
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@NotifyCharacteristicReturnValues
|
||||
public int notifyCharacteristicChanged(@NonNull BluetoothDevice device,
|
||||
@NonNull BluetoothGattCharacteristic characteristic, boolean confirm,
|
||||
@NonNull byte[] value) {
|
||||
if (VDBG) Log.d(TAG, "notifyCharacteristicChanged() - device: " + device.getAddress());
|
||||
if (mService == null || mServerIf == 0) {
|
||||
return BluetoothStatusCodes.ERROR_PROFILE_SERVICE_NOT_BOUND;
|
||||
}
|
||||
|
||||
if (characteristic == null) {
|
||||
throw new IllegalArgumentException("characteristic must not be null");
|
||||
}
|
||||
if (device == null) {
|
||||
throw new IllegalArgumentException("device must not be null");
|
||||
}
|
||||
BluetoothGattService service = characteristic.getService();
|
||||
if (service == null) {
|
||||
throw new IllegalArgumentException("Characteristic must have a non-null service");
|
||||
}
|
||||
if (value == null) {
|
||||
throw new IllegalArgumentException("Characteristic value must not be null");
|
||||
}
|
||||
|
||||
try {
|
||||
return mService.sendNotification(mServerIf, device.getAddress(),
|
||||
characteristic.getInstanceId(), confirm,
|
||||
value, mAttributionSource);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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, mAttributionSource);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a service from the list of services to be provided.
|
||||
*
|
||||
* @param service Service to be removed.
|
||||
* @return true, if the service has been removed
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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(), mAttributionSource);
|
||||
mServices.remove(intService);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all services from the list of provided services.
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public void clearServices() {
|
||||
if (DBG) Log.d(TAG, "clearServices()");
|
||||
if (mService == null || mServerIf == 0) return;
|
||||
|
||||
try {
|
||||
mService.clearServices(mServerIf, mAttributionSource);
|
||||
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.
|
||||
*
|
||||
* @return List of services. Returns an empty list if no services have been added yet.
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresNoPermission
|
||||
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.
|
||||
*
|
||||
* @param uuid UUID of the requested service
|
||||
* @return BluetoothGattService if supported, or null if the requested service is not offered by
|
||||
* this device.
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresNoPermission
|
||||
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
|
||||
@RequiresNoPermission
|
||||
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
|
||||
@RequiresNoPermission
|
||||
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
|
||||
@RequiresNoPermission
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
throw new UnsupportedOperationException(
|
||||
"Use BluetoothManager#getDevicesMatchingConnectionStates instead.");
|
||||
}
|
||||
}
|
||||
@@ -1,202 +0,0 @@
|
||||
/*
|
||||
* 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 Worker 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) {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,395 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.RequiresPermission;
|
||||
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.os.Build;
|
||||
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 this 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.
|
||||
*
|
||||
* @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.
|
||||
*
|
||||
* @param service The service to be added
|
||||
* @return true, if the included service was added to the service
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
public boolean addService(BluetoothGattService service) {
|
||||
mIncludedServices.add(service);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a characteristic to this service.
|
||||
*
|
||||
* @param characteristic The characteristics to be added
|
||||
* @return true, if the characteristic was added to the service
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public void setAdvertisePreferred(boolean advertisePreferred) {
|
||||
mAdvertisePreferred = advertisePreferred;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,323 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.NonNull;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.AttributionSource;
|
||||
import android.os.Build;
|
||||
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, Attributable {
|
||||
|
||||
/* 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();
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public void setAttributionSource(@NonNull AttributionSource attributionSource) {
|
||||
Attributable.setAttributionSource(mDevice, attributionSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public int getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets call's number.
|
||||
*
|
||||
* @return string representing phone number.
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,386 +0,0 @@
|
||||
/*
|
||||
* 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.RequiresPermission;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SuppressLint("AndroidFrameworkRequiresPermission")
|
||||
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}
|
||||
*
|
||||
* @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
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SuppressLint("AndroidFrameworkRequiresPermission")
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SuppressLint("AndroidFrameworkRequiresPermission")
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SuppressLint("AndroidFrameworkRequiresPermission")
|
||||
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> 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
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SuppressLint("AndroidFrameworkRequiresPermission")
|
||||
public ParcelFileDescriptor getMainChannelFd(BluetoothDevice device,
|
||||
BluetoothHealthAppConfiguration config) {
|
||||
Log.e(TAG, "getMainChannelFd(): BluetoothHealth is deprecated");
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current connection state of the profile.
|
||||
*
|
||||
* 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
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SuppressLint("AndroidFrameworkRequiresPermission")
|
||||
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}
|
||||
*
|
||||
* 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
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SuppressLint("AndroidFrameworkRequiresPermission")
|
||||
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>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
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SuppressLint("AndroidFrameworkRequiresPermission")
|
||||
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;
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* 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) {}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,691 +0,0 @@
|
||||
/*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
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.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@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>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
@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 final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
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(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothHearingAid proxy object for interacting with the local
|
||||
* Bluetooth Hearing Aid service.
|
||||
*/
|
||||
/* package */ BluetoothHearingAid(Context context, ServiceListener listener,
|
||||
BluetoothAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean connect(BluetoothDevice device) {
|
||||
if (DBG) log("connect(" + device + ")");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.connect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean disconnect(BluetoothDevice device) {
|
||||
if (DBG) log("disconnect(" + device + ")");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.disconnect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public @NonNull List<BluetoothDevice> getConnectedDevices() {
|
||||
if (VDBG) log("getConnectedDevices()");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
|
||||
@NonNull int[] states) {
|
||||
if (VDBG) log("getDevicesMatchingStates()");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public @BluetoothProfile.BtProfileState int getConnectionState(
|
||||
@NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("getState(" + device + ")");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
|
||||
if (DBG) log("setActiveDevice(" + device + ")");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && ((device == null) || isValidDevice(device))) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setActiveDevice(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public @NonNull List<BluetoothDevice> getActiveDevices() {
|
||||
if (VDBG) log("getActiveDevices()");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getActiveDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("getConnectionPolicy(" + device + ")");
|
||||
verifyDeviceNotNull(device, "getConnectionPolicy");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionPolicy(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public void setVolume(int volume) {
|
||||
if (DBG) Log.d(TAG, "setVolume(" + volume + ")");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver recv = new SynchronousResultReceiver();
|
||||
service.setVolume(volume, mAttributionSource, recv);
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + 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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public long getHiSyncId(@NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("getHiSyncId(" + device + ")");
|
||||
verifyDeviceNotNull(device, "getConnectionPolicy");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
final long defaultValue = HI_SYNC_ID_INVALID;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Long> recv = new SynchronousResultReceiver();
|
||||
service.getHiSyncId(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the side of the device.
|
||||
*
|
||||
* @param device Bluetooth device.
|
||||
* @return SIDE_LEFT or SIDE_RIGHT
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getDeviceSide(BluetoothDevice device) {
|
||||
if (VDBG) log("getDeviceSide(" + device + ")");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
final int defaultValue = SIDE_LEFT;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getDeviceSide(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the mode of the device.
|
||||
*
|
||||
* @param device Bluetooth device
|
||||
* @return MODE_MONAURAL or MODE_BINAURAL
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getDeviceMode(BluetoothDevice device) {
|
||||
if (VDBG) log("getDeviceMode(" + device + ")");
|
||||
final IBluetoothHearingAid service = getService();
|
||||
final int defaultValue = MODE_MONAURAL;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getDeviceMode(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,848 +0,0 @@
|
||||
/*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
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.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@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;
|
||||
private final AttributionSource mAttributionSource;
|
||||
|
||||
CallbackWrapper(Executor executor, Callback callback, AttributionSource attributionSource) {
|
||||
mExecutor = executor;
|
||||
mCallback = callback;
|
||||
mAttributionSource = attributionSource;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAppStatusChanged(BluetoothDevice pluggedDevice, boolean registered) {
|
||||
Attributable.setAttributionSource(pluggedDevice, mAttributionSource);
|
||||
final long token = clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onAppStatusChanged(pluggedDevice, registered));
|
||||
} finally {
|
||||
restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConnectionStateChanged(BluetoothDevice device, int state) {
|
||||
Attributable.setAttributionSource(device, mAttributionSource);
|
||||
final long token = clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onConnectionStateChanged(device, state));
|
||||
} finally {
|
||||
restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onGetReport(BluetoothDevice device, byte type, byte id, int bufferSize) {
|
||||
Attributable.setAttributionSource(device, mAttributionSource);
|
||||
final long token = clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onGetReport(device, type, id, bufferSize));
|
||||
} finally {
|
||||
restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetReport(BluetoothDevice device, byte type, byte id, byte[] data) {
|
||||
Attributable.setAttributionSource(device, mAttributionSource);
|
||||
final long token = clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onSetReport(device, type, id, data));
|
||||
} finally {
|
||||
restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSetProtocol(BluetoothDevice device, byte protocol) {
|
||||
Attributable.setAttributionSource(device, mAttributionSource);
|
||||
final long token = clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onSetProtocol(device, protocol));
|
||||
} finally {
|
||||
restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterruptData(BluetoothDevice device, byte reportId, byte[] data) {
|
||||
Attributable.setAttributionSource(device, mAttributionSource);
|
||||
final long token = clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onInterruptData(device, reportId, data));
|
||||
} finally {
|
||||
restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVirtualCableUnplug(BluetoothDevice device) {
|
||||
Attributable.setAttributionSource(device, mAttributionSource);
|
||||
final long token = clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onVirtualCableUnplug(device));
|
||||
} finally {
|
||||
restoreCallingIdentity(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
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(service);
|
||||
}
|
||||
};
|
||||
|
||||
BluetoothHidDevice(Context context, ServiceListener listener, BluetoothAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
mProfileConnector.connect(context, listener);
|
||||
}
|
||||
|
||||
void close() {
|
||||
mProfileConnector.disconnect();
|
||||
}
|
||||
|
||||
private IBluetoothHidDevice getService() {
|
||||
return mProfileConnector.getService();
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getConnectedDevices() {
|
||||
final IBluetoothHidDevice service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
final IBluetoothHidDevice service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getConnectionState(BluetoothDevice device) {
|
||||
final IBluetoothHidDevice service = getService();
|
||||
final int defaultValue = STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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();
|
||||
final boolean defaultValue = result;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
CallbackWrapper cbw = new CallbackWrapper(executor, callback, mAttributionSource);
|
||||
service.registerApp(sdp, inQos, outQos, cbw, mAttributionSource, recv);
|
||||
result = recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean unregisterApp() {
|
||||
final IBluetoothHidDevice service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.unregisterApp(mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean sendReport(BluetoothDevice device, int id, byte[] data) {
|
||||
final IBluetoothHidDevice service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.sendReport(device, id, data, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean replyReport(BluetoothDevice device, byte type, byte id, byte[] data) {
|
||||
final IBluetoothHidDevice service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.replyReport(device, type, id, data, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean reportError(BluetoothDevice device, byte error) {
|
||||
final IBluetoothHidDevice service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.reportError(device, error, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the application name of the current HidDeviceService user.
|
||||
*
|
||||
* @return the current user name, or empty string if cannot get the name
|
||||
* {@hide}
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public String getUserAppName() {
|
||||
final IBluetoothHidDevice service = getService();
|
||||
final String defaultValue = "";
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<String> recv = new SynchronousResultReceiver();
|
||||
service.getUserAppName(mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean connect(BluetoothDevice device) {
|
||||
final IBluetoothHidDevice service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.connect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean disconnect(BluetoothDevice device) {
|
||||
final IBluetoothHidDevice service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.disconnect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
|
||||
@ConnectionPolicy int connectionPolicy) {
|
||||
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
|
||||
final IBluetoothHidDevice service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,123 +0,0 @@
|
||||
/*
|
||||
* 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);
|
||||
}
|
||||
}
|
||||
@@ -1,831 +0,0 @@
|
||||
/*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
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.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
@SuppressLint("ActionValue")
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_CONNECTION_STATE_CHANGED =
|
||||
"android.bluetooth.input.profile.action.CONNECTION_STATE_CHANGED";
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_PROTOCOL_MODE_CHANGED =
|
||||
"android.bluetooth.input.profile.action.PROTOCOL_MODE_CHANGED";
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_HANDSHAKE =
|
||||
"android.bluetooth.input.profile.action.HANDSHAKE";
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_REPORT =
|
||||
"android.bluetooth.input.profile.action.REPORT";
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_VIRTUAL_UNPLUG_STATUS =
|
||||
"android.bluetooth.input.profile.action.VIRTUAL_UNPLUG_STATUS";
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@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 final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
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(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothHidHost proxy object for interacting with the local
|
||||
* Bluetooth Service which handles the InputDevice profile
|
||||
*/
|
||||
/* package */ BluetoothHidHost(Context context, ServiceListener listener,
|
||||
BluetoothAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean connect(BluetoothDevice device) {
|
||||
if (DBG) log("connect(" + device + ")");
|
||||
final IBluetoothHidHost service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.connect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean disconnect(BluetoothDevice device) {
|
||||
if (DBG) log("disconnect(" + device + ")");
|
||||
final IBluetoothHidHost service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.disconnect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public @NonNull List<BluetoothDevice> getConnectedDevices() {
|
||||
if (VDBG) log("getConnectedDevices()");
|
||||
final IBluetoothHidHost service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
if (VDBG) log("getDevicesMatchingStates()");
|
||||
final IBluetoothHidHost service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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();
|
||||
final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionPolicy(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @param device Remote Bluetooth Device
|
||||
* @return false on immediate error, true otherwise
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean virtualUnplug(BluetoothDevice device) {
|
||||
if (DBG) log("virtualUnplug(" + device + ")");
|
||||
final IBluetoothHidHost service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.virtualUnplug(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Get_Protocol_Mode command to the connected HID input device.
|
||||
*
|
||||
* @param device Remote Bluetooth Device
|
||||
* @return false on immediate error, true otherwise
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean getProtocolMode(BluetoothDevice device) {
|
||||
if (VDBG) log("getProtocolMode(" + device + ")");
|
||||
final IBluetoothHidHost service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.getProtocolMode(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Set_Protocol_Mode command to the connected HID input device.
|
||||
*
|
||||
* @param device Remote Bluetooth Device
|
||||
* @return false on immediate error, true otherwise
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean setProtocolMode(BluetoothDevice device, int protocolMode) {
|
||||
if (DBG) log("setProtocolMode(" + device + ")");
|
||||
final IBluetoothHidHost service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setProtocolMode(device, protocolMode, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Get_Report command to the connected HID input device.
|
||||
*
|
||||
* @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
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.getReport(device, reportType, reportId, bufferSize, mAttributionSource,
|
||||
recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Set_Report command to the connected HID input device.
|
||||
*
|
||||
* @param device Remote Bluetooth Device
|
||||
* @param reportType Report type
|
||||
* @param report Report receiving buffer size
|
||||
* @return false on immediate error, true otherwise
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean setReport(BluetoothDevice device, byte reportType, String report) {
|
||||
if (VDBG) log("setReport(" + device + "), reportType=" + reportType + " report=" + report);
|
||||
final IBluetoothHidHost service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setReport(device, reportType, report, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Send_Data command to the connected HID input device.
|
||||
*
|
||||
* @param device Remote Bluetooth Device
|
||||
* @param report Report to send
|
||||
* @return false on immediate error, true otherwise
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean sendData(BluetoothDevice device, String report) {
|
||||
if (DBG) log("sendData(" + device + "), report=" + report);
|
||||
final IBluetoothHidHost service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.sendData(device, report, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Get_Idle_Time command to the connected HID input device.
|
||||
*
|
||||
* @param device Remote Bluetooth Device
|
||||
* @return false on immediate error, true otherwise
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean getIdleTime(BluetoothDevice device) {
|
||||
if (DBG) log("getIdletime(" + device + ")");
|
||||
final IBluetoothHidHost service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.getIdleTime(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send Set_Idle_Time command to the connected HID input device.
|
||||
*
|
||||
* @param device Remote Bluetooth Device
|
||||
* @param idleTime Idle time to be set on HID Device
|
||||
* @return false on immediate error, true otherwise
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean setIdleTime(BluetoothDevice device, byte idleTime) {
|
||||
if (DBG) log("setIdletime(" + device + "), idleTime=" + idleTime);
|
||||
final IBluetoothHidHost service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setIdleTime(device, idleTime, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static void log(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
}
|
||||
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
* 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.SuppressLint;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
/**
|
||||
* BluetoothInputStream.
|
||||
*
|
||||
* Used to write to a Bluetooth socket.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
/*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);
|
||||
}
|
||||
}
|
||||
@@ -1,829 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020 HIMSA II K/S - www.himsa.com.
|
||||
* Represented by EHIMA - www.ehima.com
|
||||
*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
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.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.CloseGuard;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* This class provides the public APIs to control the LeAudio profile.
|
||||
*
|
||||
* <p>BluetoothLeAudio is a proxy object for controlling the Bluetooth LE Audio
|
||||
* Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
|
||||
* the BluetoothLeAudio proxy object.
|
||||
*
|
||||
* <p> Android only supports one set of connected Bluetooth LeAudio device at a time. Each
|
||||
* method is protected with its appropriate permission.
|
||||
*/
|
||||
public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
|
||||
private static final String TAG = "BluetoothLeAudio";
|
||||
private static final boolean DBG = false;
|
||||
private static final boolean VDBG = false;
|
||||
|
||||
private CloseGuard mCloseGuard;
|
||||
|
||||
/**
|
||||
* Intent used to broadcast the change in connection state of the LeAudio
|
||||
* 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.
|
||||
*
|
||||
* <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}.
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_LE_AUDIO_CONNECTION_STATE_CHANGED =
|
||||
"android.bluetooth.action.LE_AUDIO_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>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_LE_AUDIO_ACTIVE_DEVICE_CHANGED =
|
||||
"android.bluetooth.action.LE_AUDIO_ACTIVE_DEVICE_CHANGED";
|
||||
|
||||
/**
|
||||
* Intent used to broadcast group node status information.
|
||||
*
|
||||
* <p>This intent will have 3 extra:
|
||||
* <ul>
|
||||
* <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
|
||||
* be null if no device is active. </li>
|
||||
* <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li>
|
||||
* <li> {@link #EXTRA_LE_AUDIO_GROUP_NODE_STATUS} - Group node status. </li>
|
||||
* </ul>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_LE_AUDIO_GROUP_NODE_STATUS_CHANGED =
|
||||
"android.bluetooth.action.LE_AUDIO_GROUP_NODE_STATUS_CHANGED";
|
||||
|
||||
|
||||
/**
|
||||
* Intent used to broadcast group status information.
|
||||
*
|
||||
* <p>This intent will have 4 extra:
|
||||
* <ul>
|
||||
* <li> {@link BluetoothDevice#EXTRA_DEVICE} - The remote device. It can
|
||||
* be null if no device is active. </li>
|
||||
* <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li>
|
||||
* <li> {@link #EXTRA_LE_AUDIO_GROUP_STATUS} - Group status. </li>
|
||||
* </ul>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_LE_AUDIO_GROUP_STATUS_CHANGED =
|
||||
"android.bluetooth.action.LE_AUDIO_GROUP_STATUS_CHANGED";
|
||||
|
||||
/**
|
||||
* Intent used to broadcast group audio configuration changed information.
|
||||
*
|
||||
* <p>This intent will have 5 extra:
|
||||
* <ul>
|
||||
* <li> {@link #EXTRA_LE_AUDIO_GROUP_ID} - Group id. </li>
|
||||
* <li> {@link #EXTRA_LE_AUDIO_DIRECTION} - Direction as bit mask. </li>
|
||||
* <li> {@link #EXTRA_LE_AUDIO_SINK_LOCATION} - Sink location as per Bluetooth Assigned
|
||||
* Numbers </li>
|
||||
* <li> {@link #EXTRA_LE_AUDIO_SOURCE_LOCATION} - Source location as per Bluetooth Assigned
|
||||
* Numbers </li>
|
||||
* <li> {@link #EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS} - Available contexts for group as per
|
||||
* Bluetooth Assigned Numbers </li>
|
||||
* </ul>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_LE_AUDIO_CONF_CHANGED =
|
||||
"android.bluetooth.action.LE_AUDIO_CONF_CHANGED";
|
||||
|
||||
/**
|
||||
* Indicates unspecified audio content.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_UNSPECIFIED = 0x0001;
|
||||
|
||||
/**
|
||||
* Indicates conversation between humans as, for example, in telephony or video calls.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_COMMUNICATION = 0x0002;
|
||||
|
||||
/**
|
||||
* Indicates media as, for example, in music, public radio, podcast or video soundtrack.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_MEDIA = 0x0004;
|
||||
|
||||
/**
|
||||
* Indicates instructional audio as, for example, in navigation, traffic announcements
|
||||
* or user guidance.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_INSTRUCTIONAL = 0x0008;
|
||||
|
||||
/**
|
||||
* Indicates attention seeking audio as, for example, in beeps signalling arrival of a message
|
||||
* or keyboard clicks.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_ATTENTION_SEEKING = 0x0010;
|
||||
|
||||
/**
|
||||
* Indicates immediate alerts as, for example, in a low battery alarm, timer expiry or alarm
|
||||
* clock.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_IMMEDIATE_ALERT = 0x0020;
|
||||
|
||||
/**
|
||||
* Indicates man machine communication as, for example, with voice recognition or virtual
|
||||
* assistant.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_MAN_MACHINE = 0x0040;
|
||||
|
||||
/**
|
||||
* Indicates emergency alerts as, for example, with fire alarms or other urgent alerts.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_EMERGENCY_ALERT = 0x0080;
|
||||
|
||||
/**
|
||||
* Indicates ringtone as in a call alert.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_RINGTONE = 0x0100;
|
||||
|
||||
/**
|
||||
* Indicates audio associated with a television program and/or with metadata conforming to the
|
||||
* Bluetooth Broadcast TV profile.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_TV = 0x0200;
|
||||
|
||||
/**
|
||||
* Indicates audio associated with a low latency live audio stream.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_LIVE = 0x0400;
|
||||
|
||||
/**
|
||||
* Indicates audio associated with a video game stream.
|
||||
* @hide
|
||||
*/
|
||||
public static final int CONTEXT_TYPE_GAME = 0x0800;
|
||||
|
||||
/**
|
||||
* This represents an invalid group ID.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_ID_INVALID = IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;
|
||||
|
||||
/**
|
||||
* Contains group id.
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_LE_AUDIO_GROUP_ID =
|
||||
"android.bluetooth.extra.LE_AUDIO_GROUP_ID";
|
||||
|
||||
/**
|
||||
* Contains group node status, can be any of
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li> {@link #GROUP_NODE_ADDED} </li>
|
||||
* <li> {@link #GROUP_NODE_REMOVED} </li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_LE_AUDIO_GROUP_NODE_STATUS =
|
||||
"android.bluetooth.extra.LE_AUDIO_GROUP_NODE_STATUS";
|
||||
|
||||
/**
|
||||
* Contains group status, can be any of
|
||||
*
|
||||
* <p>
|
||||
* <ul>
|
||||
* <li> {@link #GROUP_STATUS_ACTIVE} </li>
|
||||
* <li> {@link #GROUP_STATUS_INACTIVE} </li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_LE_AUDIO_GROUP_STATUS =
|
||||
"android.bluetooth.extra.LE_AUDIO_GROUP_STATUS";
|
||||
|
||||
/**
|
||||
* Contains bit mask for direction, bit 0 set when Sink, bit 1 set when Source.
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_LE_AUDIO_DIRECTION =
|
||||
"android.bluetooth.extra.LE_AUDIO_DIRECTION";
|
||||
|
||||
/**
|
||||
* Contains source location as per Bluetooth Assigned Numbers
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_LE_AUDIO_SOURCE_LOCATION =
|
||||
"android.bluetooth.extra.LE_AUDIO_SOURCE_LOCATION";
|
||||
|
||||
/**
|
||||
* Contains sink location as per Bluetooth Assigned Numbers
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_LE_AUDIO_SINK_LOCATION =
|
||||
"android.bluetooth.extra.LE_AUDIO_SINK_LOCATION";
|
||||
|
||||
/**
|
||||
* Contains available context types for group as per Bluetooth Assigned Numbers
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_LE_AUDIO_AVAILABLE_CONTEXTS =
|
||||
"android.bluetooth.extra.LE_AUDIO_AVAILABLE_CONTEXTS";
|
||||
|
||||
private final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
/**
|
||||
* Indicating that group is Active ( Audio device is available )
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_STATUS_ACTIVE = IBluetoothLeAudio.GROUP_STATUS_ACTIVE;
|
||||
|
||||
/**
|
||||
* Indicating that group is Inactive ( Audio device is not available )
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_STATUS_INACTIVE = IBluetoothLeAudio.GROUP_STATUS_INACTIVE;
|
||||
|
||||
/**
|
||||
* Indicating that node has been added to the group.
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_NODE_ADDED = IBluetoothLeAudio.GROUP_NODE_ADDED;
|
||||
|
||||
/**
|
||||
* Indicating that node has been removed from the group.
|
||||
* @hide
|
||||
*/
|
||||
public static final int GROUP_NODE_REMOVED = IBluetoothLeAudio.GROUP_NODE_REMOVED;
|
||||
|
||||
private final BluetoothProfileConnector<IBluetoothLeAudio> mProfileConnector =
|
||||
new BluetoothProfileConnector(this, BluetoothProfile.LE_AUDIO, "BluetoothLeAudio",
|
||||
IBluetoothLeAudio.class.getName()) {
|
||||
@Override
|
||||
public IBluetoothLeAudio getServiceInterface(IBinder service) {
|
||||
return IBluetoothLeAudio.Stub.asInterface(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothLeAudio proxy object for interacting with the local
|
||||
* Bluetooth LeAudio service.
|
||||
*/
|
||||
/* package */ BluetoothLeAudio(Context context, ServiceListener listener,
|
||||
BluetoothAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
mProfileConnector.connect(context, listener);
|
||||
mCloseGuard = new CloseGuard();
|
||||
mCloseGuard.open("close");
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public void close() {
|
||||
mProfileConnector.disconnect();
|
||||
}
|
||||
|
||||
private IBluetoothLeAudio getService() {
|
||||
return mProfileConnector.getService();
|
||||
}
|
||||
|
||||
protected void finalize() {
|
||||
if (mCloseGuard != null) {
|
||||
mCloseGuard.warnIfOpen();
|
||||
}
|
||||
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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean connect(@Nullable BluetoothDevice device) {
|
||||
if (DBG) log("connect(" + device + ")");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.connect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean disconnect(@Nullable BluetoothDevice device) {
|
||||
if (DBG) log("disconnect(" + device + ")");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.disconnect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public @NonNull List<BluetoothDevice> getConnectedDevices() {
|
||||
if (VDBG) log("getConnectedDevices()");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
|
||||
@NonNull int[] states) {
|
||||
if (VDBG) log("getDevicesMatchingStates()");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("getState(" + device + ")");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select a connected device as active.
|
||||
*
|
||||
* The active device selection is per profile. An active device's
|
||||
* purpose is profile-specific. For example, LeAudio audio
|
||||
* streaming is to the active LeAudio 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_LE_AUDIO_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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean setActiveDevice(@Nullable BluetoothDevice device) {
|
||||
if (DBG) log("setActiveDevice(" + device + ")");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled() && ((device == null) || isValidDevice(device))) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setActiveDevice(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the connected LeAudio devices that are active
|
||||
*
|
||||
* @return the list of active devices. Returns empty list on error.
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getActiveDevices() {
|
||||
if (VDBG) log("getActiveDevice()");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getActiveDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get device group id. Devices with same group id belong to same group (i.e left and right
|
||||
* earbud)
|
||||
* @param device LE Audio capable device
|
||||
* @return group id that this device currently belongs to
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getGroupId(@NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("getGroupId()");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final int defaultValue = GROUP_ID_INVALID;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getGroupId(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set volume for the streaming devices
|
||||
*
|
||||
* @param volume volume to set
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED})
|
||||
public void setVolume(int volume) {
|
||||
if (VDBG) log("setVolume(vol: " + volume + " )");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver recv = new SynchronousResultReceiver();
|
||||
service.setVolume(volume, mAttributionSource, recv);
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add device to the given group.
|
||||
* @param group_id group ID the device is being added to
|
||||
* @param device the active device
|
||||
* @return true on success, otherwise false
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED
|
||||
})
|
||||
public boolean groupAddNode(int group_id, @NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("groupAddNode()");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.groupAddNode(group_id, device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove device from a given group.
|
||||
* @param group_id group ID the device is being removed from
|
||||
* @param device the active device
|
||||
* @return true on success, otherwise false
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED
|
||||
})
|
||||
public boolean groupRemoveNode(int group_id, @NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("groupRemoveNode()");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.groupRemoveNode(group_id, device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
|
||||
@ConnectionPolicy int connectionPolicy) {
|
||||
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public @ConnectionPolicy int getConnectionPolicy(@Nullable BluetoothDevice device) {
|
||||
if (VDBG) log("getConnectionPolicy(" + device + ")");
|
||||
final IBluetoothLeAudio service = getService();
|
||||
final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (mAdapter.isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionPolicy(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 + ">";
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isValidDevice(@Nullable 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);
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* Represents the codec configuration for a Bluetooth LE Audio source device.
|
||||
* <p>Contains the source codec type.
|
||||
* <p>The source codec type values are the same as those supported by the
|
||||
* device hardware.
|
||||
*
|
||||
* {@see BluetoothLeAudioCodecConfig}
|
||||
*/
|
||||
public final class BluetoothLeAudioCodecConfig {
|
||||
// Add an entry for each source codec here.
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = "SOURCE_CODEC_TYPE_", value = {
|
||||
SOURCE_CODEC_TYPE_LC3,
|
||||
SOURCE_CODEC_TYPE_INVALID
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface SourceCodecType {};
|
||||
|
||||
public static final int SOURCE_CODEC_TYPE_LC3 = 0;
|
||||
public static final int SOURCE_CODEC_TYPE_INVALID = 1000 * 1000;
|
||||
|
||||
/**
|
||||
* Represents the count of valid source codec types. Can be accessed via
|
||||
* {@link #getMaxCodecType}.
|
||||
*/
|
||||
private static final int SOURCE_CODEC_TYPE_MAX = 1;
|
||||
|
||||
private final @SourceCodecType int mCodecType;
|
||||
|
||||
/**
|
||||
* Creates a new BluetoothLeAudioCodecConfig.
|
||||
*
|
||||
* @param codecType the source codec type
|
||||
*/
|
||||
private BluetoothLeAudioCodecConfig(@SourceCodecType int codecType) {
|
||||
mCodecType = codecType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{codecName:" + getCodecName() + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the codec type.
|
||||
*
|
||||
* @return the codec type
|
||||
*/
|
||||
public @SourceCodecType int getCodecType() {
|
||||
return mCodecType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the valid codec types count.
|
||||
*/
|
||||
public static int getMaxCodecType() {
|
||||
return SOURCE_CODEC_TYPE_MAX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the codec name.
|
||||
*
|
||||
* @return the codec name
|
||||
*/
|
||||
public @NonNull String getCodecName() {
|
||||
switch (mCodecType) {
|
||||
case SOURCE_CODEC_TYPE_LC3:
|
||||
return "LC3";
|
||||
case SOURCE_CODEC_TYPE_INVALID:
|
||||
return "INVALID CODEC";
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return "UNKNOWN CODEC(" + mCodecType + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Builder for {@link BluetoothLeAudioCodecConfig}.
|
||||
* <p> By default, the codec type will be set to
|
||||
* {@link BluetoothLeAudioCodecConfig#SOURCE_CODEC_TYPE_INVALID}
|
||||
*/
|
||||
public static final class Builder {
|
||||
private int mCodecType = BluetoothLeAudioCodecConfig.SOURCE_CODEC_TYPE_INVALID;
|
||||
|
||||
/**
|
||||
* Set codec type for Bluetooth codec config.
|
||||
*
|
||||
* @param codecType of this codec
|
||||
* @return the same Builder instance
|
||||
*/
|
||||
public @NonNull Builder setCodecType(@SourceCodecType int codecType) {
|
||||
mCodecType = codecType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build {@link BluetoothLeAudioCodecConfig}.
|
||||
* @return new BluetoothLeAudioCodecConfig built
|
||||
*/
|
||||
public @NonNull BluetoothLeAudioCodecConfig build() {
|
||||
return new BluetoothLeAudioCodecConfig(mCodecType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,287 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* This class provides the public APIs to control the Bluetooth LE Broadcast Source profile.
|
||||
*
|
||||
* <p>BluetoothLeBroadcast is a proxy object for controlling the Bluetooth LE Broadcast
|
||||
* Source Service via IPC. Use {@link BluetoothAdapter#getProfileProxy}
|
||||
* to get the BluetoothLeBroadcast proxy object.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class BluetoothLeBroadcast implements BluetoothProfile {
|
||||
private static final String TAG = "BluetoothLeBroadcast";
|
||||
private static final boolean DBG = true;
|
||||
private static final boolean VDBG = false;
|
||||
|
||||
/**
|
||||
* Constants used by the LE Audio Broadcast profile for the Broadcast state
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@IntDef(prefix = {"LE_AUDIO_BROADCAST_STATE_"}, value = {
|
||||
LE_AUDIO_BROADCAST_STATE_DISABLED,
|
||||
LE_AUDIO_BROADCAST_STATE_ENABLING,
|
||||
LE_AUDIO_BROADCAST_STATE_ENABLED,
|
||||
LE_AUDIO_BROADCAST_STATE_DISABLING,
|
||||
LE_AUDIO_BROADCAST_STATE_PLAYING,
|
||||
LE_AUDIO_BROADCAST_STATE_NOT_PLAYING
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface LeAudioBroadcastState {}
|
||||
|
||||
/**
|
||||
* Indicates that LE Audio Broadcast mode is currently disabled
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_STATE_DISABLED = 10;
|
||||
|
||||
/**
|
||||
* Indicates that LE Audio Broadcast mode is being enabled
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_STATE_ENABLING = 11;
|
||||
|
||||
/**
|
||||
* Indicates that LE Audio Broadcast mode is currently enabled
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_STATE_ENABLED = 12;
|
||||
/**
|
||||
* Indicates that LE Audio Broadcast mode is being disabled
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_STATE_DISABLING = 13;
|
||||
|
||||
/**
|
||||
* Indicates that an LE Audio Broadcast mode is currently playing
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_STATE_PLAYING = 14;
|
||||
|
||||
/**
|
||||
* Indicates that LE Audio Broadcast is currently not playing
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_STATE_NOT_PLAYING = 15;
|
||||
|
||||
/**
|
||||
* Constants used by the LE Audio Broadcast profile for encryption key length
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@IntDef(prefix = {"LE_AUDIO_BROADCAST_ENCRYPTION_KEY_"}, value = {
|
||||
LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT,
|
||||
LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface LeAudioEncryptionKeyLength {}
|
||||
|
||||
/**
|
||||
* Indicates that the LE Audio Broadcast encryption key size is 32 bits.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_32BIT = 16;
|
||||
|
||||
/**
|
||||
* Indicates that the LE Audio Broadcast encryption key size is 128 bits.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_ENCRYPTION_KEY_128BIT = 17;
|
||||
|
||||
/**
|
||||
* Interface for receiving events related to broadcasts
|
||||
*/
|
||||
public interface Callback {
|
||||
/**
|
||||
* Called when broadcast state has changed
|
||||
*
|
||||
* @param prevState broadcast state before the change
|
||||
* @param newState broadcast state after the change
|
||||
*/
|
||||
@LeAudioBroadcastState
|
||||
void onBroadcastStateChange(int prevState, int newState);
|
||||
/**
|
||||
* Called when encryption key has been updated
|
||||
*
|
||||
* @param success true if the key was updated successfully, false otherwise
|
||||
*/
|
||||
void onEncryptionKeySet(boolean success);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a BluetoothLeBroadcast proxy object for interacting with the local
|
||||
* LE Audio Broadcast Source service.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
/*package*/ BluetoothLeBroadcast(Context context,
|
||||
BluetoothProfile.ServiceListener listener) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported since LE Audio Broadcasts do not establish a connection
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public int getConnectionState(BluetoothDevice device) {
|
||||
throw new UnsupportedOperationException(
|
||||
"LE Audio Broadcasts are not connection-oriented.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported since LE Audio Broadcasts do not establish a connection
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
throw new UnsupportedOperationException(
|
||||
"LE Audio Broadcasts are not connection-oriented.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported since LE Audio Broadcasts do not establish a connection
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public List<BluetoothDevice> getConnectedDevices() {
|
||||
throw new UnsupportedOperationException(
|
||||
"LE Audio Broadcasts are not connection-oriented.");
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable LE Audio Broadcast mode.
|
||||
*
|
||||
* Generates a new broadcast ID and enables sending of encrypted or unencrypted
|
||||
* isochronous PDUs
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public int enableBroadcastMode() {
|
||||
if (DBG) log("enableBroadcastMode");
|
||||
return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable LE Audio Broadcast mode.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public int disableBroadcastMode() {
|
||||
if (DBG) log("disableBroadcastMode");
|
||||
return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the current LE Audio broadcast state
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@LeAudioBroadcastState
|
||||
public int getBroadcastState() {
|
||||
if (DBG) log("getBroadcastState");
|
||||
return LE_AUDIO_BROADCAST_STATE_DISABLED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable LE Audio broadcast encryption
|
||||
*
|
||||
* @param keyLength if useExisting is true, this specifies the length of the key that should
|
||||
* be generated
|
||||
* @param useExisting true, if an existing key should be used
|
||||
* false, if a new key should be generated
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@LeAudioEncryptionKeyLength
|
||||
public int enableEncryption(boolean useExisting, int keyLength) {
|
||||
if (DBG) log("enableEncryption useExisting=" + useExisting + " keyLength=" + keyLength);
|
||||
return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable LE Audio broadcast encryption
|
||||
*
|
||||
* @param removeExisting true, if the existing key should be removed
|
||||
* false, otherwise
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public int disableEncryption(boolean removeExisting) {
|
||||
if (DBG) log("disableEncryption removeExisting=" + removeExisting);
|
||||
return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable or disable LE Audio broadcast encryption
|
||||
*
|
||||
* @param key use the provided key if non-null, generate a new key if null
|
||||
* @param keyLength 0 if encryption is disabled, 4 bytes (low security),
|
||||
* 16 bytes (high security)
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@LeAudioEncryptionKeyLength
|
||||
public int setEncryptionKey(byte[] key, int keyLength) {
|
||||
if (DBG) log("setEncryptionKey key=" + key + " keyLength=" + keyLength);
|
||||
return BluetoothStatusCodes.ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get the encryption key that was set before
|
||||
*
|
||||
* @return encryption key as a byte array or null if no encryption key was set
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public byte[] getEncryptionKey() {
|
||||
if (DBG) log("getEncryptionKey");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void log(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
}
|
||||
@@ -1,140 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.bluetooth.le.ScanResult;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* This class provides a set of callbacks that are invoked when scanning for Broadcast Sources is
|
||||
* offloaded to a Broadcast Assistant.
|
||||
*
|
||||
* <p>An LE Audio Broadcast Assistant can help a Broadcast Sink to scan for available Broadcast
|
||||
* Sources. The Broadcast Sink achieves this by offloading the scan to a Broadcast Assistant. This
|
||||
* is facilitated by the Broadcast Audio Scan Service (BASS). A BASS server is a GATT server that is
|
||||
* part of the Scan Delegator on a Broadcast Sink. A BASS client instead runs on the Broadcast
|
||||
* Assistant.
|
||||
*
|
||||
* <p>Once a GATT connection is established between the BASS client and the BASS server, the
|
||||
* Broadcast Sink can offload the scans to the Broadcast Assistant. Upon finding new Broadcast
|
||||
* Sources, the Broadcast Assistant then notifies the Broadcast Sink about these over the
|
||||
* established GATT connection. The Scan Delegator on the Broadcast Sink can also notify the
|
||||
* Assistant about changes such as addition and removal of Broadcast Sources.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public abstract class BluetoothLeBroadcastAssistantCallback {
|
||||
|
||||
/**
|
||||
* Broadcast Audio Scan Service (BASS) codes returned by a BASS Server
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@IntDef(
|
||||
prefix = "BASS_STATUS_",
|
||||
value = {
|
||||
BASS_STATUS_SUCCESS,
|
||||
BASS_STATUS_FAILURE,
|
||||
BASS_STATUS_INVALID_GATT_HANDLE,
|
||||
BASS_STATUS_TXN_TIMEOUT,
|
||||
BASS_STATUS_INVALID_SOURCE_ID,
|
||||
BASS_STATUS_COLOCATED_SRC_UNAVAILABLE,
|
||||
BASS_STATUS_INVALID_SOURCE_SELECTED,
|
||||
BASS_STATUS_SOURCE_UNAVAILABLE,
|
||||
BASS_STATUS_DUPLICATE_ADDITION,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface BassStatus {}
|
||||
|
||||
public static final int BASS_STATUS_SUCCESS = 0x00;
|
||||
public static final int BASS_STATUS_FAILURE = 0x01;
|
||||
public static final int BASS_STATUS_INVALID_GATT_HANDLE = 0x02;
|
||||
public static final int BASS_STATUS_TXN_TIMEOUT = 0x03;
|
||||
|
||||
public static final int BASS_STATUS_INVALID_SOURCE_ID = 0x04;
|
||||
public static final int BASS_STATUS_COLOCATED_SRC_UNAVAILABLE = 0x05;
|
||||
public static final int BASS_STATUS_INVALID_SOURCE_SELECTED = 0x06;
|
||||
public static final int BASS_STATUS_SOURCE_UNAVAILABLE = 0x07;
|
||||
public static final int BASS_STATUS_DUPLICATE_ADDITION = 0x08;
|
||||
public static final int BASS_STATUS_NO_EMPTY_SLOT = 0x09;
|
||||
public static final int BASS_STATUS_INVALID_GROUP_OP = 0x10;
|
||||
|
||||
/**
|
||||
* Callback invoked when a new LE Audio Broadcast Source is found.
|
||||
*
|
||||
* @param result {@link ScanResult} scan result representing a Broadcast Source
|
||||
*/
|
||||
public void onBluetoothLeBroadcastSourceFound(@NonNull ScanResult result) {}
|
||||
|
||||
/**
|
||||
* Callback invoked when the Broadcast Assistant synchronizes with Periodic Advertisements (PAs)
|
||||
* of an LE Audio Broadcast Source.
|
||||
*
|
||||
* @param source the selected Broadcast Source
|
||||
*/
|
||||
public void onBluetoothLeBroadcastSourceSelected(
|
||||
@NonNull BluetoothLeBroadcastSourceInfo source, @BassStatus int status) {}
|
||||
|
||||
/**
|
||||
* Callback invoked when the Broadcast Assistant loses synchronization with an LE Audio
|
||||
* Broadcast Source.
|
||||
*
|
||||
* @param source the Broadcast Source with which synchronization was lost
|
||||
*/
|
||||
public void onBluetoothLeBroadcastSourceLost(
|
||||
@NonNull BluetoothLeBroadcastSourceInfo source, @BassStatus int status) {}
|
||||
|
||||
/**
|
||||
* Callback invoked when a new LE Audio Broadcast Source has been successfully added to the Scan
|
||||
* Delegator (within a Broadcast Sink, for example).
|
||||
*
|
||||
* @param sink Scan Delegator device on which a new Broadcast Source has been added
|
||||
* @param source the added Broadcast Source
|
||||
*/
|
||||
public void onBluetoothLeBroadcastSourceAdded(
|
||||
@NonNull BluetoothDevice sink,
|
||||
@NonNull BluetoothLeBroadcastSourceInfo source,
|
||||
@BassStatus int status) {}
|
||||
|
||||
/**
|
||||
* Callback invoked when an existing LE Audio Broadcast Source within a remote Scan Delegator
|
||||
* has been updated.
|
||||
*
|
||||
* @param sink Scan Delegator device on which a Broadcast Source has been updated
|
||||
* @param source the updated Broadcast Source
|
||||
*/
|
||||
public void onBluetoothLeBroadcastSourceUpdated(
|
||||
@NonNull BluetoothDevice sink,
|
||||
@NonNull BluetoothLeBroadcastSourceInfo source,
|
||||
@BassStatus int status) {}
|
||||
|
||||
/**
|
||||
* Callback invoked when an LE Audio Broadcast Source has been successfully removed from the
|
||||
* Scan Delegator (within a Broadcast Sink, for example).
|
||||
*
|
||||
* @param sink Scan Delegator device from which a Broadcast Source has been removed
|
||||
* @param source the removed Broadcast Source
|
||||
*/
|
||||
public void onBluetoothLeBroadcastSourceRemoved(
|
||||
@NonNull BluetoothDevice sink,
|
||||
@NonNull BluetoothLeBroadcastSourceInfo source,
|
||||
@BassStatus int status) {}
|
||||
}
|
||||
@@ -1,788 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* This class represents an LE Audio Broadcast Source and the associated information that is needed
|
||||
* by Broadcast Audio Scan Service (BASS) residing on a Scan Delegator.
|
||||
*
|
||||
* <p>For example, the Scan Delegator on an LE Audio Broadcast Sink can use the information
|
||||
* contained within an instance of this class to synchronize with an LE Audio Broadcast Source in
|
||||
* order to listen to a Broadcast Audio Stream.
|
||||
*
|
||||
* <p>BroadcastAssistant has a BASS client which facilitates scanning and discovery of Broadcast
|
||||
* Sources on behalf of say a Broadcast Sink. Upon successful discovery of one or more Broadcast
|
||||
* sources, this information needs to be communicated to the BASS Server residing within the Scan
|
||||
* Delegator on a Broadcast Sink. This is achieved using the Periodic Advertising Synchronization
|
||||
* Transfer (PAST) procedure. This procedure uses information contained within an instance of this
|
||||
* class.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class BluetoothLeBroadcastSourceInfo implements Parcelable {
|
||||
private static final String TAG = "BluetoothLeBroadcastSourceInfo";
|
||||
private static final boolean DBG = true;
|
||||
|
||||
/**
|
||||
* Constants representing Broadcast Source address types
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@IntDef(
|
||||
prefix = "LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_",
|
||||
value = {
|
||||
LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC,
|
||||
LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM,
|
||||
LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface LeAudioBroadcastSourceAddressType {}
|
||||
|
||||
/**
|
||||
* Represents a public address used by an LE Audio Broadcast Source
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_PUBLIC = 0;
|
||||
|
||||
/**
|
||||
* Represents a random address used by an LE Audio Broadcast Source
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_RANDOM = 1;
|
||||
|
||||
/**
|
||||
* Represents an invalid address used by an LE Audio Broadcast Seurce
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID = 0xFFFF;
|
||||
|
||||
/**
|
||||
* Periodic Advertising Synchronization state
|
||||
*
|
||||
* <p>Periodic Advertising (PA) enables the LE Audio Broadcast Assistant to discover broadcast
|
||||
* audio streams as well as the audio stream configuration on behalf of an LE Audio Broadcast
|
||||
* Sink. This information can then be transferred to the LE Audio Broadcast Sink using the
|
||||
* Periodic Advertising Synchronizaton Transfer (PAST) procedure.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@IntDef(
|
||||
prefix = "LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_",
|
||||
value = {
|
||||
LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE,
|
||||
LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ,
|
||||
LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC,
|
||||
LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL,
|
||||
LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface LeAudioBroadcastSinkPaSyncState {}
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink is not synchronized with the Periodic Advertisements (PA)
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IDLE = 0;
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink requested the Broadcast Assistant to synchronize with the
|
||||
* Periodic Advertisements (PA).
|
||||
*
|
||||
* <p>This is also known as scan delegation or scan offloading.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNCINFO_REQ = 1;
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink is synchronized with the Periodic Advertisements (PA).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_IN_SYNC = 2;
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink was unable to synchronize with the Periodic Advertisements
|
||||
* (PA).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_SYNC_FAIL = 3;
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink should be synchronized with the Periodic Advertisements
|
||||
* (PA) using the Periodic Advertisements Synchronization Transfert (PAST) procedure.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_NO_PAST = 4;
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink synchornization state is invalid.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID = 0xFFFF;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(
|
||||
prefix = "LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_",
|
||||
value = {
|
||||
LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED,
|
||||
LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface LeAudioBroadcastSinkAudioSyncState {}
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink is not synchronized with a Broadcast Audio Stream.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_NOT_SYNCHRONIZED = 0;
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink is synchronized with a Broadcast Audio Stream.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_SYNCHRONIZED = 1;
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink audio synchronization state is invalid.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID = 0xFFFF;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(
|
||||
prefix = "LE_AUDIO_BROADCAST_SINK_ENC_STATE_",
|
||||
value = {
|
||||
LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED,
|
||||
LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED,
|
||||
LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING,
|
||||
LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface LeAudioBroadcastSinkEncryptionState {}
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink is synchronized with an unencrypted audio stream.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_NOT_ENCRYPTED = 0;
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink needs a Broadcast Code to synchronize with the audio
|
||||
* stream.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_CODE_REQUIRED = 1;
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink is synchronized with an encrypted audio stream.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_DECRYPTING = 2;
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink is unable to decrypt an audio stream due to an incorrect
|
||||
* Broadcast Code
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE = 3;
|
||||
|
||||
/**
|
||||
* Indicates that the Broadcast Sink encryption state is invalid.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID = 0xFF;
|
||||
|
||||
/**
|
||||
* Represents an invalid LE Audio Broadcast Source ID
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final byte LE_AUDIO_BROADCAST_SINK_INVALID_SOURCE_ID = (byte) 0x00;
|
||||
|
||||
/**
|
||||
* Represents an invalid Broadcast ID of a Broadcast Source
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int INVALID_BROADCAST_ID = 0xFFFFFF;
|
||||
|
||||
private byte mSourceId;
|
||||
private @LeAudioBroadcastSourceAddressType int mSourceAddressType;
|
||||
private BluetoothDevice mSourceDevice;
|
||||
private byte mSourceAdvSid;
|
||||
private int mBroadcastId;
|
||||
private @LeAudioBroadcastSinkPaSyncState int mPaSyncState;
|
||||
private @LeAudioBroadcastSinkEncryptionState int mEncryptionStatus;
|
||||
private @LeAudioBroadcastSinkAudioSyncState int mAudioSyncState;
|
||||
private byte[] mBadBroadcastCode;
|
||||
private byte mNumSubGroups;
|
||||
private Map<Integer, Integer> mSubgroupBisSyncState = new HashMap<Integer, Integer>();
|
||||
private Map<Integer, byte[]> mSubgroupMetadata = new HashMap<Integer, byte[]>();
|
||||
|
||||
private String mBroadcastCode;
|
||||
private static final int BIS_NO_PREF = 0xFFFFFFFF;
|
||||
private static final int BROADCAST_CODE_SIZE = 16;
|
||||
|
||||
/**
|
||||
* Constructor to create an Empty object of {@link BluetoothLeBroadcastSourceInfo } with the
|
||||
* given Source Id.
|
||||
*
|
||||
* <p>This is mainly used to represent the Empty Broadcast Source entries
|
||||
*
|
||||
* @param sourceId Source Id for this Broadcast Source info object
|
||||
* @hide
|
||||
*/
|
||||
public BluetoothLeBroadcastSourceInfo(byte sourceId) {
|
||||
mSourceId = sourceId;
|
||||
mSourceAddressType = LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID;
|
||||
mSourceDevice = null;
|
||||
mSourceAdvSid = (byte) 0x00;
|
||||
mBroadcastId = INVALID_BROADCAST_ID;
|
||||
mPaSyncState = LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID;
|
||||
mAudioSyncState = LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID;
|
||||
mEncryptionStatus = LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID;
|
||||
mBadBroadcastCode = null;
|
||||
mNumSubGroups = 0;
|
||||
mBroadcastCode = null;
|
||||
}
|
||||
|
||||
/*package*/ BluetoothLeBroadcastSourceInfo(
|
||||
byte sourceId,
|
||||
@LeAudioBroadcastSourceAddressType int addressType,
|
||||
@NonNull BluetoothDevice device,
|
||||
byte advSid,
|
||||
int broadcastId,
|
||||
@LeAudioBroadcastSinkPaSyncState int paSyncstate,
|
||||
@LeAudioBroadcastSinkEncryptionState int encryptionStatus,
|
||||
@LeAudioBroadcastSinkAudioSyncState int audioSyncstate,
|
||||
@Nullable byte[] badCode,
|
||||
byte numSubGroups,
|
||||
@NonNull Map<Integer, Integer> bisSyncState,
|
||||
@Nullable Map<Integer, byte[]> subgroupMetadata,
|
||||
@NonNull String broadcastCode) {
|
||||
mSourceId = sourceId;
|
||||
mSourceAddressType = addressType;
|
||||
mSourceDevice = device;
|
||||
mSourceAdvSid = advSid;
|
||||
mBroadcastId = broadcastId;
|
||||
mPaSyncState = paSyncstate;
|
||||
mEncryptionStatus = encryptionStatus;
|
||||
mAudioSyncState = audioSyncstate;
|
||||
|
||||
if (badCode != null && badCode.length != 0) {
|
||||
mBadBroadcastCode = new byte[badCode.length];
|
||||
System.arraycopy(badCode, 0, mBadBroadcastCode, 0, badCode.length);
|
||||
}
|
||||
mNumSubGroups = numSubGroups;
|
||||
mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState);
|
||||
mSubgroupMetadata = new HashMap<Integer, byte[]>(subgroupMetadata);
|
||||
mBroadcastCode = broadcastCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o instanceof BluetoothLeBroadcastSourceInfo) {
|
||||
BluetoothLeBroadcastSourceInfo other = (BluetoothLeBroadcastSourceInfo) o;
|
||||
return (other.mSourceId == mSourceId
|
||||
&& other.mSourceAddressType == mSourceAddressType
|
||||
&& other.mSourceDevice == mSourceDevice
|
||||
&& other.mSourceAdvSid == mSourceAdvSid
|
||||
&& other.mBroadcastId == mBroadcastId
|
||||
&& other.mPaSyncState == mPaSyncState
|
||||
&& other.mEncryptionStatus == mEncryptionStatus
|
||||
&& other.mAudioSyncState == mAudioSyncState
|
||||
&& Arrays.equals(other.mBadBroadcastCode, mBadBroadcastCode)
|
||||
&& other.mNumSubGroups == mNumSubGroups
|
||||
&& mSubgroupBisSyncState.equals(other.mSubgroupBisSyncState)
|
||||
&& mSubgroupMetadata.equals(other.mSubgroupMetadata)
|
||||
&& other.mBroadcastCode == mBroadcastCode);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an instance of {@link BluetoothLeBroadcastSourceInfo} is empty.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public boolean isEmpty() {
|
||||
boolean ret = false;
|
||||
if (mSourceAddressType == LE_AUDIO_BROADCAST_SOURCE_ADDRESS_TYPE_INVALID
|
||||
&& mSourceDevice == null
|
||||
&& mSourceAdvSid == (byte) 0
|
||||
&& mPaSyncState == LE_AUDIO_BROADCAST_SINK_PA_SYNC_STATE_INVALID
|
||||
&& mEncryptionStatus == LE_AUDIO_BROADCAST_SINK_ENC_STATE_INVALID
|
||||
&& mAudioSyncState == LE_AUDIO_BROADCAST_SINK_AUDIO_SYNC_STATE_INVALID
|
||||
&& mBadBroadcastCode == null
|
||||
&& mNumSubGroups == 0
|
||||
&& mSubgroupBisSyncState.size() == 0
|
||||
&& mSubgroupMetadata.size() == 0
|
||||
&& mBroadcastCode == null) {
|
||||
ret = true;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares an instance of {@link BluetoothLeBroadcastSourceInfo} with the provided instance.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public boolean matches(BluetoothLeBroadcastSourceInfo srcInfo) {
|
||||
boolean ret = false;
|
||||
if (srcInfo == null) {
|
||||
ret = false;
|
||||
} else {
|
||||
if (mSourceDevice == null) {
|
||||
if (mSourceAdvSid == srcInfo.getAdvertisingSid()
|
||||
&& mSourceAddressType == srcInfo.getAdvAddressType()) {
|
||||
ret = true;
|
||||
}
|
||||
} else {
|
||||
if (mSourceDevice.equals(srcInfo.getSourceDevice())
|
||||
&& mSourceAdvSid == srcInfo.getAdvertisingSid()
|
||||
&& mSourceAddressType == srcInfo.getAdvAddressType()
|
||||
&& mBroadcastId == srcInfo.getBroadcastId()) {
|
||||
ret = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(
|
||||
mSourceId,
|
||||
mSourceAddressType,
|
||||
mSourceDevice,
|
||||
mSourceAdvSid,
|
||||
mBroadcastId,
|
||||
mPaSyncState,
|
||||
mEncryptionStatus,
|
||||
mAudioSyncState,
|
||||
mBadBroadcastCode,
|
||||
mNumSubGroups,
|
||||
mSubgroupBisSyncState,
|
||||
mSubgroupMetadata,
|
||||
mBroadcastCode);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "{BluetoothLeBroadcastSourceInfo : mSourceId"
|
||||
+ mSourceId
|
||||
+ " addressType: "
|
||||
+ mSourceAddressType
|
||||
+ " sourceDevice: "
|
||||
+ mSourceDevice
|
||||
+ " mSourceAdvSid:"
|
||||
+ mSourceAdvSid
|
||||
+ " mBroadcastId:"
|
||||
+ mBroadcastId
|
||||
+ " mPaSyncState:"
|
||||
+ mPaSyncState
|
||||
+ " mEncryptionStatus:"
|
||||
+ mEncryptionStatus
|
||||
+ " mAudioSyncState:"
|
||||
+ mAudioSyncState
|
||||
+ " mBadBroadcastCode:"
|
||||
+ mBadBroadcastCode
|
||||
+ " mNumSubGroups:"
|
||||
+ mNumSubGroups
|
||||
+ " mSubgroupBisSyncState:"
|
||||
+ mSubgroupBisSyncState
|
||||
+ " mSubgroupMetadata:"
|
||||
+ mSubgroupMetadata
|
||||
+ " mBroadcastCode:"
|
||||
+ mBroadcastCode
|
||||
+ "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Source Id
|
||||
*
|
||||
* @return byte representing the Source Id, {@link
|
||||
* #LE_AUDIO_BROADCAST_ASSISTANT_INVALID_SOURCE_ID} if invalid
|
||||
* @hide
|
||||
*/
|
||||
public byte getSourceId() {
|
||||
return mSourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Source Id
|
||||
*
|
||||
* @param sourceId source Id
|
||||
* @hide
|
||||
*/
|
||||
public void setSourceId(byte sourceId) {
|
||||
mSourceId = sourceId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Broadcast Source device
|
||||
*
|
||||
* @param sourceDevice the Broadcast Source BluetoothDevice
|
||||
* @hide
|
||||
*/
|
||||
public void setSourceDevice(@NonNull BluetoothDevice sourceDevice) {
|
||||
mSourceDevice = sourceDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Broadcast Source BluetoothDevice
|
||||
*
|
||||
* @return Broadcast Source BluetoothDevice
|
||||
* @hide
|
||||
*/
|
||||
public @NonNull BluetoothDevice getSourceDevice() {
|
||||
return mSourceDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the address type of the Broadcast Source advertisements
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public void setAdvAddressType(@LeAudioBroadcastSourceAddressType int addressType) {
|
||||
mSourceAddressType = addressType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the address type used by advertisements from the Broadcast Source.
|
||||
* BluetoothLeBroadcastSourceInfo Object
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@LeAudioBroadcastSourceAddressType
|
||||
public int getAdvAddressType() {
|
||||
return mSourceAddressType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the advertising SID of the Broadcast Source advertisement.
|
||||
*
|
||||
* @param advSid advertising SID of the Broadcast Source
|
||||
* @hide
|
||||
*/
|
||||
public void setAdvertisingSid(byte advSid) {
|
||||
mSourceAdvSid = advSid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the advertising SID of the Broadcast Source advertisement.
|
||||
*
|
||||
* @return advertising SID of the Broadcast Source
|
||||
* @hide
|
||||
*/
|
||||
public byte getAdvertisingSid() {
|
||||
return mSourceAdvSid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Broadcast ID of the Broadcast Source.
|
||||
*
|
||||
* @return broadcast ID
|
||||
* @hide
|
||||
*/
|
||||
public int getBroadcastId() {
|
||||
return mBroadcastId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the Periodic Advertising (PA) Sync State.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
/*package*/ void setPaSyncState(@LeAudioBroadcastSinkPaSyncState int paSyncState) {
|
||||
mPaSyncState = paSyncState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Periodic Advertising (PA) Sync State
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public @LeAudioBroadcastSinkPaSyncState int getMetadataSyncState() {
|
||||
return mPaSyncState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the audio sync state
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
/*package*/ void setAudioSyncState(@LeAudioBroadcastSinkAudioSyncState int audioSyncState) {
|
||||
mAudioSyncState = audioSyncState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the audio sync state
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public @LeAudioBroadcastSinkAudioSyncState int getAudioSyncState() {
|
||||
return mAudioSyncState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the encryption status
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
/*package*/ void setEncryptionStatus(
|
||||
@LeAudioBroadcastSinkEncryptionState int encryptionStatus) {
|
||||
mEncryptionStatus = encryptionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the encryption status
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public @LeAudioBroadcastSinkEncryptionState int getEncryptionStatus() {
|
||||
return mEncryptionStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the incorrect broadcast code that the Scan delegator used to decrypt the Broadcast Audio
|
||||
* Stream and failed.
|
||||
*
|
||||
* <p>This code is valid only if {@link #getEncryptionStatus} returns {@link
|
||||
* #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE}
|
||||
*
|
||||
* @return byte array containing bad broadcast value, null if the current encryption status is
|
||||
* not {@link #LE_AUDIO_BROADCAST_SINK_ENC_STATE_BAD_CODE}
|
||||
* @hide
|
||||
*/
|
||||
public @Nullable byte[] getBadBroadcastCode() {
|
||||
return mBadBroadcastCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the number of subgroups.
|
||||
*
|
||||
* @return number of subgroups
|
||||
* @hide
|
||||
*/
|
||||
public byte getNumberOfSubGroups() {
|
||||
return mNumSubGroups;
|
||||
}
|
||||
|
||||
public @NonNull Map<Integer, Integer> getSubgroupBisSyncState() {
|
||||
return mSubgroupBisSyncState;
|
||||
}
|
||||
|
||||
public void setSubgroupBisSyncState(@NonNull Map<Integer, Integer> bisSyncState) {
|
||||
mSubgroupBisSyncState = new HashMap<Integer, Integer>(bisSyncState);
|
||||
}
|
||||
|
||||
/*package*/ void setBroadcastCode(@NonNull String broadcastCode) {
|
||||
mBroadcastCode = broadcastCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the broadcast code
|
||||
*
|
||||
* @return
|
||||
* @hide
|
||||
*/
|
||||
public @NonNull String getBroadcastCode() {
|
||||
return mBroadcastCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the broadcast ID
|
||||
*
|
||||
* @param broadcastId broadcast ID of the Broadcast Source
|
||||
* @hide
|
||||
*/
|
||||
public void setBroadcastId(int broadcastId) {
|
||||
mBroadcastId = broadcastId;
|
||||
}
|
||||
|
||||
private void writeSubgroupBisSyncStateToParcel(
|
||||
@NonNull Parcel dest, @NonNull Map<Integer, Integer> subgroupBisSyncState) {
|
||||
dest.writeInt(subgroupBisSyncState.size());
|
||||
for (Map.Entry<Integer, Integer> entry : subgroupBisSyncState.entrySet()) {
|
||||
dest.writeInt(entry.getKey());
|
||||
dest.writeInt(entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
private static void readSubgroupBisSyncStateFromParcel(
|
||||
@NonNull Parcel in, @NonNull Map<Integer, Integer> subgroupBisSyncState) {
|
||||
int size = in.readInt();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
Integer key = in.readInt();
|
||||
Integer value = in.readInt();
|
||||
subgroupBisSyncState.put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
private void writeSubgroupMetadataToParcel(
|
||||
@NonNull Parcel dest, @Nullable Map<Integer, byte[]> subgroupMetadata) {
|
||||
if (subgroupMetadata == null) {
|
||||
dest.writeInt(0);
|
||||
return;
|
||||
}
|
||||
|
||||
dest.writeInt(subgroupMetadata.size());
|
||||
for (Map.Entry<Integer, byte[]> entry : subgroupMetadata.entrySet()) {
|
||||
dest.writeInt(entry.getKey());
|
||||
byte[] metadata = entry.getValue();
|
||||
if (metadata != null) {
|
||||
dest.writeInt(metadata.length);
|
||||
dest.writeByteArray(metadata);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void readSubgroupMetadataFromParcel(
|
||||
@NonNull Parcel in, @NonNull Map<Integer, byte[]> subgroupMetadata) {
|
||||
int size = in.readInt();
|
||||
|
||||
for (int i = 0; i < size; i++) {
|
||||
Integer key = in.readInt();
|
||||
Integer metaDataLen = in.readInt();
|
||||
byte[] metadata = null;
|
||||
if (metaDataLen != 0) {
|
||||
metadata = new byte[metaDataLen];
|
||||
in.readByteArray(metadata);
|
||||
}
|
||||
subgroupMetadata.put(key, metadata);
|
||||
}
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<BluetoothLeBroadcastSourceInfo> CREATOR =
|
||||
new Parcelable.Creator<BluetoothLeBroadcastSourceInfo>() {
|
||||
public @NonNull BluetoothLeBroadcastSourceInfo createFromParcel(
|
||||
@NonNull Parcel in) {
|
||||
final byte sourceId = in.readByte();
|
||||
final int sourceAddressType = in.readInt();
|
||||
final BluetoothDevice sourceDevice =
|
||||
in.readTypedObject(BluetoothDevice.CREATOR);
|
||||
final byte sourceAdvSid = in.readByte();
|
||||
final int broadcastId = in.readInt();
|
||||
final int paSyncState = in.readInt();
|
||||
final int audioSyncState = in.readInt();
|
||||
final int encryptionStatus = in.readInt();
|
||||
final int badBroadcastLen = in.readInt();
|
||||
byte[] badBroadcastCode = null;
|
||||
|
||||
if (badBroadcastLen > 0) {
|
||||
badBroadcastCode = new byte[badBroadcastLen];
|
||||
in.readByteArray(badBroadcastCode);
|
||||
}
|
||||
final byte numSubGroups = in.readByte();
|
||||
final String broadcastCode = in.readString();
|
||||
Map<Integer, Integer> subgroupBisSyncState = new HashMap<Integer, Integer>();
|
||||
readSubgroupBisSyncStateFromParcel(in, subgroupBisSyncState);
|
||||
Map<Integer, byte[]> subgroupMetadata = new HashMap<Integer, byte[]>();
|
||||
readSubgroupMetadataFromParcel(in, subgroupMetadata);
|
||||
|
||||
BluetoothLeBroadcastSourceInfo srcInfo =
|
||||
new BluetoothLeBroadcastSourceInfo(
|
||||
sourceId,
|
||||
sourceAddressType,
|
||||
sourceDevice,
|
||||
sourceAdvSid,
|
||||
broadcastId,
|
||||
paSyncState,
|
||||
encryptionStatus,
|
||||
audioSyncState,
|
||||
badBroadcastCode,
|
||||
numSubGroups,
|
||||
subgroupBisSyncState,
|
||||
subgroupMetadata,
|
||||
broadcastCode);
|
||||
return srcInfo;
|
||||
}
|
||||
|
||||
public @NonNull BluetoothLeBroadcastSourceInfo[] newArray(int size) {
|
||||
return new BluetoothLeBroadcastSourceInfo[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel out, int flags) {
|
||||
out.writeByte(mSourceId);
|
||||
out.writeInt(mSourceAddressType);
|
||||
out.writeTypedObject(mSourceDevice, 0);
|
||||
out.writeByte(mSourceAdvSid);
|
||||
out.writeInt(mBroadcastId);
|
||||
out.writeInt(mPaSyncState);
|
||||
out.writeInt(mAudioSyncState);
|
||||
out.writeInt(mEncryptionStatus);
|
||||
|
||||
if (mBadBroadcastCode != null) {
|
||||
out.writeInt(mBadBroadcastCode.length);
|
||||
out.writeByteArray(mBadBroadcastCode);
|
||||
} else {
|
||||
// zero indicates that there is no "bad broadcast code"
|
||||
out.writeInt(0);
|
||||
}
|
||||
out.writeByte(mNumSubGroups);
|
||||
out.writeString(mBroadcastCode);
|
||||
writeSubgroupBisSyncStateToParcel(out, mSubgroupBisSyncState);
|
||||
writeSubgroupMetadataToParcel(out, mSubgroupMetadata);
|
||||
}
|
||||
|
||||
private static void log(@NonNull String msg) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
;
|
||||
@@ -1,285 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 HIMSA II K/S - www.himsa.com.
|
||||
* Represented by EHIMA - www.ehima.com
|
||||
*
|
||||
* 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.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.os.ParcelUuid;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Representation of Call
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class BluetoothLeCall implements Parcelable {
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = "STATE_", value = {
|
||||
STATE_INCOMING,
|
||||
STATE_DIALING,
|
||||
STATE_ALERTING,
|
||||
STATE_ACTIVE,
|
||||
STATE_LOCALLY_HELD,
|
||||
STATE_REMOTELY_HELD,
|
||||
STATE_LOCALLY_AND_REMOTELY_HELD
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface State {
|
||||
}
|
||||
|
||||
/**
|
||||
* A remote party is calling (incoming call).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_INCOMING = 0x00;
|
||||
|
||||
/**
|
||||
* The process to call the remote party has started but the remote party is not
|
||||
* being alerted (outgoing call).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_DIALING = 0x01;
|
||||
|
||||
/**
|
||||
* A remote party is being alerted (outgoing call).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_ALERTING = 0x02;
|
||||
|
||||
/**
|
||||
* The call is in an active conversation.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_ACTIVE = 0x03;
|
||||
|
||||
/**
|
||||
* The call is connected but held locally. “Locally Held” implies that either
|
||||
* the server or the client can affect the state.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_LOCALLY_HELD = 0x04;
|
||||
|
||||
/**
|
||||
* The call is connected but held remotely. “Remotely Held” means that the state
|
||||
* is controlled by the remote party of a call.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_REMOTELY_HELD = 0x05;
|
||||
|
||||
/**
|
||||
* The call is connected but held both locally and remotely.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int STATE_LOCALLY_AND_REMOTELY_HELD = 0x06;
|
||||
|
||||
/**
|
||||
* Whether the call direction is outgoing.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int FLAG_OUTGOING_CALL = 0x00000001;
|
||||
|
||||
/**
|
||||
* Whether the call URI and Friendly Name are withheld by server.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int FLAG_WITHHELD_BY_SERVER = 0x00000002;
|
||||
|
||||
/**
|
||||
* Whether the call URI and Friendly Name are withheld by network.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int FLAG_WITHHELD_BY_NETWORK = 0x00000004;
|
||||
|
||||
/** Unique UUID that identifies this call */
|
||||
private UUID mUuid;
|
||||
|
||||
/** Remote Caller URI */
|
||||
private String mUri;
|
||||
|
||||
/** Caller friendly name */
|
||||
private String mFriendlyName;
|
||||
|
||||
/** Call state */
|
||||
private @State int mState;
|
||||
|
||||
/** Call flags */
|
||||
private int mCallFlags;
|
||||
|
||||
/** @hide */
|
||||
public BluetoothLeCall(@NonNull BluetoothLeCall that) {
|
||||
mUuid = new UUID(that.getUuid().getMostSignificantBits(),
|
||||
that.getUuid().getLeastSignificantBits());
|
||||
mUri = that.mUri;
|
||||
mFriendlyName = that.mFriendlyName;
|
||||
mState = that.mState;
|
||||
mCallFlags = that.mCallFlags;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public BluetoothLeCall(@NonNull UUID uuid, @NonNull String uri, @NonNull String friendlyName,
|
||||
@State int state, int callFlags) {
|
||||
mUuid = uuid;
|
||||
mUri = uri;
|
||||
mFriendlyName = friendlyName;
|
||||
mState = state;
|
||||
mCallFlags = callFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (o == null || getClass() != o.getClass())
|
||||
return false;
|
||||
BluetoothLeCall that = (BluetoothLeCall) o;
|
||||
return mUuid.equals(that.mUuid) && mUri.equals(that.mUri)
|
||||
&& mFriendlyName.equals(that.mFriendlyName) && mState == that.mState
|
||||
&& mCallFlags == that.mCallFlags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(mUuid, mUri, mFriendlyName, mState, mCallFlags);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a string representation of this BluetoothLeCall.
|
||||
*
|
||||
* <p>
|
||||
* Currently this is the UUID.
|
||||
*
|
||||
* @return string representation of this BluetoothLeCall
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return mUuid.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel out, int flags) {
|
||||
out.writeParcelable(new ParcelUuid(mUuid), 0);
|
||||
out.writeString(mUri);
|
||||
out.writeString(mFriendlyName);
|
||||
out.writeInt(mState);
|
||||
out.writeInt(mCallFlags);
|
||||
}
|
||||
|
||||
public static final @android.annotation.NonNull Parcelable.Creator<BluetoothLeCall> CREATOR =
|
||||
new Parcelable.Creator<BluetoothLeCall>() {
|
||||
public BluetoothLeCall createFromParcel(Parcel in) {
|
||||
return new BluetoothLeCall(in);
|
||||
}
|
||||
|
||||
public BluetoothLeCall[] newArray(int size) {
|
||||
return new BluetoothLeCall[size];
|
||||
}
|
||||
};
|
||||
|
||||
private BluetoothLeCall(Parcel in) {
|
||||
mUuid = ((ParcelUuid) in.readParcelable(null)).getUuid();
|
||||
mUri = in.readString();
|
||||
mFriendlyName = in.readString();
|
||||
mState = in.readInt();
|
||||
mCallFlags = in.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an UUID of this BluetoothLeCall.
|
||||
*
|
||||
* <p>
|
||||
* An UUID is unique identifier of a BluetoothLeCall.
|
||||
*
|
||||
* @return UUID of this BluetoothLeCall
|
||||
* @hide
|
||||
*/
|
||||
public @NonNull UUID getUuid() {
|
||||
return mUuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a URI of the remote party of this BluetoothLeCall.
|
||||
*
|
||||
* @return string representation of this BluetoothLeCall
|
||||
* @hide
|
||||
*/
|
||||
public @NonNull String getUri() {
|
||||
return mUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a friendly name of the call.
|
||||
*
|
||||
* @return friendly name representation of this BluetoothLeCall
|
||||
* @hide
|
||||
*/
|
||||
public @NonNull String getFriendlyName() {
|
||||
return mFriendlyName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the call state.
|
||||
*
|
||||
* @return the state of this BluetoothLeCall
|
||||
* @hide
|
||||
*/
|
||||
public @State int getState() {
|
||||
return mState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the call flags.
|
||||
*
|
||||
* @return call flags
|
||||
* @hide
|
||||
*/
|
||||
public int getCallFlags() {
|
||||
return mCallFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the call direction is incoming.
|
||||
*
|
||||
* @return true if incoming call, false otherwise
|
||||
* @hide
|
||||
*/
|
||||
public boolean isIncomingCall() {
|
||||
return (mCallFlags & FLAG_OUTGOING_CALL) == 0;
|
||||
}
|
||||
}
|
||||
@@ -1,899 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 HIMSA II K/S - www.himsa.com.
|
||||
* Represented by EHIMA - www.ehima.com
|
||||
*
|
||||
* 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.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
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.ParcelUuid;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
import android.annotation.SuppressLint;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
/**
|
||||
* This class provides the APIs to control the Call Control profile.
|
||||
*
|
||||
* <p>
|
||||
* This class provides Bluetooth Telephone Bearer Service functionality,
|
||||
* allowing applications to expose a GATT Service based interface to control the
|
||||
* state of the calls by remote devices such as LE audio devices.
|
||||
*
|
||||
* <p>
|
||||
* BluetoothLeCallControl is a proxy object for controlling the Bluetooth Telephone Bearer
|
||||
* Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get the
|
||||
* BluetoothLeCallControl proxy object.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public final class BluetoothLeCallControl implements BluetoothProfile {
|
||||
private static final String TAG = "BluetoothLeCallControl";
|
||||
private static final boolean DBG = true;
|
||||
private static final boolean VDBG = false;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = "RESULT_", value = {
|
||||
RESULT_SUCCESS,
|
||||
RESULT_ERROR_UNKNOWN_CALL_ID,
|
||||
RESULT_ERROR_INVALID_URI,
|
||||
RESULT_ERROR_APPLICATION
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Result {
|
||||
}
|
||||
|
||||
/**
|
||||
* Opcode write was successful.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int RESULT_SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* Unknown call Id has been used in the operation.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int RESULT_ERROR_UNKNOWN_CALL_ID = 1;
|
||||
|
||||
/**
|
||||
* The URI provided in {@link Callback#onPlaceCallRequest} is invalid.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int RESULT_ERROR_INVALID_URI = 2;
|
||||
|
||||
/**
|
||||
* Application internal error.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int RESULT_ERROR_APPLICATION = 3;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = "TERMINATION_REASON_", value = {
|
||||
TERMINATION_REASON_INVALID_URI,
|
||||
TERMINATION_REASON_FAIL,
|
||||
TERMINATION_REASON_REMOTE_HANGUP,
|
||||
TERMINATION_REASON_SERVER_HANGUP,
|
||||
TERMINATION_REASON_LINE_BUSY,
|
||||
TERMINATION_REASON_NETWORK_CONGESTION,
|
||||
TERMINATION_REASON_CLIENT_HANGUP,
|
||||
TERMINATION_REASON_NO_SERVICE,
|
||||
TERMINATION_REASON_NO_ANSWER
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface TerminationReason {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remote Caller ID value used to place a call was formed improperly.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int TERMINATION_REASON_INVALID_URI = 0x00;
|
||||
|
||||
/**
|
||||
* Call fail.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int TERMINATION_REASON_FAIL = 0x01;
|
||||
|
||||
/**
|
||||
* Remote party ended call.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int TERMINATION_REASON_REMOTE_HANGUP = 0x02;
|
||||
|
||||
/**
|
||||
* Call ended from the server.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int TERMINATION_REASON_SERVER_HANGUP = 0x03;
|
||||
|
||||
/**
|
||||
* Line busy.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int TERMINATION_REASON_LINE_BUSY = 0x04;
|
||||
|
||||
/**
|
||||
* Network congestion.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int TERMINATION_REASON_NETWORK_CONGESTION = 0x05;
|
||||
|
||||
/**
|
||||
* Client terminated.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int TERMINATION_REASON_CLIENT_HANGUP = 0x06;
|
||||
|
||||
/**
|
||||
* No service.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int TERMINATION_REASON_NO_SERVICE = 0x07;
|
||||
|
||||
/**
|
||||
* No answer.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int TERMINATION_REASON_NO_ANSWER = 0x08;
|
||||
|
||||
/*
|
||||
* Flag indicating support for hold/unhold call feature.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int CAPABILITY_HOLD_CALL = 0x00000001;
|
||||
|
||||
/**
|
||||
* Flag indicating support for joining calls feature.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int CAPABILITY_JOIN_CALLS = 0x00000002;
|
||||
|
||||
private static final int MESSAGE_TBS_SERVICE_CONNECTED = 102;
|
||||
private static final int MESSAGE_TBS_SERVICE_DISCONNECTED = 103;
|
||||
|
||||
private static final int REG_TIMEOUT = 10000;
|
||||
|
||||
/**
|
||||
* The template class is used to call callback functions on events from the TBS
|
||||
* server. Callback functions are wrapped in this class and registered to the
|
||||
* Android system during app registration.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public abstract static class Callback {
|
||||
|
||||
private static final String TAG = "BluetoothLeCallControl.Callback";
|
||||
|
||||
/**
|
||||
* Called when a remote client requested to accept the call.
|
||||
*
|
||||
* <p>
|
||||
* An application must call {@link BluetoothLeCallControl#requestResult} to complete the
|
||||
* request.
|
||||
*
|
||||
* @param requestId The Id of the request
|
||||
* @param callId The call Id requested to be accepted
|
||||
* @hide
|
||||
*/
|
||||
public abstract void onAcceptCall(int requestId, @NonNull UUID callId);
|
||||
|
||||
/**
|
||||
* A remote client has requested to terminate the call.
|
||||
*
|
||||
* <p>
|
||||
* An application must call {@link BluetoothLeCallControl#requestResult} to complete the
|
||||
* request.
|
||||
*
|
||||
* @param requestId The Id of the request
|
||||
* @param callId The call Id requested to terminate
|
||||
* @hide
|
||||
*/
|
||||
public abstract void onTerminateCall(int requestId, @NonNull UUID callId);
|
||||
|
||||
/**
|
||||
* A remote client has requested to hold the call.
|
||||
*
|
||||
* <p>
|
||||
* An application must call {@link BluetoothLeCallControl#requestResult} to complete the
|
||||
* request.
|
||||
*
|
||||
* @param requestId The Id of the request
|
||||
* @param callId The call Id requested to be put on hold
|
||||
* @hide
|
||||
*/
|
||||
public void onHoldCall(int requestId, @NonNull UUID callId) {
|
||||
Log.e(TAG, "onHoldCall: unimplemented, however CAPABILITY_HOLD_CALL is set!");
|
||||
}
|
||||
|
||||
/**
|
||||
* A remote client has requested to unhold the call.
|
||||
*
|
||||
* <p>
|
||||
* An application must call {@link BluetoothLeCallControl#requestResult} to complete the
|
||||
* request.
|
||||
*
|
||||
* @param requestId The Id of the request
|
||||
* @param callId The call Id requested to unhold
|
||||
* @hide
|
||||
*/
|
||||
public void onUnholdCall(int requestId, @NonNull UUID callId) {
|
||||
Log.e(TAG, "onUnholdCall: unimplemented, however CAPABILITY_HOLD_CALL is set!");
|
||||
}
|
||||
|
||||
/**
|
||||
* A remote client has requested to place a call.
|
||||
*
|
||||
* <p>
|
||||
* An application must call {@link BluetoothLeCallControl#requestResult} to complete the
|
||||
* request.
|
||||
*
|
||||
* @param requestId The Id of the request
|
||||
* @param callId The Id to be assigned for the new call
|
||||
* @param uri The caller URI requested
|
||||
* @hide
|
||||
*/
|
||||
public abstract void onPlaceCall(int requestId, @NonNull UUID callId, @NonNull String uri);
|
||||
|
||||
/**
|
||||
* A remote client has requested to join the calls.
|
||||
*
|
||||
* <p>
|
||||
* An application must call {@link BluetoothLeCallControl#requestResult} to complete the
|
||||
* request.
|
||||
*
|
||||
* @param requestId The Id of the request
|
||||
* @param callIds The call Id list requested to join
|
||||
* @hide
|
||||
*/
|
||||
public void onJoinCalls(int requestId, @NonNull List<UUID> callIds) {
|
||||
Log.e(TAG, "onJoinCalls: unimplemented, however CAPABILITY_JOIN_CALLS is set!");
|
||||
}
|
||||
}
|
||||
|
||||
private class CallbackWrapper extends IBluetoothLeCallControlCallback.Stub {
|
||||
|
||||
private final Executor mExecutor;
|
||||
private final Callback mCallback;
|
||||
|
||||
CallbackWrapper(Executor executor, Callback callback) {
|
||||
mExecutor = executor;
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBearerRegistered(int ccid) {
|
||||
if (mCallback != null) {
|
||||
mCcid = ccid;
|
||||
} else {
|
||||
// registration timeout
|
||||
Log.e(TAG, "onBearerRegistered: mCallback is null");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAcceptCall(int requestId, ParcelUuid uuid) {
|
||||
final long identityToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onAcceptCall(requestId, uuid.getUuid()));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminateCall(int requestId, ParcelUuid uuid) {
|
||||
final long identityToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onTerminateCall(requestId, uuid.getUuid()));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHoldCall(int requestId, ParcelUuid uuid) {
|
||||
final long identityToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onHoldCall(requestId, uuid.getUuid()));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnholdCall(int requestId, ParcelUuid uuid) {
|
||||
final long identityToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onUnholdCall(requestId, uuid.getUuid()));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPlaceCall(int requestId, ParcelUuid uuid, String uri) {
|
||||
final long identityToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onPlaceCall(requestId, uuid.getUuid(), uri));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onJoinCalls(int requestId, List<ParcelUuid> parcelUuids) {
|
||||
List<UUID> uuids = new ArrayList<>();
|
||||
for (ParcelUuid parcelUuid : parcelUuids) {
|
||||
uuids.add(parcelUuid.getUuid());
|
||||
}
|
||||
|
||||
final long identityToken = Binder.clearCallingIdentity();
|
||||
try {
|
||||
mExecutor.execute(() -> mCallback.onJoinCalls(requestId, uuids));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(identityToken);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private Context mContext;
|
||||
private ServiceListener mServiceListener;
|
||||
private volatile IBluetoothLeCallControl mService;
|
||||
private BluetoothAdapter mAdapter;
|
||||
private int mCcid = 0;
|
||||
private String mToken;
|
||||
private Callback mCallback = null;
|
||||
|
||||
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 BluetoothLeCallControl proxy object for interacting with the local Bluetooth
|
||||
* telephone bearer service.
|
||||
*/
|
||||
/* package */ BluetoothLeCallControl(Context context, ServiceListener listener) {
|
||||
mContext = context;
|
||||
mAdapter = BluetoothAdapter.getDefaultAdapter();
|
||||
mServiceListener = listener;
|
||||
|
||||
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.LE_CALL_CONTROL,
|
||||
mConnection);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to bind TelephoneBearerService", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void doUnbind() {
|
||||
synchronized (mConnection) {
|
||||
if (mService != null) {
|
||||
if (VDBG)
|
||||
Log.d(TAG, "Unbinding service...");
|
||||
try {
|
||||
mAdapter.getBluetoothManager().
|
||||
unbindBluetoothProfileService(BluetoothProfile.LE_CALL_CONTROL,
|
||||
mConnection);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Unable to unbind TelephoneBearerService", e);
|
||||
} finally {
|
||||
mService = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void close() {
|
||||
if (VDBG)
|
||||
log("close()");
|
||||
unregisterBearer();
|
||||
|
||||
IBluetoothManager mgr = mAdapter.getBluetoothManager();
|
||||
if (mgr != null) {
|
||||
try {
|
||||
mgr.unregisterStateChangeCallback(mBluetoothStateChangeCallback);
|
||||
} catch (RemoteException re) {
|
||||
Log.e(TAG, "", re);
|
||||
}
|
||||
}
|
||||
mServiceListener = null;
|
||||
doUnbind();
|
||||
}
|
||||
|
||||
private IBluetoothLeCallControl getService() {
|
||||
return mService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public int getConnectionState(@Nullable BluetoothDevice device) {
|
||||
throw new UnsupportedOperationException("not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public @NonNull List<BluetoothDevice> getConnectedDevices() {
|
||||
throw new UnsupportedOperationException("not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Not supported
|
||||
*
|
||||
* @throws UnsupportedOperationException
|
||||
*/
|
||||
@Override
|
||||
public @NonNull List<BluetoothDevice> getDevicesMatchingConnectionStates(
|
||||
@NonNull int[] states) {
|
||||
throw new UnsupportedOperationException("not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Register Telephone Bearer exposing the interface that allows remote devices
|
||||
* to track and control the call states.
|
||||
*
|
||||
* <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.
|
||||
*
|
||||
* <!-- The UCI is a String identifier of the telephone bearer as defined at
|
||||
* https://www.bluetooth.com/specifications/assigned-numbers/uniform-caller-identifiers
|
||||
* (login required). -->
|
||||
*
|
||||
* <!-- The examples of common URI schemes can be found in
|
||||
* https://iana.org/assignments/uri-schemes/uri-schemes.xhtml -->
|
||||
*
|
||||
* <!-- The Technology is an integer value. The possible values are defined at
|
||||
* https://www.bluetooth.com/specifications/assigned-numbers (login required).
|
||||
* -->
|
||||
*
|
||||
* @param uci Bearer Unique Client Identifier
|
||||
* @param uriSchemes URI Schemes supported list
|
||||
* @param capabilities bearer capabilities
|
||||
* @param provider Network provider name
|
||||
* @param technology Network technology
|
||||
* @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 on success, false otherwise
|
||||
* @hide
|
||||
*/
|
||||
@SuppressLint("ExecutorRegistration")
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public boolean registerBearer(@Nullable String uci,
|
||||
@NonNull List<String> uriSchemes, int capabilities,
|
||||
@NonNull String provider, int technology,
|
||||
@NonNull Executor executor, @NonNull Callback callback) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "registerBearer");
|
||||
}
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("null parameter: " + callback);
|
||||
}
|
||||
if (mCcid != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
mToken = uci;
|
||||
|
||||
final IBluetoothLeCallControl service = getService();
|
||||
if (service != null) {
|
||||
if (mCallback != null) {
|
||||
Log.e(TAG, "Bearer can be opened only once");
|
||||
return false;
|
||||
}
|
||||
|
||||
mCallback = callback;
|
||||
try {
|
||||
CallbackWrapper callbackWrapper = new CallbackWrapper(executor, callback);
|
||||
service.registerBearer(mToken, callbackWrapper, uci, uriSchemes, capabilities,
|
||||
provider, technology);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
mCallback = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (mCcid == 0) {
|
||||
mCallback = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister Telephone Bearer Service and destroy all the associated data.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public void unregisterBearer() {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "unregisterBearer");
|
||||
}
|
||||
if (mCcid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
int ccid = mCcid;
|
||||
mCcid = 0;
|
||||
mCallback = null;
|
||||
|
||||
final IBluetoothLeCallControl service = getService();
|
||||
if (service != null) {
|
||||
try {
|
||||
service.unregisterBearer(mToken);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
}
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Content Control ID (CCID) value.
|
||||
*
|
||||
* @return ccid Content Control ID value
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public int getContentControlId() {
|
||||
return mCcid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify about the newly added call.
|
||||
*
|
||||
* <p>
|
||||
* This shall be called as early as possible after the call has been added.
|
||||
*
|
||||
* <p>
|
||||
* Requires {@link android.Manifest.permission#BLUETOOTH} permission.
|
||||
*
|
||||
* @param call Newly added call
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public void onCallAdded(@NonNull BluetoothLeCall call) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "onCallAdded: call=" + call);
|
||||
}
|
||||
if (mCcid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final IBluetoothLeCallControl service = getService();
|
||||
if (service != null) {
|
||||
try {
|
||||
service.callAdded(mCcid, call);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
}
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify about the removed call.
|
||||
*
|
||||
* <p>
|
||||
* This shall be called as early as possible after the call has been removed.
|
||||
*
|
||||
* <p>
|
||||
* Requires {@link android.Manifest.permission#BLUETOOTH} permission.
|
||||
*
|
||||
* @param callId The Id of a call that has been removed
|
||||
* @param reason Call termination reason
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public void onCallRemoved(@NonNull UUID callId, @TerminationReason int reason) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "callRemoved: callId=" + callId);
|
||||
}
|
||||
if (mCcid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final IBluetoothLeCallControl service = getService();
|
||||
if (service != null) {
|
||||
try {
|
||||
service.callRemoved(mCcid, new ParcelUuid(callId), reason);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
}
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify the call state change
|
||||
*
|
||||
* <p>
|
||||
* This shall be called as early as possible after the state of the call has
|
||||
* changed.
|
||||
*
|
||||
* <p>
|
||||
* Requires {@link android.Manifest.permission#BLUETOOTH} permission.
|
||||
*
|
||||
* @param callId The call Id that state has been changed
|
||||
* @param state Call state
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public void onCallStateChanged(@NonNull UUID callId, @BluetoothLeCall.State int state) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "callStateChanged: callId=" + callId + " state=" + state);
|
||||
}
|
||||
if (mCcid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final IBluetoothLeCallControl service = getService();
|
||||
if (service != null) {
|
||||
try {
|
||||
service.callStateChanged(mCcid, new ParcelUuid(callId), state);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
}
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the current calls list
|
||||
*
|
||||
* <p>
|
||||
* This function must be invoked after registration if application has any
|
||||
* calls.
|
||||
*
|
||||
* @param calls current calls list
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public void currentCallsList(@NonNull List<BluetoothLeCall> calls) {
|
||||
final IBluetoothLeCallControl service = getService();
|
||||
if (service != null) {
|
||||
try {
|
||||
service.currentCallsList(mCcid, calls);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Provide the network current status
|
||||
*
|
||||
* <p>
|
||||
* This function must be invoked on change of network state.
|
||||
*
|
||||
* <p>
|
||||
* Requires {@link android.Manifest.permission#BLUETOOTH} permission.
|
||||
*
|
||||
* <!-- The Technology is an integer value. The possible values are defined at
|
||||
* https://www.bluetooth.com/specifications/assigned-numbers (login required).
|
||||
* -->
|
||||
*
|
||||
* @param provider Network provider name
|
||||
* @param technology Network technology
|
||||
* @hide
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public void networkStateChanged(@NonNull String provider, int technology) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "networkStateChanged: provider=" + provider + ", technology=" + technology);
|
||||
}
|
||||
if (mCcid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final IBluetoothLeCallControl service = getService();
|
||||
if (service != null) {
|
||||
try {
|
||||
service.networkStateChanged(mCcid, provider, technology);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
}
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a response to a call control request to a remote device.
|
||||
*
|
||||
* <p>
|
||||
* This function must be invoked in when a request is received by one of these
|
||||
* callback methods:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link Callback#onAcceptCall}
|
||||
* <li>{@link Callback#onTerminateCall}
|
||||
* <li>{@link Callback#onHoldCall}
|
||||
* <li>{@link Callback#onUnholdCall}
|
||||
* <li>{@link Callback#onPlaceCall}
|
||||
* <li>{@link Callback#onJoinCalls}
|
||||
* </ul>
|
||||
*
|
||||
* @param requestId The ID of the request that was received with the callback
|
||||
* @param result The result of the request to be sent to the remote devices
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public void requestResult(int requestId, @Result int result) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "requestResult: requestId=" + requestId + " result=" + result);
|
||||
}
|
||||
if (mCcid == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
final IBluetoothLeCallControl service = getService();
|
||||
if (service != null) {
|
||||
try {
|
||||
service.requestResult(mCcid, requestId, result);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
private static boolean isValidDevice(@Nullable BluetoothDevice device) {
|
||||
return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
|
||||
}
|
||||
|
||||
private static void log(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
|
||||
private final IBluetoothProfileServiceConnection mConnection =
|
||||
new IBluetoothProfileServiceConnection.Stub() {
|
||||
@Override
|
||||
public void onServiceConnected(ComponentName className, IBinder service) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Proxy object connected");
|
||||
}
|
||||
mService = IBluetoothLeCallControl.Stub.asInterface(Binder.allowBlocking(service));
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_CONNECTED));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onServiceDisconnected(ComponentName className) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Proxy object disconnected");
|
||||
}
|
||||
doUnbind();
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MESSAGE_TBS_SERVICE_DISCONNECTED));
|
||||
}
|
||||
};
|
||||
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
|
||||
@Override
|
||||
public void handleMessage(Message msg) {
|
||||
switch (msg.what) {
|
||||
case MESSAGE_TBS_SERVICE_CONNECTED: {
|
||||
if (mServiceListener != null) {
|
||||
mServiceListener.onServiceConnected(BluetoothProfile.LE_CALL_CONTROL,
|
||||
BluetoothLeCallControl.this);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MESSAGE_TBS_SERVICE_DISCONNECTED: {
|
||||
if (mServiceListener != null) {
|
||||
mServiceListener.onServiceDisconnected(BluetoothProfile.LE_CALL_CONTROL);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,283 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.RequiresFeature;
|
||||
import android.annotation.RequiresNoPermission;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SystemService;
|
||||
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.RemoteException;
|
||||
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 AttributionSource mAttributionSource;
|
||||
private final BluetoothAdapter mAdapter;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public BluetoothManager(Context context) {
|
||||
mAttributionSource = (context != null) ? context.getAttributionSource() :
|
||||
AttributionSource.myAttributionSource();
|
||||
mAdapter = BluetoothAdapter.createAdapter(mAttributionSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the BLUETOOTH Adapter for this device.
|
||||
*
|
||||
* @return the BLUETOOTH Adapter
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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}
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getConnectedDevices(int profile) {
|
||||
if (DBG) Log.d(TAG, "getConnectedDevices");
|
||||
return getDevicesMatchingConnectionStates(profile, new int[] {
|
||||
BluetoothProfile.STATE_CONNECTED
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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 = Attributable.setAttributionSource(
|
||||
iGatt.getDevicesMatchingConnectionStates(states, mAttributionSource),
|
||||
mAttributionSource);
|
||||
} 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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 eatt_support idicates if server should use eatt channel for notifications.
|
||||
* @return BluetoothGattServer instance
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public BluetoothGattServer openGattServer(Context context,
|
||||
BluetoothGattServerCallback callback, boolean eatt_support) {
|
||||
return (openGattServer(context, callback, BluetoothDevice.TRANSPORT_AUTO, eatt_support));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public BluetoothGattServer openGattServer(Context context,
|
||||
BluetoothGattServerCallback callback, int transport) {
|
||||
return (openGattServer(context, callback, transport, false));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* @param eatt_support idicates if server should use eatt channel for notifications.
|
||||
* @return BluetoothGattServer instance
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public BluetoothGattServer openGattServer(Context context,
|
||||
BluetoothGattServerCallback callback, int transport, boolean eatt_support) {
|
||||
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, mAdapter);
|
||||
Boolean regStatus = mGattServer.registerCallback(callback, eatt_support);
|
||||
return regStatus ? mGattServer : null;
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,513 +0,0 @@
|
||||
/*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.RequiresNoPermission;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.CloseGuard;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
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 final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
private final BluetoothProfileConnector<IBluetoothMap> mProfileConnector =
|
||||
new BluetoothProfileConnector(this, BluetoothProfile.MAP,
|
||||
"BluetoothMap", IBluetoothMap.class.getName()) {
|
||||
@Override
|
||||
public IBluetoothMap getServiceInterface(IBinder service) {
|
||||
return IBluetoothMap.Stub.asInterface(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothMap proxy object.
|
||||
*/
|
||||
/* package */ BluetoothMap(Context context, ServiceListener listener,
|
||||
BluetoothAdapter adapter) {
|
||||
if (DBG) Log.d(TAG, "Create BluetoothMap proxy object");
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
mProfileConnector.connect(context, listener);
|
||||
mCloseGuard = new CloseGuard();
|
||||
mCloseGuard.open("close");
|
||||
}
|
||||
|
||||
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
|
||||
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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getState() {
|
||||
if (VDBG) log("getState()");
|
||||
final IBluetoothMap service = getService();
|
||||
final int defaultValue = BluetoothMap.STATE_ERROR;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getState(mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public BluetoothDevice getClient() {
|
||||
if (VDBG) log("getClient()");
|
||||
final IBluetoothMap service = getService();
|
||||
final BluetoothDevice defaultValue = null;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<BluetoothDevice> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getClient(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean isConnected(BluetoothDevice device) {
|
||||
if (VDBG) log("isConnected(" + device + ")");
|
||||
final IBluetoothMap service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.isConnected(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate connection. Initiation of outgoing connections is not
|
||||
* supported for MAP server.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean disconnect(BluetoothDevice device) {
|
||||
if (DBG) log("disconnect(" + device + ")");
|
||||
final IBluetoothMap service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.disconnect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public @NonNull List<BluetoothDevice> getConnectedDevices() {
|
||||
if (DBG) log("getConnectedDevices()");
|
||||
final IBluetoothMap service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of devices matching specified states. Currently at most one.
|
||||
*
|
||||
* @return list of matching devices
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
if (DBG) log("getDevicesMatchingStates()");
|
||||
final IBluetoothMap service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection state of device
|
||||
*
|
||||
* @return device connection state
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getConnectionState(BluetoothDevice device) {
|
||||
if (DBG) log("getConnectionState(" + device + ")");
|
||||
final IBluetoothMap service = getService();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
|
||||
@ConnectionPolicy int connectionPolicy) {
|
||||
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
|
||||
final IBluetoothMap service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("getConnectionPolicy(" + device + ")");
|
||||
final IBluetoothMap service = getService();
|
||||
final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionPolicy(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static void log(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
return mAdapter.isEnabled();
|
||||
}
|
||||
|
||||
private static boolean isValidDevice(BluetoothDevice device) {
|
||||
return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
|
||||
}
|
||||
}
|
||||
@@ -1,686 +0,0 @@
|
||||
/*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
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.app.PendingIntent;
|
||||
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* This class provides the APIs to control the Bluetooth MAP MCE Profile.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
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);
|
||||
|
||||
/** @hide */
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_CONNECTION_STATE_CHANGED =
|
||||
"android.bluetooth.mapmce.profile.action.CONNECTION_STATE_CHANGED";
|
||||
/** @hide */
|
||||
@RequiresPermission(android.Manifest.permission.RECEIVE_SMS)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_MESSAGE_RECEIVED =
|
||||
"android.bluetooth.mapmce.profile.action.MESSAGE_RECEIVED";
|
||||
/* Actions to be used for pending intents */
|
||||
/** @hide */
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_MESSAGE_SENT_SUCCESSFULLY =
|
||||
"android.bluetooth.mapmce.profile.action.MESSAGE_SENT_SUCCESSFULLY";
|
||||
/** @hide */
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_MESSAGE_DELIVERED_SUCCESSFULLY =
|
||||
"android.bluetooth.mapmce.profile.action.MESSAGE_DELIVERED_SUCCESSFULLY";
|
||||
|
||||
/**
|
||||
* Action to notify read status changed
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_MESSAGE_READ_STATUS_CHANGED =
|
||||
"android.bluetooth.mapmce.profile.action.MESSAGE_READ_STATUS_CHANGED";
|
||||
|
||||
/**
|
||||
* Action to notify deleted status changed
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_MESSAGE_DELETED_STATUS_CHANGED =
|
||||
"android.bluetooth.mapmce.profile.action.MESSAGE_DELETED_STATUS_CHANGED";
|
||||
|
||||
/**
|
||||
* Extras used in ACTION_MESSAGE_RECEIVED intent.
|
||||
* NOTE: HANDLE is only valid for a single session with the device.
|
||||
*/
|
||||
/** @hide */
|
||||
public static final String EXTRA_MESSAGE_HANDLE =
|
||||
"android.bluetooth.mapmce.profile.extra.MESSAGE_HANDLE";
|
||||
/** @hide */
|
||||
public static final String EXTRA_MESSAGE_TIMESTAMP =
|
||||
"android.bluetooth.mapmce.profile.extra.MESSAGE_TIMESTAMP";
|
||||
/** @hide */
|
||||
public static final String EXTRA_MESSAGE_READ_STATUS =
|
||||
"android.bluetooth.mapmce.profile.extra.MESSAGE_READ_STATUS";
|
||||
/** @hide */
|
||||
public static final String EXTRA_SENDER_CONTACT_URI =
|
||||
"android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_URI";
|
||||
/** @hide */
|
||||
public static final String EXTRA_SENDER_CONTACT_NAME =
|
||||
"android.bluetooth.mapmce.profile.extra.SENDER_CONTACT_NAME";
|
||||
|
||||
/**
|
||||
* Used as a boolean extra in ACTION_MESSAGE_DELETED_STATUS_CHANGED
|
||||
* Contains the MAP message deleted status
|
||||
* Possible values are:
|
||||
* true: deleted
|
||||
* false: undeleted
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_MESSAGE_DELETED_STATUS =
|
||||
"android.bluetooth.mapmce.profile.extra.MESSAGE_DELETED_STATUS";
|
||||
|
||||
/**
|
||||
* Extra used in ACTION_MESSAGE_READ_STATUS_CHANGED or ACTION_MESSAGE_DELETED_STATUS_CHANGED
|
||||
* Possible values are:
|
||||
* 0: failure
|
||||
* 1: success
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final String EXTRA_RESULT_CODE =
|
||||
"android.bluetooth.device.extra.RESULT_CODE";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
/** @hide */
|
||||
private static final int UPLOADING_FEATURE_BITMASK = 0x08;
|
||||
|
||||
/*
|
||||
* UNREAD, READ, UNDELETED, DELETED are passed as parameters
|
||||
* to setMessageStatus to indicate the messages new state.
|
||||
*/
|
||||
|
||||
/** @hide */
|
||||
public static final int UNREAD = 0;
|
||||
/** @hide */
|
||||
public static final int READ = 1;
|
||||
/** @hide */
|
||||
public static final int UNDELETED = 2;
|
||||
/** @hide */
|
||||
public static final int DELETED = 3;
|
||||
|
||||
private final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
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(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothMapClient proxy object.
|
||||
*/
|
||||
/* package */ BluetoothMapClient(Context context, ServiceListener listener,
|
||||
BluetoothAdapter adapter) {
|
||||
if (DBG) Log.d(TAG, "Create BluetoothMapClient proxy object");
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
mProfileConnector.connect(context, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean isConnected(BluetoothDevice device) {
|
||||
if (VDBG) Log.d(TAG, "isConnected(" + device + ")");
|
||||
final IBluetoothMapClient service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.isConnected(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate connection. Initiation of outgoing connections is not
|
||||
* supported for MAP server.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean connect(BluetoothDevice device) {
|
||||
if (DBG) Log.d(TAG, "connect(" + device + ")" + "for MAPS MCE");
|
||||
final IBluetoothMapClient service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.connect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate disconnect.
|
||||
*
|
||||
* @param device Remote Bluetooth Device
|
||||
* @return false on error, true otherwise
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean disconnect(BluetoothDevice device) {
|
||||
if (DBG) Log.d(TAG, "disconnect(" + device + ")");
|
||||
final IBluetoothMapClient service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.disconnect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of connected devices. Currently at most one.
|
||||
*
|
||||
* @return list of connected devices
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getConnectedDevices() {
|
||||
if (DBG) Log.d(TAG, "getConnectedDevices()");
|
||||
final IBluetoothMapClient service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of devices matching specified states. Currently at most one.
|
||||
*
|
||||
* @return list of matching devices
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
if (DBG) Log.d(TAG, "getDevicesMatchingStates()");
|
||||
final IBluetoothMapClient service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection state of device
|
||||
*
|
||||
* @return device connection state
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getConnectionState(BluetoothDevice device) {
|
||||
if (DBG) Log.d(TAG, "getConnectionState(" + device + ")");
|
||||
final IBluetoothMapClient service = getService();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver<>();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
|
||||
if (VDBG) Log.d(TAG, "getConnectionPolicy(" + device + ")");
|
||||
final IBluetoothMapClient service = getService();
|
||||
final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionPolicy(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 Collection 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
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.SEND_SMS,
|
||||
})
|
||||
public boolean sendMessage(@NonNull BluetoothDevice device, @NonNull Collection<Uri> contacts,
|
||||
@NonNull String message, @Nullable PendingIntent sentIntent,
|
||||
@Nullable PendingIntent deliveredIntent) {
|
||||
return sendMessage(device, contacts.toArray(new Uri[contacts.size()]), message, sentIntent,
|
||||
deliveredIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @hide
|
||||
*/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.SEND_SMS,
|
||||
})
|
||||
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();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.sendMessage(device, contacts, message, sentIntent, deliveredIntent,
|
||||
mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.READ_SMS,
|
||||
})
|
||||
public boolean getUnreadMessages(BluetoothDevice device) {
|
||||
if (DBG) Log.d(TAG, "getUnreadMessages(" + device + ")");
|
||||
final IBluetoothMapClient service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.getUnreadMessages(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean isUploadingSupported(BluetoothDevice device) {
|
||||
if (DBG) Log.d(TAG, "isUploadingSupported(" + device + ")");
|
||||
final IBluetoothMapClient service = getService();
|
||||
final int defaultValue = 0;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getSupportedFeatures(device, mAttributionSource, recv);
|
||||
return (recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue)
|
||||
& UPLOADING_FEATURE_BITMASK) > 0;
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set message status of message on MSE
|
||||
* <p>
|
||||
* When read status changed, the result will be published via
|
||||
* {@link #ACTION_MESSAGE_READ_STATUS_CHANGED}
|
||||
* When deleted status changed, the result will be published via
|
||||
* {@link #ACTION_MESSAGE_DELETED_STATUS_CHANGED}
|
||||
*
|
||||
* @param device Bluetooth device
|
||||
* @param handle message handle
|
||||
* @param status <code>UNREAD</code> for "unread", <code>READ</code> for
|
||||
* "read", <code>UNDELETED</code> for "undeleted", <code>DELETED</code> for
|
||||
* "deleted", otherwise return error
|
||||
* @return <code>true</code> if request has been sent, <code>false</code> on error
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.READ_SMS,
|
||||
})
|
||||
public boolean setMessageStatus(BluetoothDevice device, String handle, int status) {
|
||||
if (DBG) Log.d(TAG, "setMessageStatus(" + device + ", " + handle + ", " + status + ")");
|
||||
final IBluetoothMapClient service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) Log.d(TAG, Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device) && handle != null && (status == READ
|
||||
|| status == UNREAD || status == UNDELETED || status == DELETED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setMessageStatus(device, handle, status, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
return mAdapter.isEnabled();
|
||||
}
|
||||
|
||||
private static boolean isValidDevice(BluetoothDevice device) {
|
||||
return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
|
||||
}
|
||||
}
|
||||
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.Nullable;
|
||||
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(@Nullable 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;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* 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.SuppressLint;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
* BluetoothOutputStream.
|
||||
*
|
||||
* Used to read from a Bluetooth socket.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
/*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);
|
||||
}
|
||||
}
|
||||
@@ -1,525 +0,0 @@
|
||||
/*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
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.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
@SuppressLint("ActionValue")
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@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";
|
||||
|
||||
/**
|
||||
* Intent used to broadcast the change in tethering state of the Pan
|
||||
* Profile
|
||||
*
|
||||
* <p>This intent will have 1 extra:
|
||||
* <ul>
|
||||
* <li> {@link #EXTRA_TETHERING_STATE} - The current state of Bluetooth
|
||||
* tethering. </li>
|
||||
* </ul>
|
||||
*
|
||||
* <p> {@link #EXTRA_TETHERING_STATE} can be any of {@link #TETHERING_STATE_OFF} or
|
||||
* {@link #TETHERING_STATE_ON}
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_TETHERING_STATE_CHANGED =
|
||||
"android.bluetooth.action.TETHERING_STATE_CHANGED";
|
||||
|
||||
/**
|
||||
* Extra for {@link #ACTION_TETHERING_STATE_CHANGED} intent
|
||||
* The tethering state of the PAN profile.
|
||||
* It can be one of {@link #TETHERING_STATE_OFF} or {@link #TETHERING_STATE_ON}.
|
||||
*/
|
||||
public static final String EXTRA_TETHERING_STATE =
|
||||
"android.bluetooth.extra.TETHERING_STATE";
|
||||
|
||||
/** @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;
|
||||
|
||||
/** @hide **/
|
||||
@IntDef({TETHERING_STATE_OFF, TETHERING_STATE_ON})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface TetheringState{}
|
||||
|
||||
public static final int TETHERING_STATE_OFF = 1;
|
||||
|
||||
public static final int TETHERING_STATE_ON = 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 final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
private final BluetoothProfileConnector<IBluetoothPan> mProfileConnector =
|
||||
new BluetoothProfileConnector(this, BluetoothProfile.PAN,
|
||||
"BluetoothPan", IBluetoothPan.class.getName()) {
|
||||
@Override
|
||||
public IBluetoothPan getServiceInterface(IBinder service) {
|
||||
return IBluetoothPan.Stub.asInterface(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,
|
||||
BluetoothAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean connect(BluetoothDevice device) {
|
||||
if (DBG) log("connect(" + device + ")");
|
||||
final IBluetoothPan service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.connect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean disconnect(BluetoothDevice device) {
|
||||
if (DBG) log("disconnect(" + device + ")");
|
||||
final IBluetoothPan service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.disconnect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
|
||||
@ConnectionPolicy int connectionPolicy) {
|
||||
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
|
||||
final IBluetoothPan service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public @NonNull List<BluetoothDevice> getConnectedDevices() {
|
||||
if (VDBG) log("getConnectedDevices()");
|
||||
final IBluetoothPan service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
if (VDBG) log("getDevicesMatchingStates()");
|
||||
final IBluetoothPan service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public int getConnectionState(@NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("getState(" + device + ")");
|
||||
final IBluetoothPan service = getService();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turns on/off bluetooth tethering
|
||||
*
|
||||
* @param value is whether to enable or disable bluetooth tethering
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
android.Manifest.permission.TETHER_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) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver recv = new SynchronousResultReceiver();
|
||||
service.setBluetoothTethering(value, mAttributionSource, recv);
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether tethering is enabled
|
||||
*
|
||||
* @return true if tethering is on, false if not or some error occurred
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean isTetheringOn() {
|
||||
if (VDBG) log("isTetheringOn()");
|
||||
final IBluetoothPan service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.isTetheringOn(mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
@@ -1,317 +0,0 @@
|
||||
/*
|
||||
* 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.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
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}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SuppressLint("ActionValue")
|
||||
@SystemApi
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_CONNECTION_STATE_CHANGED =
|
||||
"android.bluetooth.pbap.profile.action.CONNECTION_STATE_CHANGED";
|
||||
|
||||
private final AttributionSource mAttributionSource;
|
||||
|
||||
/** @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<IBluetoothPbap> mProfileConnector =
|
||||
new BluetoothProfileConnector(this, BluetoothProfile.PBAP, "BluetoothPbap",
|
||||
IBluetoothPbap.class.getName()) {
|
||||
@Override
|
||||
public IBluetoothPbap getServiceInterface(IBinder service) {
|
||||
return IBluetoothPbap.Stub.asInterface(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothPbap proxy object.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public BluetoothPbap(Context context, ServiceListener listener, BluetoothAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
mProfileConnector.connect(context, listener);
|
||||
}
|
||||
|
||||
/** @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() {
|
||||
mProfileConnector.disconnect();
|
||||
}
|
||||
|
||||
private IBluetoothPbap getService() {
|
||||
return (IBluetoothPbap) mProfileConnector.getService();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getConnectedDevices() {
|
||||
log("getConnectedDevices()");
|
||||
final IBluetoothPbap service = getService();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
return new ArrayList<BluetoothDevice>();
|
||||
}
|
||||
try {
|
||||
return Attributable.setAttributionSource(
|
||||
service.getConnectedDevices(mAttributionSource), mAttributionSource);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
}
|
||||
return new ArrayList<BluetoothDevice>();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public @BtProfileState int getConnectionState(@NonNull BluetoothDevice device) {
|
||||
log("getConnectionState: device=" + device);
|
||||
try {
|
||||
final IBluetoothPbap service = getService();
|
||||
if (service != null && isEnabled() && isValidDevice(device)) {
|
||||
return service.getConnectionState(device, mAttributionSource);
|
||||
}
|
||||
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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
log("getDevicesMatchingConnectionStates: states=" + Arrays.toString(states));
|
||||
final IBluetoothPbap service = getService();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
return new ArrayList<BluetoothDevice>();
|
||||
}
|
||||
try {
|
||||
return Attributable.setAttributionSource(
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource),
|
||||
mAttributionSource);
|
||||
} 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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
|
||||
@ConnectionPolicy int connectionPolicy) {
|
||||
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
|
||||
try {
|
||||
final IBluetoothPbap 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, mAttributionSource);
|
||||
}
|
||||
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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean disconnect(BluetoothDevice device) {
|
||||
log("disconnect()");
|
||||
final IBluetoothPbap service = getService();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
return false;
|
||||
}
|
||||
try {
|
||||
service.disconnect(device, mAttributionSource);
|
||||
return true;
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, e.toString());
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,405 +0,0 @@
|
||||
/*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
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 final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
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(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothPbapClient proxy object.
|
||||
*/
|
||||
BluetoothPbapClient(Context context, ServiceListener listener, BluetoothAdapter adapter) {
|
||||
if (DBG) {
|
||||
Log.d(TAG, "Create BluetoothPbapClient proxy object");
|
||||
}
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean connect(BluetoothDevice device) {
|
||||
if (DBG) {
|
||||
log("connect(" + device + ") for PBAP Client.");
|
||||
}
|
||||
final IBluetoothPbapClient service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.connect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate disconnect.
|
||||
*
|
||||
* @param device Remote Bluetooth Device
|
||||
* @return false on error, true otherwise
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean disconnect(BluetoothDevice device) {
|
||||
if (DBG) {
|
||||
log("disconnect(" + device + ")" + new Exception());
|
||||
}
|
||||
final IBluetoothPbapClient service = getService();
|
||||
final boolean defaultValue = true;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.disconnect(device, mAttributionSource, recv);
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
return true;
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of connected devices.
|
||||
* Currently at most one.
|
||||
*
|
||||
* @return list of connected devices
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getConnectedDevices() {
|
||||
if (DBG) {
|
||||
log("getConnectedDevices()");
|
||||
}
|
||||
final IBluetoothPbapClient service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of devices matching specified states. Currently at most one.
|
||||
*
|
||||
* @return list of matching devices
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
if (DBG) {
|
||||
log("getDevicesMatchingStates()");
|
||||
}
|
||||
final IBluetoothPbapClient service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection state of device
|
||||
*
|
||||
* @return device connection state
|
||||
*/
|
||||
@Override
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getConnectionState(BluetoothDevice device) {
|
||||
if (DBG) {
|
||||
log("getConnectionState(" + device + ")");
|
||||
}
|
||||
final IBluetoothPbapClient service = getService();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static void log(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
return mAdapter.isEnabled();
|
||||
}
|
||||
|
||||
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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
|
||||
@ConnectionPolicy int connectionPolicy) {
|
||||
if (DBG) {
|
||||
log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
|
||||
}
|
||||
final IBluetoothPbapClient service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
|
||||
if (VDBG) {
|
||||
log("getConnectionPolicy(" + device + ")");
|
||||
}
|
||||
final IBluetoothPbapClient service = getService();
|
||||
final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionPolicy(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
@@ -1,459 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.IntDef;
|
||||
import android.annotation.RequiresNoPermission;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.os.Build;
|
||||
|
||||
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
|
||||
*/
|
||||
@SystemApi
|
||||
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;
|
||||
|
||||
/**
|
||||
* LE Audio Device
|
||||
*
|
||||
*/
|
||||
int LE_AUDIO = 22;
|
||||
|
||||
/**
|
||||
* Volume Control profile
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
int VOLUME_CONTROL = 23;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Media Control Profile server
|
||||
*
|
||||
*/
|
||||
int MCP_SERVER = 24;
|
||||
|
||||
/**
|
||||
* Coordinated Set Identification Profile set coordinator
|
||||
*
|
||||
*/
|
||||
int CSIP_SET_COORDINATOR = 25;
|
||||
|
||||
/**
|
||||
* LE Audio Broadcast Source
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
int LE_AUDIO_BROADCAST = 26;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
* Telephone Bearer Service from Call Control Profile
|
||||
*
|
||||
*/
|
||||
int LE_CALL_CONTROL = 27;
|
||||
|
||||
/**
|
||||
* 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 = 27;
|
||||
|
||||
/**
|
||||
* Default priority for devices that we try to auto-connect to and
|
||||
* and allow incoming connections for the profile
|
||||
*
|
||||
* @hide
|
||||
**/
|
||||
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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}
|
||||
*/
|
||||
@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}
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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}
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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";
|
||||
case LE_AUDIO:
|
||||
return "LE_AUDIO";
|
||||
default:
|
||||
return "UNKNOWN_PROFILE";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,220 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.ServiceConnection;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.util.CloseGuard;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.List;
|
||||
/**
|
||||
* Connector for Bluetooth profile proxies to bind manager service and
|
||||
* profile services
|
||||
* @param <T> The Bluetooth profile interface for this connection.
|
||||
* @hide
|
||||
*/
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
public abstract class BluetoothProfileConnector<T> {
|
||||
private final CloseGuard mCloseGuard = new CloseGuard();
|
||||
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 @Nullable ComponentName resolveSystemService(@NonNull Intent intent,
|
||||
@NonNull PackageManager pm, @PackageManager.ComponentInfoFlags int flags) {
|
||||
List<ResolveInfo> results = pm.queryIntentServices(intent, flags);
|
||||
if (results == null) {
|
||||
return null;
|
||||
}
|
||||
ComponentName comp = null;
|
||||
for (int i = 0; i < results.size(); i++) {
|
||||
ResolveInfo ri = results.get(i);
|
||||
if ((ri.serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
|
||||
continue;
|
||||
}
|
||||
ComponentName foundComp = new ComponentName(ri.serviceInfo.applicationInfo.packageName,
|
||||
ri.serviceInfo.name);
|
||||
if (comp != null) {
|
||||
throw new IllegalStateException("Multiple system services handle " + intent
|
||||
+ ": " + comp + ", " + foundComp);
|
||||
}
|
||||
comp = foundComp;
|
||||
}
|
||||
return comp;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
@Override
|
||||
public void finalize() {
|
||||
mCloseGuard.warnIfOpen();
|
||||
doUnbind();
|
||||
}
|
||||
|
||||
@SuppressLint("AndroidFrameworkRequiresPermission")
|
||||
private boolean doBind() {
|
||||
synchronized (mConnection) {
|
||||
if (mService == null) {
|
||||
logDebug("Binding service...");
|
||||
mCloseGuard.open("doUnbind");
|
||||
try {
|
||||
Intent intent = new Intent(mServiceName);
|
||||
ComponentName comp = resolveSystemService(intent, mContext.getPackageManager(),
|
||||
0);
|
||||
intent.setComponent(comp);
|
||||
if (comp == null || !mContext.bindServiceAsUser(intent, mConnection, 0,
|
||||
UserHandle.CURRENT)) {
|
||||
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...");
|
||||
mCloseGuard.close();
|
||||
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();
|
||||
|
||||
// Preserve legacy compatibility where apps were depending on
|
||||
// registerStateChangeCallback() performing a permissions check which
|
||||
// has been relaxed in modern platform versions
|
||||
if (context.getApplicationInfo().targetSdkVersion <= Build.VERSION_CODES.R
|
||||
&& context.checkSelfPermission(android.Manifest.permission.BLUETOOTH)
|
||||
!= PackageManager.PERMISSION_GRANTED) {
|
||||
throw new SecurityException("Need BLUETOOTH permission");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,491 +0,0 @@
|
||||
/*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.RequiresNoPermission;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SdkConstant;
|
||||
import android.annotation.SdkConstant.SdkConstantType;
|
||||
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothPermission;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.Build;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresLegacyBluetoothPermission
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
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 final BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
private final BluetoothProfileConnector<IBluetoothSap> mProfileConnector =
|
||||
new BluetoothProfileConnector(this, BluetoothProfile.SAP,
|
||||
"BluetoothSap", IBluetoothSap.class.getName()) {
|
||||
@Override
|
||||
public IBluetoothSap getServiceInterface(IBinder service) {
|
||||
return IBluetoothSap.Stub.asInterface(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothSap proxy object.
|
||||
*/
|
||||
/* package */ BluetoothSap(Context context, ServiceListener listener,
|
||||
BluetoothAdapter adapter) {
|
||||
if (DBG) Log.d(TAG, "Create BluetoothSap proxy object");
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getState() {
|
||||
if (VDBG) log("getState()");
|
||||
final IBluetoothSap service = getService();
|
||||
final int defaultValue = BluetoothSap.STATE_ERROR;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getState(mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public BluetoothDevice getClient() {
|
||||
if (VDBG) log("getClient()");
|
||||
final IBluetoothSap service = getService();
|
||||
final BluetoothDevice defaultValue = null;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<BluetoothDevice> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getClient(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean isConnected(BluetoothDevice device) {
|
||||
if (VDBG) log("isConnected(" + device + ")");
|
||||
final IBluetoothSap service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.isConnected(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initiate connection. Initiation of outgoing connections is not
|
||||
* supported for SAP server.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public boolean disconnect(BluetoothDevice device) {
|
||||
if (DBG) log("disconnect(" + device + ")");
|
||||
final IBluetoothSap service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.disconnect(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of connected devices. Currently at most one.
|
||||
*
|
||||
* @return list of connected devices
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getConnectedDevices() {
|
||||
if (DBG) log("getConnectedDevices()");
|
||||
final IBluetoothSap service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of devices matching specified states. Currently at most one.
|
||||
*
|
||||
* @return list of matching devices
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
if (DBG) log("getDevicesMatchingStates()");
|
||||
final IBluetoothSap service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection state of device
|
||||
*
|
||||
* @return device connection state
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getConnectionState(BluetoothDevice device) {
|
||||
if (DBG) log("getConnectionState(" + device + ")");
|
||||
final IBluetoothSap service = getService();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean setConnectionPolicy(BluetoothDevice device,
|
||||
@ConnectionPolicy int connectionPolicy) {
|
||||
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
|
||||
final IBluetoothSap service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public @ConnectionPolicy int getConnectionPolicy(BluetoothDevice device) {
|
||||
if (VDBG) log("getConnectionPolicy(" + device + ")");
|
||||
final IBluetoothSap service = getService();
|
||||
final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionPolicy(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private static void log(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
return mAdapter.isEnabled();
|
||||
}
|
||||
|
||||
private static boolean isValidDevice(BluetoothDevice device) {
|
||||
return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
|
||||
}
|
||||
}
|
||||
@@ -1,266 +0,0 @@
|
||||
/*
|
||||
* 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.SuppressLint;
|
||||
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.
|
||||
*
|
||||
* <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}
|
||||
*/
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
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 person-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();
|
||||
}
|
||||
}
|
||||
@@ -1,809 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.RequiresNoPermission;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.compat.annotation.UnsupportedAppUsage;
|
||||
import android.net.LocalSocket;
|
||||
import android.os.Build;
|
||||
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.
|
||||
*
|
||||
* <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(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||||
/*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;
|
||||
/** when true no SPP SDP record will be created */
|
||||
private boolean mExcludeSdp = false;
|
||||
/** when true Person-in-the-middle protection will be enabled */
|
||||
private boolean mAuthMitm = false;
|
||||
/** Minimum 16 digit pin for sec mode 2 connections */
|
||||
private boolean mMin16DigitPin = false;
|
||||
@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 person-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 = ParcelFileDescriptor.dup(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
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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();
|
||||
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
|
||||
*/
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
/*package*/ int bindListen() {
|
||||
int ret;
|
||||
if (mSocketState == SocketState.CLOSED) return EBADFD;
|
||||
IBluetooth bluetoothProxy = BluetoothAdapter.getDefaultAdapter().getBluetoothService();
|
||||
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.
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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.
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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}
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
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
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,292 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth;
|
||||
|
||||
import android.annotation.SystemApi;
|
||||
|
||||
/**
|
||||
* A class with constants representing possible return values for Bluetooth APIs. General return
|
||||
* values occupy the range 0 to 99. Profile-specific return values occupy the range 100-999.
|
||||
* API-specific return values start at 1000. The exception to this is the "UNKNOWN" error code which
|
||||
* occupies the max integer value.
|
||||
*/
|
||||
public final class BluetoothStatusCodes {
|
||||
|
||||
private BluetoothStatusCodes() {}
|
||||
|
||||
/**
|
||||
* Indicates that the API call was successful
|
||||
*/
|
||||
public static final int SUCCESS = 0;
|
||||
|
||||
/**
|
||||
* Error code indicating that Bluetooth is not enabled
|
||||
*/
|
||||
public static final int ERROR_BLUETOOTH_NOT_ENABLED = 1;
|
||||
|
||||
/**
|
||||
* Error code indicating that the API call was initiated by neither the system nor the active
|
||||
* Zuser
|
||||
*/
|
||||
public static final int ERROR_BLUETOOTH_NOT_ALLOWED = 2;
|
||||
|
||||
/**
|
||||
* Error code indicating that the Bluetooth Device specified is not bonded
|
||||
*/
|
||||
public static final int ERROR_DEVICE_NOT_BONDED = 3;
|
||||
|
||||
/**
|
||||
* Error code indicating that the Bluetooth Device specified is not connected, but is bonded
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_DEVICE_NOT_CONNECTED = 4;
|
||||
|
||||
/**
|
||||
* Error code indicating that the caller does not have the
|
||||
* {@link android.Manifest.permission#BLUETOOTH_ADVERTISE} permission
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_MISSING_BLUETOOTH_ADVERTISE_PERMISSION = 5;
|
||||
|
||||
/**
|
||||
* Error code indicating that the caller does not have the
|
||||
* {@link android.Manifest.permission#BLUETOOTH_CONNECT} permission
|
||||
*/
|
||||
public static final int ERROR_MISSING_BLUETOOTH_CONNECT_PERMISSION = 6;
|
||||
|
||||
/**
|
||||
* Error code indicating that the caller does not have the
|
||||
* {@link android.Manifest.permission#BLUETOOTH_SCAN} permission
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_MISSING_BLUETOOTH_SCAN_PERMISSION = 7;
|
||||
|
||||
/**
|
||||
* Error code indicating that the caller does not have the
|
||||
* {@link android.Manifest.permission#BLUETOOTH_PRIVILEGED} permission
|
||||
*/
|
||||
public static final int ERROR_MISSING_BLUETOOTH_PRIVILEGED_PERMISSION = 8;
|
||||
|
||||
/**
|
||||
* Error code indicating that the profile service is not bound. You can bind a profile service
|
||||
* by calling {@link BluetoothAdapter#getProfileProxy}
|
||||
*/
|
||||
public static final int ERROR_PROFILE_SERVICE_NOT_BOUND = 9;
|
||||
|
||||
/**
|
||||
* Error code indicating that the feature is not supported.
|
||||
*/
|
||||
public static final int ERROR_FEATURE_NOT_SUPPORTED = 10;
|
||||
|
||||
/**
|
||||
* A GATT writeCharacteristic request is not permitted on the remote device.
|
||||
*/
|
||||
public static final int ERROR_GATT_WRITE_NOT_ALLOWED = 101;
|
||||
|
||||
/**
|
||||
* A GATT writeCharacteristic request is issued to a busy remote device.
|
||||
*/
|
||||
public static final int ERROR_GATT_WRITE_REQUEST_BUSY = 102;
|
||||
|
||||
/**
|
||||
* If another application has already requested {@link OobData} then another fetch will be
|
||||
* disallowed until the callback is removed.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final int ERROR_ANOTHER_ACTIVE_OOB_REQUEST = 1000;
|
||||
|
||||
/**
|
||||
* Indicates that the ACL disconnected due to an explicit request from the local device.
|
||||
* <p>
|
||||
* Example cause: This is a normal disconnect reason, e.g., user/app initiates
|
||||
* disconnection.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_DISCONNECT_REASON_LOCAL_REQUEST = 1100;
|
||||
|
||||
/**
|
||||
* Indicates that the ACL disconnected due to an explicit request from the remote device.
|
||||
* <p>
|
||||
* Example cause: This is a normal disconnect reason, e.g., user/app initiates
|
||||
* disconnection.
|
||||
* <p>
|
||||
* Example solution: The app can also prompt the user to check their remote device.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_DISCONNECT_REASON_REMOTE_REQUEST = 1101;
|
||||
|
||||
/**
|
||||
* Generic disconnect reason indicating the ACL disconnected due to an error on the local
|
||||
* device.
|
||||
* <p>
|
||||
* Example solution: Prompt the user to check their local device (e.g., phone, car
|
||||
* headunit).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_DISCONNECT_REASON_LOCAL = 1102;
|
||||
|
||||
/**
|
||||
* Generic disconnect reason indicating the ACL disconnected due to an error on the remote
|
||||
* device.
|
||||
* <p>
|
||||
* Example solution: Prompt the user to check their remote device (e.g., headset, car
|
||||
* headunit, watch).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_DISCONNECT_REASON_REMOTE = 1103;
|
||||
|
||||
/**
|
||||
* Indicates that the ACL disconnected due to a timeout.
|
||||
* <p>
|
||||
* Example cause: remote device might be out of range.
|
||||
* <p>
|
||||
* Example solution: Prompt user to verify their remote device is on or in
|
||||
* connection/pairing mode.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_DISCONNECT_REASON_TIMEOUT = 1104;
|
||||
|
||||
/**
|
||||
* Indicates that the ACL disconnected due to link key issues.
|
||||
* <p>
|
||||
* Example cause: Devices are either unpaired or remote device is refusing our pairing
|
||||
* request.
|
||||
* <p>
|
||||
* Example solution: Prompt user to unpair and pair again.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_DISCONNECT_REASON_SECURITY = 1105;
|
||||
|
||||
/**
|
||||
* Indicates that the ACL disconnected due to the local device's system policy.
|
||||
* <p>
|
||||
* Example cause: privacy policy, power management policy, permissions, etc.
|
||||
* <p>
|
||||
* Example solution: Prompt the user to check settings, or check with their system
|
||||
* administrator (e.g. some corp-managed devices do not allow OPP connection).
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_DISCONNECT_REASON_SYSTEM_POLICY = 1106;
|
||||
|
||||
/**
|
||||
* Indicates that the ACL disconnected due to resource constraints, either on the local
|
||||
* device or the remote device.
|
||||
* <p>
|
||||
* Example cause: controller is busy, memory limit reached, maximum number of connections
|
||||
* reached.
|
||||
* <p>
|
||||
* Example solution: The app should wait and try again. If still failing, prompt the user
|
||||
* to disconnect some devices, or toggle Bluetooth on the local and/or the remote device.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_DISCONNECT_REASON_RESOURCE_LIMIT_REACHED = 1107;
|
||||
|
||||
/**
|
||||
* Indicates that the ACL disconnected because another ACL connection already exists.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_DISCONNECT_REASON_CONNECTION_ALREADY_EXISTS = 1108;
|
||||
|
||||
/**
|
||||
* Indicates that the ACL disconnected due to incorrect parameters passed in from the app.
|
||||
* <p>
|
||||
* Example solution: Change parameters and try again. If error persists, the app can report
|
||||
* telemetry and/or log the error in a bugreport.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_DISCONNECT_REASON_BAD_PARAMETERS = 1109;
|
||||
|
||||
/**
|
||||
* Indicates that setting the LE Audio Broadcast mode failed.
|
||||
* <p>
|
||||
* Example solution: Change parameters and try again. If error persists, the app can report
|
||||
* telemetry and/or log the error in a bugreport.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_BROADCAST_MODE_FAILED = 1110;
|
||||
|
||||
/**
|
||||
* Indicates that setting a new encryption key for Bluetooth LE Audio Broadcast Source failed.
|
||||
* <p>
|
||||
* Example solution: Change parameters and try again. If error persists, the app can report
|
||||
* telemetry and/or log the error in a bugreport.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_SET_ENCRYPTION_KEY_FAILED = 1111;
|
||||
|
||||
/**
|
||||
* Indicates that connecting to a remote Broadcast Audio Scan Service failed.
|
||||
* <p>
|
||||
* Example solution: Change parameters and try again. If error persists, the app can report
|
||||
* telemetry and/or log the error in a bugreport.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_CONNECT_FAILED = 1112;
|
||||
|
||||
/**
|
||||
* Indicates that disconnecting from a remote Broadcast Audio Scan Service failed.
|
||||
* <p>
|
||||
* Example solution: Change parameters and try again. If error persists, the app can report
|
||||
* telemetry and/or log the error in a bugreport.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_LE_AUDIO_BROADCAST_AUDIO_SCAN_SERVICE_DISCONNECT_FAILED = 1113;
|
||||
|
||||
/**
|
||||
* Indicates that enabling LE Audio Broadcast encryption failed
|
||||
* <p>
|
||||
* Example solution: Change parameters and try again. If error persists, the app can report
|
||||
* telemetry and/or log the error in a bugreport.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_ENABLE_ENCRYPTION_FAILED = 1114;
|
||||
|
||||
/**
|
||||
* Indicates that disabling LE Audio Broadcast encryption failed
|
||||
* <p>
|
||||
* Example solution: Change parameters and try again. If error persists, the app can report
|
||||
* telemetry and/or log the error in a bugreport.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static final int ERROR_LE_AUDIO_BROADCAST_SOURCE_DISABLE_ENCRYPTION_FAILED = 1115;
|
||||
|
||||
/**
|
||||
* Indicates that an unknown error has occurred has occurred.
|
||||
*/
|
||||
public static final int ERROR_UNKNOWN = Integer.MAX_VALUE;
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* {@hide}
|
||||
*/
|
||||
public final class BluetoothUtils {
|
||||
/**
|
||||
* This utility class cannot be instantiated
|
||||
*/
|
||||
private BluetoothUtils() {}
|
||||
|
||||
/**
|
||||
* Timeout value for synchronous binder call
|
||||
*/
|
||||
private static final Duration SYNC_CALLS_TIMEOUT = Duration.ofSeconds(5);
|
||||
|
||||
/**
|
||||
* @return timeout value for synchronous binder call
|
||||
*/
|
||||
static Duration getSyncTimeout() {
|
||||
return SYNC_CALLS_TIMEOUT;
|
||||
}
|
||||
}
|
||||
@@ -1,394 +0,0 @@
|
||||
/*
|
||||
* 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.SuppressLint;
|
||||
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
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
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 LE_AUDIO =
|
||||
ParcelUuid.fromString("0000184E-0000-1000-8000-00805F9B34FB");
|
||||
/** @hide */
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public static final ParcelUuid DIP =
|
||||
ParcelUuid.fromString("00001200-0000-1000-8000-00805F9B34FB");
|
||||
/** @hide */
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public static final ParcelUuid VOLUME_CONTROL =
|
||||
ParcelUuid.fromString("00001844-0000-1000-8000-00805F9B34FB");
|
||||
/** @hide */
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public static final ParcelUuid GENERIC_MEDIA_CONTROL =
|
||||
ParcelUuid.fromString("00001849-0000-1000-8000-00805F9B34FB");
|
||||
/** @hide */
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public static final ParcelUuid MEDIA_CONTROL =
|
||||
ParcelUuid.fromString("00001848-0000-1000-8000-00805F9B34FB");
|
||||
/** @hide */
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public static final ParcelUuid COORDINATED_SET =
|
||||
ParcelUuid.fromString("00001846-0000-1000-8000-00805F9B34FB");
|
||||
/** @hide */
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public static final ParcelUuid CAP =
|
||||
ParcelUuid.fromString("00001853-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() {}
|
||||
}
|
||||
@@ -1,337 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021 HIMSA II K/S - www.himsa.com.
|
||||
* Represented by EHIMA - www.ehima.com
|
||||
*
|
||||
* 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 static android.bluetooth.BluetoothUtils.getSyncTimeout;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.IntRange;
|
||||
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.bluetooth.annotations.RequiresBluetoothConnectPermission;
|
||||
import android.content.AttributionSource;
|
||||
import android.content.Context;
|
||||
import android.os.IBinder;
|
||||
import android.os.RemoteException;
|
||||
import android.util.CloseGuard;
|
||||
import android.util.Log;
|
||||
|
||||
import com.android.modules.utils.SynchronousResultReceiver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
/**
|
||||
* This class provides the public APIs to control the Bluetooth Volume Control service.
|
||||
*
|
||||
* <p>BluetoothVolumeControl is a proxy object for controlling the Bluetooth VC
|
||||
* Service via IPC. Use {@link BluetoothAdapter#getProfileProxy} to get
|
||||
* the BluetoothVolumeControl proxy object.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class BluetoothVolumeControl implements BluetoothProfile, AutoCloseable {
|
||||
private static final String TAG = "BluetoothVolumeControl";
|
||||
private static final boolean DBG = true;
|
||||
private static final boolean VDBG = false;
|
||||
|
||||
private CloseGuard mCloseGuard;
|
||||
|
||||
/**
|
||||
* Intent used to broadcast the change in connection state of the Volume Control
|
||||
* 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")
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
@SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION)
|
||||
public static final String ACTION_CONNECTION_STATE_CHANGED =
|
||||
"android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED";
|
||||
|
||||
private BluetoothAdapter mAdapter;
|
||||
private final AttributionSource mAttributionSource;
|
||||
private final BluetoothProfileConnector<IBluetoothVolumeControl> mProfileConnector =
|
||||
new BluetoothProfileConnector(this, BluetoothProfile.VOLUME_CONTROL, TAG,
|
||||
IBluetoothVolumeControl.class.getName()) {
|
||||
@Override
|
||||
public IBluetoothVolumeControl getServiceInterface(IBinder service) {
|
||||
return IBluetoothVolumeControl.Stub.asInterface(service);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a BluetoothVolumeControl proxy object for interacting with the local
|
||||
* Bluetooth Volume Control service.
|
||||
*/
|
||||
/*package*/ BluetoothVolumeControl(Context context, ServiceListener listener,
|
||||
BluetoothAdapter adapter) {
|
||||
mAdapter = adapter;
|
||||
mAttributionSource = adapter.getAttributionSource();
|
||||
mProfileConnector.connect(context, listener);
|
||||
mCloseGuard = new CloseGuard();
|
||||
mCloseGuard.open("close");
|
||||
}
|
||||
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
protected void finalize() {
|
||||
if (mCloseGuard != null) {
|
||||
mCloseGuard.warnIfOpen();
|
||||
}
|
||||
close();
|
||||
}
|
||||
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_PRIVILEGED)
|
||||
public void close() {
|
||||
mProfileConnector.disconnect();
|
||||
}
|
||||
|
||||
private IBluetoothVolumeControl getService() { return mProfileConnector.getService(); }
|
||||
|
||||
/**
|
||||
* Get the list of connected devices. Currently at most one.
|
||||
*
|
||||
* @return list of connected devices
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public @NonNull List<BluetoothDevice> getConnectedDevices() {
|
||||
if (DBG) log("getConnectedDevices()");
|
||||
final IBluetoothVolumeControl service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getConnectedDevices(mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of devices matching specified states. Currently at most one.
|
||||
*
|
||||
* @return list of matching devices
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public List<BluetoothDevice> getDevicesMatchingConnectionStates(int[] states) {
|
||||
if (DBG) log("getDevicesMatchingStates()");
|
||||
final IBluetoothVolumeControl service = getService();
|
||||
final List<BluetoothDevice> defaultValue = new ArrayList<BluetoothDevice>();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver<List<BluetoothDevice>> recv =
|
||||
new SynchronousResultReceiver();
|
||||
service.getDevicesMatchingConnectionStates(states, mAttributionSource, recv);
|
||||
return Attributable.setAttributionSource(
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue),
|
||||
mAttributionSource);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get connection state of device
|
||||
*
|
||||
* @return device connection state
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(Manifest.permission.BLUETOOTH_CONNECT)
|
||||
public int getConnectionState(BluetoothDevice device) {
|
||||
if (DBG) log("getConnectionState(" + device + ")");
|
||||
final IBluetoothVolumeControl service = getService();
|
||||
final int defaultValue = BluetoothProfile.STATE_DISCONNECTED;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionState(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells remote device to set an absolute volume.
|
||||
*
|
||||
* @param volume Absolute volume to be set on remote device.
|
||||
* Minimum value is 0 and maximum value is 255
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public void setVolume(@Nullable BluetoothDevice device,
|
||||
@IntRange(from = 0, to = 255) int volume) {
|
||||
if (DBG) log("setVolume(" + volume + ")");
|
||||
final IBluetoothVolumeControl service = getService();
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled()) {
|
||||
try {
|
||||
final SynchronousResultReceiver recv = new SynchronousResultReceiver();
|
||||
service.setVolume(device, volume, mAttributionSource, recv);
|
||||
recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public boolean setConnectionPolicy(@NonNull BluetoothDevice device,
|
||||
@ConnectionPolicy int connectionPolicy) {
|
||||
if (DBG) log("setConnectionPolicy(" + device + ", " + connectionPolicy + ")");
|
||||
final IBluetoothVolumeControl service = getService();
|
||||
final boolean defaultValue = false;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)
|
||||
&& (connectionPolicy == BluetoothProfile.CONNECTION_POLICY_FORBIDDEN
|
||||
|| connectionPolicy == BluetoothProfile.CONNECTION_POLICY_ALLOWED)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Boolean> recv = new SynchronousResultReceiver();
|
||||
service.setConnectionPolicy(device, connectionPolicy, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@RequiresBluetoothConnectPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_CONNECT,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public @ConnectionPolicy int getConnectionPolicy(@NonNull BluetoothDevice device) {
|
||||
if (VDBG) log("getConnectionPolicy(" + device + ")");
|
||||
final IBluetoothVolumeControl service = getService();
|
||||
final int defaultValue = BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
|
||||
if (service == null) {
|
||||
Log.w(TAG, "Proxy not attached to service");
|
||||
if (DBG) log(Log.getStackTraceString(new Throwable()));
|
||||
} else if (isEnabled() && isValidDevice(device)) {
|
||||
try {
|
||||
final SynchronousResultReceiver<Integer> recv = new SynchronousResultReceiver();
|
||||
service.getConnectionPolicy(device, mAttributionSource, recv);
|
||||
return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
|
||||
} catch (RemoteException | TimeoutException e) {
|
||||
Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
|
||||
}
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
private boolean isEnabled() {
|
||||
return mAdapter.getState() == BluetoothAdapter.STATE_ON;
|
||||
}
|
||||
|
||||
private static boolean isValidDevice(@Nullable BluetoothDevice device) {
|
||||
return device != null && BluetoothAdapter.checkBluetoothAddress(device.getAddress());
|
||||
}
|
||||
|
||||
private static void log(String msg) {
|
||||
Log.d(TAG, msg);
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Stores a codec's constraints on buffering length in milliseconds.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
@SystemApi
|
||||
public final class BufferConstraint implements Parcelable {
|
||||
|
||||
private static final String TAG = "BufferConstraint";
|
||||
private int mDefaultMillis;
|
||||
private int mMaxMillis;
|
||||
private int mMinMillis;
|
||||
|
||||
public BufferConstraint(int defaultMillis, int maxMillis,
|
||||
int minMillis) {
|
||||
mDefaultMillis = defaultMillis;
|
||||
mMaxMillis = maxMillis;
|
||||
mMinMillis = minMillis;
|
||||
}
|
||||
|
||||
BufferConstraint(Parcel in) {
|
||||
mDefaultMillis = in.readInt();
|
||||
mMaxMillis = in.readInt();
|
||||
mMinMillis = in.readInt();
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<BufferConstraint> CREATOR =
|
||||
new Parcelable.Creator<BufferConstraint>() {
|
||||
public BufferConstraint createFromParcel(Parcel in) {
|
||||
return new BufferConstraint(in);
|
||||
}
|
||||
|
||||
public BufferConstraint[] newArray(int size) {
|
||||
return new BufferConstraint[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel out, int flags) {
|
||||
out.writeInt(mDefaultMillis);
|
||||
out.writeInt(mMaxMillis);
|
||||
out.writeInt(mMinMillis);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the default buffer millis
|
||||
*
|
||||
* @return default buffer millis
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public int getDefaultMillis() {
|
||||
return mDefaultMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the maximum buffer millis
|
||||
*
|
||||
* @return maximum buffer millis
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public int getMaxMillis() {
|
||||
return mMaxMillis;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the minimum buffer millis
|
||||
*
|
||||
* @return minimum buffer millis
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public int getMinMillis() {
|
||||
return mMinMillis;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2020 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.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
/**
|
||||
* A parcelable collection of buffer constraints by codec type.
|
||||
*
|
||||
* {@hide}
|
||||
*/
|
||||
@SystemApi
|
||||
public final class BufferConstraints implements Parcelable {
|
||||
public static final int BUFFER_CODEC_MAX_NUM = 32;
|
||||
|
||||
private static final String TAG = "BufferConstraints";
|
||||
|
||||
private Map<Integer, BufferConstraint> mBufferConstraints;
|
||||
private List<BufferConstraint> mBufferConstraintList;
|
||||
|
||||
public BufferConstraints(@NonNull List<BufferConstraint>
|
||||
bufferConstraintList) {
|
||||
|
||||
mBufferConstraintList = new ArrayList<BufferConstraint>(bufferConstraintList);
|
||||
mBufferConstraints = new HashMap<Integer, BufferConstraint>();
|
||||
for (int i = 0; i < BUFFER_CODEC_MAX_NUM; i++) {
|
||||
mBufferConstraints.put(i, bufferConstraintList.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
BufferConstraints(Parcel in) {
|
||||
mBufferConstraintList = new ArrayList<BufferConstraint>();
|
||||
mBufferConstraints = new HashMap<Integer, BufferConstraint>();
|
||||
in.readList(mBufferConstraintList, BufferConstraint.class.getClassLoader());
|
||||
for (int i = 0; i < mBufferConstraintList.size(); i++) {
|
||||
mBufferConstraints.put(i, mBufferConstraintList.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
public static final @NonNull Parcelable.Creator<BufferConstraints> CREATOR =
|
||||
new Parcelable.Creator<BufferConstraints>() {
|
||||
public BufferConstraints createFromParcel(Parcel in) {
|
||||
return new BufferConstraints(in);
|
||||
}
|
||||
|
||||
public BufferConstraints[] newArray(int size) {
|
||||
return new BufferConstraints[size];
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel out, int flags) {
|
||||
out.writeList(mBufferConstraintList);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the buffer constraints by codec type.
|
||||
*
|
||||
* @param codec Audio codec
|
||||
* @return buffer constraints by codec type.
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public @Nullable BufferConstraint forCodec(@BluetoothCodecConfig.SourceCodecType int codec) {
|
||||
return mBufferConstraints.get(codec);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# Bug component: 27441
|
||||
|
||||
sattiraju@google.com
|
||||
baligh@google.com
|
||||
@@ -1,958 +0,0 @@
|
||||
/**
|
||||
* 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 static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* 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, QR).
|
||||
*
|
||||
* <p>References:
|
||||
* NFC AD Forum SSP 1.1 (AD)
|
||||
* {@link https://members.nfc-forum.org//apps/group_public/download.php/24620/NFCForum-AD-BTSSP_1_1.pdf}
|
||||
* Core Specification Supplement (CSS) V9
|
||||
*
|
||||
* <p>There are several BR/EDR Examples
|
||||
*
|
||||
* <p>Negotiated Handover:
|
||||
* Bluetooth Carrier Configuration Record:
|
||||
* - OOB Data Length
|
||||
* - Device Address
|
||||
* - Class of Device
|
||||
* - Simple Pairing Hash C
|
||||
* - Simple Pairing Randomizer R
|
||||
* - Service Class UUID
|
||||
* - Bluetooth Local Name
|
||||
*
|
||||
* <p>Static Handover:
|
||||
* Bluetooth Carrier Configuration Record:
|
||||
* - OOB Data Length
|
||||
* - Device Address
|
||||
* - Class of Device
|
||||
* - Service Class UUID
|
||||
* - Bluetooth Local Name
|
||||
*
|
||||
* <p>Simplified Tag Format for Single BT Carrier:
|
||||
* Bluetooth OOB Data Record:
|
||||
* - OOB Data Length
|
||||
* - Device Address
|
||||
* - Class of Device
|
||||
* - Service Class UUID
|
||||
* - Bluetooth Local Name
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public final class OobData implements Parcelable {
|
||||
|
||||
private static final String TAG = "OobData";
|
||||
/** The {@link OobData#mClassicLength} may be. (AD 3.1.1) (CSS 1.6.2) @hide */
|
||||
@SystemApi
|
||||
public static final int OOB_LENGTH_OCTETS = 2;
|
||||
/**
|
||||
* The length for the {@link OobData#mDeviceAddressWithType}(6) and Address Type(1).
|
||||
* (AD 3.1.2) (CSS 1.6.2)
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final int DEVICE_ADDRESS_OCTETS = 7;
|
||||
/** The Class of Device is 3 octets. (AD 3.1.3) (CSS 1.6.2) @hide */
|
||||
@SystemApi
|
||||
public static final int CLASS_OF_DEVICE_OCTETS = 3;
|
||||
/** The Confirmation data must be 16 octets. (AD 3.2.2) (CSS 1.6.2) @hide */
|
||||
@SystemApi
|
||||
public static final int CONFIRMATION_OCTETS = 16;
|
||||
/** The Randomizer data must be 16 octets. (AD 3.2.3) (CSS 1.6.2) @hide */
|
||||
@SystemApi
|
||||
public static final int RANDOMIZER_OCTETS = 16;
|
||||
/** The LE Device Role length is 1 octet. (AD 3.3.2) (CSS 1.17) @hide */
|
||||
@SystemApi
|
||||
public static final int LE_DEVICE_ROLE_OCTETS = 1;
|
||||
/** The {@link OobData#mLeTemporaryKey} length. (3.4.1) @hide */
|
||||
@SystemApi
|
||||
public static final int LE_TK_OCTETS = 16;
|
||||
/** The {@link OobData#mLeAppearance} length. (3.4.1) @hide */
|
||||
@SystemApi
|
||||
public static final int LE_APPEARANCE_OCTETS = 2;
|
||||
/** The {@link OobData#mLeFlags} length. (3.4.1) @hide */
|
||||
@SystemApi
|
||||
public static final int LE_DEVICE_FLAG_OCTETS = 1; // 1 octet to hold the 0-4 value.
|
||||
|
||||
// Le Roles
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(
|
||||
prefix = { "LE_DEVICE_ROLE_" },
|
||||
value = {
|
||||
LE_DEVICE_ROLE_PERIPHERAL_ONLY,
|
||||
LE_DEVICE_ROLE_CENTRAL_ONLY,
|
||||
LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL,
|
||||
LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL
|
||||
}
|
||||
)
|
||||
public @interface LeRole {}
|
||||
|
||||
/** @hide */
|
||||
@SystemApi
|
||||
public static final int LE_DEVICE_ROLE_PERIPHERAL_ONLY = 0x00;
|
||||
/** @hide */
|
||||
@SystemApi
|
||||
public static final int LE_DEVICE_ROLE_CENTRAL_ONLY = 0x01;
|
||||
/** @hide */
|
||||
@SystemApi
|
||||
public static final int LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL = 0x02;
|
||||
/** @hide */
|
||||
@SystemApi
|
||||
public static final int LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL = 0x03;
|
||||
|
||||
// Le Flags
|
||||
/** @hide */
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef(
|
||||
prefix = { "LE_FLAG_" },
|
||||
value = {
|
||||
LE_FLAG_LIMITED_DISCOVERY_MODE,
|
||||
LE_FLAG_GENERAL_DISCOVERY_MODE,
|
||||
LE_FLAG_BREDR_NOT_SUPPORTED,
|
||||
LE_FLAG_SIMULTANEOUS_CONTROLLER,
|
||||
LE_FLAG_SIMULTANEOUS_HOST
|
||||
}
|
||||
)
|
||||
public @interface LeFlag {}
|
||||
|
||||
/** @hide */
|
||||
@SystemApi
|
||||
public static final int LE_FLAG_LIMITED_DISCOVERY_MODE = 0x00;
|
||||
/** @hide */
|
||||
@SystemApi
|
||||
public static final int LE_FLAG_GENERAL_DISCOVERY_MODE = 0x01;
|
||||
/** @hide */
|
||||
@SystemApi
|
||||
public static final int LE_FLAG_BREDR_NOT_SUPPORTED = 0x02;
|
||||
/** @hide */
|
||||
@SystemApi
|
||||
public static final int LE_FLAG_SIMULTANEOUS_CONTROLLER = 0x03;
|
||||
/** @hide */
|
||||
@SystemApi
|
||||
public static final int LE_FLAG_SIMULTANEOUS_HOST = 0x04;
|
||||
|
||||
/**
|
||||
* Builds an {@link OobData} object and validates that the required combination
|
||||
* of values are present to create the LE specific OobData type.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final class LeBuilder {
|
||||
|
||||
/**
|
||||
* It is recommended that this Hash C is generated anew for each
|
||||
* pairing.
|
||||
*
|
||||
* <p>It should be noted that on passive NFC this isn't possible as the data is static
|
||||
* and immutable.
|
||||
*/
|
||||
private byte[] mConfirmationHash = null;
|
||||
|
||||
/**
|
||||
* Optional, but adds more validity to the pairing.
|
||||
*
|
||||
* <p>If not present a value of 0 is assumed.
|
||||
*/
|
||||
private byte[] mRandomizerHash = new byte[] {
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
};
|
||||
|
||||
/**
|
||||
* The Bluetooth Device user-friendly name presented over Bluetooth Technology.
|
||||
*
|
||||
* <p>This is the name that may be displayed to the device user as part of the UI.
|
||||
*/
|
||||
private byte[] mDeviceName = null;
|
||||
|
||||
/**
|
||||
* Sets the Bluetooth Device name to be used for UI purposes.
|
||||
*
|
||||
* <p>Optional attribute.
|
||||
*
|
||||
* @param deviceName byte array representing the name, may be 0 in length, not null.
|
||||
*
|
||||
* @return {@link OobData#ClassicBuilder}
|
||||
*
|
||||
* @throws NullPointerException if deviceName is null.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public LeBuilder setDeviceName(@NonNull byte[] deviceName) {
|
||||
requireNonNull(deviceName);
|
||||
this.mDeviceName = deviceName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Bluetooth Device Address is the address to which the OOB data belongs.
|
||||
*
|
||||
* <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets.
|
||||
*
|
||||
* <p> Address is encoded in Little Endian order.
|
||||
*
|
||||
* <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00
|
||||
*/
|
||||
private final byte[] mDeviceAddressWithType;
|
||||
|
||||
/**
|
||||
* During an LE connection establishment, one must be in the Peripheral mode and the other
|
||||
* in the Central role.
|
||||
*
|
||||
* <p>Possible Values:
|
||||
* {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
|
||||
* {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
|
||||
* {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
|
||||
* Peripheral Preferred
|
||||
* {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
|
||||
* 0x04 - 0xFF Reserved
|
||||
*/
|
||||
private final @LeRole int mLeDeviceRole;
|
||||
|
||||
/**
|
||||
* Temporary key value from the Security Manager.
|
||||
*
|
||||
* <p> Must be {@link LE_TK_OCTETS} in size
|
||||
*/
|
||||
private byte[] mLeTemporaryKey = null;
|
||||
|
||||
/**
|
||||
* Defines the representation of the external appearance of the device.
|
||||
*
|
||||
* <p>For example, a mouse, remote control, or keyboard.
|
||||
*
|
||||
* <p>Used for visual on discovering device to represent icon/string/etc...
|
||||
*/
|
||||
private byte[] mLeAppearance = null;
|
||||
|
||||
/**
|
||||
* Contains which discoverable mode to use, BR/EDR support and capability.
|
||||
*
|
||||
* <p>Possible LE Flags:
|
||||
* {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
|
||||
* {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
|
||||
* {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
|
||||
* LMP Feature Mask Definitions.
|
||||
* {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
|
||||
* Same Device Capable (Controller).
|
||||
* Bit 49 of LMP Feature Mask Definitions.
|
||||
* {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
|
||||
* Same Device Capable (Host).
|
||||
* Bit 55 of LMP Feature Mask Definitions.
|
||||
* <b>0x05- 0x07 Reserved</b>
|
||||
*/
|
||||
private @LeFlag int mLeFlags = LE_FLAG_GENERAL_DISCOVERY_MODE; // Invalid default
|
||||
|
||||
/**
|
||||
* Main creation method for creating a LE version of {@link OobData}.
|
||||
*
|
||||
* <p>This object will allow the caller to call {@link LeBuilder#build()}
|
||||
* to build the data object or add any option information to the builder.
|
||||
*
|
||||
* @param deviceAddressWithType the LE device address plus the address type (7 octets);
|
||||
* not null.
|
||||
* @param leDeviceRole whether the device supports Peripheral, Central,
|
||||
* Both including preference; not null. (1 octet)
|
||||
* @param confirmationHash Array consisting of {@link OobData#CONFIRMATION_OCTETS} octets
|
||||
* of data. Data is derived from controller/host stack and is
|
||||
* required for pairing OOB.
|
||||
*
|
||||
* <p>Possible Values:
|
||||
* {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
|
||||
* {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
|
||||
* {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
|
||||
* Peripheral Preferred
|
||||
* {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
|
||||
* 0x04 - 0xFF Reserved
|
||||
*
|
||||
* @throws IllegalArgumentException if any of the values fail to be set.
|
||||
* @throws NullPointerException if any argument is null.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public LeBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] deviceAddressWithType,
|
||||
@LeRole int leDeviceRole) {
|
||||
requireNonNull(confirmationHash);
|
||||
requireNonNull(deviceAddressWithType);
|
||||
if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) {
|
||||
throw new IllegalArgumentException("confirmationHash must be "
|
||||
+ OobData.CONFIRMATION_OCTETS + " octets in length.");
|
||||
}
|
||||
this.mConfirmationHash = confirmationHash;
|
||||
if (deviceAddressWithType.length != OobData.DEVICE_ADDRESS_OCTETS) {
|
||||
throw new IllegalArgumentException("confirmationHash must be "
|
||||
+ OobData.DEVICE_ADDRESS_OCTETS+ " octets in length.");
|
||||
}
|
||||
this.mDeviceAddressWithType = deviceAddressWithType;
|
||||
if (leDeviceRole < LE_DEVICE_ROLE_PERIPHERAL_ONLY
|
||||
|| leDeviceRole > LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL) {
|
||||
throw new IllegalArgumentException("leDeviceRole must be a valid value.");
|
||||
}
|
||||
this.mLeDeviceRole = leDeviceRole;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Temporary Key value to be used by the LE Security Manager during
|
||||
* LE pairing.
|
||||
*
|
||||
* @param leTemporaryKey byte array that shall be 16 bytes. Please see Bluetooth CSSv6,
|
||||
* Part A 1.8 for a detailed description.
|
||||
*
|
||||
* @return {@link OobData#Builder}
|
||||
*
|
||||
* @throws IllegalArgumentException if the leTemporaryKey is an invalid format.
|
||||
* @throws NullinterException if leTemporaryKey is null.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public LeBuilder setLeTemporaryKey(@NonNull byte[] leTemporaryKey) {
|
||||
requireNonNull(leTemporaryKey);
|
||||
if (leTemporaryKey.length != LE_TK_OCTETS) {
|
||||
throw new IllegalArgumentException("leTemporaryKey must be "
|
||||
+ LE_TK_OCTETS + " octets in length.");
|
||||
}
|
||||
this.mLeTemporaryKey = leTemporaryKey;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
|
||||
* of data. Data is derived from controller/host stack and is required for pairing OOB.
|
||||
* Also, randomizerHash may be all 0s or null in which case it becomes all 0s.
|
||||
*
|
||||
* @throws IllegalArgumentException if null or incorrect length randomizerHash was passed.
|
||||
* @throws NullPointerException if randomizerHash is null.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public LeBuilder setRandomizerHash(@NonNull byte[] randomizerHash) {
|
||||
requireNonNull(randomizerHash);
|
||||
if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) {
|
||||
throw new IllegalArgumentException("randomizerHash must be "
|
||||
+ OobData.RANDOMIZER_OCTETS + " octets in length.");
|
||||
}
|
||||
this.mRandomizerHash = randomizerHash;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the LE Flags necessary for the pairing scenario or discovery mode.
|
||||
*
|
||||
* @param leFlags enum value representing the 1 octet of data about discovery modes.
|
||||
*
|
||||
* <p>Possible LE Flags:
|
||||
* {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
|
||||
* {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
|
||||
* {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
|
||||
* LMP Feature Mask Definitions.
|
||||
* {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
|
||||
* Same Device Capable (Controller) Bit 49 of LMP Feature Mask Definitions.
|
||||
* {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
|
||||
* Same Device Capable (Host).
|
||||
* Bit 55 of LMP Feature Mask Definitions.
|
||||
* 0x05- 0x07 Reserved
|
||||
*
|
||||
* @throws IllegalArgumentException for invalid flag
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public LeBuilder setLeFlags(@LeFlag int leFlags) {
|
||||
if (leFlags < LE_FLAG_LIMITED_DISCOVERY_MODE || leFlags > LE_FLAG_SIMULTANEOUS_HOST) {
|
||||
throw new IllegalArgumentException("leFlags must be a valid value.");
|
||||
}
|
||||
this.mLeFlags = leFlags;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and builds the {@link OobData} object for LE Security.
|
||||
*
|
||||
* @return {@link OobData} with given builder values
|
||||
*
|
||||
* @throws IllegalStateException if either of the 2 required fields were not set.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public OobData build() {
|
||||
final OobData oob =
|
||||
new OobData(this.mDeviceAddressWithType, this.mLeDeviceRole,
|
||||
this.mConfirmationHash);
|
||||
|
||||
// If we have values, set them, otherwise use default
|
||||
oob.mLeTemporaryKey =
|
||||
(this.mLeTemporaryKey != null) ? this.mLeTemporaryKey : oob.mLeTemporaryKey;
|
||||
oob.mLeAppearance = (this.mLeAppearance != null)
|
||||
? this.mLeAppearance : oob.mLeAppearance;
|
||||
oob.mLeFlags = (this.mLeFlags != 0xF) ? this.mLeFlags : oob.mLeFlags;
|
||||
oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName;
|
||||
oob.mRandomizerHash = this.mRandomizerHash;
|
||||
return oob;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds an {@link OobData} object and validates that the required combination
|
||||
* of values are present to create the Classic specific OobData type.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final class ClassicBuilder {
|
||||
// Used by both Classic and LE
|
||||
/**
|
||||
* It is recommended that this Hash C is generated anew for each
|
||||
* pairing.
|
||||
*
|
||||
* <p>It should be noted that on passive NFC this isn't possible as the data is static
|
||||
* and immutable.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
private byte[] mConfirmationHash = null;
|
||||
|
||||
/**
|
||||
* Optional, but adds more validity to the pairing.
|
||||
*
|
||||
* <p>If not present a value of 0 is assumed.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
private byte[] mRandomizerHash = new byte[] {
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
};
|
||||
|
||||
/**
|
||||
* The Bluetooth Device user-friendly name presented over Bluetooth Technology.
|
||||
*
|
||||
* <p>This is the name that may be displayed to the device user as part of the UI.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
private byte[] mDeviceName = null;
|
||||
|
||||
/**
|
||||
* This length value provides the absolute length of total OOB data block used for
|
||||
* Bluetooth BR/EDR
|
||||
*
|
||||
* <p>OOB communication, which includes the length field itself and the Bluetooth
|
||||
* Device Address.
|
||||
*
|
||||
* <p>The minimum length that may be represented in this field is 8.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
private final byte[] mClassicLength;
|
||||
|
||||
/**
|
||||
* The Bluetooth Device Address is the address to which the OOB data belongs.
|
||||
*
|
||||
* <p>The length MUST be {@link OobData#DEVICE_ADDRESS_OCTETS} octets.
|
||||
*
|
||||
* <p> Address is encoded in Little Endian order.
|
||||
*
|
||||
* <p>e.g. 00:01:02:03:04:05 would be x05x04x03x02x01x00
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
private final byte[] mDeviceAddressWithType;
|
||||
|
||||
/**
|
||||
* Class of Device information is to be used to provide a graphical representation
|
||||
* to the user as part of UI involving operations.
|
||||
*
|
||||
* <p>This is not to be used to determine a particular service can be used.
|
||||
*
|
||||
* <p>The length MUST be {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
private byte[] mClassOfDevice = null;
|
||||
|
||||
/**
|
||||
* Main creation method for creating a Classic version of {@link OobData}.
|
||||
*
|
||||
* <p>This object will allow the caller to call {@link ClassicBuilder#build()}
|
||||
* to build the data object or add any option information to the builder.
|
||||
*
|
||||
* @param confirmationHash byte array consisting of {@link OobData#CONFIRMATION_OCTETS}
|
||||
* octets of data. Data is derived from controller/host stack and is required for pairing
|
||||
* OOB.
|
||||
* @param classicLength byte array representing the length of data from 8-65535 across 2
|
||||
* octets (0xXXXX).
|
||||
* @param deviceAddressWithType byte array representing the Bluetooth Address of the device
|
||||
* that owns the OOB data. (i.e. the originator) [6 octets]
|
||||
*
|
||||
* @throws IllegalArgumentException if any of the values fail to be set.
|
||||
* @throws NullPointerException if any argument is null.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public ClassicBuilder(@NonNull byte[] confirmationHash, @NonNull byte[] classicLength,
|
||||
@NonNull byte[] deviceAddressWithType) {
|
||||
requireNonNull(confirmationHash);
|
||||
requireNonNull(classicLength);
|
||||
requireNonNull(deviceAddressWithType);
|
||||
if (confirmationHash.length != OobData.CONFIRMATION_OCTETS) {
|
||||
throw new IllegalArgumentException("confirmationHash must be "
|
||||
+ OobData.CONFIRMATION_OCTETS + " octets in length.");
|
||||
}
|
||||
this.mConfirmationHash = confirmationHash;
|
||||
if (classicLength.length != OOB_LENGTH_OCTETS) {
|
||||
throw new IllegalArgumentException("classicLength must be "
|
||||
+ OOB_LENGTH_OCTETS + " octets in length.");
|
||||
}
|
||||
this.mClassicLength = classicLength;
|
||||
if (deviceAddressWithType.length != DEVICE_ADDRESS_OCTETS) {
|
||||
throw new IllegalArgumentException("deviceAddressWithType must be "
|
||||
+ DEVICE_ADDRESS_OCTETS + " octets in length.");
|
||||
}
|
||||
this.mDeviceAddressWithType = deviceAddressWithType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param randomizerHash byte array consisting of {@link OobData#RANDOMIZER_OCTETS} octets
|
||||
* of data. Data is derived from controller/host stack and is required for pairing OOB.
|
||||
* Also, randomizerHash may be all 0s or null in which case it becomes all 0s.
|
||||
*
|
||||
* @throws IllegalArgumentException if null or incorrect length randomizerHash was passed.
|
||||
* @throws NullPointerException if randomizerHash is null.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public ClassicBuilder setRandomizerHash(@NonNull byte[] randomizerHash) {
|
||||
requireNonNull(randomizerHash);
|
||||
if (randomizerHash.length != OobData.RANDOMIZER_OCTETS) {
|
||||
throw new IllegalArgumentException("randomizerHash must be "
|
||||
+ OobData.RANDOMIZER_OCTETS + " octets in length.");
|
||||
}
|
||||
this.mRandomizerHash = randomizerHash;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Bluetooth Device name to be used for UI purposes.
|
||||
*
|
||||
* <p>Optional attribute.
|
||||
*
|
||||
* @param deviceName byte array representing the name, may be 0 in length, not null.
|
||||
*
|
||||
* @return {@link OobData#ClassicBuilder}
|
||||
*
|
||||
* @throws NullPointerException if deviceName is null
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public ClassicBuilder setDeviceName(@NonNull byte[] deviceName) {
|
||||
requireNonNull(deviceName);
|
||||
this.mDeviceName = deviceName;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the Bluetooth Class of Device; used for UI purposes only.
|
||||
*
|
||||
* <p>Not an indicator of available services!
|
||||
*
|
||||
* <p>Optional attribute.
|
||||
*
|
||||
* @param classOfDevice byte array of {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
|
||||
*
|
||||
* @return {@link OobData#ClassicBuilder}
|
||||
*
|
||||
* @throws IllegalArgumentException if length is not equal to
|
||||
* {@link OobData#CLASS_OF_DEVICE_OCTETS} octets.
|
||||
* @throws NullPointerException if classOfDevice is null.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public ClassicBuilder setClassOfDevice(@NonNull byte[] classOfDevice) {
|
||||
requireNonNull(classOfDevice);
|
||||
if (classOfDevice.length != OobData.CLASS_OF_DEVICE_OCTETS) {
|
||||
throw new IllegalArgumentException("classOfDevice must be "
|
||||
+ OobData.CLASS_OF_DEVICE_OCTETS + " octets in length.");
|
||||
}
|
||||
this.mClassOfDevice = classOfDevice;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates and builds the {@link OobDat object for Classic Security.
|
||||
*
|
||||
* @return {@link OobData} with previously given builder values.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public OobData build() {
|
||||
final OobData oob =
|
||||
new OobData(this.mClassicLength, this.mDeviceAddressWithType,
|
||||
this.mConfirmationHash);
|
||||
// If we have values, set them, otherwise use default
|
||||
oob.mDeviceName = (this.mDeviceName != null) ? this.mDeviceName : oob.mDeviceName;
|
||||
oob.mClassOfDevice = (this.mClassOfDevice != null)
|
||||
? this.mClassOfDevice : oob.mClassOfDevice;
|
||||
oob.mRandomizerHash = this.mRandomizerHash;
|
||||
return oob;
|
||||
}
|
||||
}
|
||||
|
||||
// Members (Defaults for Optionals must be set or Parceling fails on NPE)
|
||||
// Both
|
||||
private final byte[] mDeviceAddressWithType;
|
||||
private final byte[] mConfirmationHash;
|
||||
private byte[] mRandomizerHash = new byte[] {
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
|
||||
};
|
||||
// Default the name to "Bluetooth Device"
|
||||
private byte[] mDeviceName = new byte[] {
|
||||
// Bluetooth
|
||||
0x42, 0x6c, 0x75, 0x65, 0x74, 0x6f, 0x6f, 0x74, 0x68,
|
||||
// <space>Device
|
||||
0x20, 0x44, 0x65, 0x76, 0x69, 0x63, 0x65
|
||||
};
|
||||
|
||||
// Classic
|
||||
private final byte[] mClassicLength;
|
||||
private byte[] mClassOfDevice = new byte[CLASS_OF_DEVICE_OCTETS];
|
||||
|
||||
// LE
|
||||
private final @LeRole int mLeDeviceRole;
|
||||
private byte[] mLeTemporaryKey = new byte[LE_TK_OCTETS];
|
||||
private byte[] mLeAppearance = new byte[LE_APPEARANCE_OCTETS];
|
||||
private @LeFlag int mLeFlags = LE_FLAG_LIMITED_DISCOVERY_MODE;
|
||||
|
||||
/**
|
||||
* @return byte array representing the MAC address of a bluetooth device.
|
||||
* The Address is 6 octets long with a 1 octet address type associated with the address.
|
||||
*
|
||||
* <p>For classic this will be 6 byte address plus the default of PUBLIC_ADDRESS Address Type.
|
||||
* For LE there are more choices for Address Type.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public byte[] getDeviceAddressWithType() {
|
||||
return mDeviceAddressWithType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return byte array representing the confirmationHash value
|
||||
* which is used to confirm the identity to the controller.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public byte[] getConfirmationHash() {
|
||||
return mConfirmationHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return byte array representing the randomizerHash value
|
||||
* which is used to verify the identity of the controller.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public byte[] getRandomizerHash() {
|
||||
return mRandomizerHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Device Name used for displaying name in UI.
|
||||
*
|
||||
* <p>Also, this will be populated with the LE Local Name if the data is for LE.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
@SystemApi
|
||||
public byte[] getDeviceName() {
|
||||
return mDeviceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return byte array representing the oob data length which is the length
|
||||
* of all of the data including these octets.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public byte[] getClassicLength() {
|
||||
return mClassicLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return byte array representing the class of device for UI display.
|
||||
*
|
||||
* <p>Does not indicate services available; for display only.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public byte[] getClassOfDevice() {
|
||||
return mClassOfDevice;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Temporary Key used for LE pairing.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
@SystemApi
|
||||
public byte[] getLeTemporaryKey() {
|
||||
return mLeTemporaryKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Appearance used for LE pairing. For use in UI situations
|
||||
* when determining what sort of icons or text to display regarding
|
||||
* the device.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Nullable
|
||||
@SystemApi
|
||||
public byte[] getLeAppearance() {
|
||||
return mLeAppearance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Flags used to determing discoverable mode to use, BR/EDR Support, and Capability.
|
||||
*
|
||||
* <p>Possible LE Flags:
|
||||
* {@link LE_FLAG_LIMITED_DISCOVERY_MODE} LE Limited Discoverable Mode.
|
||||
* {@link LE_FLAG_GENERAL_DISCOVERY_MODE} LE General Discoverable Mode.
|
||||
* {@link LE_FLAG_BREDR_NOT_SUPPORTED} BR/EDR Not Supported. Bit 37 of
|
||||
* LMP Feature Mask Definitions.
|
||||
* {@link LE_FLAG_SIMULTANEOUS_CONTROLLER} Simultaneous LE and BR/EDR to
|
||||
* Same Device Capable (Controller).
|
||||
* Bit 49 of LMP Feature Mask Definitions.
|
||||
* {@link LE_FLAG_SIMULTANEOUS_HOST} Simultaneous LE and BR/EDR to
|
||||
* Same Device Capable (Host).
|
||||
* Bit 55 of LMP Feature Mask Definitions.
|
||||
* <b>0x05- 0x07 Reserved</b>
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
@LeFlag
|
||||
public int getLeFlags() {
|
||||
return mLeFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the supported and preferred roles of the LE device.
|
||||
*
|
||||
* <p>Possible Values:
|
||||
* {@link LE_DEVICE_ROLE_PERIPHERAL_ONLY} Only Peripheral supported
|
||||
* {@link LE_DEVICE_ROLE_CENTRAL_ONLY} Only Central supported
|
||||
* {@link LE_DEVICE_ROLE_BOTH_PREFER_PERIPHERAL} Central & Peripheral supported;
|
||||
* Peripheral Preferred
|
||||
* {@link LE_DEVICE_ROLE_BOTH_PREFER_CENTRAL} Only peripheral supported; Central Preferred
|
||||
* 0x04 - 0xFF Reserved
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
@LeRole
|
||||
public int getLeDeviceRole() {
|
||||
return mLeDeviceRole;
|
||||
}
|
||||
|
||||
/**
|
||||
* Classic Security Constructor
|
||||
*/
|
||||
private OobData(@NonNull byte[] classicLength, @NonNull byte[] deviceAddressWithType,
|
||||
@NonNull byte[] confirmationHash) {
|
||||
mClassicLength = classicLength;
|
||||
mDeviceAddressWithType = deviceAddressWithType;
|
||||
mConfirmationHash = confirmationHash;
|
||||
mLeDeviceRole = -1; // Satisfy final
|
||||
}
|
||||
|
||||
/**
|
||||
* LE Security Constructor
|
||||
*/
|
||||
private OobData(@NonNull byte[] deviceAddressWithType, @LeRole int leDeviceRole,
|
||||
@NonNull byte[] confirmationHash) {
|
||||
mDeviceAddressWithType = deviceAddressWithType;
|
||||
mLeDeviceRole = leDeviceRole;
|
||||
mConfirmationHash = confirmationHash;
|
||||
mClassicLength = new byte[OOB_LENGTH_OCTETS]; // Satisfy final
|
||||
}
|
||||
|
||||
private OobData(Parcel in) {
|
||||
// Both
|
||||
mDeviceAddressWithType = in.createByteArray();
|
||||
mConfirmationHash = in.createByteArray();
|
||||
mRandomizerHash = in.createByteArray();
|
||||
mDeviceName = in.createByteArray();
|
||||
|
||||
// Classic
|
||||
mClassicLength = in.createByteArray();
|
||||
mClassOfDevice = in.createByteArray();
|
||||
|
||||
// LE
|
||||
mLeDeviceRole = in.readInt();
|
||||
mLeTemporaryKey = in.createByteArray();
|
||||
mLeAppearance = in.createByteArray();
|
||||
mLeFlags = in.readInt();
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel out, int flags) {
|
||||
// Both
|
||||
// Required
|
||||
out.writeByteArray(mDeviceAddressWithType);
|
||||
// Required
|
||||
out.writeByteArray(mConfirmationHash);
|
||||
// Optional
|
||||
out.writeByteArray(mRandomizerHash);
|
||||
// Optional
|
||||
out.writeByteArray(mDeviceName);
|
||||
|
||||
// Classic
|
||||
// Required
|
||||
out.writeByteArray(mClassicLength);
|
||||
// Optional
|
||||
out.writeByteArray(mClassOfDevice);
|
||||
|
||||
// LE
|
||||
// Required
|
||||
out.writeInt(mLeDeviceRole);
|
||||
// Required
|
||||
out.writeByteArray(mLeTemporaryKey);
|
||||
// Optional
|
||||
out.writeByteArray(mLeAppearance);
|
||||
// Optional
|
||||
out.writeInt(mLeFlags);
|
||||
}
|
||||
|
||||
// For Parcelable
|
||||
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];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @return a {@link String} representation of the OobData object.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
@NonNull
|
||||
public String toString() {
|
||||
return "OobData: \n\t"
|
||||
// Both
|
||||
+ "Device Address With Type: " + toHexString(mDeviceAddressWithType) + "\n\t"
|
||||
+ "Confirmation: " + toHexString(mConfirmationHash) + "\n\t"
|
||||
+ "Randomizer: " + toHexString(mRandomizerHash) + "\n\t"
|
||||
+ "Device Name: " + toHexString(mDeviceName) + "\n\t"
|
||||
// Classic
|
||||
+ "OobData Length: " + toHexString(mClassicLength) + "\n\t"
|
||||
+ "Class of Device: " + toHexString(mClassOfDevice) + "\n\t"
|
||||
// LE
|
||||
+ "LE Device Role: " + toHexString(mLeDeviceRole) + "\n\t"
|
||||
+ "LE Temporary Key: " + toHexString(mLeTemporaryKey) + "\n\t"
|
||||
+ "LE Appearance: " + toHexString(mLeAppearance) + "\n\t"
|
||||
+ "LE Flags: " + toHexString(mLeFlags) + "\n\t";
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String toHexString(int b) {
|
||||
return toHexString(new byte[] {(byte) b});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String toHexString(byte b) {
|
||||
return toHexString(new byte[] {b});
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private String toHexString(byte[] array) {
|
||||
if (array == null) return "null";
|
||||
StringBuilder builder = new StringBuilder(array.length * 2);
|
||||
for (byte b: array) {
|
||||
builder.append(String.format("%02x", b));
|
||||
}
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,104 +0,0 @@
|
||||
/*
|
||||
* 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 java.util.Arrays;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Data representation of a Object Push Profile Server side SDP record.
|
||||
*/
|
||||
/** @hide */
|
||||
public class SdpDipRecord implements Parcelable {
|
||||
private final int mSpecificationId;
|
||||
private final int mVendorId;
|
||||
private final int mVendorIdSource;
|
||||
private final int mProductId;
|
||||
private final int mVersion;
|
||||
private final boolean mPrimaryRecord;
|
||||
|
||||
public SdpDipRecord(int specificationId,
|
||||
int vendorId, int vendorIdSource,
|
||||
int productId, int version,
|
||||
boolean primaryRecord) {
|
||||
super();
|
||||
this.mSpecificationId = specificationId;
|
||||
this.mVendorId = vendorId;
|
||||
this.mVendorIdSource = vendorIdSource;
|
||||
this.mProductId = productId;
|
||||
this.mVersion = version;
|
||||
this.mPrimaryRecord = primaryRecord;
|
||||
}
|
||||
|
||||
public SdpDipRecord(Parcel in) {
|
||||
this.mSpecificationId = in.readInt();
|
||||
this.mVendorId = in.readInt();
|
||||
this.mVendorIdSource = in.readInt();
|
||||
this.mProductId = in.readInt();
|
||||
this.mVersion = in.readInt();
|
||||
this.mPrimaryRecord = in.readBoolean();
|
||||
}
|
||||
|
||||
public int getSpecificationId() {
|
||||
return mSpecificationId;
|
||||
}
|
||||
|
||||
public int getVendorId() {
|
||||
return mVendorId;
|
||||
}
|
||||
|
||||
public int getVendorIdSource() {
|
||||
return mVendorIdSource;
|
||||
}
|
||||
|
||||
public int getProductId() {
|
||||
return mProductId;
|
||||
}
|
||||
|
||||
public int getVersion() {
|
||||
return mVersion;
|
||||
}
|
||||
|
||||
public boolean getPrimaryRecord() {
|
||||
return mPrimaryRecord;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mSpecificationId);
|
||||
dest.writeInt(mVendorId);
|
||||
dest.writeInt(mVendorIdSource);
|
||||
dest.writeInt(mProductId);
|
||||
dest.writeInt(mVersion);
|
||||
dest.writeBoolean(mPrimaryRecord);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int describeContents() {
|
||||
/* No special objects */
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
|
||||
public SdpDipRecord createFromParcel(Parcel in) {
|
||||
return new SdpDipRecord(in);
|
||||
}
|
||||
public SdpDipRecord[] newArray(int size) {
|
||||
return new SdpDipRecord[size];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,150 +0,0 @@
|
||||
/*
|
||||
* 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
/*
|
||||
* 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* 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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
/*
|
||||
* 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,126 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.SystemApi;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
/**
|
||||
* Record of data traffic (in bytes) by an application identified by its UID.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
|
||||
public final class UidTraffic implements Cloneable, Parcelable {
|
||||
private final int mAppUid;
|
||||
private long mRxBytes;
|
||||
private long mTxBytes;
|
||||
|
||||
/** @hide */
|
||||
public UidTraffic(int appUid, long rx, long tx) {
|
||||
mAppUid = appUid;
|
||||
mRxBytes = rx;
|
||||
mTxBytes = tx;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
private UidTraffic(Parcel in) {
|
||||
mAppUid = in.readInt();
|
||||
mRxBytes = in.readLong();
|
||||
mTxBytes = in.readLong();
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public void writeToParcel(Parcel dest, int flags) {
|
||||
dest.writeInt(mAppUid);
|
||||
dest.writeLong(mRxBytes);
|
||||
dest.writeLong(mTxBytes);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void setRxBytes(long bytes) {
|
||||
mRxBytes = bytes;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void setTxBytes(long bytes) {
|
||||
mTxBytes = bytes;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void addRxBytes(long bytes) {
|
||||
mRxBytes += bytes;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
public void addTxBytes(long bytes) {
|
||||
mTxBytes += bytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return corresponding app Uid
|
||||
*/
|
||||
public int getUid() {
|
||||
return mAppUid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return rx bytes count
|
||||
*/
|
||||
public long getRxBytes() {
|
||||
return mRxBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return tx bytes count
|
||||
*/
|
||||
public long getTxBytes() {
|
||||
return mTxBytes;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@Override
|
||||
public UidTraffic clone() {
|
||||
return new UidTraffic(mAppUid, mRxBytes, mTxBytes);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
@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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth.annotations;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Build;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher,
|
||||
* this requires the {@link Manifest.permission#BLUETOOTH_ADVERTISE}
|
||||
* permission which can be gained with
|
||||
* {@link android.app.Activity#requestPermissions(String[], int)}.
|
||||
* @hide
|
||||
*/
|
||||
@Retention(SOURCE)
|
||||
@Target({METHOD, FIELD})
|
||||
public @interface RequiresBluetoothAdvertisePermission {
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth.annotations;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Build;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher,
|
||||
* this requires the {@link Manifest.permission#BLUETOOTH_CONNECT}
|
||||
* permission which can be gained with
|
||||
* {@link android.app.Activity#requestPermissions(String[], int)}.
|
||||
* @hide
|
||||
*/
|
||||
@Retention(SOURCE)
|
||||
@Target({METHOD, FIELD})
|
||||
public @interface RequiresBluetoothConnectPermission {
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth.annotations;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.Manifest;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @memberDoc In addition, this requires either the
|
||||
* {@link Manifest.permission#ACCESS_FINE_LOCATION}
|
||||
* permission or a strong assertion that you will never derive the
|
||||
* physical location of the device. You can make this assertion by
|
||||
* declaring {@code usesPermissionFlags="neverForLocation"} on the
|
||||
* relevant {@code <uses-permission>} manifest tag, but it may
|
||||
* restrict the types of Bluetooth devices you can interact with.
|
||||
* @hide
|
||||
*/
|
||||
@Retention(SOURCE)
|
||||
@Target({METHOD, FIELD})
|
||||
public @interface RequiresBluetoothLocationPermission {
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth.annotations;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Build;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @memberDoc For apps targeting {@link Build.VERSION_CODES#S} or or higher,
|
||||
* this requires the {@link Manifest.permission#BLUETOOTH_SCAN}
|
||||
* permission which can be gained with
|
||||
* {@link android.app.Activity#requestPermissions(String[], int)}.
|
||||
* @hide
|
||||
*/
|
||||
@Retention(SOURCE)
|
||||
@Target({METHOD, FIELD})
|
||||
public @interface RequiresBluetoothScanPermission {
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth.annotations;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Build;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this
|
||||
* requires the {@link Manifest.permission#BLUETOOTH_ADMIN}
|
||||
* permission which can be gained with a simple
|
||||
* {@code <uses-permission>} manifest tag.
|
||||
* @hide
|
||||
*/
|
||||
@Retention(SOURCE)
|
||||
@Target({METHOD, FIELD})
|
||||
public @interface RequiresLegacyBluetoothAdminPermission {
|
||||
}
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package android.bluetooth.annotations;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.ElementType.METHOD;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.Manifest;
|
||||
import android.os.Build;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* @memberDoc For apps targeting {@link Build.VERSION_CODES#R} or lower, this
|
||||
* requires the {@link Manifest.permission#BLUETOOTH} permission
|
||||
* which can be gained with a simple {@code <uses-permission>}
|
||||
* manifest tag.
|
||||
* @hide
|
||||
*/
|
||||
@Retention(SOURCE)
|
||||
@Target({METHOD, FIELD})
|
||||
public @interface RequiresLegacyBluetoothPermission {
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
}
|
||||
@@ -1,374 +0,0 @@
|
||||
/*
|
||||
* 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.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.Collections;
|
||||
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;
|
||||
|
||||
@NonNull
|
||||
private final List<ParcelUuid> mServiceSolicitationUuids;
|
||||
|
||||
@Nullable
|
||||
private final List<TransportDiscoveryData> mTransportDiscoveryData;
|
||||
|
||||
private final SparseArray<byte[]> mManufacturerSpecificData;
|
||||
private final Map<ParcelUuid, byte[]> mServiceData;
|
||||
private final boolean mIncludeTxPowerLevel;
|
||||
private final boolean mIncludeDeviceName;
|
||||
|
||||
private AdvertiseData(List<ParcelUuid> serviceUuids,
|
||||
List<ParcelUuid> serviceSolicitationUuids,
|
||||
List<TransportDiscoveryData> transportDiscoveryData,
|
||||
SparseArray<byte[]> manufacturerData,
|
||||
Map<ParcelUuid, byte[]> serviceData,
|
||||
boolean includeTxPowerLevel,
|
||||
boolean includeDeviceName) {
|
||||
mServiceUuids = serviceUuids;
|
||||
mServiceSolicitationUuids = serviceSolicitationUuids;
|
||||
mTransportDiscoveryData = transportDiscoveryData;
|
||||
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 a list of service solicitation UUIDs within the advertisement that we invite to connect.
|
||||
*/
|
||||
@NonNull
|
||||
public List<ParcelUuid> getServiceSolicitationUuids() {
|
||||
return mServiceSolicitationUuids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of {@link TransportDiscoveryData} within the advertisement.
|
||||
*/
|
||||
@NonNull
|
||||
public List<TransportDiscoveryData> getTransportDiscoveryData() {
|
||||
if (mTransportDiscoveryData == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return mTransportDiscoveryData;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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, mServiceSolicitationUuids, mTransportDiscoveryData,
|
||||
mManufacturerSpecificData, mServiceData, mIncludeDeviceName, mIncludeTxPowerLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable 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)
|
||||
&& Objects.equals(mServiceSolicitationUuids, other.mServiceSolicitationUuids)
|
||||
&& Objects.equals(mTransportDiscoveryData, other.mTransportDiscoveryData)
|
||||
&& BluetoothLeUtils.equals(mManufacturerSpecificData,
|
||||
other.mManufacturerSpecificData)
|
||||
&& BluetoothLeUtils.equals(mServiceData, other.mServiceData)
|
||||
&& mIncludeDeviceName == other.mIncludeDeviceName
|
||||
&& mIncludeTxPowerLevel == other.mIncludeTxPowerLevel;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AdvertiseData [mServiceUuids=" + mServiceUuids + ", mServiceSolicitationUuids="
|
||||
+ mServiceSolicitationUuids + ", mTransportDiscoveryData="
|
||||
+ mTransportDiscoveryData + ", 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);
|
||||
dest.writeTypedArray(mServiceSolicitationUuids.toArray(
|
||||
new ParcelUuid[mServiceSolicitationUuids.size()]), flags);
|
||||
|
||||
dest.writeTypedList(mTransportDiscoveryData);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
ArrayList<ParcelUuid> solicitationUuids = in.createTypedArrayList(ParcelUuid.CREATOR);
|
||||
for (ParcelUuid uuid : solicitationUuids) {
|
||||
builder.addServiceSolicitationUuid(uuid);
|
||||
}
|
||||
|
||||
List<TransportDiscoveryData> transportDiscoveryData =
|
||||
in.createTypedArrayList(TransportDiscoveryData.CREATOR);
|
||||
for (TransportDiscoveryData tdd : transportDiscoveryData) {
|
||||
builder.addTransportDiscoveryData(tdd);
|
||||
}
|
||||
|
||||
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>();
|
||||
@NonNull
|
||||
private List<ParcelUuid> mServiceSolicitationUuids = new ArrayList<ParcelUuid>();
|
||||
@Nullable
|
||||
private List<TransportDiscoveryData> mTransportDiscoveryData =
|
||||
new ArrayList<TransportDiscoveryData>();
|
||||
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 serviceUuid} is null.
|
||||
*/
|
||||
public Builder addServiceUuid(ParcelUuid serviceUuid) {
|
||||
if (serviceUuid == null) {
|
||||
throw new IllegalArgumentException("serviceUuid is null");
|
||||
}
|
||||
mServiceUuids.add(serviceUuid);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a service solicitation UUID to advertise data.
|
||||
*
|
||||
* @param serviceSolicitationUuid A service solicitation UUID to be advertised.
|
||||
* @throws IllegalArgumentException If the {@code serviceSolicitationUuid} is null.
|
||||
*/
|
||||
@NonNull
|
||||
public Builder addServiceSolicitationUuid(@NonNull ParcelUuid serviceSolicitationUuid) {
|
||||
if (serviceSolicitationUuid == null) {
|
||||
throw new IllegalArgumentException("serviceSolicitationUuid is null");
|
||||
}
|
||||
mServiceSolicitationUuids.add(serviceSolicitationUuid);
|
||||
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 Transport Discovery Data to advertise data.
|
||||
*
|
||||
* @param transportDiscoveryData Transport Discovery Data, consisting of one or more
|
||||
* Transport Blocks. Transport Discovery Data AD Type Code is already included.
|
||||
* @throws IllegalArgumentException If the {@code transportDiscoveryData} is empty
|
||||
*/
|
||||
@NonNull
|
||||
public Builder addTransportDiscoveryData(
|
||||
@NonNull TransportDiscoveryData transportDiscoveryData) {
|
||||
if (transportDiscoveryData == null) {
|
||||
throw new IllegalArgumentException("transportDiscoveryData is null");
|
||||
}
|
||||
mTransportDiscoveryData.add(transportDiscoveryData);
|
||||
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, mServiceSolicitationUuids,
|
||||
mTransportDiscoveryData, mManufacturerSpecificData, mServiceData,
|
||||
mIncludeTxPowerLevel, mIncludeDeviceName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
/*
|
||||
* 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.SystemApi;
|
||||
import android.bluetooth.le.AdvertisingSetParameters.AddressTypeStatus;
|
||||
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 final int mOwnAddressType;
|
||||
|
||||
private AdvertiseSettings(int advertiseMode, int advertiseTxPowerLevel,
|
||||
boolean advertiseConnectable, int advertiseTimeout,
|
||||
@AddressTypeStatus int ownAddressType) {
|
||||
mAdvertiseMode = advertiseMode;
|
||||
mAdvertiseTxPowerLevel = advertiseTxPowerLevel;
|
||||
mAdvertiseConnectable = advertiseConnectable;
|
||||
mAdvertiseTimeoutMillis = advertiseTimeout;
|
||||
mOwnAddressType = ownAddressType;
|
||||
}
|
||||
|
||||
private AdvertiseSettings(Parcel in) {
|
||||
mAdvertiseMode = in.readInt();
|
||||
mAdvertiseTxPowerLevel = in.readInt();
|
||||
mAdvertiseConnectable = in.readInt() != 0;
|
||||
mAdvertiseTimeoutMillis = in.readInt();
|
||||
mOwnAddressType = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the own address type for advertising
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public @AddressTypeStatus int getOwnAddressType() {
|
||||
return mOwnAddressType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Settings [mAdvertiseMode=" + mAdvertiseMode
|
||||
+ ", mAdvertiseTxPowerLevel=" + mAdvertiseTxPowerLevel
|
||||
+ ", mAdvertiseConnectable=" + mAdvertiseConnectable
|
||||
+ ", mAdvertiseTimeoutMillis=" + mAdvertiseTimeoutMillis
|
||||
+ ", mOwnAddressType=" + mOwnAddressType + "]";
|
||||
}
|
||||
|
||||
@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);
|
||||
dest.writeInt(mOwnAddressType);
|
||||
}
|
||||
|
||||
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;
|
||||
private int mOwnAddressType = AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set own address type for advertising to control public or privacy mode. If used to set
|
||||
* address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT},
|
||||
* then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the
|
||||
* time of starting advertising.
|
||||
*
|
||||
* @throws IllegalArgumentException If the {@code ownAddressType} is invalid
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) {
|
||||
if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT
|
||||
|| ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) {
|
||||
throw new IllegalArgumentException("unknown address type " + ownAddressType);
|
||||
}
|
||||
mOwnAddressType = ownAddressType;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the {@link AdvertiseSettings} object.
|
||||
*/
|
||||
public AdvertiseSettings build() {
|
||||
return new AdvertiseSettings(mMode, mTxPowerLevel, mConnectable, mTimeoutMillis,
|
||||
mOwnAddressType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,230 +0,0 @@
|
||||
/*
|
||||
* 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.RequiresNoPermission;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.IBluetoothGatt;
|
||||
import android.bluetooth.IBluetoothManager;
|
||||
import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
|
||||
import android.content.AttributionSource;
|
||||
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.
|
||||
*
|
||||
* @see AdvertiseData
|
||||
*/
|
||||
public final class AdvertisingSet {
|
||||
private static final String TAG = "AdvertisingSet";
|
||||
|
||||
private final IBluetoothGatt mGatt;
|
||||
private int mAdvertiserId;
|
||||
private AttributionSource mAttributionSource;
|
||||
|
||||
/* package */ AdvertisingSet(int advertiserId, IBluetoothManager bluetoothManager,
|
||||
AttributionSource attributionSource) {
|
||||
mAdvertiserId = advertiserId;
|
||||
mAttributionSource = attributionSource;
|
||||
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()}.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
public void enableAdvertising(boolean enable, int duration,
|
||||
int maxExtendedAdvertisingEvents) {
|
||||
try {
|
||||
mGatt.enableAdvertisingSet(mAdvertiserId, enable, duration,
|
||||
maxExtendedAdvertisingEvents, mAttributionSource);
|
||||
} 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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
public void setAdvertisingData(AdvertiseData advertiseData) {
|
||||
try {
|
||||
mGatt.setAdvertisingData(mAdvertiserId, advertiseData, mAttributionSource);
|
||||
} 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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
public void setScanResponseData(AdvertiseData scanResponse) {
|
||||
try {
|
||||
mGatt.setScanResponseData(mAdvertiserId, scanResponse, mAttributionSource);
|
||||
} 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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
public void setAdvertisingParameters(AdvertisingSetParameters parameters) {
|
||||
try {
|
||||
mGatt.setAdvertisingParameters(mAdvertiserId, parameters, mAttributionSource);
|
||||
} 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()}.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
public void setPeriodicAdvertisingParameters(PeriodicAdvertisingParameters parameters) {
|
||||
try {
|
||||
mGatt.setPeriodicAdvertisingParameters(mAdvertiserId, parameters, mAttributionSource);
|
||||
} 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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
public void setPeriodicAdvertisingData(AdvertiseData periodicData) {
|
||||
try {
|
||||
mGatt.setPeriodicAdvertisingData(mAdvertiserId, periodicData, mAttributionSource);
|
||||
} 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).
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
public void setPeriodicAdvertisingEnabled(boolean enable) {
|
||||
try {
|
||||
mGatt.setPeriodicAdvertisingEnable(mAdvertiserId, enable, mAttributionSource);
|
||||
} 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.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_ADVERTISE,
|
||||
android.Manifest.permission.BLUETOOTH_PRIVILEGED,
|
||||
})
|
||||
public void getOwnAddress() {
|
||||
try {
|
||||
mGatt.getOwnAddress(mAdvertiserId, mAttributionSource);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "remote exception - ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns advertiserId associated with this advertising set.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
public int getAdvertiserId() {
|
||||
return mAdvertiserId;
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
}
|
||||
@@ -1,513 +0,0 @@
|
||||
/*
|
||||
* 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.IntDef;
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/** @hide */
|
||||
@IntDef(prefix = "ADDRESS_TYPE_", value = {
|
||||
ADDRESS_TYPE_DEFAULT,
|
||||
ADDRESS_TYPE_PUBLIC,
|
||||
ADDRESS_TYPE_RANDOM
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface AddressTypeStatus {}
|
||||
|
||||
/**
|
||||
* Advertise own address type that corresponds privacy settings of the device.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final int ADDRESS_TYPE_DEFAULT = -1;
|
||||
|
||||
/**
|
||||
* Advertise own public address type.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final int ADDRESS_TYPE_PUBLIC = 0;
|
||||
|
||||
/**
|
||||
* Generate and adverise own resolvable private address.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final int ADDRESS_TYPE_RANDOM = 1;
|
||||
|
||||
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 final int mOwnAddressType;
|
||||
|
||||
private AdvertisingSetParameters(boolean connectable, boolean scannable, boolean isLegacy,
|
||||
boolean isAnonymous, boolean includeTxPower,
|
||||
int primaryPhy, int secondaryPhy,
|
||||
int interval, int txPowerLevel, @AddressTypeStatus int ownAddressType) {
|
||||
mConnectable = connectable;
|
||||
mScannable = scannable;
|
||||
mIsLegacy = isLegacy;
|
||||
mIsAnonymous = isAnonymous;
|
||||
mIncludeTxPower = includeTxPower;
|
||||
mPrimaryPhy = primaryPhy;
|
||||
mSecondaryPhy = secondaryPhy;
|
||||
mInterval = interval;
|
||||
mTxPowerLevel = txPowerLevel;
|
||||
mOwnAddressType = ownAddressType;
|
||||
}
|
||||
|
||||
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();
|
||||
mOwnAddressType = 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the own address type for advertising
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public @AddressTypeStatus int getOwnAddressType() {
|
||||
return mOwnAddressType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "AdvertisingSetParameters [connectable=" + mConnectable
|
||||
+ ", isLegacy=" + mIsLegacy
|
||||
+ ", isAnonymous=" + mIsAnonymous
|
||||
+ ", includeTxPower=" + mIncludeTxPower
|
||||
+ ", primaryPhy=" + mPrimaryPhy
|
||||
+ ", secondaryPhy=" + mSecondaryPhy
|
||||
+ ", interval=" + mInterval
|
||||
+ ", txPowerLevel=" + mTxPowerLevel
|
||||
+ ", ownAddressType=" + mOwnAddressType + "]";
|
||||
}
|
||||
|
||||
@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);
|
||||
dest.writeInt(mOwnAddressType);
|
||||
}
|
||||
|
||||
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;
|
||||
private int mOwnAddressType = ADDRESS_TYPE_DEFAULT;
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set own address type for advertising to control public or privacy mode. If used to set
|
||||
* address type anything other than {@link AdvertisingSetParameters#ADDRESS_TYPE_DEFAULT},
|
||||
* then it will require BLUETOOTH_PRIVILEGED permission and will be checked at the
|
||||
* time of starting advertising.
|
||||
*
|
||||
* @throws IllegalArgumentException If the {@code ownAddressType} is invalid
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public @NonNull Builder setOwnAddressType(@AddressTypeStatus int ownAddressType) {
|
||||
if (ownAddressType < AdvertisingSetParameters.ADDRESS_TYPE_DEFAULT
|
||||
|| ownAddressType > AdvertisingSetParameters.ADDRESS_TYPE_RANDOM) {
|
||||
throw new IllegalArgumentException("unknown address type " + ownAddressType);
|
||||
}
|
||||
mOwnAddressType = ownAddressType;
|
||||
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,
|
||||
mOwnAddressType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,756 +0,0 @@
|
||||
/*
|
||||
* 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.RequiresNoPermission;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothUuid;
|
||||
import android.bluetooth.IBluetoothGatt;
|
||||
import android.bluetooth.IBluetoothManager;
|
||||
import android.bluetooth.annotations.RequiresBluetoothAdvertisePermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
|
||||
import android.content.AttributionSource;
|
||||
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;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* @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 BluetoothAdapter mBluetoothAdapter;
|
||||
private final IBluetoothManager mBluetoothManager;
|
||||
private final AttributionSource mAttributionSource;
|
||||
|
||||
private final Handler mHandler;
|
||||
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(BluetoothAdapter bluetoothAdapter) {
|
||||
mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
|
||||
mBluetoothManager = mBluetoothAdapter.getBluetoothManager();
|
||||
mAttributionSource = mBluetoothAdapter.getAttributionSource();
|
||||
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}.
|
||||
*
|
||||
* @param settings Settings for Bluetooth LE advertising.
|
||||
* @param advertiseData Advertisement data to be broadcasted.
|
||||
* @param callback Callback for advertising status.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
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}.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
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
|
||||
parameters.setOwnAddressType(settings.getOwnAddressType());
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint({
|
||||
"AndroidFrameworkBluetoothPermission",
|
||||
"AndroidFrameworkRequiresPermission",
|
||||
})
|
||||
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}.
|
||||
*
|
||||
* @param callback {@link AdvertiseCallback} identifies the advertising instance to stop.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
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
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
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,
|
||||
mAttributionSource);
|
||||
} 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}.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
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, mAttributionSource);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to stop advertising - ", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up advertisers. Should be called when bluetooth is down.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
public void cleanup() {
|
||||
mLegacyAdvertisers.clear();
|
||||
mCallbackWrappers.clear();
|
||||
mAdvertisingSets.clear();
|
||||
}
|
||||
|
||||
// Compute the size of advertisement data or scan resp
|
||||
@RequiresBluetoothAdvertisePermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_ADVERTISE)
|
||||
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;
|
||||
}
|
||||
}
|
||||
if (data.getServiceSolicitationUuids() != null) {
|
||||
int num16BitUuids = 0;
|
||||
int num32BitUuids = 0;
|
||||
int num128BitUuids = 0;
|
||||
for (ParcelUuid uuid : data.getServiceSolicitationUuids()) {
|
||||
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 (TransportDiscoveryData transportDiscoveryData : data.getTransportDiscoveryData()) {
|
||||
size += OVERHEAD_BYTES_PER_FIELD + transportDiscoveryData.totalBytes();
|
||||
}
|
||||
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()) {
|
||||
final int length = mBluetoothAdapter.getNameLengthForAdvertise();
|
||||
if (length >= 0) {
|
||||
size += OVERHEAD_BYTES_PER_FIELD + length;
|
||||
}
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
private int byteLength(byte[] array) {
|
||||
return array == null ? 0 : array.length;
|
||||
}
|
||||
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
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, mAttributionSource);
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
private void postStartSetFailure(Handler handler, final AdvertisingSetCallback callback,
|
||||
final int error) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onAdvertisingSetStarted(null, 0, error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
private void postStartFailure(final AdvertiseCallback callback, final int error) {
|
||||
mHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onStartFailure(error);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
private void postStartSuccess(final AdvertiseCallback callback,
|
||||
final AdvertiseSettings settings) {
|
||||
mHandler.post(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
callback.onStartSuccess(settings);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,658 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.RequiresNoPermission;
|
||||
import android.annotation.RequiresPermission;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.SystemApi;
|
||||
import android.app.PendingIntent;
|
||||
import android.bluetooth.Attributable;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothGatt;
|
||||
import android.bluetooth.IBluetoothGatt;
|
||||
import android.bluetooth.IBluetoothManager;
|
||||
import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
|
||||
import android.bluetooth.annotations.RequiresBluetoothScanPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
|
||||
import android.content.AttributionSource;
|
||||
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;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*
|
||||
* @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 BluetoothAdapter mBluetoothAdapter;
|
||||
private final IBluetoothManager mBluetoothManager;
|
||||
private final AttributionSource mAttributionSource;
|
||||
|
||||
private final Handler mHandler;
|
||||
private final Map<ScanCallback, BleScanCallbackWrapper> mLeScanClients;
|
||||
|
||||
/**
|
||||
* 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(BluetoothAdapter bluetoothAdapter) {
|
||||
mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
|
||||
mBluetoothManager = mBluetoothAdapter.getBluetoothManager();
|
||||
mAttributionSource = mBluetoothAdapter.getAttributionSource();
|
||||
mHandler = new Handler(Looper.getMainLooper());
|
||||
mLeScanClients = new HashMap<ScanCallback, BleScanCallbackWrapper>();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 have
|
||||
* {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission
|
||||
* in order to get results. An App targeting Android Q or later must have
|
||||
* {@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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresBluetoothLocationPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
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 have
|
||||
* {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission
|
||||
* in order to get results. An App targeting Android Q or later must have
|
||||
* {@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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresBluetoothLocationPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
public void startScan(List<ScanFilter> filters, ScanSettings settings,
|
||||
final ScanCallback callback) {
|
||||
startScan(filters, settings, null, callback, /*callbackIntent=*/ 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 have
|
||||
* {@link android.Manifest.permission#ACCESS_COARSE_LOCATION ACCESS_COARSE_LOCATION} permission
|
||||
* in order to get results. An App targeting Android Q or later must have
|
||||
* {@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)
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresBluetoothLocationPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresBluetoothLocationPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_SCAN,
|
||||
android.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
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresBluetoothLocationPermission
|
||||
@RequiresPermission(allOf = {
|
||||
android.Manifest.permission.BLUETOOTH_SCAN,
|
||||
android.Manifest.permission.UPDATE_DEVICE_STATS
|
||||
})
|
||||
@SuppressLint("AndroidFrameworkRequiresPermission")
|
||||
public void startScanFromSource(List<ScanFilter> filters, ScanSettings settings,
|
||||
final WorkSource workSource, final ScanCallback callback) {
|
||||
startScan(filters, settings, workSource, callback, null);
|
||||
}
|
||||
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
private int startScan(List<ScanFilter> filters, ScanSettings settings,
|
||||
final WorkSource workSource, final ScanCallback callback,
|
||||
final PendingIntent callbackIntent) {
|
||||
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);
|
||||
wrapper.startRegistration();
|
||||
} else {
|
||||
try {
|
||||
gatt.startScanForIntent(callbackIntent, settings, filters,
|
||||
mAttributionSource);
|
||||
} catch (RemoteException e) {
|
||||
return ScanCallback.SCAN_FAILED_INTERNAL_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ScanCallback.NO_ERROR;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops an ongoing Bluetooth LE scan.
|
||||
*
|
||||
* @param callback
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
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)
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
public void stopScan(PendingIntent callbackIntent) {
|
||||
BluetoothLeUtils.checkAdapterStateOn(mBluetoothAdapter);
|
||||
IBluetoothGatt gatt;
|
||||
try {
|
||||
gatt = mBluetoothManager.getBluetoothGatt();
|
||||
gatt.stopScanForIntent(callbackIntent, mAttributionSource);
|
||||
} 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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_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.
|
||||
*
|
||||
* @deprecated this is not used anywhere
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Deprecated
|
||||
@SystemApi
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
public void startTruncatedScan(List<TruncatedFilter> truncatedFilters, ScanSettings settings,
|
||||
final ScanCallback callback) {
|
||||
int filterSize = truncatedFilters.size();
|
||||
List<ScanFilter> scanFilters = new ArrayList<ScanFilter>(filterSize);
|
||||
for (TruncatedFilter filter : truncatedFilters) {
|
||||
scanFilters.add(filter.getFilter());
|
||||
}
|
||||
startScan(scanFilters, settings, null, callback, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleans up scan clients. Should be called when bluetooth is down.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@RequiresNoPermission
|
||||
public void cleanup() {
|
||||
mLeScanClients.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Bluetooth GATT interface callbacks
|
||||
*/
|
||||
@SuppressLint("AndroidFrameworkRequiresPermission")
|
||||
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;
|
||||
|
||||
// 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) {
|
||||
mBluetoothGatt = bluetoothGatt;
|
||||
mFilters = filters;
|
||||
mSettings = settings;
|
||||
mWorkSource = workSource;
|
||||
mScanCallback = scanCallback;
|
||||
mScannerId = 0;
|
||||
}
|
||||
|
||||
public void startRegistration() {
|
||||
synchronized (this) {
|
||||
// Scan stopped.
|
||||
if (mScannerId == -1 || mScannerId == -2) return;
|
||||
try {
|
||||
mBluetoothGatt.registerScanner(this, mWorkSource, mAttributionSource);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
public void stopLeScan() {
|
||||
synchronized (this) {
|
||||
if (mScannerId <= 0) {
|
||||
Log.e(TAG, "Error state, mLeHandle: " + mScannerId);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mBluetoothGatt.stopScan(mScannerId, mAttributionSource);
|
||||
mBluetoothGatt.unregisterScanner(mScannerId, mAttributionSource);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to stop scan and unregister", e);
|
||||
}
|
||||
mScannerId = -1;
|
||||
}
|
||||
}
|
||||
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
void flushPendingBatchResults() {
|
||||
synchronized (this) {
|
||||
if (mScannerId <= 0) {
|
||||
Log.e(TAG, "Error state, mLeHandle: " + mScannerId);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mBluetoothGatt.flushPendingBatchResults(mScannerId, mAttributionSource);
|
||||
} 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, mAttributionSource);
|
||||
} else {
|
||||
mScannerId = scannerId;
|
||||
mBluetoothGatt.startScan(mScannerId, mSettings, mFilters,
|
||||
mAttributionSource);
|
||||
}
|
||||
} 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) {
|
||||
Attributable.setAttributionSource(scanResult, mAttributionSource);
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "onScanResult() - mScannerId=" + mScannerId);
|
||||
}
|
||||
if (VDBG) Log.d(TAG, "onScanResult() - " + scanResult.toString());
|
||||
|
||||
// Check null in case the scan has been stopped
|
||||
synchronized (this) {
|
||||
if (mScannerId <= 0) {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Ignoring result as scan stopped.");
|
||||
}
|
||||
return;
|
||||
};
|
||||
}
|
||||
Handler handler = new Handler(Looper.getMainLooper());
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "onScanResult() - handler run");
|
||||
}
|
||||
mScanCallback.onScanResult(ScanSettings.CALLBACK_TYPE_ALL_MATCHES, scanResult);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onBatchScanResults(final List<ScanResult> results) {
|
||||
Attributable.setAttributionSource(results, mAttributionSource);
|
||||
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) {
|
||||
Attributable.setAttributionSource(scanResult, mAttributionSource);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
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;
|
||||
}
|
||||
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* 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");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares two UUIDs with a UUID mask.
|
||||
*
|
||||
* @param data first {@link #UUID} to compare.
|
||||
* @param uuid second {@link #UUID} to compare.
|
||||
* @param mask mask {@link #UUID}.
|
||||
* @return true if both UUIDs are equals when masked, false otherwise.
|
||||
*/
|
||||
static boolean maskedEquals(UUID data, UUID uuid, UUID mask) {
|
||||
if (mask == null) {
|
||||
return Objects.equals(data, uuid);
|
||||
}
|
||||
return (data.getLeastSignificantBits() & mask.getLeastSignificantBits())
|
||||
== (uuid.getLeastSignificantBits() & mask.getLeastSignificantBits())
|
||||
&& (data.getMostSignificantBits() & mask.getMostSignificantBits())
|
||||
== (uuid.getMostSignificantBits() & mask.getMostSignificantBits());
|
||||
}
|
||||
}
|
||||
@@ -1,4 +0,0 @@
|
||||
# Bug component: 27441
|
||||
|
||||
zachoverflow@google.com
|
||||
siyuanh@google.com
|
||||
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
}
|
||||
@@ -1,264 +0,0 @@
|
||||
/*
|
||||
* 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.RequiresPermission;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.bluetooth.Attributable;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.IBluetoothGatt;
|
||||
import android.bluetooth.IBluetoothManager;
|
||||
import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
|
||||
import android.bluetooth.annotations.RequiresBluetoothScanPermission;
|
||||
import android.bluetooth.annotations.RequiresLegacyBluetoothAdminPermission;
|
||||
import android.content.AttributionSource;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
*
|
||||
* @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 BluetoothAdapter mBluetoothAdapter;
|
||||
private final IBluetoothManager mBluetoothManager;
|
||||
private final AttributionSource mAttributionSource;
|
||||
|
||||
/* 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(BluetoothAdapter bluetoothAdapter) {
|
||||
mBluetoothAdapter = Objects.requireNonNull(bluetoothAdapter);
|
||||
mBluetoothManager = mBluetoothAdapter.getBluetoothManager();
|
||||
mAttributionSource = mBluetoothAdapter.getAttributionSource();
|
||||
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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresBluetoothLocationPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresBluetoothLocationPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
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, mAttributionSource);
|
||||
} 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.
|
||||
*/
|
||||
@RequiresLegacyBluetoothAdminPermission
|
||||
@RequiresBluetoothScanPermission
|
||||
@RequiresPermission(android.Manifest.permission.BLUETOOTH_SCAN)
|
||||
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, mAttributionSource);
|
||||
} catch (RemoteException e) {
|
||||
Log.e(TAG, "Failed to cancel sync creation - ", e);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
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) {
|
||||
Attributable.setAttributionSource(device, mAttributionSource);
|
||||
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);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,121 +0,0 @@
|
||||
/*
|
||||
* 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
|
||||
* PeriodicAdvertisingParameters.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,186 +0,0 @@
|
||||
/*
|
||||
* 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(@Nullable 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* 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.
|
||||
*
|
||||
* @deprecated this is not used anywhere
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@Deprecated
|
||||
@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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
/*
|
||||
* 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) {
|
||||
}
|
||||
}
|
||||
@@ -1,910 +0,0 @@
|
||||
/*
|
||||
* 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 static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.SystemApi;
|
||||
import android.bluetooth.BluetoothAdapter;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.bluetooth.BluetoothDevice.AddressType;
|
||||
import android.os.Parcel;
|
||||
import android.os.ParcelUuid;
|
||||
import android.os.Parcelable;
|
||||
|
||||
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;
|
||||
|
||||
private final @AddressType int mAddressType;
|
||||
|
||||
@Nullable
|
||||
private final byte[] mIrk;
|
||||
|
||||
@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,
|
||||
@AddressType int addressType, @Nullable byte[] irk) {
|
||||
mDeviceName = name;
|
||||
mServiceUuid = uuid;
|
||||
mServiceUuidMask = uuidMask;
|
||||
mServiceSolicitationUuid = solicitationUuid;
|
||||
mServiceSolicitationUuidMask = solicitationUuidMask;
|
||||
mDeviceAddress = deviceAddress;
|
||||
mServiceDataUuid = serviceDataUuid;
|
||||
mServiceData = serviceData;
|
||||
mServiceDataMask = serviceDataMask;
|
||||
mManufacturerId = manufacturerId;
|
||||
mManufacturerData = manufacturerData;
|
||||
mManufacturerDataMask = manufacturerDataMask;
|
||||
mAddressType = addressType;
|
||||
mIrk = irk;
|
||||
}
|
||||
|
||||
@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);
|
||||
}
|
||||
}
|
||||
|
||||
// IRK
|
||||
if (mDeviceAddress != null) {
|
||||
dest.writeInt(mAddressType);
|
||||
dest.writeInt(mIrk == null ? 0 : 1);
|
||||
if (mIrk != null) {
|
||||
dest.writeByteArray(mIrk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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());
|
||||
}
|
||||
String address = null;
|
||||
// If we have a non-null address
|
||||
if (in.readInt() == 1) {
|
||||
address = 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);
|
||||
}
|
||||
}
|
||||
|
||||
// IRK
|
||||
if (address != null) {
|
||||
final int addressType = in.readInt();
|
||||
if (in.readInt() == 1) {
|
||||
final byte[] irk = new byte[16];
|
||||
in.readByteArray(irk);
|
||||
builder.setDeviceAddress(address, addressType, irk);
|
||||
} else {
|
||||
builder.setDeviceAddress(address, addressType);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public @AddressType int getAddressType() {
|
||||
return mAddressType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@Nullable
|
||||
public byte[] getIrk() {
|
||||
return mIrk;
|
||||
}
|
||||
|
||||
@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 BluetoothLeUtils.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 BluetoothLeUtils.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(@Nullable 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 {
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final int LEN_IRK_OCTETS = 16;
|
||||
|
||||
private String mDeviceName;
|
||||
private String mDeviceAddress;
|
||||
private @AddressType int mAddressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC;
|
||||
private byte[] mIrk;
|
||||
|
||||
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}. The @AddressType is defaulted to {@link
|
||||
* BluetoothDevice#ADDRESS_TYPE_PUBLIC}
|
||||
* @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
|
||||
*/
|
||||
public Builder setDeviceAddress(String deviceAddress) {
|
||||
if (deviceAddress == null) {
|
||||
mDeviceAddress = deviceAddress;
|
||||
return this;
|
||||
}
|
||||
return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter on Address with AddressType
|
||||
*
|
||||
* <p>This key is used to resolve a private address from a public 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}. May be any type of address.
|
||||
* @param addressType indication of the type of address
|
||||
* e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
|
||||
* or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
|
||||
*
|
||||
* @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
|
||||
* @throws IllegalArgumentException If the {@code addressType} is invalid length
|
||||
* @throws NullPointerException if {@code deviceAddress} is null.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public Builder setDeviceAddress(@NonNull String deviceAddress,
|
||||
@AddressType int addressType) {
|
||||
return setDeviceAddressInternal(deviceAddress, addressType, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter on Address with AddressType and the Identity Resolving Key (IRK).
|
||||
*
|
||||
* <p>The IRK is used to resolve a {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} from
|
||||
* a PRIVATE_ADDRESS type.
|
||||
*
|
||||
* @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}. This Address type must only be PUBLIC OR RANDOM
|
||||
* STATIC.
|
||||
* @param addressType indication of the type of address
|
||||
* e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
|
||||
* or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
|
||||
* @param irk non-null byte array representing the Identity Resolving Key
|
||||
*
|
||||
* @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
|
||||
* @throws IllegalArgumentException if the {@code irk} is invalid length.
|
||||
* @throws IllegalArgumentException If the {@code addressType} is invalid length or is not
|
||||
* PUBLIC or RANDOM STATIC when an IRK is present.
|
||||
* @throws NullPointerException if {@code deviceAddress} or {@code irk} is null.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
@SystemApi
|
||||
public Builder setDeviceAddress(@NonNull String deviceAddress,
|
||||
@AddressType int addressType,
|
||||
@NonNull byte[] irk) {
|
||||
requireNonNull(irk);
|
||||
if (irk.length != LEN_IRK_OCTETS) {
|
||||
throw new IllegalArgumentException("'irk' is invalid length!");
|
||||
}
|
||||
return setDeviceAddressInternal(deviceAddress, addressType, irk);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set filter on Address with AddressType and the Identity Resolving Key (IRK).
|
||||
*
|
||||
* <p>Internal setter for the 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}.
|
||||
* @param addressType indication of the type of address
|
||||
* e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
|
||||
* @param irk non-null byte array representing the Identity Resolving Address; nullable
|
||||
* internally.
|
||||
*
|
||||
* @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
|
||||
* @throws IllegalArgumentException If the {@code addressType} is invalid length.
|
||||
* @throws NullPointerException if {@code deviceAddress} is null.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@NonNull
|
||||
private Builder setDeviceAddressInternal(@NonNull String deviceAddress,
|
||||
@AddressType int addressType,
|
||||
@Nullable byte[] irk) {
|
||||
|
||||
// Make sure our deviceAddress is valid!
|
||||
requireNonNull(deviceAddress);
|
||||
if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
|
||||
throw new IllegalArgumentException("invalid device address " + deviceAddress);
|
||||
}
|
||||
|
||||
// Verify type range
|
||||
if (addressType < BluetoothDevice.ADDRESS_TYPE_PUBLIC
|
||||
|| addressType > BluetoothDevice.ADDRESS_TYPE_RANDOM) {
|
||||
throw new IllegalArgumentException("'addressType' is invalid!");
|
||||
}
|
||||
|
||||
// IRK can only be used for a PUBLIC or RANDOM (STATIC) Address.
|
||||
if (addressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) {
|
||||
// Don't want a bad combination of address and irk!
|
||||
if (irk != null) {
|
||||
// Since there are 3 possible RANDOM subtypes we must check to make sure
|
||||
// the correct type of address is used.
|
||||
if (!BluetoothAdapter.isAddressRandomStatic(deviceAddress)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Invalid combination: IRK requires either a PUBLIC or "
|
||||
+ "RANDOM (STATIC) Address");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// PUBLIC doesn't require extra work
|
||||
// Without an IRK any address may be accepted
|
||||
|
||||
mDeviceAddress = deviceAddress;
|
||||
mAddressType = addressType;
|
||||
mIrk = irk;
|
||||
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,
|
||||
mAddressType, mIrk);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,378 +0,0 @@
|
||||
/*
|
||||
* 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.annotation.SuppressLint;
|
||||
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;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Represents a scan record from Bluetooth LE scan.
|
||||
*/
|
||||
@SuppressLint("AndroidFrameworkBluetoothPermission")
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if any fields contained inside this scan record are matched by the
|
||||
* given matcher.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public boolean matchesAnyField(@NonNull Predicate<byte[]> matcher) {
|
||||
int pos = 0;
|
||||
while (pos < mBytes.length) {
|
||||
final int length = mBytes[pos] & 0xFF;
|
||||
if (length == 0) {
|
||||
break;
|
||||
}
|
||||
if (matcher.test(Arrays.copyOfRange(mBytes, pos, pos + length + 1))) {
|
||||
return true;
|
||||
}
|
||||
pos += length + 1;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,361 +0,0 @@
|
||||
/*
|
||||
* 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.Attributable;
|
||||
import android.bluetooth.BluetoothDevice;
|
||||
import android.content.AttributionSource;
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* ScanResult for Bluetooth LE scan.
|
||||
*/
|
||||
public final class ScanResult implements Parcelable, Attributable {
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/** {@hide} */
|
||||
public void setAttributionSource(@NonNull AttributionSource attributionSource) {
|
||||
Attributable.setAttributionSource(mDevice, attributionSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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(@Nullable 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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,438 +0,0 @@
|
||||
/*
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* Perform Bluetooth LE scan in ambient discovery mode. This mode has lower duty cycle and more
|
||||
* aggressive scan interval than balanced mode that provides a good trade-off between scan
|
||||
* latency and power consumption.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
public static final int SCAN_MODE_AMBIENT_DISCOVERY = 3;
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
switch (scanMode) {
|
||||
case SCAN_MODE_OPPORTUNISTIC:
|
||||
case SCAN_MODE_LOW_POWER:
|
||||
case SCAN_MODE_BALANCED:
|
||||
case SCAN_MODE_LOW_LATENCY:
|
||||
case SCAN_MODE_AMBIENT_DISCOVERY:
|
||||
mScanMode = scanMode;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("invalid scan mode " + 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. If set to 0, you will be notified of
|
||||
* scan results immediately. If > 0, scan results are queued up and delivered after the
|
||||
* requested delay or 5000 milliseconds (whichever is higher). Note scan results may be
|
||||
* delivered sooner if the internal buffers fill up.
|
||||
*
|
||||
* @param reportDelayMillis how frequently scan results should be delivered in
|
||||
* milliseconds
|
||||
* @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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,177 +0,0 @@
|
||||
/*
|
||||
* 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.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import android.util.Log;
|
||||
|
||||
import java.nio.BufferOverflowException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* Wrapper for Transport Discovery Data Transport Blocks.
|
||||
* This class represents a Transport Block from a Transport Discovery Data.
|
||||
*
|
||||
* @see TransportDiscoveryData
|
||||
* @see AdvertiseData
|
||||
*/
|
||||
public final class TransportBlock implements Parcelable {
|
||||
private static final String TAG = "TransportBlock";
|
||||
private final int mOrgId;
|
||||
private final int mTdsFlags;
|
||||
private final int mTransportDataLength;
|
||||
private final byte[] mTransportData;
|
||||
|
||||
/**
|
||||
* Creates an instance of TransportBlock from raw data.
|
||||
*
|
||||
* @param orgId the Organization ID
|
||||
* @param tdsFlags the TDS flags
|
||||
* @param transportDataLength the total length of the Transport Data
|
||||
* @param transportData the Transport Data
|
||||
*/
|
||||
public TransportBlock(int orgId, int tdsFlags, int transportDataLength,
|
||||
@Nullable byte[] transportData) {
|
||||
mOrgId = orgId;
|
||||
mTdsFlags = tdsFlags;
|
||||
mTransportDataLength = transportDataLength;
|
||||
mTransportData = transportData;
|
||||
}
|
||||
|
||||
private TransportBlock(Parcel in) {
|
||||
mOrgId = in.readInt();
|
||||
mTdsFlags = in.readInt();
|
||||
mTransportDataLength = in.readInt();
|
||||
if (mTransportDataLength > 0) {
|
||||
mTransportData = new byte[mTransportDataLength];
|
||||
in.readByteArray(mTransportData);
|
||||
} else {
|
||||
mTransportData = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
||||
dest.writeInt(mOrgId);
|
||||
dest.writeInt(mTdsFlags);
|
||||
dest.writeInt(mTransportDataLength);
|
||||
if (mTransportData != null) {
|
||||
dest.writeByteArray(mTransportData);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public int describeContents() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(@Nullable Object obj) {
|
||||
if (this == obj) {
|
||||
return true;
|
||||
}
|
||||
if (obj == null || getClass() != obj.getClass()) {
|
||||
return false;
|
||||
}
|
||||
TransportBlock other = (TransportBlock) obj;
|
||||
return Arrays.equals(toByteArray(), other.toByteArray());
|
||||
}
|
||||
|
||||
public static final @NonNull Creator<TransportBlock> CREATOR = new Creator<TransportBlock>() {
|
||||
@Override
|
||||
public TransportBlock createFromParcel(Parcel in) {
|
||||
return new TransportBlock(in);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TransportBlock[] newArray(int size) {
|
||||
return new TransportBlock[size];
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets the Organization ID of the Transport Block which corresponds to one of the
|
||||
* the Bluetooth SIG Assigned Numbers.
|
||||
*/
|
||||
public int getOrgId() {
|
||||
return mOrgId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the TDS flags of the Transport Block which represents the role of the device and
|
||||
* information about its state and supported features.
|
||||
*/
|
||||
public int getTdsFlags() {
|
||||
return mTdsFlags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the total number of octets in the Transport Data field in this Transport Block.
|
||||
*/
|
||||
public int getTransportDataLength() {
|
||||
return mTransportDataLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Transport Data of the Transport Block which contains organization-specific data.
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] getTransportData() {
|
||||
return mTransportData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts this TransportBlock to byte array
|
||||
*
|
||||
* @return byte array representation of this Transport Block or null if the conversion failed
|
||||
*/
|
||||
@Nullable
|
||||
public byte[] toByteArray() {
|
||||
try {
|
||||
ByteBuffer buffer = ByteBuffer.allocate(totalBytes());
|
||||
buffer.put((byte) mOrgId);
|
||||
buffer.put((byte) mTdsFlags);
|
||||
buffer.put((byte) mTransportDataLength);
|
||||
if (mTransportData != null) {
|
||||
buffer.put(mTransportData);
|
||||
}
|
||||
return buffer.array();
|
||||
} catch (BufferOverflowException e) {
|
||||
Log.e(TAG, "Error converting to byte array: " + e.toString());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return total byte count of this TransportBlock
|
||||
*/
|
||||
public int totalBytes() {
|
||||
// 3 uint8 + byte[] length
|
||||
int size = 3 + mTransportDataLength;
|
||||
return size;
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user