bluetooth-lowenergyibeaconbeaconibeacon-android

Can I use Apple and Google's Contact Tracing Spec?


I want to use Apple and Google's new APIs to support Covid contact tracing as desribedin this API document. But when I try to use these APIs in XCode, the classes are not found:

let request = CTSelfTracingInfoRequest()

How do I enable these APIs?


Solution

  • The APIs for iOS are restricted. While you can write code against the ExposureNotifcation framework using XCode 11.5 and iOS 13.5, you can't run the code even in a simulator without Apple granting you a provisioning profile with the com.apple.developer.exposure-notification entitlement. And Apple is only giving that entitlement to developers associated with government health agencies after a manual approval process.

    Below is more information on what you can do without special permission from Apple.

    iOS releases prior to 13.5 disallowed transmitting the Exposure Notification Service beacon bluetooth advertising format in the specification. Starting with 13.5, advertising is possible only by the operating system -- 3rd party apps cannot emit that advertisement without using higher-level APIs.

    Starting with iOS 13.5, Apple also blocks direct detection of this beacon format by third party apps, forcing them to use higher-level APIs. Earlier versions of iOS do allow detection of this beacon format.

    Android, however, is another story.

    While Google has similarly restricted use of these APIs in Google Play Services to API keys with special permissions granted from Google, Android versions 5.0+ allows 3rd party apps to both sending and detect the Exposure Notification Service beacon advertisement that the bluetooth specification envisions:

    Using the free and open-source Android Beacon Library 2.17+, you can transmit this beacon like this:

    String uuidString = "01020304-0506-0708-090a-0b0c0d0e0f10";
    Beacon beacon = new Beacon.Builder()
        .setId1(uuidString)
        .build();
    // This beacon layout is for the Exposure Notification Service Bluetooth Spec
    BeaconParser contactDetectionBeaconParser = new BeaconParser()
        .setBeaconLayout("s:0-1=fd6f,p:-:-59,i:2-17");
    BeaconTransmitter beaconTransmitter = new 
    BeaconTransmitter(getApplicationContext(), contactDetectionBeaconParser);
    beaconTransmitter.startAdvertising(beacon
    

    And scan for it like this:

    BeaconManager beaconManager = BeaconManager.getInstanceForApplication(this);
    beaconManager.getBeaconParsers().add(new BeaconParser().setBeaconLayout("s:0-1=fd6f,p:-:-59,i:2-17"));
    ...
    
    beaconManager.startRangingBeaconsInRegion(new Region("All Exposure Notification Service beacons", null));
    ...
    
    @Override
    public void didRangeBeaconsInRegion(Collection<Beacon> beacons, Region region) {
      for (Beacon beacon: beacons) {
          Log.i(TAG, "I see an Exposure Notification Service beacon with rolling proximity identifier "+beacon.getId1());
      }
    }
    

    On Android, the above transmission and detection is possible even in the background. See library documentation for details.

    The ability to transmit and receive Exposure Notification Service beacons is built into the BeaconScope Android app. You can use this as a tool to help test any apps you build.

    You can read more in my blog post which shows you how to build your own app to do this.

    As for iOS, while transmission is impossible as of this writing, you can scan for these beacons on iOS 13.4.x and earlier with code like this:

    let exposureNotificationServiceUuid = CBUUID(string: "FD6F")
    centralManager?.scanForPeripherals(withServices: [exposureNotificationServiceUuid], options: [CBCentralManagerScanOptionAllowDuplicatesKey: true])
    
    ...
    func centralManager(_ central: CBCentralManager, didDiscover peripheral: CBPeripheral, advertisementData: [String : Any], rssi RSSI: NSNumber) {
       if let advDatas = advertisementData[CBAdvertisementDataServiceDataKey] as? NSDictionary {
           if let advData = advDatas.object(forKey: CBUUID(string: "FD6F")) as? Data {
               let hexString = advData.map { String(format: "%02hhx", $0) }.joined()
    
               let proximityId = String(hexString.prefix(32))
               let metadata = hexString.suffix(8)
               NSLog("Discovered Exposure Notification Service Beacon with Proximity ID\(proximityId), metadata \(metadata) and RSSI \(RSSI)")
           }
       }
    }
    

    Beware, however, that Apple blocked this from working as of iOS 13.5 beta 2. The didDiscover method above is never called for advertisements with the Exposure Notification Service UUID.

    Full Disclosure: I am the lead developer on the Android Beacon Library open source project and the author of the BeaconScope app built on this library.

    EDIT April 26, 2020: Updated answer above to link to the revised 1.1 version of the Exposure Notification Service Bluetooth Spec, to update naming conventions from that change, and to revise code samples to show the metadata.

    EDIT April 30, 2020: Updated answer based on Apple's release of iOS 13.5 beta 2 and XCode 11.5 beta, and the fact that Apple now blocks 3rd party apps from detecting the Exposure Notification Service beacon.

    EDIT June 2, 2020: Updated answer based on Apple's final release of iOS 13.5 and Google's release of Google Play Services.