├── .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 m_devicesMap = new HashMap<>(); 43 | // Called From C# to set the Activity Instance 44 | // This needs to be called after the C# side has requested the proper SDK permissions. 45 | public void setUnityActivity(Activity activity, IArctopServiceBindCallback callback) { 46 | mUnityActivity = activity; 47 | mBindCallback = callback; 48 | doBindService(); 49 | } 50 | 51 | public void setSdkCallback(IArctopSdkCallback callback){ 52 | mSdkCallback = callback; 53 | } 54 | 55 | private void doBindService() { 56 | try { 57 | // Create an intent based on the class name 58 | Intent serviceIntent = new Intent(IArctopSdk.class.getName()); 59 | // Use package manager to find intent receiver 60 | List matches=mUnityActivity.getPackageManager() 61 | .queryIntentServices(serviceIntent, 0); 62 | if (matches.isEmpty()) { 63 | Log.d(TAG, "Cannot find a matching service!"); 64 | if (mBindCallback != null){ 65 | mBindCallback.onFailure(IArctopServiceBindCallback.BindError.ServiceNotFound); 66 | } 67 | } 68 | else if (matches.size() > 1) { 69 | // This is really just a sanity check 70 | // and should never occur in a real life scenario 71 | Log.d(TAG, "Found multiple matching services!"); 72 | if (mBindCallback != null){ 73 | mBindCallback.onFailure(IArctopServiceBindCallback.BindError.MultipleServicesFound); 74 | } 75 | } 76 | else { 77 | // Create an explicit intent 78 | Intent explicit=new Intent(serviceIntent); 79 | ServiceInfo svcInfo=matches.get(0).serviceInfo; 80 | ComponentName cn=new ComponentName(svcInfo.applicationInfo.packageName, 81 | svcInfo.name); 82 | explicit.setComponent(cn); 83 | // Bind using AUTO_CREATE 84 | if (mUnityActivity.bindService(explicit, mConnection, BIND_AUTO_CREATE)){ 85 | Log.d(TAG, "Bound to Arctop Service"); 86 | } else { 87 | Log.d(TAG, "Failed to bind to Arctop Service"); 88 | // TODO: Verify this is the right error 89 | if (mBindCallback != null){ 90 | mBindCallback.onFailure(IArctopServiceBindCallback.BindError.PermissionDenied); 91 | } 92 | } 93 | } 94 | } catch (Exception e) { 95 | Log.e(TAG, "can't bind to ArctopService, check permission in Manifest"); 96 | } 97 | } 98 | 99 | private final ServiceConnection mConnection = new ServiceConnection() { 100 | public void onServiceConnected(ComponentName className, 101 | IBinder service) { 102 | mService = IArctopSdk.Stub.asInterface(service); 103 | try { 104 | mService.registerSDKCallback(ArctopUnityBridge.this); 105 | } catch (RemoteException e) { 106 | throw new RuntimeException(e); 107 | } 108 | if (mBindCallback != null){ 109 | mBindCallback.onSuccess(); 110 | } 111 | Log.d(TAG, "Unity connected to service"); 112 | } 113 | 114 | public void onServiceDisconnected(ComponentName className) { 115 | mService = null; 116 | Log.d(TAG, "Unity disconnected to service"); 117 | } 118 | }; 119 | 120 | public int arctopSDKInit(String apiKey){ 121 | try { 122 | return mService.initializeArctop(apiKey); 123 | } catch (RemoteException e) { 124 | throw new RuntimeException(e); 125 | } 126 | } 127 | 128 | public void arctopSDKShutdown() 129 | { 130 | try { 131 | mService.shutdownSdk(); 132 | mService.unregisterSDKCallback(this); 133 | mService = null; 134 | mLoginCallback = null; 135 | mSdkCallback = null; 136 | mUnityActivity = null; 137 | mBindCallback = null; 138 | 139 | } catch (RemoteException e) { 140 | throw new RuntimeException(e); 141 | } 142 | } 143 | private Intent getExplicitIntent(Intent activityIntent){ 144 | List matches = mUnityActivity.getPackageManager() 145 | .queryIntentActivities(activityIntent, PackageManager.MATCH_ALL); 146 | if (matches.isEmpty()){ 147 | return null; 148 | } 149 | Intent explicit = new Intent(activityIntent); 150 | ActivityInfo activityInfo = matches.get(0).activityInfo; 151 | ComponentName cn = new ComponentName(activityInfo.applicationInfo.packageName 152 | ,activityInfo.name); 153 | explicit.setComponent(cn); 154 | return explicit; 155 | } 156 | 157 | private class LoginResultReceiver extends BroadcastReceiver{ 158 | @Override 159 | public void onReceive(Context context, Intent intent) { 160 | mUnityActivity.unregisterReceiver(mLoginResultReceiver); 161 | // TODO: We don't really have a fail here. 162 | // TODO: If you fail it's handled in the other activity. 163 | mLoginCallback.onSuccess(); 164 | mLoginCallback = null; 165 | } 166 | } 167 | 168 | @SuppressLint("UnspecifiedRegisterReceiverFlag") 169 | public void arctopLaunchLogin(IArctopSdkSuccessOrFailureCallback callback){ 170 | Intent activityIntent = getExplicitIntent(new Intent(ArctopSDK.ARCTOP_LOGIN)); 171 | mLoginCallback = callback; 172 | mLoginResultReceiver = new LoginResultReceiver(); 173 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { 174 | mUnityActivity.registerReceiver(mLoginResultReceiver , 175 | new IntentFilter(ArctopSDK.ARCTOP_LOGIN_RESULT), RECEIVER_EXPORTED); 176 | } 177 | else{ 178 | mUnityActivity.registerReceiver(mLoginResultReceiver , 179 | new IntentFilter(ArctopSDK.ARCTOP_LOGIN_RESULT)); 180 | } 181 | 182 | mUnityActivity.startActivity(activityIntent); 183 | } 184 | 185 | public int arctopSDKIsUserLoggedIn() 186 | { 187 | try { 188 | return mService.getUserLoginStatus(); 189 | } catch (RemoteException e) { 190 | throw new RuntimeException(e); 191 | } 192 | } 193 | 194 | public int arctopSDKLogoutUser(){ 195 | try { 196 | return mService.logoutUser(); 197 | } catch (RemoteException e) { 198 | throw new RuntimeException(e); 199 | } 200 | } 201 | 202 | 203 | public int arctopSDKGetUserCalibrationStatus() 204 | { 205 | try { 206 | return mService.checkUserCalibrationStatus(); 207 | } catch (RemoteException e) { 208 | throw new RuntimeException(e); 209 | } 210 | } 211 | 212 | 213 | public void arctopSDKScanForDevices() 214 | { 215 | try { 216 | mService.scanForDevices(); 217 | } catch (RemoteException e) { 218 | throw new RuntimeException(e); 219 | } 220 | } 221 | 222 | 223 | public void arctopSDKConnectToDeviceId(String deviceId) 224 | { 225 | Object macAddress = m_devicesMap.get(deviceId); 226 | if (macAddress != null) { 227 | try { 228 | mService.connectSensorDevice(macAddress.toString()); 229 | } catch (RemoteException e) { 230 | throw new RuntimeException(e); 231 | } 232 | } 233 | else{ 234 | Log.d(TAG, "arctopSDKConnectToDeviceId: device not found"); 235 | } 236 | } 237 | 238 | 239 | public void arctopSDKDisconnectDevice() 240 | { 241 | try { 242 | mService.disconnectSensorDevice(); 243 | } catch (RemoteException e) { 244 | throw new RuntimeException(e); 245 | } 246 | } 247 | 248 | 249 | public int arctopSDKStartPredictions(String predictionName) 250 | { 251 | try { 252 | return mService.startPredictionSession(predictionName); 253 | } catch (RemoteException e) { 254 | throw new RuntimeException(e); 255 | } 256 | } 257 | 258 | 259 | public int arctopSDKEndPrediction() 260 | { 261 | try { 262 | return mService.finishSession(); 263 | } catch (RemoteException e) { 264 | throw new RuntimeException(e); 265 | } 266 | } 267 | 268 | 269 | public void arctopSDKWriteUserMarker(String markerData) 270 | { 271 | try { 272 | mService.writeUserMarker(markerData); 273 | } catch (RemoteException e) { 274 | throw new RuntimeException(e); 275 | } 276 | } 277 | 278 | @Override 279 | public void onConnectionChanged(int previousConnection, int currentConnection) throws RemoteException { 280 | if (mSdkCallback!=null) { 281 | mSdkCallback.ConnectionStatusCallback(previousConnection, currentConnection); 282 | } 283 | } 284 | 285 | @Override 286 | public void onValueChanged(String key, float value) throws RemoteException { 287 | if (mSdkCallback!=null) { 288 | mSdkCallback.ValueChangedCallback(key, value); 289 | } 290 | } 291 | 292 | @Override 293 | public void onQAStatus(boolean passed, int type) throws RemoteException { 294 | if (mSdkCallback!=null) { 295 | mSdkCallback.QAStatusCallback(passed,type); 296 | } 297 | } 298 | 299 | @Override 300 | public void onSessionComplete() throws RemoteException { 301 | if (mSdkCallback!=null) { 302 | mSdkCallback.SessionCompleteCallback(); 303 | } 304 | } 305 | 306 | @Override //TODO: rethink this in general. can we avoid this? 307 | public void onError(int errorCode, String message) throws RemoteException { 308 | // TODO: 309 | Log.d(TAG, "onError: got error code: " + errorCode + "\nMessage: "+ message); 310 | } 311 | 312 | @Override 313 | public void onDeviceList(Map deviceList) throws RemoteException { 314 | m_devicesMap = deviceList; 315 | if (mSdkCallback!=null) { 316 | for (Object item: deviceList.keySet() 317 | ) { 318 | mSdkCallback.ScanResultCallback(item.toString()); 319 | } 320 | } 321 | } 322 | 323 | @Override 324 | public void onSignalQuality(String quality) throws RemoteException { 325 | if (mSdkCallback!=null) { 326 | mSdkCallback.SignalQualityCallback(quality); 327 | } 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /arctopSDK/src/main/java/com/arctop/unity/IArctopSdkCallback.java: -------------------------------------------------------------------------------- 1 | package com.arctop.unity; 2 | /** 3 | * Unity callback interface. 4 | * Used in Java -> C# interop to notify the native plugin on events in the SDK 5 | * */ 6 | public interface IArctopSdkCallback { 7 | void ScanResultCallback(String device); 8 | // delegate for connection status 9 | void ConnectionStatusCallback(int previous, int current); 10 | // delegate for realtime values 11 | void ValueChangedCallback(String key, float value); 12 | // delegate for qa 13 | void QAStatusCallback(Boolean passed, int errorCode); 14 | // delegate for signalQuality 15 | void SignalQualityCallback(String signalQuality); 16 | // delegate for session complete 17 | void SessionCompleteCallback(); 18 | } 19 | -------------------------------------------------------------------------------- /arctopSDK/src/main/java/com/arctop/unity/IArctopSdkSuccessOrFailureCallback.java: -------------------------------------------------------------------------------- 1 | package com.arctop.unity; 2 | 3 | /** 4 | * Callback interface for a success / failure of operation 5 | * */ 6 | public interface IArctopSdkSuccessOrFailureCallback { 7 | void onSuccess(); 8 | void onFailure(int response); 9 | } 10 | 11 | -------------------------------------------------------------------------------- /arctopSDK/src/main/java/com/arctop/unity/IArctopServiceBindCallback.java: -------------------------------------------------------------------------------- 1 | package com.arctop.unity; 2 | /** 3 | * Callback for service bind. 4 | * Notifies the Unity agent on status of binding for the service 5 | * */ 6 | public interface IArctopServiceBindCallback { 7 | enum BindError { 8 | ServiceNotFound, 9 | MultipleServicesFound, 10 | PermissionDenied, 11 | UnknownError 12 | } 13 | 14 | void onSuccess(); 15 | 16 | void onFailure(BindError error); 17 | } 18 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | maven { url 'https://jitpack.io' } 7 | } 8 | dependencies { 9 | classpath "com.android.tools.build:gradle:4.2.2" 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:1.6.10" 11 | 12 | // NOTE: Do not place your application dependencies here; they belong 13 | // in the individual module build.gradle files 14 | } 15 | } 16 | 17 | allprojects { 18 | repositories { 19 | google() 20 | mavenCentral() 21 | maven { url 'https://jitpack.io' } 22 | } 23 | } -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app"s APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arctop/Android-SDK/a33d2be6c63c3c09f00a6cbd2ff7787ff8d6b275/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Feb 09 00:45:54 IST 2022 2 | distributionBase=GRADLE_USER_HOME 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip 4 | distributionPath=wrapper/dists 5 | zipStorePath=wrapper/dists 6 | zipStoreBase=GRADLE_USER_HOME 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = "Arctop SDK" 2 | include ':arctopSDK' 3 | --------------------------------------------------------------------------------