NE Context SDK Guide

Overview

NESDK is a utility that provides real-time contextual information about app users. It is intended to help apps become more context-aware of their users and change behaviour accordingly, such as automatically muting a message alert when the user is at work, and making a louder sound when outdoors.

Getting Started

Installation

NESDK for Android is held in our own Maven/Gradle server at http://repo.numbereight.me/artifactory/gradle-release-local

To import it into a Gradle project, just add the following lines to your module-level build.gradle file:

repositories {
    maven { url 'http://repo.numbereight.me/artifactory/gradle-release-local' }
}

dependencies {
    implementation('me.numbereight.sdk:nesdk:0.8.+@aar') {
        transitive = true
    }
}

Note

The transitive = true line instructs Gradle to download all NESDK’s dependencies too.

Coming soon…

First Use

NESDK provides a publish-subscribe style interface through its internal Engine. All sensors publish to this central location, but sleep when there aren’t any subscribers listening to them.

To get started, you just need to initialise the service, which prepares the internal sensors:

import me.numbereight.sdk.NE

...

val ne = NE(context)
ne.start()
let ne = NE()
ne.start()

After calling start(), either stop() should be called when no longer needed, or runInBackground(). This sets up the background service to either stop or remain alive respectively, and is usually done when the app is resumed and paused like so:

import android.app.Activity
import me.numbereight.sdk.NE

class MainActivity : Activity() {

    val ne = NE(this)
    val runInBackground = true

    override fun onResume() {
        super.onResume()
        ne.start() // or ne.resume(), whichever you prefer (they do the same thing)
    }

    override fun onPause() {
        super.onPause()

        if (runInBackground) {
            ne.runInBackground()
        } else {
            ne.stop()
        }
    }

}

Once you have a service instance, you can call ne.subscribe(topic, callback), which takes a topic and a callback:

val subscription = ne.subscribe(NE.ACTIVITY, Engine.Callback { topic, event ->
    println("User is currently ${event.value()}")
})
let subscription = ne.subscribe(NE.ACTIVITY, { topic, event in
    print("User is currently \(event.value())")
})

Note

Simple tasks can be performed directly with the NE class, which is just a convenience interface for invoking the same methods on the Engine subcomponent (e.g. ne.engine.publisherExists(...))

Now, every time the user’s activity changes, such as when they start running, a message will be printed in the debug console.

When subscribing, the subscription will last only while the variable is in scope. Hence, it is important to store it somewhere permanent if you want it to remain subscribed. For example:

import me.numbereight.sdk.NE

class ContextHandler {
    val ne: NE
    var subscription: Engine.Subscription? = null

    init {
        ne = NE()
        ne.start()
    }

    fun thisWorks() {
        subscription = ne.subscribe(NE.ALL, Engine.Callback { topic, event ->
            println("I will be called on every single NE event!")
        })
    }

    fun thisBarelyWorks() {
        val temporarySubscription = ne.subscribe(NE.ALL, Engine.Callback { topic, event ->
            println("I won't be called very much!")
        })
    }

    fun thisDoesntWork() {
        ne.subscribe(NE.ALL, Engine.Callback { topic, event ->
            println("I probably won't be called at all!")
        })
    }
}
class ContextHandler {
    let ne: NE
    var subscription: Engine.Subscription?

    init() {
        ne = NE()
        ne.start()
    }

    func thisWorks() {
        subscription = ne.subscribe(NE.ALL, { _, _ in
            print("I will be called on every single NE event!")
        })
    }

    func thisBarelyWorks() {
        let temporarySubscription = ne.subscribe(NE.ALL, { _, _ in
            print("I won't be called very much!")
        })
    }

    func thisDoesntWork() {
        ne.subscribe(NE.ALL, { _, _ in
            print("I probably won't be called at all!")
        })
    }
}

Methods

The main methods of the NE/Engine interface are:

  • get(topic) manually retrieves the latest event on a topic. If no event has been published, the method will return null.
  • subscribe(topic, callback) as described above.
  • request(topic, callback) asks all sensors on the topic to produce an event immediately, if supported. Otherwise, the last known event is used. The callback will be called at-most once from the first sensor to honour the request. If no publisher exists on the given topic, a NoPublishers exception is thrown.
  • publisherExists(topic) returns true if at least one publisher is active on the given topic.

Topics

The topics given to subscribe(topic, callback) exist in a sort of hierarchy. For simplicity, we recommend that you use the topic constants defined within the NE class. For example:

  • NE.SITUATION
  • NE.ACTIVITY
  • NE.INDOOR_OUTDOOR
  • NE.PLACE
  • NE.WEATHER

You can also provide a topic as a string sensor path. This allows you to listen to specific sensors, or a group of them. Here’s an example of how they are organised:

- "" # The 'root' topic. This receives everything.

  - "motion" # Receives motion-related events.
    - "motion/accelerometer" # Receives acceleration vectors.
      - "motion/accelerometer/0" # The primary accelerometer of the device.
      - "motion/accelerometer/1" # The secondary accelerometer.

    - "motion/gyroscope" # Receives angular velocity vectors.
      - "motion/gyroscope/0" # The primary gyroscope of the device.

    - "motion/device_movement" # Receives events related to physical device movement.
      - "motion/device_movement/significant" # Receives events when a device has moved a significant amount.

Events

All data to and from the Engine is encapsulated in an Event. This class includes:

  • The timestamp of when the event was created
  • The name of the sensor that produced it
  • One or more values along with confidence ratings

Most events have one value, but if there is uncertainty, a probability distribution is given. For example, if the activity sensor is unsure of the user’s current activity, it may emit an event like this:

- Event:
  - source: "ActivitySensor"
  - timestamp: "2018-04-27T17:15:30.325000Z"
  - values:
    - RUNNING: 0.7
    - CYCLING: 0.2
    - WALKING: 0.1
Retrieving Values

Event.values() retrieves all values as a list of pairs, Event.value() retrieves the most likely value, and Event.confidence() retrieves the most likely value’s confidence. This example will print out all of the values:

val subscription = ne.subscribe(NE.ACTIVITY, Engine.Callback { topic, event ->
    for (pair in event.values()) {
        println("${pair.value}=${pair.confidence}")
    }
})
let subscription = ne.subscribe(NE.ACTIVITY, { topic, event in
    for (pair in event.values()) {
        println("\(pair.value)=\(pair.confidence)")
    }
})
Data Types

The values inside events conform to one of several datatypes. All datatypes inherit the NEType class, which allows conversion to a string and a cloning method. By casting the value to its original datatype, you can get the raw data. Here are a list of currently supported data types:

Datatype Description
NEActivity
  • State: (Unknown, Stationary, Walking, Running, Cycling, InVehicle)
  • Mode of Transport: (Unknown, Bicycle, Bus, Car, Motorbike, Subway, Train, Tram, Boat)
NEDevicePosition
  • State: (Unknown, InHand, InPocket, InBag, OnSurface, OnArm, AgainstEar)
  • Orientation: (Unknown, FaceDown, FaceUp, PointDown, PointUp, Sideways)
NEDouble A floating-point number
NEIndoorOutdoor An enumeration of indoor/outdoor states (Unknown, Indoor, Outdoor, Enclosed)
NEInteger An integer
NELocation Latitude and longitude in decimal degrees, and a speed in metres per second
NELockStatus An enumeration of phone lock states (Unlocked, Locked)
NEMotion An enumeration of motion states (Unknown, Moving, NotMoving)
NEPlace
  • Semantic Name: (Unknown, Home, Work)
  • Major Type: (Unknown, Academic, Cultural, Entertainment, FoodAndDrink, Office, Recreational, Residential, ShopsAndServices, Sport, Travel)
  • Minor Type: (Unknown, Airport, ArtGallery, Bank, Bar, Beach, BusStop, Cafe, Campsite, Cemetery, Cinema, Concert, Exhibition, Festival, Gardens, Gym, HistoricSite, Lake, Library, Museum, Nightclub, Park, Port, RecreationCentre, Restaurant, SportsField, Stadium, SubwayStation, TrainStation)
NEReachability An enumeration of device network reachability (Unreachable, CellularOnly, WifiOnly, WifiAndCellular)
NESignal Information about the currently connected cellular base station, including signal strength in decibels, and an id as a concatenation of MCC, MNC, LAC/TAC, And CID/CI
NESituation High level statements about a user’s current situation (Unknown, MorningRituals, Commuting, Working, WorkingOut, Shopping, Leisure, Social, Housework, Sleeping)
NETime
  • Time Of Day (Unknown, EarlyMorning, Morning, Breakfast, BeforeLunch, Lunch, Afternoon, Evening, Dinner, Night)
  • Type Of Day (Unknown, Weekday, Weekend, Holiday)
NEVector3D A 3D vector of floating-point numbers for representing points, lines, or fields in 3D space
NEWeather
  • Temperature (Unknown, VeryHot, Hot, Warm, Cold, Freezing)
  • Conditions (Unknown, Clear, Sunny, Cloudy, Windy, Breezy, Snow, Rain, Drizzle, Thunderstorm, ExtremeStorm)

Note

An event can only contain values of the same data type. If you disagree with this constraint, let us know!

Filters

Filters are a way of which events can notify your subscription callback. There are currently 3 filters: ‘type’, ‘dropout’, and ‘burst’. Filters can be specified as the second argument of subscribe(topic, filter, callback), get(topic, filter), and request(topic, filter, callback) methods. These are given as strings and can be chained together in the following format:

filtername:arg1,arg2|anotherfiltername:arg1|...

If the filter syntax is incorrect, an InvalidFilter exception will be thrown.

Type Filter

Because topic groups can receive data from multiple sensors, it’s useful to restrict subscriptions to a specific data type. The format for a type filter is:

type:TYPENAME where TYPENAME is one of the types listed under Data Types.

Dropout Filter

Some sensors, such as the accelerometer, may produce events very quickly. To limit the rate at which events arrive, a dropout filter is used, specifying either a count or a timeout as follows:

dropout:9 receives every 10th event. dropout:5s receives every 5 seconds. The following time suffixes can be given in the argument: μs or us, ms, s, m, h for microseconds, milliseconds, seconds, minutes, and hours respectively.

Burst Filter

If a dropout filter is too aggressive, and you still want high frequency data over certain periods, use a burst filter:

burst:3,100 receives the first 3 events for every 100 events. burst:3s,1m receives the first 3 seconds of data every minute. The same time suffixes used in the Dropout Filter are used.

Times and counts can be mixed-and-matched, e.g. burst:3,10s.

Chained Filter Example
val subscription = ne.subscribe(topic = "motion", filter = "type:NEVector3D|dropout:5s", callback = { topic, event ->
    val vector: NEVector3D = event.value() as NEVector3D
})
let subscription = ne.subscribe(topic: "motion", filter: "type:NEVector3D|dropout:5s", callback: { topic, event in
    let vector: NEVector3D = event.value() as NEVector3D
})

Permissions

By default, NESDK requires only Internet permissions on Android, and permission to run in the background for iOS. Sensors that require extra permissions, such as location, are disabled until the host app grants that permission. This keeps the SDK lean, with a privacy-by-design philosophy. The following sensors require extra permissions:

Name Permissions
Motion Activity com.google.android.gms.permission.ACTIVITY_RECOGNITION
Reachability android.permission.READ_PHONE_STATE + android.permission.ACCESS_WIFI_STATE
Cellular Signal android.permission.READ_PHONE_STATE
GPS Location android.permission.ACCESS_FINE_LOCATION
Low-Power Location android.permission.ACCESS_COARSE_LOCATION [1]
Place Type android.permission.ACCESS_COARSE_LOCATION [1]
Location Cluster ID android.permission.ACCESS_COARSE_LOCATION [1]
Cluster Centroid android.permission.ACCESS_COARSE_LOCATION [1]
Weather android.permission.ACCESS_COARSE_LOCATION [1]
Bluetooth Devices android.permission.BLUETOOTH + android.permission.BLUETOOTH_ADMIN
Bluetooth LE Devices android.permission.BLUETOOTH + android.permission.BLUETOOTH_ADMIN + android.permission.ACCESS_COARSE_LOCATION [1]
WiFi Hotspots android.permission.ACCESS_WIFI_STATE + android.permission.CHANGE_WIFI_STATE
Name Permissions
Motion Activity Motion Usage
GPS Location Location Usage [2]
Place Type Location Usage [2]
Location Cluster ID Location Usage [2]
Cluster Centroid Location Usage [2]
Weather Location Usage [2]
Bluetooth LE Devices Location Usage [2]
[1](1, 2, 3, 4, 5, 6) All sensors that require ACCESS_COARSE_LOCATION will also work with ACCESS_FINE_LOCATION.
[2](1, 2, 3, 4, 5, 6) To use location-based services in the background, you must also enable Location in Background permissions.

Available Sensors

These tables list the available sensors across each platform, along with their publishing topic and any permissions required to enable them.

Note

Some sensors, such as ambient pressure, may not be available on all devices despite being supported by the platform. To see if a publisher exists, use ne.publisherExists(topic).

High-Level
Name Topic Data Type Android iOS
Situation "situation" NESituation Yes Yes
Activity "activity" NEActivity Yes Yes
Indoor/Outdoor "indoor_outdoor" NEIndoorOutdoor Yes Yes
Place Type "place" NEPlace Yes Yes
Intermediate
Name Topic Data Type Android iOS
Location Cluster ID "location_cluster/id" NEInteger Yes Yes
Cluster Centroid "location_cluster/centroid" NELocation Yes Yes
Lock Status "lock_status" NELockStatus Yes No
Time "time" NETime Yes Yes
Weather "weather" NEWeather Yes Yes
Magnetic Variance "magnetism/magnetic_variance" NEDouble Yes Yes
Low-Level
Name Topic Data Type Android iOS
Accelerometer "motion/accelerometer" NEVector3D Yes Yes
Gyroscope "motion/gyroscope" NEVector3D Yes Yes
Device Movement "motion/device_movement" NEMotion Yes No
Motion Activity "motion/activity" NEActivity Yes Yes
Step Detector "motion/step" NEMotion Yes Yes
Step Counter "step_count" NEInteger Yes Yes
Magnetometer "magnetism/magnetometer" NEVector3D Yes Yes
Ambient Pressure "ambient/pressure" NEDouble Yes Yes
Ambient Temperature "ambient/temperature" NEDouble Yes No
Ambient Humidity "ambient/humidity" NEDouble Yes No
Ambient Light "ambient/light" NEDouble Yes No
Screen Brightness "screen_brightness" NEDouble No Yes
Proximity "proximity" NEDouble Yes No
Reachability "reachability" NEReachability Yes Yes
Cellular Signal "signal/cellular" NESignal Yes No
Bluetooth Devices "signal/bluetooth/classic/scanner" NEInteger Yes Yes
Bluetooth LE Devices "signal/bluetooth/le/scanner" NEInteger Yes Yes
WiFi Hotspots "signal/wifi/scanner" NEInteger Yes No
GPS Location "location/gps" NELocation Yes Yes [3]
Low-Power Location "location/low_power" NELocation Yes No
[3]iOS does not distinguish between GPS and low-power location sources.