/* * 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 * *

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 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(); 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 CREATOR = new Parcelable.Creator() { 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(); ArrayList 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. * *

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. * *

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

Setting the write type of a characteristic determines how the * {@link BluetoothGatt#writeCharacteristic} function write this * characteristic. * * @param writeType The write type to for this characteristic. Can be one of: {@link * #WRITE_TYPE_DEFAULT}, {@link #WRITE_TYPE_NO_RESPONSE} or {@link #WRITE_TYPE_SIGNED}. */ public void setWriteType(int writeType) { mWriteType = writeType; } /** * Set the desired key size. * * @hide */ @UnsupportedAppUsage public void setKeySize(int keySize) { mKeySize = keySize; } /** * Returns a list of descriptors for this characteristic. * * @return Descriptors for this characteristic */ public List 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. * *

This function returns the stored value for this characteristic as * retrieved by calling {@link BluetoothGatt#readCharacteristic}. The cached * value of the characteristic is updated as a result of a read characteristic * operation or if a characteristic update notification has been received. * * @return Cached value of the characteristic */ public byte[] getValue() { return mValue; } /** * Return the stored value of this characteristic. * *

The formatType parameter determines how the characteristic value * is to be interpreted. For example, settting formatType to * {@link #FORMAT_UINT16} specifies that the first two bytes of the * characteristic value at the given offset are interpreted to generate the * return value. * * @param formatType The format type used to interpret the characteristic value. * @param offset Offset at which the integer value can be found. * @return Cached value of the characteristic or null of offset exceeds value size. */ public Integer getIntValue(int formatType, int offset) { if ((offset + getTypeLen(formatType)) > mValue.length) return null; switch (formatType) { case FORMAT_UINT8: return unsignedByteToInt(mValue[offset]); case FORMAT_UINT16: return unsignedBytesToInt(mValue[offset], mValue[offset + 1]); case FORMAT_UINT32: return unsignedBytesToInt(mValue[offset], mValue[offset + 1], mValue[offset + 2], mValue[offset + 3]); case FORMAT_SINT8: return unsignedToSigned(unsignedByteToInt(mValue[offset]), 8); case FORMAT_SINT16: return unsignedToSigned(unsignedBytesToInt(mValue[offset], mValue[offset + 1]), 16); case FORMAT_SINT32: return unsignedToSigned(unsignedBytesToInt(mValue[offset], mValue[offset + 1], mValue[offset + 2], mValue[offset + 3]), 32); } return null; } /** * Return the stored value of this characteristic. *

See {@link #getValue} for details. * * @param formatType The format type used to interpret the characteristic value. * @param offset Offset at which the float value can be found. * @return Cached value of the characteristic at a given offset or null if the requested offset * exceeds the value size. */ public Float getFloatValue(int formatType, int offset) { if ((offset + getTypeLen(formatType)) > mValue.length) return null; switch (formatType) { case FORMAT_SFLOAT: return bytesToFloat(mValue[offset], mValue[offset + 1]); case FORMAT_FLOAT: return bytesToFloat(mValue[offset], mValue[offset + 1], mValue[offset + 2], mValue[offset + 3]); } return null; } /** * Return the stored value of this characteristic. *

See {@link #getValue} for details. * * @param offset Offset at which the string value can be found. * @return Cached value of the characteristic */ public String getStringValue(int offset) { if (mValue == null || offset > mValue.length) return null; byte[] strBytes = new byte[mValue.length - offset]; for (int i = 0; i != (mValue.length - offset); ++i) strBytes[i] = mValue[offset + i]; return new String(strBytes); } /** * Updates the locally stored value of this characteristic. * *

This function modifies the locally stored cached value of this * characteristic. To send the value to the remote device, call * {@link BluetoothGatt#writeCharacteristic} to send the value to the * remote device. * * @param value New value for this characteristic * @return true if the locally stored value has been set, false if the requested value could not * be stored locally. */ public boolean setValue(byte[] value) { mValue = value; return true; } /** * Set the locally stored value of this characteristic. *

See {@link #setValue(byte[])} for details. * * @param value New value for this characteristic * @param formatType Integer format type used to transform the value parameter * @param offset Offset at which the value should be placed * @return true if the locally stored value has been set */ public boolean setValue(int value, int formatType, int offset) { int len = offset + getTypeLen(formatType); if (mValue == null) mValue = new byte[len]; if (len > mValue.length) return false; switch (formatType) { case FORMAT_SINT8: value = intToSignedBits(value, 8); // Fall-through intended case FORMAT_UINT8: mValue[offset] = (byte) (value & 0xFF); break; case FORMAT_SINT16: value = intToSignedBits(value, 16); // Fall-through intended case FORMAT_UINT16: mValue[offset++] = (byte) (value & 0xFF); mValue[offset] = (byte) ((value >> 8) & 0xFF); break; case FORMAT_SINT32: value = intToSignedBits(value, 32); // Fall-through intended case FORMAT_UINT32: mValue[offset++] = (byte) (value & 0xFF); mValue[offset++] = (byte) ((value >> 8) & 0xFF); mValue[offset++] = (byte) ((value >> 16) & 0xFF); mValue[offset] = (byte) ((value >> 24) & 0xFF); break; default: return false; } return true; } /** * Set the locally stored value of this characteristic. *

See {@link #setValue(byte[])} for details. * * @param mantissa Mantissa for this characteristic * @param exponent exponent value for this characteristic * @param formatType Float format type used to transform the value parameter * @param offset Offset at which the value should be placed * @return true if the locally stored value has been set */ public boolean setValue(int mantissa, int exponent, int formatType, int offset) { int len = offset + getTypeLen(formatType); if (mValue == null) mValue = new byte[len]; if (len > mValue.length) return false; switch (formatType) { case FORMAT_SFLOAT: mantissa = intToSignedBits(mantissa, 12); exponent = intToSignedBits(exponent, 4); mValue[offset++] = (byte) (mantissa & 0xFF); mValue[offset] = (byte) ((mantissa >> 8) & 0x0F); mValue[offset] += (byte) ((exponent & 0x0F) << 4); break; case FORMAT_FLOAT: mantissa = intToSignedBits(mantissa, 24); exponent = intToSignedBits(exponent, 8); mValue[offset++] = (byte) (mantissa & 0xFF); mValue[offset++] = (byte) ((mantissa >> 8) & 0xFF); mValue[offset++] = (byte) ((mantissa >> 16) & 0xFF); mValue[offset] += (byte) (exponent & 0xFF); break; default: return false; } return true; } /** * Set the locally stored value of this characteristic. *

See {@link #setValue(byte[])} for details. * * @param value New value for this characteristic * @return true if the locally stored value has been set */ public boolean setValue(String value) { mValue = value.getBytes(); return true; } /** * Returns the size of a give value type. */ private int getTypeLen(int formatType) { return formatType & 0xF; } /** * Convert a signed byte to an unsigned int. */ private int unsignedByteToInt(byte b) { return b & 0xFF; } /** * Convert signed bytes to a 16-bit unsigned int. */ private int unsignedBytesToInt(byte b0, byte b1) { return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8)); } /** * Convert signed bytes to a 32-bit unsigned int. */ private int unsignedBytesToInt(byte b0, byte b1, byte b2, byte b3) { return (unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8)) + (unsignedByteToInt(b2) << 16) + (unsignedByteToInt(b3) << 24); } /** * Convert signed bytes to a 16-bit short float value. */ private float bytesToFloat(byte b0, byte b1) { int mantissa = unsignedToSigned(unsignedByteToInt(b0) + ((unsignedByteToInt(b1) & 0x0F) << 8), 12); int exponent = unsignedToSigned(unsignedByteToInt(b1) >> 4, 4); return (float) (mantissa * Math.pow(10, exponent)); } /** * Convert signed bytes to a 32-bit short float value. */ private float bytesToFloat(byte b0, byte b1, byte b2, byte b3) { int mantissa = unsignedToSigned(unsignedByteToInt(b0) + (unsignedByteToInt(b1) << 8) + (unsignedByteToInt(b2) << 16), 24); return (float) (mantissa * Math.pow(10, b3)); } /** * Convert an unsigned integer value to a two's-complement encoded * signed value. */ private int unsignedToSigned(int unsigned, int size) { if ((unsigned & (1 << size - 1)) != 0) { unsigned = -1 * ((1 << size - 1) - (unsigned & ((1 << size - 1) - 1))); } return unsigned; } /** * Convert an integer into the signed bits of a given length. */ private int intToSignedBits(int i, int size) { if (i < 0) { i = (1 << size - 1) + (i & ((1 << size - 1) - 1)); } return i; } }