blob: bd6c0503fb7dda64f398ec367c1e48828ecca6f1 [file]
/*
* Copyright (C) 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS 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.Manifest.permission.BLUETOOTH_CONNECT;
import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
import static java.util.Objects.requireNonNull;
import android.annotation.FlaggedApi;
import android.annotation.Hide;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresNoPermission;
import android.annotation.RequiresPermission;
import android.annotation.SystemApi;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.Log;
import com.android.bluetooth.flags.Flags;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
import java.util.Optional;
/**
* GATT offload session information. This class will be returned by {@link
* BluetoothGatt#offloadCharacteristics() or @link BluetoothGattServer#offloadCharacteristics()}.
*/
@Hide
@FlaggedApi(Flags.FLAG_GATT_OFFLOAD_API)
@SystemApi
public final class GattOffloadSession implements AutoCloseable {
private static final String TAG = GattOffloadSession.class.getSimpleName();
/**
* Constant representing an invalid offload session ID. This value indicates that a session ID
* has not been assigned or is not valid.
*/
public static final int OFFLOAD_SESSION_ID_UNKNOWN = 0;
/**
* Annotation to define the status of a GATT offload session.
*
* <p>This specifies the possible outcomes when attempting to offload or unoffload GATT
* characteristics.
*/
@Hide
@IntDef(
prefix = {"STATUS_"},
value = {
STATUS_SUCCESS,
STATUS_INVALID_CONNECTION,
STATUS_INSUFFICIENT_SECURITY,
STATUS_DATABASE_OUT_OF_SYNC,
STATUS_HAL_ERROR,
STATUS_SERVICE_UNAVAILABLE,
STATUS_CHARACTERISTIC_BUSY,
STATUS_REQUEST_TIMEOUT,
STATUS_ILLEGAL_PARAMETER,
STATUS_DUPLICATE_SESSION,
STATUS_FAILURE,
})
@Retention(RetentionPolicy.SOURCE)
public @interface Status {}
/** Indicates that the operation was successful. */
public static final int STATUS_SUCCESS = 0x00;
/** Indicates that the BluetoothGatt connection is no longer valid. */
public static final int STATUS_INVALID_CONNECTION = 0x01;
/** Indicates that the current security state is insufficient for the operation. */
public static final int STATUS_INSUFFICIENT_SECURITY = 0x05;
/** Indicates that the local GATT database is out of sync with the remote device's database. */
public static final int STATUS_DATABASE_OUT_OF_SYNC = 0x12;
/** Indicates an error occurred in the Hardware Abstraction Layer. */
public static final int STATUS_HAL_ERROR = 0x81;
/** Indicates that the Bluetooth service is unavailable. */
public static final int STATUS_SERVICE_UNAVAILABLE = 0x82;
/** Indicates that the characteristic is currently in use. */
public static final int STATUS_CHARACTERISTIC_BUSY = 0x84;
/** Indicates that the operation timed out before a response was received. */
public static final int STATUS_REQUEST_TIMEOUT = 0x85;
/** Indicates that one or more parameters provided for the operation were illegal or invalid. */
public static final int STATUS_ILLEGAL_PARAMETER = 0x87;
/** Indicates that a session using same resource already exists. */
public static final int STATUS_DUPLICATE_SESSION = 0x90;
/** Indicates a generic failure not covered by other more specific status codes. */
public static final int STATUS_FAILURE = 0x101;
private final int mSessionId;
private final BluetoothDevice mDevice;
private final Optional<BluetoothGatt> mBluetoothGatt;
private final Optional<BluetoothGattServer> mBluetoothGattServer;
private final BluetoothGattService mGattService;
private final List<BluetoothGattCharacteristic> mGattCharacteristics;
private final long mEndpointId;
private final long mHubId;
@Hide
public GattOffloadSession(
int sessionId,
@NonNull BluetoothDevice device,
@Nullable BluetoothGatt gattInstance,
@Nullable BluetoothGattServer gattServerInstance,
@NonNull BluetoothGattService service,
@NonNull List<BluetoothGattCharacteristic> characteristics,
long endpointId,
long hubId) {
if (sessionId == OFFLOAD_SESSION_ID_UNKNOWN) {
throw new IllegalArgumentException("Invalid session ID");
}
requireNonNull(device);
requireNonNull(service);
requireNonNull(characteristics);
if (gattInstance == null && gattServerInstance == null) {
throw new IllegalArgumentException("Both client and server are null");
}
if (gattInstance != null && gattServerInstance != null) {
throw new IllegalArgumentException("Both client and server are configured");
}
mSessionId = sessionId;
mDevice = device;
mBluetoothGatt = Optional.ofNullable(gattInstance);
mBluetoothGattServer = Optional.ofNullable(gattServerInstance);
mGattService = service;
mGattCharacteristics = List.copyOf(characteristics);
mEndpointId = endpointId;
mHubId = hubId;
}
/**
* Returns the offload session ID assigned from host stack.
*
* @return The offloaded session ID
*/
@Hide
@SystemApi
@RequiresNoPermission
public int getSessionId() {
return mSessionId;
}
/**
* Returns the GATT service associated with this offload session.
*
* @return The {@link BluetoothGattService} instance for this session
*/
@Hide
@SystemApi
@RequiresNoPermission
public @NonNull BluetoothGattService getGattService() {
return mGattService;
}
/**
* Returns the list of GATT characteristics included in this offload session.
*
* @return A list of the {@link BluetoothGattCharacteristic}s in this session.
*/
@Hide
@SystemApi
@RequiresNoPermission
public @NonNull List<BluetoothGattCharacteristic> getGattCharacteristics() {
return mGattCharacteristics;
}
/**
* Returns the ID of the endpoint within the hub associated with this offload session.
*
* @return The ID of the endpoint
*/
@Hide
@SystemApi
@RequiresNoPermission
public long getEndpointId() {
return mEndpointId;
}
/**
* Returns the ID of the hub associated with this offload session.
*
* @return The ID of the hub
*/
@Hide
@SystemApi
@RequiresNoPermission
public long getHubId() {
return mHubId;
}
/**
* Closes this GATT offload session and unloads all associated characteristics.
*
* <p>This method allows the application to revoke the delegation of handling specific
* characteristics previously offloaded via {@link BluetoothGatt#offloadCharacteristics} or
* {@link BluetoothGattServer#offloadCharacteristics}. Upon successful execution, the handling
* of the characteristics reverts from the endpoint back to the host stack.
*
* <p>This session must be an active offload session that was successfully created by a prior
* call to @link BluetoothGatt#offloadCharacteristics} or {@link
* BluetoothGattServer#offloadCharacteristics}.
*
* <p>Note that calling this method only stops the offloading of characteristics and does not
* impact the GATT connection itself. The GATT connection remains active unless explicitly
* disconnected.
*
* <p>The operation is considered complete when {@link
* BluetoothGattCallback#onCharacteristicsUnoffloaded} or {@link
* BluetoothGattServerCallback#onCharacteristicsUnoffloaded} is invoked.
*
* @throws SecurityException if the caller does not have the necessary permissions.
* @throws IllegalArgumentException if session is not valid
*/
@Hide
@Override
@SystemApi
@RequiresBluetoothConnectPermission
@RequiresPermission(allOf = {BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED})
public void close() {
if (getSessionId() == OFFLOAD_SESSION_ID_UNKNOWN) {
Log.w(TAG, "Invalid session to close");
return;
}
mBluetoothGatt.ifPresent(gatt -> gatt.unoffloadCharacteristics(this));
mBluetoothGattServer.ifPresent(
gattServer -> gattServer.unoffloadCharacteristics(mDevice, this));
}
@Override
public String toString() {
return "GattOffloadSession{sessionId= "
+ getSessionId()
+ ", device= "
+ mDevice
+ ", gattService= "
+ mGattService
+ ", gattCharacteristics= "
+ mGattCharacteristics
+ ", endpointId= "
+ mEndpointId
+ ", hubId= "
+ mHubId
+ ", identityHashCode= "
+ Integer.toHexString(System.identityHashCode(this))
+ "}";
}
@Hide
public static final class InnerParcel implements Parcelable {
private final int mSessionId;
private final @Status int mStatus;
public InnerParcel(int sessionId, @Status int status) {
mSessionId = sessionId;
mStatus = status;
}
@RequiresNoPermission
/* package */ int getSessionId() {
return mSessionId;
}
@RequiresNoPermission
/* package */ int getStatus() {
return mStatus;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel out, int flags) {
out.writeInt(mSessionId);
out.writeInt(mStatus);
}
public static final @NonNull Parcelable.Creator<InnerParcel> CREATOR =
new Creator<InnerParcel>() {
@Override
public InnerParcel createFromParcel(Parcel in) {
return new InnerParcel(in.readInt(), in.readInt());
}
@Override
public InnerParcel[] newArray(int size) {
return new InnerParcel[size];
}
};
}
}