1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.bluetooth.gatt; 18 19import android.bluetooth.BluetoothUuid; 20import android.bluetooth.le.AdvertiseData; 21import android.os.ParcelUuid; 22import android.util.Log; 23import com.android.bluetooth.Utils; 24import java.io.ByteArrayOutputStream; 25 26class AdvertiseHelper { 27 28 private static final String TAG = "AdvertiseHelper"; 29 30 private static final int DEVICE_NAME_MAX = 26; 31 32 private static final int COMPLETE_LIST_16_BIT_SERVICE_UUIDS = 0X03; 33 private static final int COMPLETE_LIST_32_BIT_SERVICE_UUIDS = 0X05; 34 private static final int COMPLETE_LIST_128_BIT_SERVICE_UUIDS = 0X07; 35 private static final int SHORTENED_LOCAL_NAME = 0X08; 36 private static final int COMPLETE_LOCAL_NAME = 0X09; 37 private static final int TX_POWER_LEVEL = 0x0A; 38 private static final int SERVICE_DATA_16_BIT_UUID = 0X16; 39 private static final int SERVICE_DATA_32_BIT_UUID = 0X20; 40 private static final int SERVICE_DATA_128_BIT_UUID = 0X21; 41 private static final int MANUFACTURER_SPECIFIC_DATA = 0XFF; 42 43 public static byte[] advertiseDataToBytes(AdvertiseData data, String name) { 44 45 if (data == null) return new byte[0]; 46 47 // Flags are added by lower layers of the stack, only if needed; 48 // no need to add them here. 49 50 ByteArrayOutputStream ret = new ByteArrayOutputStream(); 51 52 if (data.getIncludeDeviceName()) { 53 try { 54 byte[] nameBytes = name.getBytes("UTF-8"); 55 56 int nameLength = nameBytes.length; 57 byte type; 58 59 // TODO(jpawlowski) put a better limit on device name! 60 if (nameLength > DEVICE_NAME_MAX) { 61 nameLength = DEVICE_NAME_MAX; 62 type = SHORTENED_LOCAL_NAME; 63 } else { 64 type = COMPLETE_LOCAL_NAME; 65 } 66 67 ret.write(nameLength + 1); 68 ret.write(type); 69 ret.write(nameBytes, 0, nameLength); 70 } catch (java.io.UnsupportedEncodingException e) { 71 Log.e(TAG, "Can't include name - encoding error!", e); 72 } 73 } 74 75 for (int i = 0; i < data.getManufacturerSpecificData().size(); i++) { 76 int manufacturerId = data.getManufacturerSpecificData().keyAt(i); 77 78 byte[] manufacturerData = data.getManufacturerSpecificData().get(manufacturerId); 79 int dataLen = 2 + (manufacturerData == null ? 0 : manufacturerData.length); 80 byte[] concated = new byte[dataLen]; 81 // First two bytes are manufacturer id in little-endian. 82 concated[0] = (byte) (manufacturerId & 0xFF); 83 concated[1] = (byte) ((manufacturerId >> 8) & 0xFF); 84 if (manufacturerData != null) { 85 System.arraycopy(manufacturerData, 0, concated, 2, manufacturerData.length); 86 } 87 88 ret.write(concated.length + 1); 89 ret.write(MANUFACTURER_SPECIFIC_DATA); 90 ret.write(concated, 0, concated.length); 91 } 92 93 if (data.getIncludeTxPowerLevel()) { 94 ret.write(2 /* Length */); 95 ret.write(TX_POWER_LEVEL); 96 ret.write(0); // lower layers will fill this value. 97 } 98 99 if (data.getServiceUuids() != null) { 100 ByteArrayOutputStream serviceUuids16 = new ByteArrayOutputStream(); 101 ByteArrayOutputStream serviceUuids32 = new ByteArrayOutputStream(); 102 ByteArrayOutputStream serviceUuids128 = new ByteArrayOutputStream(); 103 104 for (ParcelUuid parcelUuid : data.getServiceUuids()) { 105 byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid); 106 107 if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) { 108 serviceUuids16.write(uuid, 0, uuid.length); 109 } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) { 110 serviceUuids32.write(uuid, 0, uuid.length); 111 } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ { 112 serviceUuids128.write(uuid, 0, uuid.length); 113 } 114 } 115 116 if (serviceUuids16.size() != 0) { 117 ret.write(serviceUuids16.size() + 1); 118 ret.write(COMPLETE_LIST_16_BIT_SERVICE_UUIDS); 119 ret.write(serviceUuids16.toByteArray(), 0, serviceUuids16.size()); 120 } 121 122 if (serviceUuids32.size() != 0) { 123 ret.write(serviceUuids32.size() + 1); 124 ret.write(COMPLETE_LIST_32_BIT_SERVICE_UUIDS); 125 ret.write(serviceUuids32.toByteArray(), 0, serviceUuids32.size()); 126 } 127 128 if (serviceUuids128.size() != 0) { 129 ret.write(serviceUuids128.size() + 1); 130 ret.write(COMPLETE_LIST_128_BIT_SERVICE_UUIDS); 131 ret.write(serviceUuids128.toByteArray(), 0, serviceUuids128.size()); 132 } 133 } 134 135 if (!data.getServiceData().isEmpty()) { 136 for (ParcelUuid parcelUuid : data.getServiceData().keySet()) { 137 byte[] serviceData = data.getServiceData().get(parcelUuid); 138 139 byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid); 140 int uuidLen = uuid.length; 141 142 int dataLen = uuidLen + (serviceData == null ? 0 : serviceData.length); 143 byte[] concated = new byte[dataLen]; 144 145 System.arraycopy(uuid, 0, concated, 0, uuidLen); 146 147 if (serviceData != null) { 148 System.arraycopy(serviceData, 0, concated, uuidLen, serviceData.length); 149 } 150 151 if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) { 152 ret.write(concated.length + 1); 153 ret.write(SERVICE_DATA_16_BIT_UUID); 154 ret.write(concated, 0, concated.length); 155 } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) { 156 ret.write(concated.length + 1); 157 ret.write(SERVICE_DATA_32_BIT_UUID); 158 ret.write(concated, 0, concated.length); 159 } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ { 160 ret.write(concated.length + 1); 161 ret.write(SERVICE_DATA_128_BIT_UUID); 162 ret.write(concated, 0, concated.length); 163 } 164 } 165 } 166 167 return ret.toByteArray(); 168 } 169} 170