code: /*
   * Copyright 2016 Google Inc. All rights reserved.
   *
   * 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 org.physical_web.physicalweb.ble;
  
  import android.annotation.TargetApi;
  import android.bluetooth.le.AdvertiseData;
  import android.bluetooth.le.AdvertiseSettings;
  import android.os.ParcelUuid;
  import android.util.Log;
  import android.util.SparseArray;
  import android.webkit.URLUtil;
  
  import java.nio.ByteBuffer;
  import java.nio.ByteOrder;
  import java.util.Locale;
  import java.util.UUID;
  
   /**
   *Helper class to simplify Eddystone URL encoding.
  **/
  public class AdvertiseDataUtils {
      private static final String TAG = "AdvertiseDataUtils";
      private static final ParcelUuid EDDYSTONE_BEACON_UUID = ParcelUuid.fromString(
          "0000FEAA-0000-1000-8000-00805F9B34FB");
      /**
       * URI Scheme maps a byte code into the scheme and an optional scheme specific prefix.
       */
      private static final SparseArray<String> URI_SCHEMES = new SparseArray<String>() {{
          put((byte) 0, "http://www.");
          put((byte) 1, "https://www.");
          put((byte) 2, "http://");
          put((byte) 3, "https://");
          put((byte) 4, "urn:uuid:");    // RFC 2141 and RFC 4122};
      }};
  
      /**
       * Expansion strings for "http" and "https" schemes. These contain strings appearing
       * anywhere in a URL. Restricted to Generic TLDs. <p/> Note: this is a scheme specific encoding.
       */
      private static final SparseArray<String> URL_CODES = new SparseArray<String>() {{
          put((byte) 0, ".com/");
          put((byte) 1, ".org/");
          put((byte) 2, ".edu/");
          put((byte) 3, ".net/");
          put((byte) 4, ".info/");
          put((byte) 5, ".biz/");
          put((byte) 6, ".gov/");
          put((byte) 7, ".com");
          put((byte) 8, ".org");
          put((byte) 9, ".edu");
          put((byte) 10, ".net");
          put((byte) 11, ".info");
          put((byte) 12, ".biz");
          put((byte) 13, ".gov");
      }};
     private static final byte URL_FRAME_TYPE = 0x10;
     private static final byte FAT_BEACON = 0x0e;
  
  
     /**
       * Creates the Uri string with embedded expansion codes.
       *
       * @param uri to be encoded
       * @return the Uri string with expansion codes.
       */
      public static byte[] encodeUri(String uri) {
          if (uri == null || uri.length() == 0) {
              Log.i(TAG, "null or empty uri");
              return new byte[0];
          }
          ByteBuffer bb = ByteBuffer.allocate(uri.length());
          // UUIDs are ordered as byte array, which means most significant first
          bb.order(ByteOrder.BIG_ENDIAN);
          int position = 0;
  
          // Add the byte code for the scheme or return null if none
          Byte schemeCode = encodeUriScheme(uri);
          if (schemeCode == null) {
              Log.i(TAG, "null scheme code");
              return null;
          }
          String scheme = URI_SCHEMES.get(schemeCode);
          bb.put(schemeCode);
          position += scheme.length();
  
          if (URLUtil.isNetworkUrl(scheme)) {
              Log.i(TAG, "is network URL");
              return encodeUrl(uri, position, bb);
          } else if ("urn:uuid:".equals(scheme)) {
              Log.i(TAG, "is UUID");
              return encodeUrnUuid(uri, position, bb);
          }
          return null;
      }
  
      /**
       * Finds the longest expansion from the uri at the current position.
       *
       * @param uriString the Uri
       * @param pos start position
       * @return an index in URI_MAP or 0 if none.
       */
      private static byte findLongestExpansion(String uriString, int pos) {
          byte expansion = -1;
          int expansionLength = 0;
          for (int i = 0; i < URL_CODES.size(); i++) {
              // get the key and value.
              int key = URL_CODES.keyAt(i);
              String value = URL_CODES.valueAt(i);
              if (value.length() > expansionLength && uriString.startsWith(value, pos)) {
                  expansion = (byte) key;
                  expansionLength = value.length();
              }
          }
          return expansion;
      }
  
      private static Byte encodeUriScheme(String uri) {
          String lowerCaseUri = uri.toLowerCase(Locale.ENGLISH);
          for (int i = 0; i < URI_SCHEMES.size(); i++) {
              // get the key and value.
              int key = URI_SCHEMES.keyAt(i);
              String value = URI_SCHEMES.valueAt(i);
              if (lowerCaseUri.startsWith(value)) {
                  return (byte) key;
              }
          }
          return null;
      }
  
      private static byte[] encodeUrl(String url, int position, ByteBuffer bb) {
          while (position < url.length()) {
              byte expansion = findLongestExpansion(url, position);
              if (expansion >= 0) {
                  bb.put(expansion);
                  position += URL_CODES.get(expansion).length();
              } else {
                  bb.put((byte) url.charAt(position++));
              }
          }
          return byteBufferToArray(bb);
      }
  
      private static byte[] encodeUrnUuid(String urn, int position, ByteBuffer bb) {
          String uuidString = urn.substring(position, urn.length());
          UUID uuid;
          try {
              uuid = UUID.fromString(uuidString);
          } catch (IllegalArgumentException e) {
              //Log.w(TAG, "encodeUrnUuid invalid urn:uuid format - " + urn);
              return null;
          }
          // UUIDs are ordered as byte array, which means most significant first
          bb.order(ByteOrder.BIG_ENDIAN);
          bb.putLong(uuid.getMostSignificantBits());
          bb.putLong(uuid.getLeastSignificantBits());
          return byteBufferToArray(bb);
      }
  
      private static byte[] byteBufferToArray(ByteBuffer bb) {
          byte[] bytes = new byte[bb.position()];
          bb.rewind();
          bb.get(bytes, 0, bytes.length);
          return bytes;
      }
  
      // Generate the advertising bytes for the given URL
      @TargetApi(21)
      public static AdvertiseData getAdvertisementData(byte[] urlData) {
        AdvertiseData.Builder builder = new AdvertiseData.Builder();
        builder.setIncludeTxPowerLevel(false); // reserve advertising space for URI
  
        // Manually build the advertising info
        // See https://github.com/google/eddystone/tree/master/eddystone-url
        if (urlData == null || urlData.length == 0) {
          return null;
        }
  
        byte[] beaconData = new byte[urlData.length + 2];
        System.arraycopy(urlData, 0, beaconData, 2, urlData.length);
        beaconData[0] = URL_FRAME_TYPE; // frame type: url
        beaconData[1] = (byte) 0xBA; // calibrated tx power at 0 m
  
        builder.addServiceData(EDDYSTONE_BEACON_UUID, beaconData);
  
        // Adding 0xFEAA to the "Service Complete List UUID 16" (0x3) for iOS compatibility
        builder.addServiceUuid(EDDYSTONE_BEACON_UUID);
  
        return builder.build();
      }
  
     // Build and return the advertising bytes for the given FatBeacon advertisement
     @TargetApi(21)
     public static AdvertiseData getFatBeaconAdvertisementData(byte[] fatBeaconAdvertisement) {
  
       // Manually build the advertising info
       int length = Math.min(fatBeaconAdvertisement.length, 17);
       byte[] beaconData = new byte[length + 3];
       System.arraycopy(fatBeaconAdvertisement, 0, beaconData, 3, length);
       beaconData[0] = URL_FRAME_TYPE;
       beaconData[1] = (byte) 0xBA;
       beaconData[2] = FAT_BEACON;
       return new AdvertiseData.Builder()
           .setIncludeTxPowerLevel(false) // reserve advertising space for URI
           .addServiceData(EDDYSTONE_BEACON_UUID, beaconData)
           // Adding 0xFEAA to the "Service Complete List UUID 16" (0x3) for iOS compatibility
           .addServiceUuid(EDDYSTONE_BEACON_UUID)
           .build();
     }
  
      // Build and return the ble advertising settings
      @TargetApi(21)
      public static AdvertiseSettings getAdvertiseSettings(boolean connectable) {
          AdvertiseSettings.Builder builder = new AdvertiseSettings.Builder();
          builder.setAdvertiseMode(AdvertiseSettings.ADVERTISE_MODE_LOW_POWER);
          builder.setTxPowerLevel(AdvertiseSettings.ADVERTISE_TX_POWER_MEDIUM);
          builder.setConnectable(connectable);
  
          return builder.build();
      }
  }
  