Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 80305d7a authored by Treehugger Robot's avatar Treehugger Robot Committed by Gerrit Code Review
Browse files

Merge "Move some AdvertiseManager methods into separate helper"

parents 873ee5e5 b96ee406
Loading
Loading
Loading
Loading
+230 −0
Original line number Diff line number Diff line
/*
 * 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 com.android.bluetooth.gatt;

import android.bluetooth.BluetoothUuid;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.os.ParcelUuid;
import android.util.Log;
import com.android.bluetooth.Utils;
import java.io.ByteArrayOutputStream;

class AdvertiseHelper {

  private static final String TAG = "AdvertiseHelper";

  // Advertise interval for different modes.
  private static final int ADVERTISING_INTERVAL_HIGH_MILLS = 1000;
  private static final int ADVERTISING_INTERVAL_MEDIUM_MILLS = 250;
  private static final int ADVERTISING_INTERVAL_LOW_MILLS = 100;

  private static final int ADVERTISING_TX_POWER_MIN = 0;
  private static final int ADVERTISING_TX_POWER_LOW = 1;
  private static final int ADVERTISING_TX_POWER_MID = 2;
  private static final int ADVERTISING_TX_POWER_UPPER = 3;
  // Note this is not exposed to the Java API.
  private static final int ADVERTISING_TX_POWER_MAX = 4;

  // Note we don't expose connectable directed advertising to API.
  private static final int ADVERTISING_EVENT_TYPE_CONNECTABLE = 0;
  private static final int ADVERTISING_EVENT_TYPE_SCANNABLE = 2;
  private static final int ADVERTISING_EVENT_TYPE_NON_CONNECTABLE = 3;

  private static final int DEVICE_NAME_MAX = 18;

  private static final int COMPLETE_LIST_16_BIT_SERVICE_UUIDS = 0X03;
  private static final int COMPLETE_LIST_32_BIT_SERVICE_UUIDS = 0X05;
  private static final int COMPLETE_LIST_128_BIT_SERVICE_UUIDS = 0X07;
  private static final int SHORTENED_LOCAL_NAME = 0X08;
  private static final int COMPLETE_LOCAL_NAME = 0X09;
  private static final int TX_POWER_LEVEL = 0x0A;
  private static final int SERVICE_DATA_16_BIT_UUID = 0X16;
  private static final int SERVICE_DATA_32_BIT_UUID = 0X20;
  private static final int SERVICE_DATA_128_BIT_UUID = 0X21;
  private static final int MANUFACTURER_SPECIFIC_DATA = 0XFF;

  public static byte[] advertiseDataToBytes(AdvertiseData data, String name) {

    if (data == null) return new byte[0];

    // Flags are added by lower layers of the stack, only if needed;
    // no need to add them here.

    ByteArrayOutputStream ret = new ByteArrayOutputStream();

    if (data.getIncludeDeviceName()) {
      try {
        byte[] nameBytes = name.getBytes("UTF-8");

        int nameLength = nameBytes.length;
        byte type;

        // TODO(jpawlowski) put a better limit on device name!
        if (nameLength > DEVICE_NAME_MAX) {
          nameLength = DEVICE_NAME_MAX;
          type = SHORTENED_LOCAL_NAME;
        } else {
          type = COMPLETE_LOCAL_NAME;
        }

        ret.write(nameLength + 1);
        ret.write(type);
        ret.write(nameBytes, 0, nameLength);
      } catch (java.io.UnsupportedEncodingException e) {
        Log.e(TAG, "Can't include name - encoding error!", e);
      }
    }

    for (int i = 0; i < data.getManufacturerSpecificData().size(); i++) {
      int manufacturerId = data.getManufacturerSpecificData().keyAt(i);

      byte[] manufacturerData = data.getManufacturerSpecificData().get(manufacturerId);
      int dataLen = 2 + (manufacturerData == null ? 0 : manufacturerData.length);
      byte[] concated = new byte[dataLen];
      // First two bytes are manufacturer id in little-endian.
      concated[0] = (byte) (manufacturerId & 0xFF);
      concated[1] = (byte) ((manufacturerId >> 8) & 0xFF);
      if (manufacturerData != null) {
        System.arraycopy(manufacturerData, 0, concated, 2, manufacturerData.length);
      }

      ret.write(concated.length + 1);
      ret.write(MANUFACTURER_SPECIFIC_DATA);
      ret.write(concated, 0, concated.length);
    }

    if (data.getIncludeTxPowerLevel()) {
      ret.write(2 /* Length */);
      ret.write(TX_POWER_LEVEL);
      ret.write(0); // lower layers will fill this value.
    }

    if (data.getServiceUuids() != null) {
      ByteArrayOutputStream serviceUuids16 = new ByteArrayOutputStream();
      ByteArrayOutputStream serviceUuids32 = new ByteArrayOutputStream();
      ByteArrayOutputStream serviceUuids128 = new ByteArrayOutputStream();

      for (ParcelUuid parcelUuid : data.getServiceUuids()) {
        byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid);

        if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) {
          serviceUuids16.write(uuid, 0, uuid.length);
        } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) {
          serviceUuids32.write(uuid, 0, uuid.length);
        } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ {
          serviceUuids128.write(uuid, 0, uuid.length);
        }
      }

      if (serviceUuids16.size() != 0) {
        ret.write(serviceUuids16.size() + 1);
        ret.write(COMPLETE_LIST_16_BIT_SERVICE_UUIDS);
        ret.write(serviceUuids16.toByteArray(), 0, serviceUuids16.size());
      }

      if (serviceUuids32.size() != 0) {
        ret.write(serviceUuids32.size() + 1);
        ret.write(COMPLETE_LIST_32_BIT_SERVICE_UUIDS);
        ret.write(serviceUuids32.toByteArray(), 0, serviceUuids32.size());
      }

      if (serviceUuids128.size() != 0) {
        ret.write(serviceUuids128.size() + 1);
        ret.write(COMPLETE_LIST_128_BIT_SERVICE_UUIDS);
        ret.write(serviceUuids128.toByteArray(), 0, serviceUuids128.size());
      }
    }

    if (!data.getServiceData().isEmpty()) {
      for (ParcelUuid parcelUuid : data.getServiceData().keySet()) {
        byte[] serviceData = data.getServiceData().get(parcelUuid);

        byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid);
        int uuidLen = uuid.length;

        int dataLen = uuidLen + (serviceData == null ? 0 : serviceData.length);
        byte[] concated = new byte[dataLen];

        System.arraycopy(uuid, 0, concated, 0, uuidLen);

        if (serviceData != null) {
          System.arraycopy(serviceData, 0, concated, uuidLen, serviceData.length);
        }

        if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) {
          ret.write(concated.length + 1);
          ret.write(SERVICE_DATA_16_BIT_UUID);
          ret.write(concated, 0, concated.length);
        } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) {
          ret.write(concated.length + 1);
          ret.write(SERVICE_DATA_32_BIT_UUID);
          ret.write(concated, 0, concated.length);
        } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ {
          ret.write(concated.length + 1);
          ret.write(SERVICE_DATA_128_BIT_UUID);
          ret.write(concated, 0, concated.length);
        }
      }
    }

    return ret.toByteArray();
  }

  // Convert settings tx power level to stack tx power level.
  public static int getTxPowerLevel(AdvertiseSettings settings) {
    switch (settings.getTxPowerLevel()) {
      case AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW:
        return ADVERTISING_TX_POWER_MIN;
      case AdvertiseSettings.ADVERTISE_TX_POWER_LOW:
        return ADVERTISING_TX_POWER_LOW;
      case AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM:
        return ADVERTISING_TX_POWER_MID;
      case AdvertiseSettings.ADVERTISE_TX_POWER_HIGH:
        return ADVERTISING_TX_POWER_UPPER;
      default:
        // Shouldn't happen, just in case.
        return ADVERTISING_TX_POWER_MID;
    }
  }

  // Convert advertising event type to stack values.
  public static int getAdvertisingEventType(AdvertiseClient client) {
    AdvertiseSettings settings = client.settings;
    if (settings.isConnectable()) {
      return ADVERTISING_EVENT_TYPE_CONNECTABLE;
    }
    return client.scanResponse == null
        ? ADVERTISING_EVENT_TYPE_NON_CONNECTABLE
        : ADVERTISING_EVENT_TYPE_SCANNABLE;
  }

  // Convert advertising milliseconds to advertising units(one unit is 0.625 millisecond).
  public static long getAdvertisingIntervalUnit(AdvertiseSettings settings) {
    switch (settings.getMode()) {
      case AdvertiseSettings.ADVERTISE_MODE_LOW_POWER:
        return Utils.millsToUnit(ADVERTISING_INTERVAL_HIGH_MILLS);
      case AdvertiseSettings.ADVERTISE_MODE_BALANCED:
        return Utils.millsToUnit(ADVERTISING_INTERVAL_MEDIUM_MILLS);
      case AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY:
        return Utils.millsToUnit(ADVERTISING_INTERVAL_LOW_MILLS);
      default:
        // Shouldn't happen, just in case.
        return Utils.millsToUnit(ADVERTISING_INTERVAL_HIGH_MILLS);
    }
  }
}
+8 −215
Original line number Diff line number Diff line
@@ -16,24 +16,16 @@

package com.android.bluetooth.gatt;

import android.bluetooth.BluetoothUuid;
import android.bluetooth.le.AdvertiseCallback;
import android.bluetooth.le.AdvertiseData;
import android.bluetooth.le.AdvertiseSettings;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.util.Log;

import com.android.bluetooth.Utils;
import com.android.bluetooth.btservice.AdapterService;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.io.ByteArrayOutputStream;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
@@ -247,10 +239,6 @@ class AdvertiseManager {

    // Class that wraps advertise native related constants, methods etc.
    private class AdvertiseNative {
        // Advertise interval for different modes.
        private static final int ADVERTISING_INTERVAL_HIGH_MILLS = 1000;
        private static final int ADVERTISING_INTERVAL_MEDIUM_MILLS = 250;
        private static final int ADVERTISING_INTERVAL_LOW_MILLS = 100;

        // Add some randomness to the advertising min/max interval so the controller can do some
        // optimization.
@@ -263,29 +251,19 @@ class AdvertiseManager {
        private static final int ADVERTISING_CHANNEL_ALL =
            ADVERTISING_CHANNEL_37 | ADVERTISING_CHANNEL_38 | ADVERTISING_CHANNEL_39;

        private static final int ADVERTISING_TX_POWER_MIN = 0;
        private static final int ADVERTISING_TX_POWER_LOW = 1;
        private static final int ADVERTISING_TX_POWER_MID = 2;
        private static final int ADVERTISING_TX_POWER_UPPER = 3;
        // Note this is not exposed to the Java API.
        private static final int ADVERTISING_TX_POWER_MAX = 4;

        // Note we don't expose connectable directed advertising to API.
        private static final int ADVERTISING_EVENT_TYPE_CONNECTABLE = 0;
        private static final int ADVERTISING_EVENT_TYPE_SCANNABLE = 2;
        private static final int ADVERTISING_EVENT_TYPE_NON_CONNECTABLE = 3;

        boolean startAdverising(AdvertiseClient client) {
            logd("starting advertising");

            int advertiserId = client.advertiserId;
            int minAdvertiseUnit = (int) getAdvertisingIntervalUnit(client.settings);
            int minAdvertiseUnit = (int) AdvertiseHelper.getAdvertisingIntervalUnit(client.settings);
            int maxAdvertiseUnit = minAdvertiseUnit + ADVERTISING_INTERVAL_DELTA_UNIT;
            int advertiseEventType = getAdvertisingEventType(client);
            int txPowerLevel = getTxPowerLevel(client.settings);
            int advertiseEventType = AdvertiseHelper.getAdvertisingEventType(client);
            int txPowerLevel = AdvertiseHelper.getTxPowerLevel(client.settings);

            byte [] adv_data = advertiseDataToBytes(client.advertiseData);
            byte [] scan_resp_data = advertiseDataToBytes(client.scanResponse);
            byte [] adv_data = AdvertiseHelper.advertiseDataToBytes(client.advertiseData,
                                                                    mAdapterService.getName());
            byte [] scan_resp_data = AdvertiseHelper.advertiseDataToBytes(client.scanResponse,
                                                                          mAdapterService.getName());

            int advertiseTimeoutSeconds = (int) TimeUnit.MILLISECONDS.toSeconds(
                    client.settings.getTimeout());
@@ -319,191 +297,6 @@ class AdvertiseManager {
            }
        }

        private static final int DEVICE_NAME_MAX = 18;

        private static final int COMPLETE_LIST_16_BIT_SERVICE_UUIDS = 0X03;
        private static final int COMPLETE_LIST_32_BIT_SERVICE_UUIDS = 0X05;
        private static final int COMPLETE_LIST_128_BIT_SERVICE_UUIDS = 0X07;
        private static final int SHORTENED_LOCAL_NAME = 0X08;
        private static final int COMPLETE_LOCAL_NAME = 0X09;
        private static final int TX_POWER_LEVEL = 0x0A;
        private static final int SERVICE_DATA_16_BIT_UUID = 0X16;
        private static final int SERVICE_DATA_32_BIT_UUID = 0X20;
        private static final int SERVICE_DATA_128_BIT_UUID = 0X21;
        private static final int MANUFACTURER_SPECIFIC_DATA = 0XFF;

        private byte[] advertiseDataToBytes(AdvertiseData data) {

            if (data == null)
                return new byte[0];

            // Flags are added by lower layers of the stack, only if needed;
            // no need to add them here.

            ByteArrayOutputStream ret = new ByteArrayOutputStream();

            if (data.getIncludeDeviceName()) {
                String name = mAdapterService.getName();
                try {
                    byte[] nameBytes = name.getBytes("UTF-8");

                    int nameLength = nameBytes.length;
                    byte type;

                    // TODO(jpawlowski) put a better limit on device name!
                    if (nameLength > DEVICE_NAME_MAX) {
                      nameLength = DEVICE_NAME_MAX;
                      type = SHORTENED_LOCAL_NAME;
                    } else {
                      type = COMPLETE_LOCAL_NAME;
                    }

                    ret.write(nameLength + 1);
                    ret.write(type);
                    ret.write(nameBytes, 0, nameLength);
                } catch (java.io.UnsupportedEncodingException e) {
                    loge("Can't include name - encoding error!", e);
                }
            }

            for (int i = 0; i< data.getManufacturerSpecificData().size(); i++) {
                int manufacturerId = data.getManufacturerSpecificData().keyAt(i);

                byte[] manufacturerData = data.getManufacturerSpecificData().get(
                        manufacturerId);
                int dataLen = 2 + (manufacturerData == null ? 0 : manufacturerData.length);
                byte[] concated = new byte[dataLen];
                // First two bytes are manufacturer id in little-endian.
                concated[0] = (byte) (manufacturerId & 0xFF);
                concated[1] = (byte) ((manufacturerId >> 8) & 0xFF);
                if (manufacturerData != null) {
                    System.arraycopy(manufacturerData, 0, concated, 2, manufacturerData.length);
                }

                ret.write(concated.length + 1);
                ret.write(MANUFACTURER_SPECIFIC_DATA);
                ret.write(concated, 0, concated.length);
            }

            if (data.getIncludeTxPowerLevel()) {
                ret.write(2 /* Length */);
                ret.write(TX_POWER_LEVEL);
                ret.write(0);  // lower layers will fill this value.
            }

            if (data.getServiceUuids() != null) {
                ByteArrayOutputStream serviceUuids16 = new ByteArrayOutputStream();
                ByteArrayOutputStream serviceUuids32 = new ByteArrayOutputStream();
                ByteArrayOutputStream serviceUuids128 = new ByteArrayOutputStream();

                for (ParcelUuid parcelUuid : data.getServiceUuids()) {
                    byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid);

                    if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) {
                        serviceUuids16.write(uuid, 0, uuid.length);
                    } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) {
                        serviceUuids32.write(uuid, 0, uuid.length);
                    } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ {
                        serviceUuids128.write(uuid, 0, uuid.length);
                    }
                }

                if (serviceUuids16.size() != 0) {
                    ret.write(serviceUuids16.size() + 1);
                    ret.write(COMPLETE_LIST_16_BIT_SERVICE_UUIDS);
                    ret.write(serviceUuids16.toByteArray(), 0, serviceUuids16.size());
                }

                if (serviceUuids32.size() != 0) {
                    ret.write(serviceUuids32.size() + 1);
                    ret.write(COMPLETE_LIST_32_BIT_SERVICE_UUIDS);
                    ret.write(serviceUuids32.toByteArray(), 0, serviceUuids32.size());
                }

                if (serviceUuids128.size() != 0) {
                    ret.write(serviceUuids128.size() + 1);
                    ret.write(COMPLETE_LIST_128_BIT_SERVICE_UUIDS);
                    ret.write(serviceUuids128.toByteArray(), 0, serviceUuids128.size());
                }
            }

            if (!data.getServiceData().isEmpty()) {
                for (ParcelUuid parcelUuid: data.getServiceData().keySet()) {
                    byte[] serviceData = data.getServiceData().get(parcelUuid);

                    byte[] uuid = BluetoothUuid.uuidToBytes(parcelUuid);
                    int uuidLen = uuid.length;

                    int dataLen = uuidLen + (serviceData == null ? 0 : serviceData.length);
                    byte[] concated = new byte[dataLen];

                    System.arraycopy(uuid, 0, concated, 0, uuidLen);

                    if (serviceData != null) {
                        System.arraycopy(serviceData, 0, concated, uuidLen, serviceData.length);
                    }

                    if (uuid.length == BluetoothUuid.UUID_BYTES_16_BIT) {
                        ret.write(concated.length + 1);
                        ret.write(SERVICE_DATA_16_BIT_UUID);
                        ret.write(concated, 0, concated.length);
                    } else if (uuid.length == BluetoothUuid.UUID_BYTES_32_BIT) {
                        ret.write(concated.length + 1);
                        ret.write(SERVICE_DATA_32_BIT_UUID);
                        ret.write(concated, 0, concated.length);
                    } else /*if (uuid.length == BluetoothUuid.UUID_BYTES_128_BIT)*/ {
                        ret.write(concated.length + 1);
                        ret.write(SERVICE_DATA_128_BIT_UUID);
                        ret.write(concated, 0, concated.length);
                    }
                }
            }

            return ret.toByteArray();
        }

        // Convert settings tx power level to stack tx power level.
        private int getTxPowerLevel(AdvertiseSettings settings) {
            switch (settings.getTxPowerLevel()) {
                case AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW:
                    return ADVERTISING_TX_POWER_MIN;
                case AdvertiseSettings.ADVERTISE_TX_POWER_LOW:
                    return ADVERTISING_TX_POWER_LOW;
                case AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM:
                    return ADVERTISING_TX_POWER_MID;
                case AdvertiseSettings.ADVERTISE_TX_POWER_HIGH:
                    return ADVERTISING_TX_POWER_UPPER;
                default:
                    // Shouldn't happen, just in case.
                    return ADVERTISING_TX_POWER_MID;
            }
        }

        // Convert advertising event type to stack values.
        private int getAdvertisingEventType(AdvertiseClient client) {
            AdvertiseSettings settings = client.settings;
            if (settings.isConnectable()) {
                return ADVERTISING_EVENT_TYPE_CONNECTABLE;
            }
            return client.scanResponse == null ? ADVERTISING_EVENT_TYPE_NON_CONNECTABLE
                    : ADVERTISING_EVENT_TYPE_SCANNABLE;
        }

        // Convert advertising milliseconds to advertising units(one unit is 0.625 millisecond).
        private long getAdvertisingIntervalUnit(AdvertiseSettings settings) {
            switch (settings.getMode()) {
                case AdvertiseSettings.ADVERTISE_MODE_LOW_POWER:
                    return Utils.millsToUnit(ADVERTISING_INTERVAL_HIGH_MILLS);
                case AdvertiseSettings.ADVERTISE_MODE_BALANCED:
                    return Utils.millsToUnit(ADVERTISING_INTERVAL_MEDIUM_MILLS);
                case AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY:
                    return Utils.millsToUnit(ADVERTISING_INTERVAL_LOW_MILLS);
                default:
                    // Shouldn't happen, just in case.
                    return Utils.millsToUnit(ADVERTISING_INTERVAL_HIGH_MILLS);
            }
        }

        // Native functions

        private native void registerAdvertiserNative(long app_uuid_lsb,