├── .github
└── workflows
│ └── gradle-publish.yml
├── .gitignore
├── Arctop-Stream.md
├── README.md
├── arctopSDK
├── .gitignore
├── README.md
├── build.gradle
├── consumer-rules.pro
├── proguard-rules.pro
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── aidl
│ └── com
│ │ └── arctop
│ │ ├── IArctopSdk.aidl
│ │ └── IArctopSdkListener.aidl
│ └── java
│ └── com
│ └── arctop
│ ├── ArctopQAProperties.kt
│ ├── ArctopSDK.java
│ ├── ArctopStreamService.kt
│ └── unity
│ ├── ArctopUnityBridge.java
│ ├── IArctopSdkCallback.java
│ ├── IArctopSdkSuccessOrFailureCallback.java
│ └── IArctopServiceBindCallback.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.github/workflows/gradle-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub.
2 | # They are provided by a third-party and are governed by
3 | # separate terms of service, privacy policy, and support
4 | # documentation.
5 | # This workflow will build a package using Gradle and then publish it to GitHub packages when a release is created
6 | # For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#Publishing-using-gradle
7 |
8 | name: Gradle Package
9 |
10 | on:
11 | release:
12 | types: [created]
13 |
14 | jobs:
15 | build:
16 |
17 | runs-on: ubuntu-latest
18 | permissions:
19 | contents: read
20 | packages: write
21 |
22 | steps:
23 | - uses: actions/checkout@v4
24 | - name: Set up JDK 17
25 | uses: actions/setup-java@v4
26 | with:
27 | java-version: '17'
28 | distribution: 'temurin'
29 | server-id: github # Value of the distributionManagement/repository/id field of the pom.xml
30 | settings-path: ${{ github.workspace }} # location for the settings.xml file
31 |
32 | - name: Setup Gradle
33 | uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0
34 |
35 | - name: Build with Gradle
36 | run: ./gradlew build
37 |
38 | # The USERNAME and TOKEN need to correspond to the credentials environment variables used in
39 | # the publishing section of your build.gradle
40 | - name: Publish to GitHub Packages
41 | run: ./gradlew publish
42 | env:
43 | USERNAME: ${{ github.actor }}
44 | TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 |
4 | /.idea/
5 |
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 | .cxx
11 | local.properties
12 |
--------------------------------------------------------------------------------
/Arctop-Stream.md:
--------------------------------------------------------------------------------
1 | # Arctop Stream Server
2 |
3 | The purpose of Stream Server is to provide a platform independent API.
4 | The server is running on the mobile device and operates on the user's LAN. It allows authorized clients to access the realtime data that Arctop provides.
5 |
6 | ## General Structure
7 |
8 | The Arctop app launches a socket server on the LAN it is connected to. It publishes the service for discovery, and awaits connections from clients.
9 |
10 | When a client connects, an authentication handshake is performed, and once successful, the server will transmit the data received over the network stream. The data is identical to what a client connecting directly to Arctop Native SDK receives via the IArctopSdkListener interface.
11 |
12 | The data is transmitted as JSON objects, allowing easy interpretation in almost any programming language.
13 |
14 | ## Stream data structure
15 |
16 | Currently, the server is implemented on Android, it uses a BIG ENDIAN byte order.
17 |
18 | All data is sent with a leading 2 bytes defining the size of the following message as an unsigned short / UInt16.
19 | To properly read the stream, a developer should first read 2 byte, convert the endianness if needed and convert to a UShort.
20 | This will provide the length (in bytes) of the following message, which you can retrieve by reading that exact amount of bytes off the stream.
21 | Those bytes should then be converted to a UTF8 string, that will contain the JSON object.
22 |
23 | ## Command Structure
24 |
25 | Every command that is sent between the server and the client follows the same structure.
26 |
27 | 1. A mandatory *command* field, describing the type of command.
28 | 2. Optional values defined per command (see [ArctopStreamService.kt](arctopSDK/src/main/java/com/arctop/ArctopStreamService.kt))
29 | 3. Timestamp ( only available within commands sent as part of the stream ) as a long int (Unix MS)
30 |
31 | an example of an object notifying the client of a new value received for enjoyment:
32 | ```JSON
33 | {"command" : "valueChange" , "timestamp" : 123612321 , "key" : "enjoyment" , "value" : 33.4442}
34 | ```
35 | All the available commands, values, and object key are listed in the [ArctopStreamService.kt](arctopSDK/src/main/java/com/arctop/ArctopStreamService.kt) file.
36 |
37 | ## Connection flow
38 |
39 | ### Connect to server
40 |
41 | The Arctop app provides its IP / Port on the SDK Stream page, your client will need to connect to that address.
42 | The server also publishes itself on the LAN as a service by the name of *ArctopService* and the protocol *_http._tcp*.
43 |
44 | ### Send authentication request
45 |
46 | Once connected the server awaits your client to send a JSON object containing your application's API key.
47 | The expected JSON contains 2 fields, one defining the command and another holding the value of the key.
48 | ```JSON
49 | {"command" : "auth" , "apiKey" : "yourkeystringhere"}
50 | ```
51 | The server will reply with a command of either "auth-success" or "auth-failed".
52 | In the event of a failed authentication, the connection will be closed and you can try again.
53 | If the authentication succeeds the server will begin transmitting events.
54 |
55 | ### Working with the stream
56 |
57 | Once the server starts streaming commands, you should read the stream to extract the data.
58 |
59 | As described in [Stream data structure](#stream-data-structure), before each message the server writes an unsigned short (16 bit uint) into the stream to let the client know the size of the next message in bytes.
60 | This allows the client to read messages fully, and convert into a string/JSON for processing.
61 |
62 | For a better understanding of commands, values, and constants, please review the [ArctopSDK.java](arctopSDK/src/main/java/com/arctop/ArctopSDK.java) and [IArctopSdkListener.aidl](arctopSDK/src/main/aidl/com/arctop/IArctopSdkListener.aidl) files.
63 |
64 | ### Session complete
65 |
66 | When the user finishes the session on the Arctop app, the server will send out a "sessionComplete" command to the client.
67 | This will be the final message before the server is shutting down. Please use it as your notification to release all resources and shut down the connection on your client side.
68 |
69 | ## Example repo
70 |
71 | The Arctop Socket Client repo contains a functional example for Unity3D / C#
72 |
73 | [https://github.com/arctop/arctop-socket-client](https://github.com/arctop/arctop-socket-client)
74 |
75 | ## C# example (Based on a Unity 3D client)
76 |
77 | The following code illustrates connecting and reading values using a C# Unity3D client.
78 | ```C#
79 | // Connects to the socket server
80 | public void ConnectSocket()
81 | {
82 | try
83 | {
84 | // Generate a server address
85 | IPEndPoint serverAddress = new IPEndPoint(IPAddress.Parse(serverIpInput.text), serverPort);
86 | // Construct a TCP socket
87 | m_Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
88 | // Connect to the address
89 | m_Socket.Connect(serverAddress);
90 | // Send auth command
91 | SendAuth();
92 | }
93 | catch (FormatException e)
94 | {
95 | errorField.text = e.Message;
96 | }
97 | catch (SocketException ex)
98 | {
99 | errorField.text = ex.Message;
100 |
101 | }
102 | }
103 | // Sends the Auth Command to the server
104 | private void SendAuth()
105 | {
106 | // Using JSON.Net to produce JSON
107 | var JObject = new JObject();
108 | JObject.Add(new JProperty("command", "auth"));
109 | JObject.Add(new JProperty("apiKey", API_KEY));
110 | string toSend = JObject.toString();
111 | // Calculate the byte count of our JSON object
112 | ushort toSendLen = (ushort)Encoding.UTF8.GetByteCount(toSend);
113 | // Get the bytes from the JSON object to send
114 | byte[] toSendBytes = Encoding.UTF8.GetBytes(toSend);
115 | // Get the bytes describing the length
116 | byte[] toSendLenBytes = BitConverter.GetBytes(toSendLen);
117 | // Make sure the length bytes are in BIG_ENDIAN
118 | if (BitConverter.IsLittleEndian)
119 | Array.Reverse(toSendLenBytes);
120 | // Send the size bytes
121 | m_Socket.Send(toSendLenBytes);
122 | // Send the message bytes
123 | m_Socket.Send(toSendBytes);
124 | // Await response from server
125 | GetAuthResponse();
126 | }
127 | // Gets a single message from the stream
128 | private string GetMessage()
129 | {
130 | // Receiving
131 | byte[] m_CommandLength = new byte[2];
132 | // Read the next command's length
133 | m_Socket.Receive(m_CommandLength);
134 | // Make sure it is in BIG_ENDIAN
135 | if (BitConverter.IsLittleEndian)
136 | Array.Reverse(m_CommandLength);
137 | // Convert to unsigned short
138 | int rcvLen = BitConverter.ToUInt16(m_CommandLength, 0);
139 | // the from stream into buffer the length we just got
140 | m_Socket.Receive(m_recBuffer , rcvLen , SocketFlags.None);
141 | // convert it to a string that represents the JSON object
142 | string rcv = Encoding.UTF8.GetString(m_recBuffer, 0, rcvLen);
143 | return rcv;
144 | }
145 | // Checks the response from the auth command
146 | private void GetAuthResponse()
147 | {
148 | var msg = GetMessage();
149 | var response = JObject.Parse(msg);
150 | var commandValue = ((string)response.Property("command")?.Value);
151 | if (commandValue == "auth-success")
152 | {
153 | IsConnected = true;
154 | /// you are sucessfully connected, start reading the stream in
155 | /// some update loop
156 | }
157 | else
158 | {
159 | errorField.text = "Failed to authenticate with server";
160 | }
161 | }
162 | // Update loop reads one message at a time
163 | private void Update()
164 | {
165 | // check that we have at least 2 bytes availabe to read
166 | if (m_Socket.Available > 2)
167 | {
168 | // get one message
169 | var data = GetMessage();
170 | // turn it into a JSON object for easier parsing
171 | var response = JObject.Parse(data);
172 | // extract the command value
173 | var commandValue = (string)response.Property("command")?.Value;
174 | // Command is a value change, read the data
175 | if (commandValue == "valueChange")
176 | {
177 | var key = (string)response.Property("key")?.Value;
178 | var value = (float)response.Property("value")?.Value;
179 | switch (key)
180 | {
181 | case "zone_state":
182 | {
183 | m_values.ZoneValue = value;
184 | // Do something with the zone value
185 | break;
186 | }
187 | case "avg_motion":
188 | {
189 | m_values.MotionValue = value;
190 | // Do something with the motion value
191 | break;
192 | }
193 | case "enjoyment":
194 | {
195 | m_values.EnjoymentValue = value;
196 | // Do something with the enjoyment value
197 | break;
198 | }
199 | case "focus":
200 | {
201 | m_values.FocusValue = value;
202 | // Do something with the focus value
203 | break;
204 | }
205 | default:
206 | {
207 | break;
208 | }
209 | }
210 | }
211 | else if (commandValue == "sessionComplete")
212 | {
213 | Disconnect();
214 | }
215 | }
216 | }
217 | ```
218 |
219 | ## Kotlin Example (Android Client)
220 |
221 | This example shows a usage of Android's network discovery API to scan for the Arctop Stream Socket before connecting.
222 | ```kotlin
223 | //Main Activity, scans for the service
224 | class MainActivity : AppCompatActivity() {
225 | companion object {
226 | private const val TAG = "Arctop-Service"
227 | private const val SERVICE_NAME = "ArctopService"
228 | private const val SERVICE_TYPE = "_http._tcp"
229 | }
230 | lateinit var nsdManager: NsdManager
231 | var mService:NsdServiceInfo? = null
232 | var found:Boolean = false
233 | val ip = mutableStateOf("")
234 | val port = mutableStateOf("")
235 | val hasService = mutableStateOf(false)
236 |
237 | override fun onCreate(savedInstanceState: Bundle?) {
238 | super.onCreate(savedInstanceState)
239 | nsdManager = (getSystemService(Context.NSD_SERVICE) as NsdManager)
240 | setContent {
241 | mainScreen()
242 | }
243 | }
244 | // This function launches the connection
245 | fun launchStream(){
246 | val streamIntent = Intent(this, ArctopStreamActivity::class.java)
247 | streamIntent.putExtra(ArctopStreamActivity.IP , ip.value)
248 | streamIntent.putExtra(ArctopStreamActivity.PORT , port.value)
249 | startActivity(streamIntent)
250 | }
251 | @Composable
252 | fun mainScreen(){
253 | Column {
254 | Row {
255 | Button(enabled = !hasService.value, onClick = {
256 | nsdManager.discoverServices(
257 | SERVICE_TYPE,
258 | NsdManager.PROTOCOL_DNS_SD,
259 | discoveryListener
260 | )})
261 | {
262 | Text("Scan")
263 | }
264 | }
265 | Row{
266 | Text("IP ${ip.value}" , color = Color.White)
267 | Text("Port ${port.value}" , color = Color.White)
268 | }
269 | Row{
270 | Button( enabled = hasService.value,
271 | onClick = {
272 | launchStream()
273 | })
274 | {
275 | Text("Connect")
276 | }
277 | }
278 | }
279 | }
280 | private val resolveListener = object : NsdManager.ResolveListener {
281 | override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) {
282 | // Called when the resolve fails. Use the error code to debug.
283 | Log.e(TAG, "Resolve failed: $errorCode")
284 | }
285 |
286 | override fun onServiceResolved(serviceInfo: NsdServiceInfo) {
287 | Log.e(TAG, "Resolve Succeeded. $serviceInfo")
288 | mService = serviceInfo
289 | ip.value = serviceInfo.host.hostAddress
290 | port.value = serviceInfo.port.toString()
291 | hasService.value = true
292 | Log.d(TAG, "Service found ${ip.value}:${port.value}")
293 | }
294 | }
295 | // Instantiate a new DiscoveryListener
296 | private val discoveryListener = object : NsdManager.DiscoveryListener {
297 |
298 | // Called as soon as service discovery begins.
299 | override fun onDiscoveryStarted(regType: String) {
300 | Log.d(TAG, "Service discovery started")
301 | }
302 |
303 | override fun onServiceFound(service: NsdServiceInfo) {
304 | // A service was found! Do something with it.
305 | Log.d(TAG, "Service discovery success $service")
306 | if (service.serviceName.equals(SERVICE_NAME) && !found){
307 | found = true
308 | nsdManager.resolveService(service, resolveListener)
309 | // The name of the service tells the user what they'd be
310 | }
311 | }
312 |
313 | override fun onServiceLost(service: NsdServiceInfo) {
314 | // When the network service is no longer available.
315 | // Internal bookkeeping code goes here.
316 | Log.e(TAG, "service lost: $service")
317 | }
318 |
319 | override fun onDiscoveryStopped(serviceType: String) {
320 | Log.i(TAG, "Discovery stopped: $serviceType")
321 | }
322 |
323 | override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) {
324 | Log.e(TAG, "Discovery failed: Error code:$errorCode")
325 | nsdManager.stopServiceDiscovery(this)
326 | }
327 |
328 | override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) {
329 | Log.e(TAG, "Discovery failed: Error code:$errorCode")
330 | nsdManager.stopServiceDiscovery(this)
331 | }
332 | }
333 |
334 | }
335 |
336 |
337 | class ArctopStreamActivity : AppCompatActivity() {
338 | companion object {
339 | const val API_KEY = "your api key"
340 | const val IP = "ip"
341 | const val PORT = "port"
342 | const val TAG = "Arctop-Service"
343 | const val COMMAND = "command"
344 | const val VALUE = "value"
345 | const val AUTH = "auth"
346 | const val AUTH_SUCCESS = "auth-success"
347 | const val AUTH_FAILED = "auth-failed"
348 | const val API_KEY_HEADER = "apiKey"
349 | private enum class Status{
350 | AUTHENTICATING,
351 | AWAITING_RESPONSE,
352 | AUTHENTICATED
353 | }
354 | }
355 | private var currentStatus = Status.AUTHENTICATING
356 | private val working = AtomicBoolean(true)
357 | private var socket: Socket? = null
358 | private var dataInputStream: DataInputStream? = null
359 | private var dataOutputStream: DataOutputStream? = null
360 | private lateinit var ip:String
361 | private var port:Int = 0
362 | private val runnable = Runnable {
363 | try {
364 | val ip = InetAddress.getByName(ip)
365 | socket = Socket(ip, port)
366 | dataInputStream = DataInputStream(socket!!.getInputStream())
367 | dataOutputStream = DataOutputStream(socket!!.getOutputStream())
368 | while (working.get()) {
369 | try {
370 | when (currentStatus){
371 | Status.AUTHENTICATING -> {
372 | // Send initial auth request
373 | val jsonObj = JSONObject()
374 | jsonObj.put(COMMAND , AUTH)
375 | jsonObj.put(API_KEY_HEADER , API_KEY)
376 | dataOutputStream!!.writeUTF(jsonObj.toString())
377 | currentStatus = Status.AWAITING_RESPONSE
378 | Thread.sleep(100L)
379 | }
380 | Status.AWAITING_RESPONSE -> {
381 | if (dataInputStream!!.available() > 0){
382 | val nextCommand = JSONObject(dataInputStream!!.readUTF())
383 | if (nextCommand.has(COMMAND)) {
384 | when (nextCommand.get(COMMAND)){
385 | AUTH_SUCCESS -> {
386 | currentStatus = Status.AUTHENTICATED
387 | }
388 | AUTH_FAILED -> {
389 | // todo
390 | }
391 | }
392 |
393 | }
394 | }
395 | else{
396 | Thread.sleep(100L)
397 | }
398 | }
399 | Status.AUTHENTICATED -> {
400 | // This part reads the messages after we are authenticated
401 | // as this is on android, the readUTF() function performes reading the length
402 | // of the message and all the convertions out of the box
403 | while (dataInputStream!!.available() > 0){
404 | val data = dataInputStream!!.readUTF()
405 | Log.i("ArctopData", "Received: $data")
406 | }
407 | Thread.sleep(1000L)
408 | }
409 |
410 | }
411 |
412 | } catch (e: IOException) {
413 | e.printStackTrace()
414 | try {
415 | dataInputStream!!.close()
416 | dataOutputStream!!.close()
417 | } catch (ex: IOException) {
418 | ex.printStackTrace()
419 | }
420 | } catch (e: InterruptedException) {
421 | e.printStackTrace()
422 | try {
423 | dataInputStream!!.close()
424 | dataOutputStream!!.close()
425 | } catch (ex: IOException) {
426 | ex.printStackTrace()
427 | }
428 | }
429 | }
430 | try {
431 | dataInputStream!!.close()
432 | dataOutputStream!!.close()
433 | } catch (ex: IOException) {
434 | ex.printStackTrace()
435 | }
436 | } catch (e: IOException) {
437 | e.printStackTrace()
438 | }
439 | }
440 |
441 | override fun onCreate(savedInstanceState: Bundle?) {
442 | super.onCreate(savedInstanceState)
443 | // get the port and ip off the intent
444 | ip = intent.getStringExtra(IP)!!
445 | port = intent.getStringExtra(PORT)!!.toInt()
446 | Thread(runnable).start()
447 | }
448 | override fun onDestroy() {
449 | working.set(false)
450 | super.onDestroy()
451 | }
452 | ```
453 |
454 |
455 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | arctopSDK/README.md
--------------------------------------------------------------------------------
/arctopSDK/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/arctopSDK/README.md:
--------------------------------------------------------------------------------
1 | # Arctop™ Android Software Development Kit (SDK)
2 |
3 | Arctop's SDK repository contains everything you need to connect your application to Arctop’s services.
4 |
5 | ## Background
6 |
7 | Arctop is a software company that makes real-time cognition decoding technology. Arctop's software applies artificial intelligence to electric measurements of brain activity, translating people’s feelings, reactions, and intent into actionable data that empower applications with new capabilities. Since its founding in 2016, Arctop has worked to develop a cross-platform SDK that provides noninvasive brain-computer interface capabilities. The SDK is being developed continuously and is the result of deep R&D, such as [this peer-reviewed study](https://www.frontiersin.org/articles/10.3389/fncom.2021.760561/full) published in _Frontiers in Computational Neuroscience_ where Arctop's SDK was used in a personalized audio application.
8 |
9 | The current version of the Arctop SDK provides four unique cognition data streams: focus, enjoyment, flow, and sleep state. It also provides body data streams including eye blinks, heart rate, and heart rate variability. All data streams are provided in real-time, meaning applications receive new data points several times per second.
10 |
11 | In short, Arctop's SDK gives the ability to add new streams of personalized cognition data directly into applications.
12 |
13 | Examples of how this data can be used include:
14 | * Developing neurology, psychiatry, and psychology applications.
15 | * Enabling new modes of communication and accessibility features.
16 | * Creating high performance training scenarios that are more effective with cognition data in the feedback loop.
17 | * Building generative AI applications to improve foundation models and add empathy and responsiveness to individual preferences in the moment.
18 | * Gaining play-by-play insights into how users respond to products.
19 | * Tracking personal brain health measurements from home.
20 | * Developing novel headwear for gamers.
21 | * Creating adaptive audio applications that tailor sound to user mood.
22 |
23 | One way Arctop achieves its high performance analysis is by calibrating data processing models to each new user. This is done through a one-time 10-minute session in Arctop's mobile app that consists of a series of interactive tasks and questions. The calibration process is important since it allows Arctop's AI models of brain function to be individually customized to each user's unique brain data baseline and deliver personalized dynamics measures that are accurate.
24 |
25 | After the one-time calibration, real-time cognition metrics for endless usage are unlocked. For more information about calibration, see the section titled [Verify That a User Has Been Calibrated for Arctop](#2-verify-that-a-user-has-been-calibrated-for-arctop) within this file.
26 |
27 |
28 | ## Installation
29 |
30 | To add the SDK to your project use **ONE** of the following methods:
31 |
32 | 1. Use [JitPack](https://jitpack.io/private#arctop/android-sdk) to install the SDK as a dependency with gradle or maven. (Recommended)
33 | 2. Clone the repository locally, and add it as a module into your Android project.
34 |
35 |
36 | ## Package Structure
37 | The SDK contains the following components.
38 |
39 | * The _ArctopSDK.java_ class with all constants used to communicate with your application
40 |
41 | * A set of AIDL files, which define the interface of communication with the service:
42 | * _IArctopSdk.aidl_ provides the public service API accessible by your app.
43 | * _IArctopSdkListener.aidl_ defines the callback interface by which the service provides responses and notifications back to your app.
44 | * _IArctopQAListener.aidl_ defines a callback interface that provides QA values from the sensor device to your app.
45 | * _IArctopSessionUploadListener.aidl_ defines a callback interface that provides session upload monitoring.
46 |
47 | ## Workflow
48 |
49 | > **_GENERAL NOTE:_** The SDK is designed to work in a specific flow.
50 | > The Setup Phase only needs to be done once, as long as your application is running. The Session Phase can be done multiple times.
51 |
52 |
53 |
54 | ### Setup Phase
55 |
56 | 1. [Prerequisites](#1-prerequisites)
57 | 2. [Permissions](#2-permissions)
58 | 3. [Bind to the Service](#3-bind-to-the-service)
59 | 4. [Initialize the SDK with Your API Key](#4-initialize-the-sdk-with-your-api-key)
60 | 5. [Register for Callbacks](#5-register-for-callbacks)
61 |
62 | ### Session Phase
63 |
64 | 1. [Verify That a User is Logged In](#1-verify-that-a-user-is-logged-in)
65 | 2. [Verify That a User Has Been Calibrated for Arctop](#2-verify-that-a-user-has-been-calibrated-for-arctop)
66 | 3. [Connect to an Arctop Sensor Device](#3-connect-to-an-arctop-sensor-device)
67 | 4. [Verify Signal Quality of Device](#4-verify-signal-quality-of-device)
68 | 5. [Begin a Session](#5-begin-a-session)
69 | 6. [Work with Session Data](#6-work-with-session-data)
70 | 7. [Finish Session](#7-finish-session)
71 |
72 | ### Cleanup Phase
73 |
74 | 1. [Shut Down the SDK](#1-shut-down-the-sdk)
75 | 2. [Unbind From the Service](#2-unbind-from-the-service)
76 |
77 |
78 | ## Phase Instructions
79 | ### Setup Phase
80 |
81 | #### 1. Prerequisites
82 |
83 | ###### Mobile App
84 | To use the Arctop SDK, you'll need to install the Arctop app on your mobile device. The Arctop app is available on both the App Store (iOS) and Google Play (Android) and can be found by searching "Arctop".
85 |
86 | After downloading the mobile app, use the Sign Up screen to create an account. Follow instructions in the _Supplementary User Instructions_ document provided to you via email for guidance on how to set up and use the mobile app for Arctop streaming.
87 |
88 | ###### API Key
89 | You will also need to create an API key in order to use the Arctop SDK with your app. To do so, please submit a request via the Arctop DevKit form provided to you in the “Welcome” email. Feel free to contact us at support@arctop.com with any questions you may have.
90 |
91 | #### 2. Permissions
92 |
93 | Before binding to Arctop and receiving data, you will need to request permissions from the user.
94 | In your _AndroidManifest.XML_, declare that you will be using the ARCTOP_DATA permission:
95 |
96 |
97 |
98 | Then, at runtime, verify that you have that permission, or request it from the user, as per:
99 |
100 | [Requesting Runtime Permissions Reference in Android Developer Guide](https://developer.android.com/training/permissions/requesting)
101 |
102 | #### 3. Bind to the Service
103 |
104 | The SDK revolves around a single service entry point that your application will need to bind to.
105 | Note that Arctop's service currently allows only 1 app to connect at a time, so make sure you have only 1 app binding to the SDK at any time.
106 |
107 | In order to perform the bind, you will need to declare that your application will query the service package.
108 | This is done by adding this snippet into your _AndroidManifest.XML_
109 |
110 |
111 |
112 |
113 |
114 | [Query Element Reference in Android Developer Guide](https://developer.android.com/guide/topics/manifest/queries-element)
115 |
116 | Then, in your code, locate the service, and perform the binding.
117 |
118 | void doBindService() {
119 | try {
120 | // Create an intent based on the class name
121 | Intent serviceIntent = new Intent(IArctopSdk.class.getName());
122 | // Use package manager to find intent reciever
123 | List matches=getPackageManager()
124 | .queryIntentServices(serviceIntent, 0);
125 | if (matches.size() == 0) {
126 | Log.d(TAG, "Cannot find a matching service!");
127 | Toast.makeText(this, "Cannot find a matching service!",
128 | Toast.LENGTH_LONG).show();
129 | }
130 | else if (matches.size() > 1) {
131 | // This is really just a sanity check
132 | // and should never occur in a real life scenario
133 | Log.d(TAG, "Found multiple matching services!");
134 | Toast.makeText(this, "Found multiple matching services!",
135 | Toast.LENGTH_LONG).show();
136 | }
137 | else {
138 | // Create an explicit intent
139 | Intent explicit=new Intent(serviceIntent);
140 | ServiceInfo svcInfo=matches.get(0).serviceInfo;
141 | ComponentName cn=new ComponentName(svcInfo.applicationInfo.packageName,
142 | svcInfo.name);
143 | explicit.setComponent(cn);
144 | // Bind using AUTO_CREATE
145 | if (bindService(explicit, mConnection, BIND_AUTO_CREATE)){
146 | Log.d(TAG, "Bound to Arctop Service");
147 | } else {
148 | Log.d(TAG, "Failed to bind to Arctop Service");
149 | }
150 | }
151 | } catch (Exception e) {
152 | Log.e(TAG, "can't bind to ArctopService, check permission in Manifest");
153 | }
154 | }
155 |
156 | Your application will also need to create a _ServiceConnection_ class that will handle connection and disconnection responses.
157 |
158 | private ServiceConnection mConnection = new ServiceConnection() {
159 | public void onServiceConnected(ComponentName className,
160 | IBinder service) {
161 | Log.d(TAG, "Attached.");
162 | // Once connected, you can cast the binder object
163 | // into the proper type
164 | mService = IArctopSdk.Stub.asInterface(service);
165 | //And start interacting with the service.
166 | try {
167 | // Initialize the service API with your API key
168 | int response = mService.initializeArctop(API_KEY);
169 | if ( response == ArctopSDK.ResponseCodes.SUCCESS){
170 | // Register for service callbacks
171 | response = mService.registerSDKCallback(mCallback);
172 | if ( response == ArctopSDK.ResponseCodes.SUCCESS){
173 | // Service is ready to work with
174 | }
175 | }
176 | } catch (Exception e) {
177 | Log.e(TAG, e.getLocalizedMessage());
178 | }
179 | }
180 |
181 | public void onServiceDisconnected(ComponentName className) {
182 | // This will be called when an Unexpected disconnection happens.
183 | Log.d(TAG, "Detached.");
184 | }
185 | };
186 |
187 | More information on bound services can be found in the [Android Developer Guide](https://developer.android.com/guide/components/bound-services).
188 |
189 | #### 4. Initialize the SDK With Your API Key
190 |
191 | You need an API key to use the Arctop SDK in your app (see [Prerequisites](#1-prerequisites)). Once you have your API key and are ready to start working with the service, you will need to initialize the SDK.
192 |
193 | This can be done with your API key by calling **initializeArctop(API_KEY)** method of the service. The service will return a response code letting you know if it successfully initialized or if there is an error.
194 |
195 | #### 5. Register for Callbacks
196 |
197 | The service runs in its own process and not the calling application process, so some of the calls inside _IArctopSdk.aidl_ are defined as _oneway_. This effectively creates an asynchronous call into the service process which returns immediately. The service reports results via the _IArctopSdkListener.aidl_ interface.
198 |
199 | The **onSdkError(int errorCode, String message)** call is available for reporting errors back to the calling application.
200 |
201 | For more information on the AIDL interface, and calling IPC methods, visit the [Android Developer Guide](https://developer.android.com/guide/components/aidl#Calling).
202 |
203 | The callback interface is defined in [_IArctopSdkListener.aidl_](https://github.com/arctop/android-sdk/blob/25fdc767f6f9ca0b1f5d33a6515bc7f954267566/arctopSDK/src/main/aidl/com/arctop/IArctopSdkListener.aidl).
204 |
205 | Note that this entire interface is defined as _oneway_ as all calls from the service work via a messaging thread, and do not wait for the listener to perform any actions before returning and notifying the next listener in line.
206 |
207 | Implementing the interface is as simple as creating a private class deriving from *IArctopSdkListener.Stub()*
208 |
209 | private final IArctopSdkListener mCallback = new IArctopSdkListener.Stub() {
210 | // Implement your interface here
211 | }
212 |
213 | ### Session Phase
214 |
215 | #### 1. Verify That a User is Logged In
216 |
217 | After successful initialization, your first step is to verify a user is logged into the Arctop mobile application.
218 |
219 | You can query the service to get the logged in status of a user.
220 | In case a user is not logged in, launch an intent that will take the user to the Login / Sign Up screen of the Arctop mobile app.
221 |
222 | int status = mService.getUserLoginStatus();
223 | switch (status){
224 | case ArctopSDK.LoginStatus.LOGGED_IN:{
225 | Log.i(TAG, "login: Logged In");
226 | break;
227 | }
228 | case ArctopSDK.LoginStatus.NOT_LOGGED_IN:{
229 | Log.i(TAG, "login: Not Logged In");
230 | launchHome();
231 | break;
232 | }
233 | }
234 |
235 | To launch the login page of the Arctop app, start an activity with the following intent:
236 |
237 | Intent activityIntent = new Intent(ArctopSDK.ARCTOP_LOGIN);
238 |
239 | The Arctop login activity will report close and report a result once complete.
240 | You can either listen to that request or check the login status again.
241 |
242 | #### 2. Verify That a User Has Been Calibrated for Arctop
243 |
244 | Before Arctop services can be used in any sessions, a user must complete calibration to generate their personal Arctop model. This is done via the Arctop mobile app.
245 |
246 | Calibration takes approximately 10 minutes and only needs to be completed once per user. It consists of five short tasks (1-3 minutes each) and is performed while wearing a compatible headwear device to record brain signals throughout. At the end of each task, users are asked to rank their experience using slider scales and short questionnaires.
247 |
248 | This process is important for Arctop’s software to learn individual users' unique patterns and tune its algorithms to be as accurate and robust as possible.
249 |
250 | The best practices users should follow when completing calibration are listed below.
251 | * Before starting the calibration session:
252 | * Go to a quiet place where you will not be interrupted for 10 minutes.
253 | * Unplug your headwear and mobile device from any chargers.
254 | * During calibration:
255 | * Try to move as little as possible. Headwear sensors are very sensitive to motion.
256 | * Do not eat, drink, or close your eyes. It is alright to blink normally.
257 | * Do not multitask or exit the Arctop app. Focus all of your efforts on the tasks presented.
258 | * Complete the session within one sitting and in the same location. Moving around too much during calibration will impact results.
259 | * Answer all questions honestly, related to how you felt during the tasks. Calibration takes into account individual user feedback so answer as accurately as you can.
260 |
261 | To verify that a user is calibrated, call the service to check the status:
262 |
263 | mService.checkUserCalibrationStatus();
264 |
265 | In case the user is not calibrated, launch an intent to send the user into the calibration:
266 |
267 | Intent activityIntent = new Intent(ArctopSDK.ARCTOP_CALIBRATION);
268 | activityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
269 | startActivity(activityIntent)
270 |
271 | #### 3. Connect to an Arctop Sensor Device
272 |
273 | Connecting to an Arctop sensor device, for example a headband, is accomplished by calling **connectSensorDevice(String macAddress)**.
274 | Available in the SDK is the _PairingActivity_ class, which handles scanning and extracting the device's MAC address using builtin CompanionDeviceManager. You can launch the activity using the following code:
275 |
276 | Intent activityIntent = new Intent(ArctopSDK.ARCTOP_PAIR_DEVICE);
277 |
278 | The activity will dispatch a broadcast once the user has selected a device. You will need to create a BroadcastReceiver that will be called with the result.
279 |
280 | private class DevicePairingBroadcastReceiver extends BroadcastReceiver {
281 | @Override
282 | public void onReceive(Context context, Intent intent) {
283 | String address = intent.getStringExtra(ArctopSDK.DEVICE_ADDRESS_EXTRA);
284 | Log.d(TAG, "Connection Intent : " + address);
285 | try {
286 | // Tell the service to initiate a connection to the selected device
287 | mService.connectSensorDevice(address);
288 | } catch (RemoteException e) {
289 | Log.e(TAG, e.getLocalizedMessage());
290 | }
291 | }
292 | }
293 |
294 | Before launching the activity you will need to register for the broadcast.
295 |
296 | devicePairingBroadcastReceiver = new DevicePairingBroadcastReceiver();
297 | registerReceiver(devicePairingBroadcastReceiver,
298 | new IntentFilter(ArctopSDK.IO_ARCTOP_DEVICE_PAIRING_ACTION));
299 |
300 | The call to **connectSensorDevice(String macAddress)** will initiate a series of callbacks to **onConnectionChanged(int previousConnection ,int currentConnection)**
301 |
302 | The values for _previousConnection_ and _currentConnection_ are defined in the _ArctopSDK.java_ class.
303 | Once the value of _currentConnection == ArctopSDK.ConnectionState.CONNECTED_, you may begin a session.
304 |
305 | #### 4. Verify Signal Quality of Device
306 |
307 | Once the device is connected, you should verify that the user's headwear is receiving proper signal.
308 |
309 | The easiest way is to launch the QA activity bundled along with the app. This frees you from implementing your own version, and provides constants that are verified before the activity returns its result.
310 | The screen also verifies that the user isn't actively charging their device, which creates noise in the signal readings.
311 |
312 | Create an intent to launch the screen:
313 |
314 | Intent activityIntent = new Intent(ArctopSDK.ARCTOP_QA_SCREEN);
315 |
316 | Add extras into the intent to denote you want it to be stand alone (required, or it won't work):
317 |
318 | activityIntent.putExtra(ArctopQAProperties.STAND_ALONE , true);
319 |
320 | Add properties for the screen to verify:
321 |
322 | activityIntent.putExtra(ArctopQAProperties.TASK_PROPERTIES ,
323 | new ArctopQAProperties(ArctopQAProperties.Quality.Good , ArctopQAProperties.INFINITE_TIMEOUT));
324 |
325 | The [_ArctopQAProperties_](https://github.com/arctop/android-sdk/blob/25fdc767f6f9ca0b1f5d33a6515bc7f954267566/arctopSDK/src/main/java/com/arctop/ArctopQAProperties.kt) class contains further explanation on different settings you can use to define your user's QA experience.
326 |
327 | Optionally, you can add a flag to run the screen in debug mode. This is helpful when developing your apps.
328 | It provides 2 features that aren't available in a release setting:
329 |
330 | 1. There will be a "Continue" button at the bottom of the screen, allowing you to simulate successful QA.
331 | 2. Once you dismiss the "Please unplug your device" dialog, it will not show up again, and you can continue working with the device connected.
332 |
333 | Adding the flag is done as follows:
334 |
335 | activityIntent.putExtra(ArctopQAProperties.RUN_IN_DEBUG , true);
336 |
337 |
338 | The activity will call finish() with either RESULT_OK or RESULT_CANCELED, which you can use to determine your next steps.
339 |
340 | #### 5. Begin a Session
341 |
342 | For a calibrated user, call **startPredictionSession(String predictionName)** to begin running the Arctop real-time prediction service.
343 | You can find the predictions in **ArctopSDK.Predictions**.
344 |
345 | #### 6. Work with Session Data
346 |
347 | At this point, your app will receive results via the **onValueChanged(String key,float value)** callback.
348 |
349 | Users will also be provided with a post-session report and CSV files containing metric data, timestamps, and tags from their most recent session. Reports and CSV files for each session will automatically upload to their Developer Portal. Users can access their centralized session history within their Developer Portal at any time.
350 |
351 | Arctop's focus, enjoyment, flow, sleep, and eye blink metrics are derived exclusively from brain data, while heart rate and heart rate variability metrics are derived from body data.
352 |
353 | Focus, enjoyment, and flow data is provided within the "...Cognition" CSV file. These values range from 0-100. The neutral point for each user is at 50, meaning that values above 50 reflect high levels of the measured quantity. For example, a 76 in focus is a high level of focus, while a 99 is nearly the highest focus that can be achieved. Values below 50 represent the opposite, meaning lack of focus or lack of enjoyment or lack of flow. For example, a 32 in focus is a lower level that reflects the user may not be paying much attention, while a 12 in enjoyment can mean the user dislikes the current experience. A value of 23 in flow indicates that the user is not in a high flow state.
354 |
355 | Sleep data is presented in binary values of 0 or 1 in the "Sleep Detection" column of the provided CSV data file ("...Sleep"). This information tells whether a user is detected to be asleep or awake at each timestamp, with the awake state indicated by a 0 and asleep state indicated by a 1. Focus, enjoyment, flow, and blink metrics are currently unavailable during sleep sessions. No report will be generated for sleep sessions at this time. Additional sleep metrics will be provided in a future version.
356 |
357 | Eye blink values are also recorded as a binary. The presence of a blink will be indicated by a value of 1 within the "...Blinks" CSV data file. Blink data for each individual eye will be provided in a future version.
358 |
359 | Within the "...Heart Rate" CSV file, heart rate data is provided in units of beats per minute and heart rate variability (HRV) data is provided in units of milliseconds. Heart rate is currently capped at 120 beats per minute; values over this will be displayed as "120".
360 |
361 | Any tags added during a session will be provided with their timestamps corresponding to when the user initiated the tag creation. This data is displayed in the "...Tags" CSV file. This file will only be present if tags were added during the session.
362 |
363 | Excessively noisy data that cannot be decoded accurately in Arctop’s SDK is represented as either -1 or NaN. These values should be ignored as they reflect low confidence periods of analysis. This can occur for many reasons, such as a lapse in sensor connection to the skin. If you notice an excess of -1 or NaN values in your data, please contact Arctop for support as these values typically only occur on a limited basis.
364 |
365 | Signal QA is reported via **onQAStatus(boolean passed ,int type)** callback. If QA failed during the analysis window, the **passed** parameter will be false, and the type of failure will be reported in **type**. Valid types are defined in **QAFailureType** class inside [ArctopSDK.java](https://github.com/arctop/android-sdk/blob/25fdc767f6f9ca0b1f5d33a6515bc7f954267566/arctopSDK/src/main/java/com/arctop/ArctopSDK.java).
366 |
367 | #### 7. Finish Session
368 |
369 | When you are ready to complete the session, call **finishSession()**. The session will close and notify your app when done via **onSessionComplete()** callback.
370 | You can use the **IArctopSessionUploadListener** interface to monitor the progress of uploading the session to the Arctop server. (Optional)
371 |
372 | ### Cleanup Phase
373 |
374 | #### 1. Shut Down the SDK
375 |
376 | Call **shutdownSdk()** to have Arctop release all of its resources.
377 |
378 | #### 2. Unbind From the Service
379 |
380 | Once the SDK is shut down, you can safely unbind from the service.
381 |
382 | ## Using the SDK Outside of Android and iOS
383 |
384 | Arctop™ provides a LAN webserver that allows non-Android, non-iOS clients access to the SDK data. For more info see [Stream Server Docs](https://github.com/arctop/android-sdk/blob/main/Arctop-Stream.md).
385 |
386 | It is highly recommended to first read through this documentation to have a better understanding of the SDK before trying to work with the stream server.
387 |
388 |
--------------------------------------------------------------------------------
/arctopSDK/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'kotlin-android'
4 | id 'kotlin-parcelize'
5 | id 'maven-publish'
6 | }
7 |
8 |
9 | android {
10 | compileSdk 34
11 | namespace "com.arctop.sdk"
12 | defaultConfig {
13 | minSdkVersion 29
14 | targetSdkVersion 34
15 | versionCode 1
16 | versionName "1.0"
17 | consumerProguardFiles "consumer-rules.pro"
18 | }
19 |
20 | buildTypes {
21 | release {
22 | minifyEnabled false
23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
24 | }
25 | }
26 | compileOptions {
27 | sourceCompatibility JavaVersion.VERSION_1_8
28 | targetCompatibility JavaVersion.VERSION_1_8
29 | }
30 | kotlinOptions {
31 | jvmTarget = "1.8"
32 | }
33 | buildFeatures {
34 | aidl true
35 | }
36 | }
37 |
38 | dependencies {
39 | //sentry
40 | implementation 'io.sentry:sentry-android:5.6.0'
41 | }
42 |
--------------------------------------------------------------------------------
/arctopSDK/consumer-rules.pro:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/arctop/Android-SDK/a33d2be6c63c3c09f00a6cbd2ff7787ff8d6b275/arctopSDK/consumer-rules.pro
--------------------------------------------------------------------------------
/arctopSDK/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
--------------------------------------------------------------------------------
/arctopSDK/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/arctopSDK/src/main/aidl/com/arctop/IArctopSdk.aidl:
--------------------------------------------------------------------------------
1 | // IArctopSdk.aidl
2 | package com.arctop;
3 | import com.arctop.IArctopSdkListener;
4 |
5 | interface IArctopSdk {
6 |
7 | int initializeArctop(in String apiKey);
8 | /**
9 | * Shuts down the sdk and releases resources
10 | */
11 | void shutdownSdk();
12 | /**
13 | * Retrieves the user's login status.
14 | * @return int value from {@link ArctopSDK#LoginStatus}
15 | * */
16 | int getUserLoginStatus();
17 |
18 | /**
19 | * Checks the current user's calibration status
20 | * only calibrated users with available models can run predictions
21 | * @return int value from {@link ArctopSDK#UserCalibrationStatus}
22 | */
23 | int checkUserCalibrationStatus();
24 | /**
25 | * Begins a prediction session for the desired prediction
26 | * @param predictionName the prediction component's name / key to run
27 | * @return int value from {@link ArctopSDK#ResponseCodes}
28 | */
29 | int startPredictionSession(in String predictionName);
30 | /**
31 | * Finishes a running prediction session.
32 | * This will close out all the data files and upload them to arctopCloud
33 | * calls {@link IArctopSdkListener#onSessionComplete()} once the operation completed
34 | * the return code only pertains to the session close functionality, and is used to validate
35 | * that your app's call was accepted. You should still listen for the callback to complete.
36 | * @return int value from {@link ArctopSDK#ResponseCodes}
37 | */
38 | int finishSession();
39 | /**
40 | * Requests a marker to be written into the current session's data files
41 | * Markers will be written with current timestamp
42 | * @param line extra data line, can be plain text or JSON encoded values
43 | */
44 | void writeUserMarker(in String line);
45 | /**
46 | * Requests a marker to be written into the current session's data files with a specified timestamp
47 | * @param line extra data line, can be plain text or JSON encoded values
48 | * @param timeStamp unix time stamp in MS to use for marker
49 | */
50 | void writeUserTimedMarker(in String line , in long timeStamp);
51 | /**
52 | * Registers for SDK callbacks
53 | * @param listener IArctopSdkListener implementation
54 | * @return int value from {@link ArctopSDK#ResponseCodes}
55 | */
56 | int registerSDKCallback(in IArctopSdkListener listener);
57 | /**
58 | * Unregisters from SDK callbacks
59 | * @param listener previously registered listener
60 | */
61 | void unregisterSDKCallback(in IArctopSdkListener listener);
62 | /**
63 | * Requests connection to a sensor device via it's MAC Address
64 | * connection status is reported back via {@link IArctopSdkListener#onConnectionChanged(int previousConnection ,int currentConnection)}
65 | * @param macAddress the device's MAC address to attempt connection to
66 | */
67 | oneway void connectSensorDevice(in String macAddress);
68 | /**
69 | * Requests a disconnect from currently connected sensor device
70 | * connection status is reported back via {@link IArctopSdkListener#onConnectionChanged(int previousConnection ,int currentConnection)}
71 | */
72 | oneway void disconnectSensorDevice();
73 | /**
74 | * Starts a scan for headwear devices.
75 | * Device list is reported back via {@link IArctopSdkListener#onDeviceList(in Map deviceList)}
76 | */
77 | oneway void scanForDevices();
78 |
79 | int logoutUser();
80 | }
--------------------------------------------------------------------------------
/arctopSDK/src/main/aidl/com/arctop/IArctopSdkListener.aidl:
--------------------------------------------------------------------------------
1 | // IArctopSdkListener.aidl
2 | package com.arctop;
3 |
4 | /**
5 | * SDK Listener interface.
6 | * Provides callbacks from service into client
7 | */
8 | oneway interface IArctopSdkListener {
9 | /**
10 | * Reports headband connection status changes.
11 | * See {@link ArctopSDK#ConnectionState} for valid values
12 | * @param previousConnection the previous connection status
13 | * @param currentConnection the current connection status
14 | */
15 | void onConnectionChanged(in int previousConnection ,in int currentConnection);
16 |
17 | /**
18 | * Reports a value changed during prediciton.
19 | * @param key the value's key name {@link ArctopSDK#PredictionValues}
20 | * @param value the current value
21 | */
22 | void onValueChanged(in String key,in float value);
23 | /**
24 | * Reports QA status during prediction
25 | * See {@link ArctopSDK#QAFailureType}
26 | * @param passed did QA pass for current run
27 | * @param type if QA failed, provides the reason for failure
28 | */
29 | void onQAStatus(in boolean passed ,in int type);
30 | /**
31 | * Notifies client that a running session has completed
32 | */
33 | void onSessionComplete();
34 | /**
35 | * Callback for SDK errors encountered during opertion
36 | * see {@link ArctopSDK#ResponseCodes} for valid codes
37 | * @param errorCode the current error code
38 | * @param message extra data on the error
39 | */
40 | void onError(in int errorCode ,in String message);
41 | /**
42 | * Reports the found devices from a scan
43 | * @param Map deviceList - Hashmap with device name / mac address
44 | */
45 | void onDeviceList(in Map deviceList);
46 | /**
47 | * Reports the signal quality values for each
48 | * electrode on the headband.
49 | * 0 - perfect quality
50 | * 113 - no signal
51 | * reports a string deliniated by commas, one per electrode
52 | * 0 -> TP9
53 | * 1 -> AF7
54 | * 2 -> AF8
55 | * 3 -> TP10
56 | */
57 | void onSignalQuality(in String quality);
58 | }
--------------------------------------------------------------------------------
/arctopSDK/src/main/java/com/arctop/ArctopQAProperties.kt:
--------------------------------------------------------------------------------
1 | package com.arctop
2 |
3 | import android.os.Parcelable
4 | import kotlinx.parcelize.Parcelize
5 |
6 | /***
7 | * Data class the provide QA properties when launching QA from third party client
8 | * Include this as extras in your intent
9 | * You can set a maximum timeout to receive allow the user to get the requested quality
10 | * If timeout expires, RESULT_CANCELED will be returned from the activity.
11 | * Otherwise RESULT_OK will return
12 | */
13 | @Parcelize
14 | data class ArctopQAProperties(
15 | val quality:Quality,
16 | val maxTimeout:Float = INFINITE_TIMEOUT
17 |
18 | ) : Parcelable {
19 | /**
20 | * Defines the quality of signal you wish to verify before QA screen returns.
21 | * */
22 | enum class Quality { Perfect, Good, Normal }
23 | companion object {
24 | const val TASK_PROPERTIES: String = "properties"
25 | const val STAND_ALONE:String = "standalone"
26 | const val RUN_IN_DEBUG:String = "debug"
27 | const val INFINITE_TIMEOUT:Float = 0f
28 | const val APPLICATION_NAME:String = "appname"
29 | const val APPLICATION_CATEGORY:String = "appcategory"
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/arctopSDK/src/main/java/com/arctop/ArctopSDK.java:
--------------------------------------------------------------------------------
1 | package com.arctop;
2 |
3 | /**
4 | * arctopSDK Constants definitions
5 | * Holds all values arriving via {@link IArctopSdkListener} AIDL callback methods
6 | * */
7 | public final class ArctopSDK {
8 |
9 | /**
10 | * Connection values.
11 | * These arrive via {@link IArctopSdkListener#onConnectionChanged(int, int)}
12 | **/
13 | public static final class ConnectionState {
14 | public static final int UNKNOWN = 0;
15 | public static final int CONNECTING = 1;
16 | public static final int CONNECTED = 2;
17 | public static final int CONNECTION_FAILED = 3;
18 | public static final int DISCONNECTED = 4;
19 | public static final int DISCONNECTED_UPON_REQUEST = 5;
20 | }
21 |
22 | /**
23 | * Response Codes.
24 | * These arrive via {@link IArctopSdkListener#onError(int, String)}
25 | * Or are returned as responses from functions
26 | **/
27 |
28 | public static final class ResponseCodes {
29 | public static final int UNKNOWN_ERROR = -200;
30 | public static final int NOT_ALLOWED = -100;
31 | public static final int SUCCESS = 0;
32 | public static final int NOT_INITIALIZED = -1;
33 | public static final int ALREADY_INITIALIZED = -2;
34 | public static final int API_KEY_ERROR = -3;
35 | public static final int MODEL_DOWNLOAD_ERROR = -4;
36 | public static final int SESSION_UPDATE_FAILURE = -5;
37 | public static final int SESSION_UPLOAD_FAILURE = -6;
38 | public static final int USER_NOT_LOGGED_IN = -7;
39 | public static final int CHECK_CALIBRATION_FAILED = -8;
40 | public static final int SESSION_CREATE_FAILURE = -9;
41 | public static final int SERVER_CONNECTION_ERROR = -10;
42 | public static final int MODELS_NOT_AVAILABLE = -11;
43 | public static final int PREDICTION_NOT_AVAILABLE = -12;
44 | }
45 |
46 | /**
47 | * User login status results
48 | * Received via {@link IArctopSdk#getUserLoginStatus()}
49 | */
50 | public static final class LoginStatus {
51 | public static final int NOT_LOGGED_IN = 0;
52 | public static final int LOGGED_IN = 1;
53 | }
54 |
55 | /**
56 | * User calibration status results
57 | * Received via {@link IArctopSdk#checkUserCalibrationStatus()}
58 | */
59 | public static final class UserCalibrationStatus {
60 | public static final int NEEDS_CALIBRATION = 0;
61 | public static final int CALIBRATION_DONE = 1;
62 | public static final int MODELS_AVAILABLE = 2;
63 | public static final int BLOCKED = 4;
64 | }
65 |
66 | /**
67 | * QA Failure type results
68 | * Recieved via {@link IArctopSdkListener#onQAStatus(boolean, int)}
69 | * */
70 | public static final class QAFailureType {
71 | public static final int HEADBAND_OFF_HEAD = 1;
72 | public static final int MOTION_TOO_HIGH = 2;
73 | public static final int EEG_FAILURE = 3;
74 | }
75 |
76 | /**
77 | * Names of predictions available
78 | * Use these when calling {@link IArctopSdk#startPredictionSession(String)}
79 | */
80 | public static final class Predictions {
81 | public static final String ZONE = "zone";
82 | public static final String GAME = "game_zone";
83 | public static final String SLEEP = "sleep";
84 | }
85 |
86 | /**
87 | * Names of prediction values available via {@link IArctopSdkListener#onValueChanged(String, float)}
88 | */
89 | public static final class PredictionValues{
90 | public static final String ZONE_STATE = "zone_state";
91 | public static final String FOCUS_STATE = "focus";
92 | public static final String ENJOYMENT_STATE = "enjoyment";
93 | public static final String AVG_MOTION = "avg_motion";
94 | public static final String HEART_RATE = "heart_rate";
95 | }
96 |
97 | /**
98 | * Values for session upload status messages
99 | * */
100 | public static final class UploadStatus {
101 | public static final int STARTING = 1;
102 | public static final int COMPRESSING = 2;
103 | public static final int UPLOADING = 3;
104 | public static final int SUCCESS = 4;
105 | public static final int FAILED = 5;
106 | }
107 |
108 | /*
109 | * Permission name constant
110 | * */
111 | public static final String ARCTOP_PERMISSION = "com.arctop.permission.ARCTOP_DATA";
112 |
113 | /**
114 | * Public activity names that can be launched by a client
115 | * */
116 | public static final String ARCTOP_PAIR_DEVICE = "com.arctop.PairDevice";
117 | public static final String ARCTOP_LOGIN ="com.arctop.ArctopLogin";
118 | public static final String ARCTOP_QA_SCREEN = "com.arctop.QAScreen";
119 | public static final String ARCTOP_CALIBRATION = "com.arctop.ArctopCalibration";
120 |
121 | /**
122 | * Constants used for pairing activity
123 | * */
124 | public static final String IO_ARCTOP_DEVICE_PAIRING_ACTION = "com.arctop.device_connect";
125 | public static final int SELECT_DEVICE_REQUEST_CODE = 66;
126 | public static final String DEVICE_ADDRESS_EXTRA = "deviceAddress";
127 | public static final String DEVICE_NAME_EXTRA = "deviceName";
128 |
129 | public static final String ARCTOP_LOGIN_RESULT = "com.arctop.arctop-login-result";
130 | }
131 |
--------------------------------------------------------------------------------
/arctopSDK/src/main/java/com/arctop/ArctopStreamService.kt:
--------------------------------------------------------------------------------
1 | package com.arctop
2 | object ArctopStreamService {
3 | /**
4 | * General Constants
5 | * */
6 | // Name of broadcasting service on the local network
7 | const val SERVICE_NAME = "ArctopService"
8 | // Service type / protocol for network discovery
9 | const val SERVICE_TYPE = "_http._tcp"
10 |
11 | /**
12 | * List of constants that can be sent / received as keys inside JSON objects over the
13 | * socket connection
14 | * */
15 | object StreamObjectKeys {
16 | // defines the command that is encapsulated in this JSON
17 | const val COMMAND = "command"
18 | // defines the times stamp in unix ms that the data has arrived from the prediction service
19 | // value is a long int
20 | const val TIME_STAMP = "timestamp"
21 | // key for previous connection state ( when COMMAND == CONNECTION)
22 | const val PREVIOUS = "previous"
23 | // key for current connection state ( when COMMAND == CONNECTION)
24 | const val CURRENT = "current"
25 | // key that holds the key name of the stream value ( when COMMAND == VALUE_CHANGED )
26 | const val KEY = "key"
27 | // key that holds the new value ( when COMMAND == VALUE_CHANGED )
28 | const val VALUE = "value"
29 | // key that holds the challenge request / response ( when COMMAND == CHALLENGE )
30 | const val CHALLENGE_DATA = "challenge_data"
31 | // key to use when sending apiKey to server ( when COMMAND == AUTH )
32 | const val API_KEY = "apiKey"
33 | // key that holds the passed value of QA data ( when COMMAND == QA )
34 | const val PASSED = "passed"
35 | // key that holds the type of QA failure if PASSED == false ( when COMMAND == QA )
36 | const val TYPE = "type"
37 | // key that holds the error code sent by an error command ( when COMMAND == ERROR )
38 | const val ERROR_CODE = "errorCode"
39 | // key that holds the error message sent by an error command ( when COMMAND == ERROR )
40 | const val MESSAGE = "message"
41 | }
42 |
43 | /**
44 | * Possible values that a COMMAND key can hold in a transported object
45 | * */
46 | object StreamCommandValues {
47 | // object describes a connection change
48 | const val CONNECTION = "connection"
49 | // object describes a value change
50 | const val VALUE_CHANGED = "valueChange"
51 | // object describes QA status
52 | const val QA = "qa"
53 | // object notifies that the session has completed
54 | const val SESSION_COMPLETE = "sessionComplete"
55 | // object describes an error
56 | const val ERROR = "error"
57 | // object for requesting authentication with server
58 | const val AUTH = "auth"
59 | // object contains auth challenge / response
60 | const val CHALLENGE = "challenge"
61 | // object notifies successful authentication
62 | const val AUTH_SUCCESS = "auth-success"
63 | // object notifies failed authentication
64 | const val AUTH_FAILED = "auth-failed"
65 | }
66 | }
67 |
68 |
--------------------------------------------------------------------------------
/arctopSDK/src/main/java/com/arctop/unity/ArctopUnityBridge.java:
--------------------------------------------------------------------------------
1 | package com.arctop.unity;
2 |
3 | import static android.content.Context.BIND_AUTO_CREATE;
4 | import static android.content.Context.RECEIVER_EXPORTED;
5 |
6 | import android.annotation.SuppressLint;
7 | import android.app.Activity;
8 | import android.content.BroadcastReceiver;
9 | import android.content.ComponentName;
10 | import android.content.Context;
11 | import android.content.Intent;
12 | import android.content.IntentFilter;
13 | import android.content.ServiceConnection;
14 | import android.content.pm.ActivityInfo;
15 | import android.content.pm.PackageManager;
16 | import android.content.pm.ResolveInfo;
17 | import android.content.pm.ServiceInfo;
18 | import android.os.Build;
19 | import android.os.IBinder;
20 | import android.os.RemoteException;
21 | import android.util.Log;
22 |
23 | import com.arctop.ArctopSDK;
24 | import com.arctop.IArctopSdk;
25 | import com.arctop.IArctopSdkListener;
26 | import java.util.HashMap;
27 | import java.util.List;
28 | import java.util.Map;
29 |
30 | /**
31 | * Unity bridge. Entry point for C# -> Java communication between service and Unity3D
32 | * */
33 | public class ArctopUnityBridge extends IArctopSdkListener.Stub {
34 | private final String TAG = "Arctop-Unity-Bridge";
35 | private IArctopSdk mService = null;
36 | private Activity mUnityActivity;
37 | private IArctopSdkCallback mSdkCallback;
38 | private IArctopServiceBindCallback mBindCallback;
39 | private LoginResultReceiver mLoginResultReceiver;
40 | private IArctopSdkSuccessOrFailureCallback mLoginCallback;
41 |
42 | private Map