content: package org.altbeacon.beacon;
  
  import android.annotation.TargetApi;
  import android.bluetooth.BluetoothAdapter;
  import android.bluetooth.BluetoothManager;
  import android.bluetooth.le.AdvertiseCallback;
  import android.bluetooth.le.AdvertiseData;
  import android.bluetooth.le.AdvertiseSettings;
  import android.bluetooth.le.BluetoothLeAdvertiser;
  import android.content.Context;
  import android.content.pm.PackageManager;
  import android.os.ParcelUuid;
  
  import org.altbeacon.beacon.logging.LogManager;
  
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.util.UUID;
  
  /**
   * Provides a mechanism for transmitting as a beacon.   Requires Android 5.0
   */
  @TargetApi(21)
  public class BeaconTransmitter {
      public static final int SUPPORTED = 0;
      public static final int NOT_SUPPORTED_MIN_SDK = 1;
      public static final int NOT_SUPPORTED_BLE = 2;
      // isMultipleAdvertisementSupported returning false no longer indicates that transmission is not
      // possible
      @Deprecated
      public static final int NOT_SUPPORTED_MULTIPLE_ADVERTISEMENTS = 3;
      public static final int NOT_SUPPORTED_CANNOT_GET_ADVERTISER = 4;
      public static final int NOT_SUPPORTED_CANNOT_GET_ADVERTISER_MULTIPLE_ADVERTISEMENTS = 5;
      private static final String TAG = "BeaconTransmitter";
      private BluetoothAdapter mBluetoothAdapter;
      private BluetoothLeAdvertiser mBluetoothLeAdvertiser;
      private int mAdvertiseMode = AdvertiseSettings.ADVERTISE_MODE_LOW_POWER;
      private int mAdvertiseTxPowerLevel = AdvertiseSettings.ADVERTISE_TX_POWER_HIGH;
      private Beacon mBeacon;
      private BeaconParser mBeaconParser;
      private AdvertiseCallback mAdvertisingClientCallback;
      private boolean mStarted;
      private AdvertiseCallback mAdvertiseCallback;
      private boolean mConnectable = false;
  
      /**
       * Creates a new beacon transmitter capable of transmitting beacons with the format
       * specified in the BeaconParser and with the data fields specified in the Beacon object
       * @param context
       * @param parser specifies the format of the beacon transmission
       */
      public BeaconTransmitter(Context context, BeaconParser parser) {
          mBeaconParser = parser;
          BluetoothManager bluetoothManager =
                  (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE);
          if (bluetoothManager != null) {
              mBluetoothAdapter = bluetoothManager.getAdapter();
              mBluetoothLeAdvertiser = mBluetoothAdapter.getBluetoothLeAdvertiser();
              LogManager.d(TAG, "new BeaconTransmitter constructed.  mbluetoothLeAdvertiser is %s",
                      mBluetoothLeAdvertiser);
          }
          else {
              LogManager.e(TAG, "Failed to get BluetoothManager");
          }
      }
  
      /**
       * Tells if transmission has started
       * @return
       */
      public boolean isStarted() {
          return mStarted;
      }
  
      /**
       * Sets the beacon whose fields will be transmitted
       * @param beacon
       */
      public void setBeacon(Beacon beacon) {
          mBeacon = beacon;
      }
  
      /**
       * Sets the beaconParsser used for formatting the transmission
       * @param beaconParser
       */
      public void setBeaconParser(BeaconParser beaconParser) {
          mBeaconParser = beaconParser;
      }
  
      /**
       * @see #setAdvertiseMode(int)
       * @return advertiseMode
       */
      public int getAdvertiseMode() {
          return mAdvertiseMode;
      }
  
      /**
       * AdvertiseSettings.ADVERTISE_MODE_BALANCED 3 Hz
       * AdvertiseSettings.ADVERTISE_MODE_LOW_LATENCY 10 Hz
       * AdvertiseSettings.ADVERTISE_MODE_LOW_POWER 1 Hz
       * @param mAdvertiseMode
       */
      public void setAdvertiseMode(int mAdvertiseMode) {
          this.mAdvertiseMode = mAdvertiseMode;
      }
  
      /**
       * @see #setAdvertiseTxPowerLevel(int mAdvertiseTxPowerLevel)
       * @return txPowerLevel
       */
      public int getAdvertiseTxPowerLevel() {
          return mAdvertiseTxPowerLevel;
      }
  
      /**
       * AdvertiseSettings.ADVERTISE_TX_POWER_HIGH -56 dBm @ 1 meter with Nexus 5
       * AdvertiseSettings.ADVERTISE_TX_POWER_LOW -75 dBm @ 1 meter with Nexus 5
       * AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM -66 dBm @ 1 meter with Nexus 5
       * AdvertiseSettings.ADVERTISE_TX_POWER_ULTRA_LOW not detected with Nexus 5
       * @param mAdvertiseTxPowerLevel
       */
      public void setAdvertiseTxPowerLevel(int mAdvertiseTxPowerLevel) {
          this.mAdvertiseTxPowerLevel = mAdvertiseTxPowerLevel;
      }
  
      /**
       * Whether the advertisement should indicate the device is connectable.
       * @param connectable
       */
      public void setConnectable(boolean connectable) {
          this.mConnectable = connectable;
      }
  
      /**
       * @see #setConnectable(boolean)
       * @return connectable
       */
      public boolean isConnectable() {
          return mConnectable;
      }
  
      /**
       * Starts advertising with fields from the passed beacon
       * @param beacon
       */
      public void startAdvertising(Beacon beacon) {
          startAdvertising(beacon, null);
      }
  
      /**
       * Starts advertising with fields from the passed beacon
       * @param beacon
       */
      public void startAdvertising(Beacon beacon, AdvertiseCallback callback) {
          mBeacon = beacon;
          mAdvertisingClientCallback = callback;
          startAdvertising();
      }
  
      /**
       * Starts this beacon advertising
       */
      public void startAdvertising() {
          if (mBeacon == null) {
              throw new NullPointerException("Beacon cannot be null.  Set beacon before starting advertising");
          }
          int manufacturerCode = mBeacon.getManufacturer();
          int serviceUuid = -1;
          byte[] serviceUuid128Bit = {};
          if (mBeaconParser.getServiceUuid() != null) {
              serviceUuid = mBeaconParser.getServiceUuid().intValue();
          }
          if (mBeaconParser.getServiceUuid128Bit() != null) {
              serviceUuid128Bit = mBeaconParser.getServiceUuid128Bit();
          }
  
          if (mBeaconParser == null) {
              throw new NullPointerException("You must supply a BeaconParser instance to BeaconTransmitter.");
          }
  
          byte[] advertisingBytes = mBeaconParser.getBeaconAdvertisementData(mBeacon);
          String byteString = "";
          for (int i= 0; i < advertisingBytes.length; i++) {
              byteString += String.format("%02X", advertisingBytes[i]);
              byteString += " ";
          }
          LogManager.d(TAG, "Starting advertising with ID1: %s ID2: %s ID3: %s and data: %s of size "
                          + "%s", mBeacon.getId1(),
                          mBeacon.getIdentifiers().size() > 1 ? mBeacon.getId2() : "",
                          mBeacon.getIdentifiers().size() > 2 ? mBeacon.getId3() : "", byteString,
                  advertisingBytes.length);
  
          try{
              AdvertiseData.Builder dataBuilder = new AdvertiseData.Builder();
              if (serviceUuid > 0) {
                  byte[] serviceUuidBytes = new byte[] {
                          (byte) (serviceUuid & 0xff),
                          (byte) ((serviceUuid >> 8) & 0xff)};
                  ParcelUuid parcelUuid = parseUuidFrom(serviceUuidBytes);
                  dataBuilder.addServiceData(parcelUuid, advertisingBytes);
                  if (serviceUuid128Bit == null) {
                      dataBuilder.addServiceUuid(parcelUuid);
                  }
                  dataBuilder.setIncludeTxPowerLevel(false);
                  dataBuilder.setIncludeDeviceName(false);
  
              }
              else if (serviceUuid128Bit != null && serviceUuid128Bit.length == 16) {
                  ParcelUuid parcelUuid = parseUuidFrom(serviceUuid128Bit);
                  dataBuilder.addServiceData(parcelUuid, advertisingBytes);
                  //dataBuilder.addServiceUuid(parcelUuid); // no room for 128 bit service UUIDs and data
                  dataBuilder.setIncludeTxPowerLevel(false);
                  dataBuilder.setIncludeDeviceName(false);
              }
              else if (serviceUuid128Bit != null && serviceUuid128Bit.length == 4) {
                  ParcelUuid parcelUuid = parseUuidFrom(serviceUuid128Bit);
                  dataBuilder.addServiceData(parcelUuid, advertisingBytes);
                  dataBuilder.addServiceUuid(parcelUuid);
                  dataBuilder.setIncludeTxPowerLevel(false);
                  dataBuilder.setIncludeDeviceName(false);
              }
              else {
                  dataBuilder.addManufacturerData(manufacturerCode, advertisingBytes);
              }
  
              AdvertiseSettings.Builder settingsBuilder = new AdvertiseSettings.Builder();
  
              settingsBuilder.setAdvertiseMode(mAdvertiseMode);
              settingsBuilder.setTxPowerLevel(mAdvertiseTxPowerLevel);
              settingsBuilder.setConnectable(mConnectable);
  
              mBluetoothLeAdvertiser.startAdvertising(settingsBuilder.build(), dataBuilder.build(), getAdvertiseCallback());
              LogManager.d(TAG, "Started advertisement with callback: %s", getAdvertiseCallback());
  
          } catch (Exception e){
              LogManager.e(e, TAG, "Cannot start advertising due to exception");
          }
      }
  
      /**
       * Stops this beacon from advertising
       */
      public void stopAdvertising() {
          if (!mStarted) {
              LogManager.d(TAG, "Skipping stop advertising -- not started");
              return;
          }
          LogManager.d(TAG, "Stopping advertising with object %s", mBluetoothLeAdvertiser);
          mAdvertisingClientCallback = null;
          try {
              mBluetoothLeAdvertiser.stopAdvertising(getAdvertiseCallback());
          }
          catch (IllegalStateException e) {
              LogManager.w(TAG, "Bluetooth is turned off. Transmitter stop call failed.");
          }
          mStarted = false;
      }
  
      /**
       * Checks to see if this device supports beacon advertising
       * @return SUPPORTED if yes, otherwise:
       *          NOT_SUPPORTED_MIN_SDK
       *          NOT_SUPPORTED_BLE
       *          NOT_SUPPORTED_MULTIPLE_ADVERTISEMENTS
       *          NOT_SUPPORTED_CANNOT_GET_ADVERTISER
       */
      public static int checkTransmissionSupported(Context context) {
          int returnCode = SUPPORTED;
  
          if (android.os.Build.VERSION.SDK_INT < 21) {
              returnCode = NOT_SUPPORTED_MIN_SDK;
          }
          else if (!context.getApplicationContext().getPackageManager().hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE)) {
              returnCode = NOT_SUPPORTED_BLE;
          }
          else {
              try {
                  // Check to see if the getBluetoothLeAdvertiser is available.  If not, this will throw an exception indicating we are not running Android L
                  if (((BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter().getBluetoothLeAdvertiser() == null) {
                      if (!((BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE)).getAdapter().isMultipleAdvertisementSupported()) {
                          returnCode = NOT_SUPPORTED_CANNOT_GET_ADVERTISER_MULTIPLE_ADVERTISEMENTS;
                      }
                      else {
                          returnCode = NOT_SUPPORTED_CANNOT_GET_ADVERTISER;
                      }
                  }
              } catch (Exception e) {
                  returnCode = NOT_SUPPORTED_CANNOT_GET_ADVERTISER;
              }
          }
  
          return returnCode;
      }
  
      private AdvertiseCallback getAdvertiseCallback() {
          if (mAdvertiseCallback == null) {
              mAdvertiseCallback = new AdvertiseCallback() {
                  @Override
                  public void onStartFailure(int errorCode) {
                      LogManager.e(TAG,"Advertisement start failed, code: %s", errorCode);
                      if (mAdvertisingClientCallback != null) {
                          mAdvertisingClientCallback.onStartFailure(errorCode);
                      }
  
                  }
  
                  @Override
                  public void onStartSuccess(AdvertiseSettings settingsInEffect) {
                      LogManager.i(TAG,"Advertisement start succeeded.");
                      mStarted = true;
                      if (mAdvertisingClientCallback != null) {
                          mAdvertisingClientCallback.onStartSuccess(settingsInEffect);
                      }
  
                  }
              };
  
  
          }
          return mAdvertiseCallback;
      }
  
      /**
       * 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.
       *
       * Copied from java/android/bluetooth/BluetoothUuid.java
       * Copyright (C) 2009 The Android Open Source Project
       * Licensed under the Apache License, Version 2.0
       */
      private static ParcelUuid parseUuidFrom(byte[] uuidBytes) {
          /** Length of bytes for 16 bit UUID */
          final int UUID_BYTES_16_BIT = 2;
          /** Length of bytes for 32 bit UUID */
          final int UUID_BYTES_32_BIT = 4;
          /** Length of bytes for 128 bit UUID */
          final int UUID_BYTES_128_BIT = 16;
          final ParcelUuid BASE_UUID =
                  ParcelUuid.fromString("00000000-0000-1000-8000-00805F9B34FB");
          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));
      }
  
  }
  