Migrating frameworks/base BT files

Bug: 206121418
Test: Compile and Test

Merged-In: Idb55371e9d678296fe46e5f4231ec2d12ec8b978
Change-Id: Idb55371e9d678296fe46e5f4231ec2d12ec8b978
This commit is contained in:
Roopa Sattiraju
2022-01-17 20:48:03 -08:00
parent 6dc2fbcf27
commit 33a02acc17
130 changed files with 4 additions and 50693 deletions

View File

@@ -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",

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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) {
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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.");
}
}

View File

@@ -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) {
}
}

View File

@@ -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

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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) {}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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) {}
}

View File

@@ -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);
}
}
}
;

View File

@@ -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;
}
}

View File

@@ -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;
}
}
}
};
}

View File

@@ -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;
}
}
}

View File

@@ -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());
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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";
}
}
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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() {}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -1,4 +0,0 @@
# Bug component: 27441
sattiraju@google.com
baligh@google.com

View File

@@ -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();
}
}

View File

@@ -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];
}
};
}

View File

@@ -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];
}
};
}

View File

@@ -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];
}
};
}

View File

@@ -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];
}
};
}

View File

@@ -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];
}
};
}

View File

@@ -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;
}
}

View File

@@ -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];
}
};
}

View File

@@ -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];
}
};
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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 {
}

View File

@@ -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) {
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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) {
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
});
}
}

View File

@@ -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;
}
}

View File

@@ -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());
}
}

View File

@@ -1,4 +0,0 @@
# Bug component: 27441
zachoverflow@google.com
siyuanh@google.com

View File

@@ -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) {
}
}

View File

@@ -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);
}
});
}
};
}
}

View File

@@ -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);
}
}
}

View File

@@ -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];
}
};
}

View File

@@ -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];
}
};
}

View File

@@ -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) {
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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];
}
};
}

View File

@@ -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 &gt; 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} &lt; 0
*/
public Builder setReportDelay(long reportDelayMillis) {
if (reportDelayMillis < 0) {
throw new IllegalArgumentException("reportDelay must be > 0");
}
mReportDelayMillis = reportDelayMillis;
return this;
}
/**
* Set the number of matches for Bluetooth LE scan filters hardware match
*
* @param numOfMatches The num of matches can be one of
* {@link ScanSettings#MATCH_NUM_ONE_ADVERTISEMENT}
* or {@link ScanSettings#MATCH_NUM_FEW_ADVERTISEMENT} or {@link
* ScanSettings#MATCH_NUM_MAX_ADVERTISEMENT}
* @throws IllegalArgumentException If the {@code matchMode} is invalid.
*/
public Builder setNumOfMatches(int numOfMatches) {
if (numOfMatches < MATCH_NUM_ONE_ADVERTISEMENT
|| numOfMatches > MATCH_NUM_MAX_ADVERTISEMENT) {
throw new IllegalArgumentException("invalid numOfMatches " + numOfMatches);
}
mNumOfMatchesPerFilter = numOfMatches;
return this;
}
/**
* Set match mode for Bluetooth LE scan filters hardware match
*
* @param matchMode The match mode can be one of {@link ScanSettings#MATCH_MODE_AGGRESSIVE}
* or {@link ScanSettings#MATCH_MODE_STICKY}
* @throws IllegalArgumentException If the {@code matchMode} is invalid.
*/
public Builder setMatchMode(int matchMode) {
if (matchMode < MATCH_MODE_AGGRESSIVE
|| matchMode > MATCH_MODE_STICKY) {
throw new IllegalArgumentException("invalid matchMode " + matchMode);
}
mMatchMode = matchMode;
return this;
}
/**
* Set whether only legacy advertisments should be returned in scan results.
* Legacy advertisements include advertisements as specified by the
* Bluetooth core specification 4.2 and below. This is true by default
* for compatibility with older apps.
*
* @param legacy true if only legacy advertisements will be returned
*/
public Builder setLegacy(boolean legacy) {
mLegacy = legacy;
return this;
}
/**
* Set the Physical Layer to use during this scan.
* This is used only if {@link ScanSettings.Builder#setLegacy}
* is set to false.
* {@link android.bluetooth.BluetoothAdapter#isLeCodedPhySupported}
* may be used to check whether LE Coded phy is supported by calling
* {@link android.bluetooth.BluetoothAdapter#isLeCodedPhySupported}.
* Selecting an unsupported phy will result in failure to start scan.
*
* @param phy Can be one of {@link BluetoothDevice#PHY_LE_1M}, {@link
* BluetoothDevice#PHY_LE_CODED} or {@link ScanSettings#PHY_LE_ALL_SUPPORTED}
*/
public Builder setPhy(int phy) {
mPhy = phy;
return this;
}
/**
* Build {@link ScanSettings}.
*/
public ScanSettings build() {
return new ScanSettings(mScanMode, mCallbackType, mScanResultType,
mReportDelayMillis, mMatchMode,
mNumOfMatchesPerFilter, mLegacy, mPhy);
}
}
}

View File

@@ -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