├── source ├── app │ ├── .gitignore │ ├── src │ │ └── main │ │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── black_arrow.png │ │ │ │ └── ic_launcher_background.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ └── ic_launcher_round.webp │ │ │ ├── values │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ ├── themes.xml │ │ │ │ └── strings.xml │ │ │ ├── xml │ │ │ │ ├── backup_rules.xml │ │ │ │ └── data_extraction_rules.xml │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── jetpackexample │ │ │ │ ├── managers │ │ │ │ ├── LocationManagerImpl.java │ │ │ │ ├── UwbManagerImpl.java │ │ │ │ └── BluetoothManagerImpl.java │ │ │ │ ├── UwbPhoneConfigData.java │ │ │ │ ├── UwbDeviceConfigData.java │ │ │ │ ├── utils │ │ │ │ └── Utils.java │ │ │ │ └── MainActivity.java │ │ │ └── AndroidManifest.xml │ ├── proguard-rules.pro │ └── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── local.properties ├── build.gradle ├── settings.gradle ├── gradle.properties ├── gradlew.bat └── gradlew ├── UWB_jetpack_example.apk ├── SCR.txt ├── README.rst └── LICENSE.txt /source/app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /UWB_jetpack_example.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/UWB_jetpack_example.apk -------------------------------------------------------------------------------- /source/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /source/app/src/main/res/drawable/black_arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/app/src/main/res/drawable/black_arrow.png -------------------------------------------------------------------------------- /source/app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /source/app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /source/app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /source/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /source/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /source/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /source/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /source/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /source/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /source/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nxp-uwb/UWBJetpackExample/HEAD/source/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /SCR.txt: -------------------------------------------------------------------------------- 1 | ----------------------------------------------------------- 2 | Release Name: UWB jetpack example 3 | Release Version: 1.2 4 | Outgoing License: Apache-2.0 5 | Description: Android UWB Demo Application 6 | Location: https://github.com/NXP-UWB/UWBJetpackExample 7 | Origin: NXP (Apache-2.0) 8 | ----------------------------------------------------------- -------------------------------------------------------------------------------- /source/local.properties: -------------------------------------------------------------------------------- 1 | ## This file must *NOT* be checked into Version Control Systems, 2 | # as it contains information specific to your local configuration. 3 | # 4 | # Location of the SDK. This is only used by Gradle. 5 | # For customization when using a Version Control System, please read the 6 | # header note. 7 | #Fri Dec 16 16:26:11 CET 2022 8 | sdk.dir=C\:\\Users\\frq05125\\AppData\\Local\\Android\\Sdk 9 | -------------------------------------------------------------------------------- /source/app/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 -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | UWB jetpack example 2 | ==================================================================== 3 | 4 | UWB jetpack example is a simple Android application demonstrating usage of UWB Jetpack API. 5 | 6 | It can be run on UWB enabled Android phones supporting UWB Jetpack (https://developer.android.com/jetpack/androidx/releases/core-uwb). 7 | 8 | List of UWB-enabled devices supporting Jetpack can be found at https://developer.android.com/guide/topics/connectivity/uwb#uwb-enabled_mobile_devices. 9 | 10 | For demonstration, NXP's SR150 (https://www.nxp.com/products/wireless/secure-ultra-wideband-uwb/trimension-sr150-secure-uwb-solution-for-iot-devices:SR150) or SR040 (https://www.nxp.com/products/wireless/secure-ultra-wideband-uwb/trimension-sr040-reliable-uwb-solution-for-iot:SR040) UWBIOT accessory can be used as counterpart device (refer to demo_nearby_interaction example). 11 | 12 | 13 | -------------------------------------------------------------------------------- /source/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2022 NXP 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | #Fri Jul 15 09:06:12 CEST 2022 18 | distributionBase=GRADLE_USER_HOME 19 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip 20 | distributionPath=wrapper/dists 21 | zipStorePath=wrapper/dists 22 | zipStoreBase=GRADLE_USER_HOME 23 | -------------------------------------------------------------------------------- /source/app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 16dp 21 | 16dp 22 | 23 | -------------------------------------------------------------------------------- /source/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 NXP 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 18 | plugins { 19 | id "com.android.application" version '8.13.0' apply false 20 | id "com.android.library" version '8.13.0' apply false 21 | id "org.jetbrains.kotlin.android" version "2.0.21" apply false 22 | } 23 | 24 | task clean(type: Delete) { 25 | delete rootProject.buildDir 26 | } -------------------------------------------------------------------------------- /source/settings.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 NXP 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | pluginManagement { 18 | repositories { 19 | gradlePluginPortal() 20 | google() 21 | mavenCentral() 22 | } 23 | } 24 | dependencyResolutionManagement { 25 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 26 | repositories { 27 | google() 28 | mavenCentral() 29 | } 30 | } 31 | rootProject.name = "UWB Jetpack example" 32 | include ':app' 33 | -------------------------------------------------------------------------------- /source/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | #FFBB86FC 20 | #FF6200EE 21 | #FF3700B3 22 | #FF03DAC5 23 | #FF018786 24 | #FF000000 25 | #FFFFFFFF 26 | -------------------------------------------------------------------------------- /source/app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 24 | 25 | 29 | -------------------------------------------------------------------------------- /source/app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 22 | 23 | 24 | 28 | 29 | 35 | -------------------------------------------------------------------------------- /source/app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | 19 | 32 | -------------------------------------------------------------------------------- /source/app/src/main/java/com/jetpackexample/managers/LocationManagerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 NXP 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jetpackexample.managers; 18 | 19 | import android.content.Context; 20 | import android.location.LocationManager; 21 | 22 | public class LocationManagerImpl { 23 | 24 | private static final String TAG = LocationManagerImpl.class.getName(); 25 | 26 | private LocationManager locationManager; 27 | 28 | private static LocationManagerImpl mInstance = null; 29 | 30 | private LocationManagerImpl(final Context context) { 31 | locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); 32 | } 33 | 34 | public static synchronized LocationManagerImpl getInstance(final Context context) { 35 | if (mInstance == null) { 36 | mInstance = new LocationManagerImpl(context); 37 | } 38 | 39 | return mInstance; 40 | } 41 | 42 | public boolean isSupported() { 43 | return locationManager != null; 44 | } 45 | 46 | public boolean isEnabled() { 47 | return locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER) || 48 | locationManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /source/gradle.properties: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright 2022 NXP 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); 5 | # you may not use this file except in compliance with the License. 6 | # You may obtain a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | 17 | # Project-wide Gradle settings. 18 | # IDE (e.g. Android Studio) users: 19 | # Gradle settings configured through the IDE *will override* 20 | # any settings specified in this file. 21 | # For more details on how to configure your build environment visit 22 | # http://www.gradle.org/docs/current/userguide/build_environment.html 23 | # Specifies the JVM arguments used for the daemon process. 24 | # The setting is particularly useful for tweaking memory settings. 25 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 26 | # When configured, Gradle will run in incubating parallel mode. 27 | # This option should only be used with decoupled projects. More details, visit 28 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 29 | # org.gradle.parallel=true 30 | # AndroidX package structure to make it clearer which packages are bundled with the 31 | # Android operating system, and which are packaged with your app"s APK 32 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 33 | android.useAndroidX=true 34 | # Enables namespacing of each library's R class so that its R class includes only the 35 | # resources declared in the library itself and none from the library's dependencies, 36 | # thereby reducing the size of the R class for that library 37 | android.nonTransitiveRClass=true -------------------------------------------------------------------------------- /source/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 20 | 21 | 24 | 25 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /source/app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 NXP 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | id 'com.android.application' 19 | } 20 | 21 | tasks.register("prepareKotlinBuildScriptModel"){} 22 | 23 | android { 24 | namespace "com.jetpackexample" 25 | compileSdk 35 26 | 27 | defaultConfig { 28 | applicationId "com.jetpackexample" 29 | minSdk 31 30 | targetSdk 35 31 | versionCode 1 32 | versionName "1.2" 33 | 34 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 35 | } 36 | 37 | buildTypes { 38 | release { 39 | minifyEnabled false 40 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 41 | } 42 | } 43 | 44 | compileOptions { 45 | sourceCompatibility JavaVersion.VERSION_1_8 46 | targetCompatibility JavaVersion.VERSION_1_8 47 | } 48 | } 49 | 50 | dependencies { 51 | 52 | implementation 'androidx.appcompat:appcompat:1.4.2' 53 | implementation 'com.google.android.material:material:1.6.1' 54 | implementation 'androidx.constraintlayout:constraintlayout:2.1.4' 55 | 56 | // Use to implement UWB (ultra-wideband) on supported devices 57 | implementation "androidx.core.uwb:uwb-rxjava3:1.0.0-alpha06" 58 | implementation "androidx.core.uwb:uwb:1.0.0-alpha10" 59 | 60 | // Align versions of all Kotlin components to avoid compilation issues with alpha05 61 | implementation(platform("org.jetbrains.kotlin:kotlin-bom:1.8.0")) 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib:1.8.0" 63 | 64 | // Used to serialize objects 65 | implementation group: 'org.openmuc', name: 'jasn1-compiler', version:'1.8.0' 66 | implementation group: 'com.fazecast', name: 'jSerialComm', version:'1.3.11' 67 | implementation group: 'org.apache.commons', name: 'commons-lang3', version:'3.7' 68 | implementation group: 'commons-io', name: 'commons-io', version:'2.6' 69 | } -------------------------------------------------------------------------------- /source/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 18 | UWB Jetpack example 19 | 20 | This demo cannot be executed on this phone because required technologies are missing 21 | UWB is not enabled. Please enable UWB in settings 22 | Location is not enabled. Please enable location in settings 23 | "Bluetooth is not enabled. Please enable bluetooth in settings", 24 | 25 | Permission has been denied by flagging NEVER ASK AGAIN. Please enable the permission from Settings 26 | You need grant required permissions for the application to work as expected 27 | 28 | UWB ranging device: %1$s 29 | UWB ranging not started 30 | Distance: %1$.2f (m) 31 | Distance: %1$.0f (cm) 32 | Distance: - (m) 33 | AoA Azimuth: %1$.0f (°) 34 | AoA Azimuth: - (°) 35 | 36 | 37 | BLE State 38 | Unknown 39 | Not started 40 | Scanning 41 | Connected 42 | 43 | 44 | UWB State 45 | Unknown 46 | Not started 47 | Configuring 48 | Ranging 49 | Stopped 50 | 51 | 52 | OK 53 | Accept 54 | Cancel 55 | -------------------------------------------------------------------------------- /source/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /source/app/src/main/java/com/jetpackexample/UwbPhoneConfigData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 NXP 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jetpackexample; 18 | 19 | import com.jetpackexample.utils.Utils; 20 | 21 | import java.io.Serializable; 22 | 23 | public class UwbPhoneConfigData implements Serializable { 24 | short specVerMajor; 25 | short specVerMinor; 26 | int sessionId; 27 | byte preambleId; 28 | byte channel; 29 | byte profileId; 30 | byte deviceRangingRole; 31 | byte[] phoneMacAddress; 32 | 33 | public UwbPhoneConfigData() { 34 | 35 | } 36 | 37 | public UwbPhoneConfigData(short specVerMajor, short specVerMinor, int sessionId, byte preambleId, byte channel, byte profileId, byte deviceRangingRole, byte[] phoneMacAddress) { 38 | this.specVerMajor = specVerMajor; 39 | this.specVerMinor = specVerMinor; 40 | this.sessionId = sessionId; 41 | this.preambleId = preambleId; 42 | this.channel = channel; 43 | this.profileId = profileId; 44 | this.deviceRangingRole = deviceRangingRole; 45 | this.phoneMacAddress = phoneMacAddress; 46 | } 47 | 48 | public short getSpecVerMajor() { 49 | return specVerMajor; 50 | } 51 | 52 | public void setSpecVerMajor(short specVerMajor) { 53 | this.specVerMajor = specVerMajor; 54 | } 55 | 56 | public short getSpecVerMinor() { 57 | return specVerMinor; 58 | } 59 | 60 | public void setSpecVerMinor(short specVerMinor) { 61 | this.specVerMinor = specVerMinor; 62 | } 63 | 64 | public int getSessionId() { 65 | return sessionId; 66 | } 67 | 68 | public void setSessionId(int sessionId) { 69 | this.sessionId = sessionId; 70 | } 71 | 72 | public byte getPreambleId() { 73 | return preambleId; 74 | } 75 | 76 | public void setPreambleId(byte preambleId) { 77 | this.preambleId = preambleId; 78 | } 79 | 80 | public byte getChannel() { 81 | return channel; 82 | } 83 | 84 | public void setChannel(byte channel) { 85 | this.channel = channel; 86 | } 87 | 88 | public byte getProfileId() { 89 | return profileId; 90 | } 91 | 92 | public void setProfileId(byte profileId) { 93 | this.profileId = profileId; 94 | } 95 | 96 | public byte getDeviceRangingRole() { 97 | return deviceRangingRole; 98 | } 99 | 100 | public void setDeviceRangingRole(byte deviceRangingRole) { 101 | this.deviceRangingRole = deviceRangingRole; 102 | } 103 | 104 | public byte[] getPhoneMacAddress() { 105 | return phoneMacAddress; 106 | } 107 | 108 | public void setPhoneMacAddress(byte[] phoneMacAddress) { 109 | this.phoneMacAddress = phoneMacAddress; 110 | } 111 | 112 | public byte[] toByteArray() { 113 | byte[] response = null; 114 | response = Utils.concat(response, Utils.shortToByteArray(this.specVerMajor)); 115 | response = Utils.concat(response, Utils.shortToByteArray(this.specVerMinor)); 116 | response = Utils.concat(response, Utils.intToByteArray(this.sessionId)); 117 | response = Utils.concat(response, Utils.byteToByteArray(this.preambleId)); 118 | response = Utils.concat(response, Utils.byteToByteArray(this.channel)); 119 | response = Utils.concat(response, Utils.byteToByteArray(this.profileId)); 120 | response = Utils.concat(response, Utils.byteToByteArray(this.deviceRangingRole)); 121 | response = Utils.concat(response, this.phoneMacAddress); 122 | 123 | return response; 124 | } 125 | 126 | public static UwbPhoneConfigData fromByteArray(byte[] data) { 127 | UwbPhoneConfigData uwbPhoneConfigData = new UwbPhoneConfigData(); 128 | uwbPhoneConfigData.setSpecVerMajor(Utils.byteArrayToShort(Utils.extract(data, 2, 0))); 129 | uwbPhoneConfigData.setSpecVerMinor(Utils.byteArrayToShort(Utils.extract(data, 2, 2))); 130 | uwbPhoneConfigData.setSessionId(Utils.byteArrayToShort(Utils.extract(data, 4, 4))); 131 | uwbPhoneConfigData.setPreambleId(Utils.byteArrayToByte(Utils.extract(data, 1, 8))); 132 | uwbPhoneConfigData.setChannel(Utils.byteArrayToByte(Utils.extract(data, 1, 9))); 133 | uwbPhoneConfigData.setProfileId(Utils.byteArrayToByte(Utils.extract(data, 1, 10))); 134 | uwbPhoneConfigData.setDeviceRangingRole(Utils.byteArrayToByte(Utils.extract(data, 1, 11))); 135 | uwbPhoneConfigData.setPhoneMacAddress(Utils.extract(data, 2, 12)); 136 | 137 | return uwbPhoneConfigData; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /source/app/src/main/java/com/jetpackexample/UwbDeviceConfigData.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 NXP 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jetpackexample; 18 | 19 | import com.jetpackexample.utils.Utils; 20 | 21 | import java.io.Serializable; 22 | 23 | public class UwbDeviceConfigData implements Serializable { 24 | public short specVerMajor; 25 | public short specVerMinor; 26 | public byte[] chipId = new byte[2]; 27 | public byte[] chipFwVersion = new byte[2]; 28 | public byte[] mwVersion = new byte[3]; 29 | public int supportedUwbProfileIds; 30 | public byte supportedDeviceRangingRoles; 31 | public byte[] deviceMacAddress; 32 | 33 | public UwbDeviceConfigData() { 34 | 35 | } 36 | 37 | public UwbDeviceConfigData(short specVerMajor, short specVerMinor, byte[] chipId, byte[] chipFwVersion, byte[] mwVersion, int supportedUwbProfileIds, byte supportedDeviceRangingRoles, byte[] deviceMacAddress) { 38 | this.specVerMajor = specVerMajor; 39 | this.specVerMinor = specVerMinor; 40 | this.chipId = chipId; 41 | this.chipFwVersion = chipFwVersion; 42 | this.mwVersion = mwVersion; 43 | this.supportedUwbProfileIds = supportedUwbProfileIds; 44 | this.supportedDeviceRangingRoles = supportedDeviceRangingRoles; 45 | this.deviceMacAddress = deviceMacAddress; 46 | } 47 | 48 | public short getSpecVerMajor() { 49 | return specVerMajor; 50 | } 51 | 52 | public void setSpecVerMajor(short specVerMajor) { 53 | this.specVerMajor = specVerMajor; 54 | } 55 | 56 | public short getSpecVerMinor() { 57 | return specVerMinor; 58 | } 59 | 60 | public void setSpecVerMinor(short specVerMinor) { 61 | this.specVerMinor = specVerMinor; 62 | } 63 | 64 | public byte[] getChipId() { 65 | return chipId; 66 | } 67 | 68 | public void setChipId(byte[] chipId) { 69 | this.chipId = chipId; 70 | } 71 | 72 | public byte[] getChipFwVersion() { 73 | return chipFwVersion; 74 | } 75 | 76 | public void setChipFwVersion(byte[] chipFwVersion) { 77 | this.chipFwVersion = chipFwVersion; 78 | } 79 | 80 | public byte[] getMwVersion() { 81 | return mwVersion; 82 | } 83 | 84 | public void setMwVersion(byte[] mwVersion) { 85 | this.mwVersion = mwVersion; 86 | } 87 | 88 | public int getSupportedUwbProfileIds() { 89 | return supportedUwbProfileIds; 90 | } 91 | 92 | public void setSupportedUwbProfileIds(int supportedUwbProfileIds) { 93 | this.supportedUwbProfileIds = supportedUwbProfileIds; 94 | } 95 | 96 | public byte getSupportedDeviceRangingRoles() { 97 | return supportedDeviceRangingRoles; 98 | } 99 | 100 | public void setSupportedDeviceRangingRoles(byte supportedDeviceRangingRoles) { 101 | this.supportedDeviceRangingRoles = supportedDeviceRangingRoles; 102 | } 103 | 104 | public byte[] getDeviceMacAddress() { 105 | return deviceMacAddress; 106 | } 107 | 108 | public void setDeviceMacAddress(byte[] deviceMacAddress) { 109 | this.deviceMacAddress = deviceMacAddress; 110 | } 111 | 112 | public byte[] toByteArray() { 113 | byte[] response = null; 114 | response = Utils.concat(response, Utils.shortToByteArray(this.specVerMajor)); 115 | response = Utils.concat(response, Utils.shortToByteArray(this.specVerMinor)); 116 | response = Utils.concat(response, this.chipId); 117 | response = Utils.concat(response, this.chipFwVersion); 118 | response = Utils.concat(response, this.mwVersion); 119 | response = Utils.concat(response, Utils.intToByteArray(this.supportedUwbProfileIds)); 120 | response = Utils.concat(response, Utils.byteToByteArray(this.supportedDeviceRangingRoles)); 121 | response = Utils.concat(response, this.deviceMacAddress); 122 | 123 | return response; 124 | } 125 | 126 | public static UwbDeviceConfigData fromByteArray(byte[] data) { 127 | UwbDeviceConfigData uwbDeviceConfigData = new UwbDeviceConfigData(); 128 | uwbDeviceConfigData.setSpecVerMajor(Utils.byteArrayToShort(Utils.extract(data, 2, 0))); 129 | uwbDeviceConfigData.setSpecVerMinor(Utils.byteArrayToShort(Utils.extract(data, 2, 2))); 130 | uwbDeviceConfigData.setChipId(Utils.extract(data, 2, 4)); 131 | uwbDeviceConfigData.setChipFwVersion(Utils.extract(data, 2, 6)); 132 | uwbDeviceConfigData.setMwVersion(Utils.extract(data, 3, 8)); 133 | uwbDeviceConfigData.setSupportedUwbProfileIds(Utils.byteArrayToInt(Utils.extract(data, 4, 11))); 134 | uwbDeviceConfigData.setSupportedDeviceRangingRoles(Utils.byteArrayToByte(Utils.extract(data, 1, 15))); 135 | uwbDeviceConfigData.setDeviceMacAddress(Utils.extract(data, 2, 16)); 136 | 137 | return uwbDeviceConfigData; 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /source/app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 27 | 28 | 35 | 36 | 42 | 43 | 50 | 51 | 52 | 60 | 61 | 67 | 68 | 75 | 76 | 77 | 87 | 88 | 100 | 101 | 113 | 114 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /source/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /source/app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 23 | 26 | 31 | 36 | 41 | 46 | 51 | 56 | 61 | 66 | 71 | 76 | 81 | 86 | 91 | 96 | 101 | 106 | 111 | 116 | 121 | 126 | 131 | 136 | 141 | 146 | 151 | 156 | 161 | 166 | 171 | 176 | 181 | 186 | 187 | -------------------------------------------------------------------------------- /source/app/src/main/java/com/jetpackexample/utils/Utils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 NXP 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jetpackexample.utils; 18 | 19 | import android.app.AlertDialog; 20 | import android.content.Context; 21 | import android.content.DialogInterface; 22 | 23 | public class Utils { 24 | 25 | /** 26 | * Convert an hexadecimal string into a byte array. 27 | * 28 | * @param hexString the string to be converted 29 | * @return the corresponding byte array 30 | * @throws IllegalArgumentException if not a valid string representation of a byte array 31 | */ 32 | public static byte[] hexStringToByteArray(String hexString) { 33 | byte[] result; 34 | 35 | if (hexString == null) { 36 | throw new IllegalArgumentException("Null input"); 37 | } 38 | 39 | if ((hexString.length() == 0) || (1 == hexString.length() % 2)) { 40 | throw new IllegalArgumentException("Invalid length"); 41 | } 42 | 43 | result = new byte[hexString.length() / 2]; 44 | 45 | for (int i = 0; i < result.length; i++) { 46 | result[i] = (byte) Integer.parseInt(hexString.substring(2 * i, 2 * i + 2), 16); 47 | } 48 | 49 | return result; 50 | } 51 | 52 | /** 53 | * Convert a byte array to an hexadecimal string 54 | * 55 | * @param bytes the byte array to be converted 56 | * @return the output string 57 | */ 58 | public static String byteArrayToHexString(byte[] bytes) { 59 | if (bytes == null) { 60 | throw new IllegalArgumentException("Null input"); 61 | } 62 | 63 | final char[] hexArray = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; 64 | char[] hexChars = new char[bytes.length * 2]; 65 | int v; 66 | for (int j = 0; j < bytes.length; j++) { 67 | v = bytes[j] & 0xFF; 68 | hexChars[j * 2] = hexArray[v >>> 4]; 69 | hexChars[j * 2 + 1] = hexArray[v & 0x0F]; 70 | } 71 | return new String(hexChars); 72 | } 73 | 74 | /** 75 | * Trim leading bytes from byte array 76 | * 77 | * @param inputBytes String to trim 78 | * @param amountOfBytesToTrim Number of bytes to trim 79 | * @return trimmed String 80 | */ 81 | public static byte[] trimLeadingBytes(byte[] inputBytes, final int amountOfBytesToTrim) { 82 | final byte[] outputBytes = new byte[inputBytes.length - amountOfBytesToTrim]; 83 | System.arraycopy(inputBytes, amountOfBytesToTrim, outputBytes, 0, inputBytes.length - amountOfBytesToTrim); 84 | return outputBytes; 85 | } 86 | 87 | /** 88 | * Trim bytes from byte array 89 | * 90 | * @param inputBytes String to trim 91 | * @param amountOfBytesToTrim Number of bytes to trim 92 | * @return trimmed String 93 | */ 94 | public static byte[] trimByteArray(byte[] inputBytes, final int amountOfBytesToTrim) { 95 | final byte[] outputBytes = new byte[inputBytes.length - amountOfBytesToTrim]; 96 | System.arraycopy(inputBytes, 0, outputBytes, 0, inputBytes.length - amountOfBytesToTrim); 97 | return outputBytes; 98 | } 99 | 100 | /** 101 | * Shows dialog with only positive button 102 | * 103 | * @param title Dialog Title 104 | * @param message Dialog Message 105 | * @param rightButtonTxt Positive button text 106 | * @param rightButtonListener Positive button listener 107 | */ 108 | public static void showDialog(Context context, String title, String message, String rightButtonTxt, 109 | final DialogInterface.OnClickListener rightButtonListener) { 110 | showDialog(context, title, message, null, null, rightButtonTxt, rightButtonListener); 111 | } 112 | 113 | /** 114 | * Shows dialog with both positive and negative buttons 115 | * 116 | * @param title Dialog Title 117 | * @param message Dialog Message 118 | * @param leftButtonTxt Negative button listener 119 | * @param leftButtonListener Negative button listener 120 | * @param rightButtonTxt Positive button text 121 | * @param rightButtonListener Positive button listener 122 | */ 123 | public static void showDialog(Context context, String title, String message, final String leftButtonTxt, final DialogInterface.OnClickListener leftButtonListener, 124 | final String rightButtonTxt, final DialogInterface.OnClickListener rightButtonListener) { 125 | // Use the Builder class for convenient dialog construction 126 | AlertDialog.Builder builder = new AlertDialog.Builder(context); 127 | builder.setTitle(title); 128 | builder.setMessage(message); 129 | builder.setCancelable(false); 130 | 131 | if (leftButtonTxt != null && !leftButtonTxt.isEmpty()) { 132 | builder.setNegativeButton(leftButtonTxt, (dialog, id) -> { 133 | dialog.dismiss(); 134 | 135 | if (leftButtonListener != null) { 136 | leftButtonListener.onClick(dialog, id); 137 | } 138 | }); 139 | } 140 | 141 | if (rightButtonTxt != null && !rightButtonTxt.isEmpty()) { 142 | builder.setPositiveButton(rightButtonTxt, (dialog, id) -> { 143 | dialog.dismiss(); 144 | 145 | if (rightButtonListener != null) { 146 | rightButtonListener.onClick(dialog, id); 147 | } 148 | }); 149 | } 150 | 151 | try { 152 | builder.create(); 153 | builder.show(); 154 | } catch (Exception e) { 155 | e.printStackTrace(); 156 | } 157 | } 158 | 159 | /** 160 | * Concatenates the two given arrays 161 | * 162 | * @param b1 the first byte array 163 | * @param b2 the second byte array 164 | * @return the resulting byte array 165 | */ 166 | public static byte[] concat(byte[] b1, byte[] b2) { 167 | if (b1 == null) { 168 | return b2; 169 | } else if (b2 == null) { 170 | return b1; 171 | } else { 172 | byte[] result = new byte[b1.length + b2.length]; 173 | System.arraycopy(b1, 0, result, 0, b1.length); 174 | System.arraycopy(b2, 0, result, b1.length, b2.length); 175 | return result; 176 | } 177 | } 178 | 179 | /** 180 | * Convert the byte array to an int 181 | * 182 | * @param b The byte array 183 | * @return The integer 184 | */ 185 | public static int byteArrayToInt(byte[] b) { 186 | if (b.length == 1) { 187 | return b[0] & 0xFF; 188 | } else if (b.length == 2) { 189 | return ((b[0] & 0xFF) << 8) + (b[1] & 0xFF); 190 | } else if (b.length == 3) { 191 | return ((b[0] & 0xFF) << 16) + ((b[1] & 0xFF) << 8) + (b[2] & 0xFF); 192 | } else if (b.length == 4) { 193 | return (b[0] << 24) + ((b[1] & 0xFF) << 16) + ((b[2] & 0xFF) << 8) + (b[3] & 0xFF); 194 | } else 195 | throw new IndexOutOfBoundsException(); 196 | } 197 | 198 | /** 199 | * Convert the int to a byte array 200 | * 201 | * @param value The integer 202 | * @return The byte array 203 | */ 204 | public static byte[] intToByteArray(int value) { 205 | byte[] result = new byte[4]; 206 | result[3] = (byte) (value & 0xff); 207 | result[2] = (byte) ((value >> 8) & 0xff); 208 | result[1] = (byte) ((value >> 16) & 0xff); 209 | result[0] = (byte) ((value >> 24) & 0xff); 210 | return result; 211 | } 212 | 213 | /** 214 | * Convert the short to a byte array 215 | * 216 | * @param value The short 217 | * @return The byte array 218 | */ 219 | public static byte[] shortToByteArray(short value) { 220 | byte[] result = new byte[2]; 221 | result[1] = (byte) (value & 0xff); 222 | result[0] = (byte) ((value >> 8) & 0xff); 223 | return result; 224 | } 225 | 226 | /** 227 | * Convert the byte array to a short 228 | * 229 | * @param b The byte array 230 | * @return The short 231 | */ 232 | public static short byteArrayToShort(byte[] b) { 233 | if (b.length == 1) { 234 | return (short) (b[0] & 0xFF); 235 | } else if (b.length == 2) { 236 | return (short) (((b[0] & 0xFF) << 8) + (b[1] & 0xFF)); 237 | } else 238 | throw new IndexOutOfBoundsException(); 239 | } 240 | 241 | /** 242 | * Convert the byte array to a short 243 | * 244 | * @param b The byte array 245 | * @return The short 246 | */ 247 | public static byte byteArrayToByte(byte[] b) { 248 | if (b.length == 1) { 249 | return (byte) (b[0] & 0xFF); 250 | } else 251 | throw new IndexOutOfBoundsException(); 252 | } 253 | 254 | /** 255 | * Convert the short to a byte array 256 | * 257 | * @param value The short 258 | * @return The byte array 259 | */ 260 | public static byte[] byteToByteArray(byte value) { 261 | byte[] result = new byte[1]; 262 | result[0] = (byte) (value & 0xff); 263 | return result; 264 | } 265 | 266 | /** 267 | * Extract data from byte array buffer 268 | * 269 | * @param buffer Byte array 270 | * @param length Length to extract 271 | * @param offset Offset in byte array 272 | * @return Extracted byte array 273 | */ 274 | public static byte[] extract(byte[] buffer, int length, int offset) { 275 | byte[] result = new byte[length]; 276 | System.arraycopy(buffer, offset, result, 0, length); 277 | return result; 278 | } 279 | 280 | /** 281 | * Revert byte array 282 | * 283 | * @param data Byte array 284 | * @return Reverted byte array 285 | */ 286 | public static byte[] revert(byte[] data) { 287 | int length = data.length; 288 | byte[] result = new byte[length]; 289 | for (int i = 0; i < length; i++) { 290 | result[i] = data[length - 1 - i]; 291 | } 292 | return result; 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /source/app/src/main/java/com/jetpackexample/managers/UwbManagerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 NXP 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jetpackexample.managers; 18 | 19 | import android.content.Context; 20 | import android.content.pm.PackageManager; 21 | import android.util.Log; 22 | 23 | import androidx.core.uwb.RangingParameters; 24 | import androidx.core.uwb.RangingResult; 25 | import androidx.core.uwb.UwbAddress; 26 | import androidx.core.uwb.UwbComplexChannel; 27 | import androidx.core.uwb.UwbControleeSessionScope; 28 | import androidx.core.uwb.UwbControllerSessionScope; 29 | import androidx.core.uwb.UwbDevice; 30 | import androidx.core.uwb.UwbManager; 31 | import androidx.core.uwb.rxjava3.UwbClientSessionScopeRx; 32 | import androidx.core.uwb.rxjava3.UwbManagerRx; 33 | 34 | import com.jetpackexample.utils.Utils; 35 | import com.jetpackexample.UwbDeviceConfigData; 36 | import com.jetpackexample.UwbPhoneConfigData; 37 | 38 | import java.math.BigInteger; 39 | import java.util.ArrayList; 40 | import java.util.List; 41 | import java.util.Random; 42 | import java.util.concurrent.TimeUnit; 43 | 44 | import io.reactivex.rxjava3.core.Flowable; 45 | import io.reactivex.rxjava3.core.Single; 46 | import io.reactivex.rxjava3.disposables.Disposable; 47 | import io.reactivex.rxjava3.subscribers.DisposableSubscriber; 48 | 49 | public class UwbManagerImpl { 50 | 51 | private static final String TAG = UwbManagerImpl.class.getName(); 52 | 53 | public static final int UWB_CHANNEL = 9; 54 | public static final int UWB_PREAMBLE_INDEX = 10; 55 | 56 | private UwbManager uwbManager = null; 57 | 58 | private Single controllerSessionScopeSingle = null; 59 | private UwbControllerSessionScope controllerSessionScope = null; 60 | private Single controleeSessionScopeSingle = null; 61 | private UwbControleeSessionScope controleeSessionScope = null; 62 | private Disposable disposable = null; 63 | 64 | 65 | private static UwbManagerImpl mInstance = null; 66 | 67 | public interface UwbRangingListener { 68 | void onRangingStarted(UwbPhoneConfigData uwbPhoneConfigData); 69 | 70 | void onRangingResult(RangingResult rangingResult); 71 | 72 | void onRangingError(Throwable error); 73 | 74 | void onRangingComplete(); 75 | } 76 | 77 | private UwbManagerImpl(final Context context) { 78 | // Create the Uwb Manager if supported by this device 79 | PackageManager packageManager = context.getPackageManager(); 80 | if (packageManager.hasSystemFeature("android.hardware.uwb")) { 81 | uwbManager = UwbManager.createInstance(context); 82 | } 83 | } 84 | 85 | public static synchronized UwbManagerImpl getInstance(final Context context) { 86 | if (mInstance == null) { 87 | mInstance = new UwbManagerImpl(context); 88 | } 89 | 90 | return mInstance; 91 | } 92 | 93 | public boolean isSupported() { 94 | return uwbManager != null; 95 | } 96 | 97 | public boolean isEnabled() { 98 | return true; 99 | } 100 | 101 | public void startRanging(UwbDeviceConfigData uwbDeviceConfigData, UwbRangingListener uwbRangingListener) { 102 | Thread t = new Thread(() -> { 103 | 104 | byte uwbDeviceRangingRole = selectUwbDeviceRangingRole(uwbDeviceConfigData.getSupportedDeviceRangingRoles()); 105 | Log.d(TAG, "Uwb device supported ranging roles: " + uwbDeviceConfigData.getSupportedDeviceRangingRoles() + ", selected role for UWB device: " + uwbDeviceRangingRole); 106 | 107 | int uwbProfileId = selectUwbProfileId(uwbDeviceConfigData.getSupportedUwbProfileIds()); 108 | Log.d(TAG, "Uwb device supported UWB profile IDs: " + uwbDeviceConfigData.getSupportedUwbProfileIds() + ", selected UWB profile ID: " + uwbProfileId); 109 | 110 | UwbAddress localAddress; 111 | if (uwbDeviceRangingRole == 0x01) { 112 | Log.d(TAG, "Android device will act as Controlee!"); 113 | controleeSessionScopeSingle = UwbManagerRx.controleeSessionScopeSingle(uwbManager); 114 | controleeSessionScope = controleeSessionScopeSingle.blockingGet(); 115 | localAddress = controleeSessionScope.getLocalAddress(); 116 | } else { 117 | Log.d(TAG, "Android device will act as Controller!"); 118 | controllerSessionScopeSingle = UwbManagerRx.controllerSessionScopeSingle(uwbManager); 119 | controllerSessionScope = controllerSessionScopeSingle.blockingGet(); 120 | localAddress = controllerSessionScope.getLocalAddress(); 121 | } 122 | 123 | // Assign a random Session ID 124 | int sessionId = new Random().nextInt(); 125 | int subSessionId = new Random().nextInt(); 126 | Log.d(TAG, "UWB sessionId: " + sessionId); 127 | 128 | UwbComplexChannel uwbComplexChannel = new UwbComplexChannel(UWB_CHANNEL, UWB_PREAMBLE_INDEX); 129 | Log.d(TAG, "UWB Channel params, Channel: " + UWB_CHANNEL + " preambleIndex: " + UWB_PREAMBLE_INDEX); 130 | 131 | // Need to pass the local address to the other peer 132 | Log.d(TAG, "UWB Local Address: " + localAddress); 133 | 134 | // UWB Shield device 135 | UwbAddress shieldUwbAddress = new UwbAddress(uwbDeviceConfigData.getDeviceMacAddress()); 136 | UwbDevice shieldUwbDevice = new UwbDevice(shieldUwbAddress); 137 | Log.d(TAG, "UWB Destination Address: " + shieldUwbAddress); 138 | 139 | List listUwbDevices = new ArrayList<>(); 140 | listUwbDevices.add(shieldUwbDevice); 141 | 142 | // https://developer.android.com/guide/topics/connectivity/uwb#known_issue_byte_order_reversed_for_mac_address_and_static_sts_vendor_id_fields 143 | // GMS Core update is doing byte reverse as per UCI spec 144 | // SessionKey is used to match Vendor ID in UWB Device firmware 145 | byte[] sessionKey = Utils.hexStringToByteArray("0807010203040506"); 146 | byte[] subSessionKeyInfo = Utils.hexStringToByteArray("0807010203040506"); 147 | 148 | Log.d(TAG, "Configure ranging parameters for Profile ID: " + uwbProfileId); 149 | RangingParameters rangingParameters = new RangingParameters( 150 | uwbProfileId, 151 | sessionId, 152 | subSessionId, 153 | sessionKey, 154 | subSessionKeyInfo, 155 | uwbComplexChannel, 156 | listUwbDevices, 157 | RangingParameters.RANGING_UPDATE_RATE_AUTOMATIC 158 | ); 159 | 160 | Flowable rangingResultFlowable; 161 | if (uwbDeviceRangingRole == 0x01) { 162 | Log.d(TAG, "Configure controlee flowable"); 163 | rangingResultFlowable = 164 | UwbClientSessionScopeRx.rangingResultsFlowable(controleeSessionScope, 165 | rangingParameters); 166 | } else { 167 | Log.d(TAG, "Configure controller flowable"); 168 | rangingResultFlowable = 169 | UwbClientSessionScopeRx.rangingResultsFlowable(controllerSessionScope, 170 | rangingParameters); 171 | } 172 | 173 | // Consume ranging results from Flowable using Disposable 174 | disposable = rangingResultFlowable 175 | .delay(199, TimeUnit.MILLISECONDS) 176 | .subscribeWith(new DisposableSubscriber() { 177 | @Override 178 | public void onStart() { 179 | Log.d(TAG, "UWB Disposable started"); 180 | request(1); 181 | } 182 | 183 | @Override 184 | public void onNext(RangingResult rangingResult) { 185 | Log.d(TAG, "UWB Ranging notification received"); 186 | uwbRangingListener.onRangingResult(rangingResult); 187 | request(1); 188 | } 189 | 190 | @Override 191 | public void onError(Throwable error) { 192 | Log.d(TAG, "UWB Ranging error received"); 193 | uwbRangingListener.onRangingError(error); 194 | } 195 | 196 | @Override 197 | public void onComplete() { 198 | Log.d(TAG, "UWB Ranging session completed"); 199 | uwbRangingListener.onRangingComplete(); 200 | } 201 | }); 202 | 203 | // Create ShareableData with configured UWB Session params 204 | UwbPhoneConfigData uwbPhoneConfigData = new UwbPhoneConfigData(); 205 | uwbPhoneConfigData.setSpecVerMajor((short) 0x0100); 206 | uwbPhoneConfigData.setSpecVerMinor((short) 0x0000); 207 | uwbPhoneConfigData.setSessionId(sessionId); 208 | uwbPhoneConfigData.setPreambleId((byte) UWB_PREAMBLE_INDEX); 209 | uwbPhoneConfigData.setChannel((byte) UWB_CHANNEL); 210 | uwbPhoneConfigData.setProfileId((byte)uwbProfileId); 211 | uwbPhoneConfigData.setDeviceRangingRole(uwbDeviceRangingRole); 212 | uwbPhoneConfigData.setPhoneMacAddress(localAddress.getAddress()); 213 | 214 | // Send the UWB ranging session configuration data back to the listener 215 | uwbRangingListener.onRangingStarted(uwbPhoneConfigData); 216 | }); 217 | 218 | t.start(); 219 | } 220 | 221 | public void stopRanging() { 222 | if (disposable != null) { 223 | disposable.dispose(); 224 | } 225 | } 226 | 227 | public void close() { 228 | if (disposable != null) { 229 | disposable.dispose(); 230 | } 231 | } 232 | 233 | private byte selectUwbProfileId(int supportedUwbProfileIds) { 234 | if (BigInteger.valueOf(supportedUwbProfileIds).testBit(RangingParameters.CONFIG_UNICAST_DS_TWR)) { 235 | return (byte) RangingParameters.CONFIG_UNICAST_DS_TWR; 236 | } 237 | 238 | return 0; 239 | } 240 | 241 | private byte selectUwbDeviceRangingRole(int supportedUwbDeviceRangingRoles) { 242 | if (BigInteger.valueOf(supportedUwbDeviceRangingRoles).testBit(0)) { 243 | return 1; 244 | } else if (BigInteger.valueOf(supportedUwbDeviceRangingRoles).testBit(1)) { 245 | return 2; 246 | } 247 | 248 | return 0; 249 | } 250 | } -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /source/app/src/main/java/com/jetpackexample/managers/BluetoothManagerImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 NXP 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jetpackexample.managers; 18 | 19 | import android.Manifest; 20 | import android.bluetooth.BluetoothAdapter; 21 | import android.bluetooth.BluetoothDevice; 22 | import android.bluetooth.BluetoothGatt; 23 | import android.bluetooth.BluetoothGattCallback; 24 | import android.bluetooth.BluetoothGattCharacteristic; 25 | import android.bluetooth.BluetoothGattDescriptor; 26 | import android.bluetooth.BluetoothGattService; 27 | import android.bluetooth.BluetoothManager; 28 | import android.bluetooth.BluetoothProfile; 29 | import android.bluetooth.le.BluetoothLeScanner; 30 | import android.bluetooth.le.ScanCallback; 31 | import android.bluetooth.le.ScanFilter; 32 | import android.bluetooth.le.ScanResult; 33 | import android.bluetooth.le.ScanSettings; 34 | import android.content.Context; 35 | import android.content.pm.PackageManager; 36 | import android.os.Handler; 37 | import android.os.Looper; 38 | import android.os.ParcelUuid; 39 | import android.util.Log; 40 | 41 | import androidx.core.app.ActivityCompat; 42 | 43 | import com.jetpackexample.utils.Utils; 44 | 45 | import java.util.ArrayList; 46 | import java.util.List; 47 | import java.util.UUID; 48 | 49 | public class BluetoothManagerImpl { 50 | 51 | private static final String TAG = BluetoothManagerImpl.class.getName(); 52 | 53 | /// MK UWB Kit defined UUIDs (QPP Profile) 54 | protected static UUID serviceUUID = UUID.fromString("6E400001-B5A3-F393-E0A9-E50E24DCCA9E"); 55 | protected static UUID rxCharacteristicUUID = UUID.fromString("6E400002-B5A3-F393-E0A9-E50E24DCCA9E"); 56 | protected static UUID txCharacteristicUUID = UUID.fromString("6E400003-B5A3-F393-E0A9-E50E24DCCA9E"); 57 | protected static UUID descriptorUUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb"); 58 | 59 | private Context context; 60 | private BluetoothManager bluetoothManager = null; 61 | private BluetoothAdapter bluetoothAdapter = null; 62 | private BluetoothLeScanner bluetoothLeScanner = null; 63 | private BluetoothScanListener bluetoothScanListener = null; 64 | private BluetoothConnectionListener bluetoothConnectionListener = null; 65 | private BluetoothDataReceivedListener bluetoothDataReceivedListener = null; 66 | 67 | private BluetoothGatt bluetoothGatt; 68 | private BluetoothGattCharacteristic txCharacteristic; 69 | private BluetoothGattCharacteristic rxCharacteristic; 70 | 71 | private static BluetoothManagerImpl mInstance = null; 72 | 73 | public interface BluetoothScanListener { 74 | void onDeviceScanned(BluetoothDevice device); 75 | } 76 | 77 | public interface BluetoothConnectionListener { 78 | void onConnect(String remoteDeviceName); 79 | 80 | void onDisconnect(); 81 | } 82 | 83 | public interface BluetoothDataReceivedListener { 84 | void onDataReceived(byte[] data); 85 | } 86 | 87 | /** 88 | * This class is the entry point for Bluetooth LE Communication. 89 | * 90 | * @param context Application context 91 | */ 92 | private BluetoothManagerImpl(final Context context) { 93 | this.context = context; 94 | 95 | this.bluetoothManager = (BluetoothManager) context.getSystemService(Context.BLUETOOTH_SERVICE); 96 | this.bluetoothAdapter = bluetoothManager.getAdapter(); 97 | this.bluetoothLeScanner = bluetoothAdapter.getBluetoothLeScanner(); 98 | this.bluetoothConnectionListener = (BluetoothConnectionListener) context; 99 | this.bluetoothDataReceivedListener = (BluetoothDataReceivedListener) context; 100 | } 101 | 102 | public static synchronized BluetoothManagerImpl getInstance(final Context context) { 103 | if (mInstance == null) { 104 | mInstance = new BluetoothManagerImpl(context); 105 | } 106 | 107 | return mInstance; 108 | } 109 | 110 | public boolean isSupported() { 111 | return bluetoothAdapter != null; 112 | } 113 | 114 | public boolean isEnabled() { 115 | return bluetoothAdapter != null && bluetoothAdapter.isEnabled(); 116 | } 117 | 118 | public boolean isDiscovering() { 119 | return bluetoothAdapter != null && bluetoothAdapter.isDiscovering(); 120 | } 121 | 122 | public boolean isConnected() { 123 | return bluetoothGatt != null && bluetoothGatt.getDevice() != null; 124 | } 125 | 126 | public BluetoothDevice getRemoteDevice() { 127 | return bluetoothGatt.getDevice(); 128 | } 129 | 130 | /** 131 | * Start scanning for BLE devices 132 | * 133 | * @param bluetoothScanListener Listener where new detected devices will be informed 134 | */ 135 | public void startLeDeviceScan(BluetoothScanListener bluetoothScanListener) { 136 | Log.d(TAG, "Bluetooth starting LE Scanning"); 137 | this.bluetoothScanListener = bluetoothScanListener; 138 | 139 | List filters = new ArrayList<>(); 140 | ScanFilter filter = new ScanFilter.Builder().setServiceUuid(new ParcelUuid(serviceUUID)).build(); 141 | filters.add(filter); 142 | 143 | ScanSettings.Builder settings = new ScanSettings.Builder(); 144 | settings.setScanMode(ScanSettings.SCAN_MODE_BALANCED); 145 | settings.setReportDelay(0); 146 | 147 | if (bluetoothLeScanner != null) { 148 | if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED) { 149 | Log.d(TAG, "Bluetooth SCAN successfully started"); 150 | bluetoothLeScanner.startScan(filters, settings.build(), scanCallback); 151 | } else { 152 | Log.d(TAG, "Missing required permission to scan for BLE devices"); 153 | } 154 | } 155 | } 156 | 157 | /** 158 | * Stop scanning for BLE devices 159 | */ 160 | public void stopLeDeviceScan() { 161 | Log.d(TAG, "Bluetooth stopping LE Scanning"); 162 | 163 | if (bluetoothLeScanner != null) { 164 | if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED) { 165 | bluetoothLeScanner.flushPendingScanResults(scanCallback); 166 | bluetoothLeScanner.stopScan(scanCallback); 167 | } 168 | } 169 | } 170 | 171 | // Device scan callback. 172 | private ScanCallback scanCallback = new ScanCallback() { 173 | 174 | @Override 175 | public void onScanResult(int callbackType, ScanResult result) { 176 | Log.d(TAG, "New device discovered"); 177 | if (ActivityCompat.checkSelfPermission(context, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) { 178 | onScan(result.getDevice()); 179 | } 180 | } 181 | }; 182 | 183 | private final BluetoothGattCallback mGattCallback = new BluetoothGattCallback() { 184 | 185 | @Override 186 | public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { 187 | 188 | Log.d(TAG, "BluetoothGattCallback onConnectionStateChange. Status: " + status + " State: " + newState); 189 | 190 | if (status == BluetoothGatt.GATT_SUCCESS) { 191 | if (newState == BluetoothProfile.STATE_CONNECTED) { 192 | 193 | // Look for target Service 194 | bluetoothGatt.discoverServices(); 195 | 196 | } else if (newState == BluetoothProfile.STATE_DISCONNECTED) { 197 | onDisconnect(); 198 | 199 | if (bluetoothGatt != null) { 200 | bluetoothGatt.close(); 201 | bluetoothGatt = null; 202 | } 203 | } 204 | } else { 205 | //If wearable devices showdown or removed from plug 206 | onDisconnect(); 207 | 208 | if (bluetoothGatt != null) { 209 | bluetoothGatt.close(); 210 | bluetoothGatt = null; 211 | } 212 | } 213 | } 214 | 215 | @Override 216 | public void onServicesDiscovered(BluetoothGatt gatt, int status) { 217 | 218 | Log.d(TAG, "BluetoothGattCallback onServicesDiscovered status: " + status); 219 | 220 | BluetoothGattService service = gatt.getService(serviceUUID); 221 | if (service == null) { 222 | Log.d(TAG, "Service not found"); 223 | return; 224 | } 225 | 226 | List bluetoothGattCharacteristics = service.getCharacteristics(); 227 | for (int j = 0; j < bluetoothGattCharacteristics.size(); j++) { 228 | BluetoothGattCharacteristic bluetoothGattCharacteristic = bluetoothGattCharacteristics.get(j); 229 | if (bluetoothGattCharacteristic.getUuid().equals(rxCharacteristicUUID)) { 230 | Log.i(TAG, "Write characteristic found, UUID is: " + bluetoothGattCharacteristic.getUuid().toString()); 231 | rxCharacteristic = bluetoothGattCharacteristic; 232 | } else if (bluetoothGattCharacteristic.getUuid().equals(txCharacteristicUUID)) { 233 | Log.i(TAG, "Notify characteristic found, UUID is " + bluetoothGattCharacteristic.getUuid().toString()); 234 | txCharacteristic = bluetoothGattCharacteristic; 235 | } 236 | } 237 | 238 | if (!gatt.setCharacteristicNotification(txCharacteristic, true)) { 239 | Log.d(TAG, "Failed setCharacteristicNotification txCharacteristic"); 240 | } 241 | 242 | if (!gatt.setCharacteristicNotification(rxCharacteristic, true)) { 243 | Log.d(TAG, "Failed setCharacteristicNotification rxCharacteristic"); 244 | } 245 | 246 | try { 247 | BluetoothGattDescriptor descriptor = txCharacteristic.getDescriptor(descriptorUUID); 248 | if (descriptor != null) { 249 | descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); 250 | bluetoothGatt.writeDescriptor(descriptor); 251 | Log.d(TAG, "descriptor written"); 252 | } else { 253 | Log.d(TAG, "descriptor is null"); 254 | } 255 | } catch (NullPointerException e) { 256 | Log.d(TAG, "NullPointerException!" + e); 257 | } catch (IllegalArgumentException e) { 258 | Log.d(TAG, "IllegalArgumentException!" + e); 259 | } 260 | } 261 | 262 | @Override 263 | public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { 264 | Log.d(TAG, "onCharacteristicChanged"); 265 | 266 | if (gatt == null || characteristic == null) { 267 | Log.d(TAG, "invalid arguments"); 268 | return; 269 | } 270 | 271 | final byte[] data = characteristic.getValue(); 272 | if (data != null && data.length > 0) { 273 | Log.d(TAG, "Bluetooth LE Data received: " + Utils.byteArrayToHexString(data)); 274 | bluetoothDataReceivedListener.onDataReceived(data); 275 | } 276 | } 277 | 278 | @Override 279 | public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) { 280 | Log.d(TAG, "onDescriptorWrite status: " + status); 281 | 282 | // Request MTU update 283 | gatt.requestMtu(84); 284 | } 285 | 286 | @Override 287 | public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) { 288 | 289 | Log.d(TAG, "onCharacteristicWrite status: " + status); 290 | } 291 | 292 | @Override 293 | public void onCharacteristicRead(BluetoothGatt gatt, 294 | BluetoothGattCharacteristic characteristic, int status) { 295 | Log.d(TAG, "onCharacteristicRead status: " + status); 296 | } 297 | 298 | @Override 299 | public void onDescriptorRead(BluetoothGatt gatt, 300 | BluetoothGattDescriptor descriptor, int status) { 301 | Log.d(TAG, "onDescriptorRead status: " + status); 302 | } 303 | 304 | @Override 305 | public void onMtuChanged(BluetoothGatt gatt, 306 | int mtu, 307 | int status) { 308 | Log.d(TAG, "onMtuChanged status: " + status + " mtu: " + mtu); 309 | 310 | // We are done establishing the connection 311 | onConnect(gatt.getDevice().getName()); 312 | } 313 | }; 314 | 315 | /** 316 | * Connects to a Bluetooth device given by its Bluetooth MAC Address 317 | * 318 | * @param address Bluetooth device MAC address 319 | * @return true if connection was launched, else false 320 | */ 321 | public boolean connect(final String address) { 322 | 323 | if (bluetoothAdapter == null || address == null) { 324 | Log.d(TAG, "BluetoothAdapter not initialized or unspecified address."); 325 | return false; 326 | } 327 | 328 | final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(address); 329 | if (device == null) { 330 | Log.d(TAG, "Device not found. Unable to connect."); 331 | return false; 332 | } else { 333 | bluetoothGatt = device.connectGatt(context, false, mGattCallback); 334 | // bluetoothGatt.requestMtu(84); 335 | return true; 336 | } 337 | } 338 | 339 | public void transmit(byte[] data) { 340 | 341 | if (bluetoothGatt == null || rxCharacteristic == null) { 342 | return; 343 | } 344 | 345 | Log.d(TAG, "Bluetooth LE Data to transmit: " + Utils.byteArrayToHexString(data)); 346 | 347 | rxCharacteristic.setValue(data); 348 | txCharacteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT); 349 | bluetoothGatt.writeCharacteristic(rxCharacteristic); 350 | } 351 | 352 | /** 353 | * Closes bluetooth managed 354 | */ 355 | public void close() { 356 | 357 | if (bluetoothGatt != null) { 358 | bluetoothGatt.close(); 359 | } 360 | 361 | bluetoothGatt = null; 362 | } 363 | 364 | /** 365 | * Calls onScan method on SEScanListener object 366 | * Call is done on the Main UI Thread to get out of the Bluetooth Binder thread 367 | * 368 | * @param device Bluetooth device 369 | */ 370 | private void onScan(final BluetoothDevice device) { 371 | // Send callback to app on the UI Thread 372 | new Handler(Looper.getMainLooper()).post(new Runnable() { 373 | @Override 374 | public void run() { 375 | // Send callback to app on the UI Thread 376 | bluetoothScanListener.onDeviceScanned(device); 377 | } 378 | }); 379 | } 380 | 381 | /** 382 | * Calls onConnect method on SEConnectionListener object 383 | * Call is done on the Main UI Thread to get out of the Bluetooth Binder thread 384 | */ 385 | private void onConnect(final String name) { 386 | // Send callback to app on the UI Thread 387 | new Handler(Looper.getMainLooper()).post(new Runnable() { 388 | @Override 389 | public void run() { 390 | // Send callback to app on the UI Thread 391 | bluetoothConnectionListener.onConnect(name); 392 | } 393 | }); 394 | } 395 | 396 | /** 397 | * Calls onDisconnect method on SEConnectionListener object 398 | * Call is done on the Main UI Thread to get out of the Bluetooth Binder thread 399 | */ 400 | private void onDisconnect() { 401 | // Send callback to app on the UI Thread 402 | new Handler(Looper.getMainLooper()).post(new Runnable() { 403 | @Override 404 | public void run() { 405 | // Send callback to app on the UI Thread 406 | bluetoothConnectionListener.onDisconnect(); 407 | } 408 | }); 409 | } 410 | } -------------------------------------------------------------------------------- /source/app/src/main/java/com/jetpackexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 NXP 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.jetpackexample; 18 | 19 | import android.Manifest; 20 | import android.content.Intent; 21 | import android.content.pm.PackageManager; 22 | import android.net.Uri; 23 | import android.os.Bundle; 24 | import android.provider.Settings; 25 | import android.util.Log; 26 | import android.widget.ImageView; 27 | import android.widget.TextView; 28 | import android.widget.Toast; 29 | 30 | import androidx.annotation.NonNull; 31 | import androidx.appcompat.app.AppCompatActivity; 32 | import androidx.appcompat.app.AppCompatDelegate; 33 | import androidx.core.app.ActivityCompat; 34 | import androidx.core.uwb.RangingResult; 35 | 36 | import com.jetpackexample.managers.BluetoothManagerImpl; 37 | import com.jetpackexample.managers.LocationManagerImpl; 38 | import com.jetpackexample.managers.UwbManagerImpl; 39 | import com.jetpackexample.utils.Utils; 40 | 41 | public class MainActivity extends AppCompatActivity 42 | implements BluetoothManagerImpl.BluetoothConnectionListener, BluetoothManagerImpl.BluetoothDataReceivedListener { 43 | 44 | private static final String TAG = MainActivity.class.getName(); 45 | 46 | public static final int PERMISSION_REQUEST_CODE = 0x0001; 47 | 48 | private LocationManagerImpl locationManagerImpl = null; 49 | private BluetoothManagerImpl bluetoothManagerImpl = null; 50 | private UwbManagerImpl uwbManagerImpl = null; 51 | 52 | private String remoteDeviceName; 53 | 54 | private TextView bleState; 55 | private TextView uwbState; 56 | private TextView uwbDistanceInfo; 57 | private TextView uwbAoaInfo; 58 | private ImageView uwbAoaArrow; 59 | private TextView uwbRangingDevice; 60 | 61 | // App states 62 | public enum AppState { 63 | notStarted, 64 | bleScanning, 65 | bleConnected, 66 | uwbConfiguring, 67 | uwbStarted, 68 | uwbStopped 69 | } 70 | 71 | // Android UWB OoB protocol 72 | public enum MessageId { 73 | // Messages from the Uwb device 74 | uwbDeviceConfigurationData((byte) 0x01), 75 | uwbDidStart((byte) 0x02), 76 | uwbDidStop((byte) 0x03), 77 | 78 | // Messages from the Uwb phone 79 | initialize((byte) 0xA5), 80 | uwbPhoneConfigurationData((byte) 0x0B), 81 | stop((byte) 0x0C); 82 | 83 | private final byte value; 84 | 85 | MessageId(final byte newValue) { 86 | value = newValue; 87 | } 88 | 89 | public byte getValue() { 90 | return value; 91 | } 92 | } 93 | 94 | /* 95 | Runnable mUpdater = new Runnable() { 96 | @Override 97 | public void run() { 98 | try { 99 | updateRangingAoaInfo(new Random().nextInt(91) -45); 100 | updateRangingDistanceInfo(new Random().nextFloat()*3); 101 | } finally { 102 | // 100% guarantee that this always happens, even if 103 | // your update method throws an exception 104 | mHandler.postDelayed(mUpdater, 1000); 105 | } 106 | } 107 | };*/ 108 | 109 | @Override 110 | protected void onCreate(Bundle savedInstanceState) { 111 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); 112 | super.onCreate(savedInstanceState); 113 | setContentView(R.layout.activity_main); 114 | 115 | uwbManagerImpl = UwbManagerImpl.getInstance(MainActivity.this); 116 | locationManagerImpl = LocationManagerImpl.getInstance(MainActivity.this); 117 | bluetoothManagerImpl = BluetoothManagerImpl.getInstance(MainActivity.this); 118 | 119 | initViews(); 120 | } 121 | 122 | @Override 123 | protected void onStart() { 124 | super.onStart(); 125 | 126 | // Automatically proceed with demo 127 | initializeBleUwb(); 128 | } 129 | 130 | @Override 131 | protected void onDestroy() { 132 | super.onDestroy(); 133 | 134 | if (bluetoothManagerImpl != null) { 135 | bluetoothManagerImpl.stopLeDeviceScan(); 136 | bluetoothManagerImpl.close(); 137 | } 138 | 139 | if (uwbManagerImpl != null) { 140 | uwbManagerImpl.close(); 141 | } 142 | 143 | updateAppState(AppState.notStarted); 144 | resetRangingInfo(); 145 | } 146 | 147 | @Override 148 | public void onBackPressed() { 149 | super.onBackPressed(); 150 | 151 | if (bluetoothManagerImpl != null) { 152 | bluetoothManagerImpl.stopLeDeviceScan(); 153 | bluetoothManagerImpl.close(); 154 | } 155 | 156 | if (uwbManagerImpl != null) { 157 | uwbManagerImpl.close(); 158 | } 159 | 160 | updateAppState(AppState.notStarted); 161 | resetRangingInfo(); 162 | } 163 | 164 | private void initViews() { 165 | bleState = findViewById(R.id.ble_state); 166 | uwbState = findViewById(R.id.uwb_state); 167 | uwbDistanceInfo = findViewById(R.id.uwb_distance_info); 168 | uwbAoaInfo = findViewById(R.id.uwb_aoa_info); 169 | uwbAoaArrow = findViewById(R.id.imgArrow); 170 | uwbRangingDevice = findViewById(R.id.uwb_ranging_device); 171 | } 172 | 173 | private void initializeBleUwb() { 174 | if (checkPermissions()) { 175 | if (bluetoothManagerImpl.isSupported() && locationManagerImpl.isSupported() && uwbManagerImpl.isSupported()) { 176 | if (bluetoothManagerImpl.isEnabled()) { 177 | if (locationManagerImpl.isEnabled()) { 178 | if (uwbManagerImpl.isEnabled()) { 179 | if (!bluetoothManagerImpl.isConnected()) { 180 | updateAppState(AppState.bleScanning); 181 | 182 | Log.d(TAG, "Start Bluetooth LE Device scanning"); 183 | bluetoothManagerImpl.startLeDeviceScan(device -> { 184 | if (ActivityCompat.checkSelfPermission(this, Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) { 185 | // Ignore devices that do not define name or address 186 | if ((device.getName() == null) || (device.getAddress() == null)) { 187 | return; 188 | } 189 | 190 | Log.d(TAG, "Let's proceed to connect to: " + device.getName()); 191 | 192 | // Stop scanning and further proceed to connect to the device 193 | bluetoothManagerImpl.stopLeDeviceScan(); 194 | bluetoothManagerImpl.connect(device.getAddress()); 195 | } else { 196 | Log.e(TAG, "Missing required permission to read Bluetooth device name!"); 197 | } 198 | }); 199 | } 200 | } else { 201 | updateAppState(AppState.notStarted); 202 | enableUwbDialog(); 203 | } 204 | } else { 205 | updateAppState(AppState.notStarted); 206 | enableLocationDialog(); 207 | } 208 | } else { 209 | updateAppState(AppState.notStarted); 210 | enableBluetoothDialog(); 211 | } 212 | } else { 213 | updateAppState(AppState.notStarted); 214 | missingRequiredTechnologiesDialog(); 215 | } 216 | } else { 217 | updateAppState(AppState.notStarted); 218 | requestPermissions(); 219 | } 220 | } 221 | 222 | public void processUwbRangingConfigurationData(byte[] data) { 223 | byte messageId = data[0]; 224 | 225 | if (messageId == MessageId.uwbDeviceConfigurationData.getValue()) { 226 | byte[] trimmedData = Utils.trimLeadingBytes(data, 1); 227 | configureUwbRangingSession(trimmedData); 228 | } else if (messageId == MessageId.uwbDidStart.getValue()) { 229 | uwbRangingSessionStarted(); 230 | } else if (messageId == MessageId.uwbDidStop.getValue()) { 231 | uwbRangingSessionStopped(); 232 | } else { 233 | throw new IllegalArgumentException("Unexpected value"); 234 | } 235 | } 236 | 237 | public void startUwbRangingConfiguration() { 238 | bluetoothManagerImpl.transmit(new byte[]{MessageId.initialize.getValue()}); 239 | } 240 | 241 | public void transmitUwbPhoneConfigData(UwbPhoneConfigData uwbPhoneConfigData) { 242 | bluetoothManagerImpl.transmit(Utils.concat( 243 | new byte[]{MessageId.uwbPhoneConfigurationData.getValue()}, 244 | uwbPhoneConfigData.toByteArray())); 245 | } 246 | 247 | public void transmitUwbRangingStop() { 248 | bluetoothManagerImpl.transmit(new byte[]{MessageId.stop.getValue()}); 249 | } 250 | 251 | public void configureUwbRangingSession(byte[] data) { 252 | Log.d(TAG, "UWB Configure UwbDeviceConfigData: " + Utils.byteArrayToHexString(data)); 253 | updateAppState(AppState.uwbConfiguring); 254 | 255 | final UwbDeviceConfigData uwbDeviceConfigData = UwbDeviceConfigData.fromByteArray(data); 256 | uwbManagerImpl.startRanging(uwbDeviceConfigData, new UwbManagerImpl.UwbRangingListener() { 257 | @Override 258 | public void onRangingStarted(UwbPhoneConfigData uwbPhoneConfigData) { 259 | transmitUwbPhoneConfigData(uwbPhoneConfigData); 260 | } 261 | 262 | @Override 263 | public void onRangingResult(RangingResult rangingResult) { 264 | displayRangingResult(rangingResult); 265 | } 266 | 267 | @Override 268 | public void onRangingError(Throwable error) { 269 | displayRangingError(error); 270 | } 271 | 272 | @Override 273 | public void onRangingComplete() { 274 | // Do nothing 275 | } 276 | }); 277 | } 278 | 279 | private void uwbRangingSessionStarted() { 280 | updateAppState(AppState.uwbStarted); 281 | updateRangingPartner(remoteDeviceName); 282 | } 283 | 284 | public void uwbRangingSessionStopped() { 285 | updateAppState(AppState.uwbStopped); 286 | } 287 | 288 | public void stopRanging() { 289 | uwbManagerImpl.stopRanging(); 290 | } 291 | 292 | private void displayRangingResult(RangingResult rangingResult) { 293 | // Update UI 294 | if (rangingResult instanceof RangingResult.RangingResultPosition) { 295 | 296 | RangingResult.RangingResultPosition rangingResultPosition = (RangingResult.RangingResultPosition) rangingResult; 297 | if (rangingResultPosition.getPosition().getDistance() != null) { 298 | float distance = rangingResultPosition.getPosition().getDistance().getValue(); 299 | Log.d(TAG, "Position distance: " + distance); 300 | updateRangingDistanceInfo(distance); 301 | } else { 302 | Log.e(TAG, "Unexpected rangingResult value, distance is null!"); 303 | } 304 | if (rangingResultPosition.getPosition().getAzimuth() != null) { 305 | float aoaAzimuth = rangingResultPosition.getPosition().getAzimuth().getValue(); 306 | Log.d(TAG, "Position AoA Azimuth: " + aoaAzimuth); 307 | updateRangingAoaInfo(aoaAzimuth); 308 | } else { 309 | Log.e(TAG, "Unexpected rangingResult value, Azimuth is null!"); 310 | } 311 | if (rangingResultPosition.getPosition().getElevation() != null) { 312 | float aoaElevation = rangingResultPosition.getPosition().getElevation().getValue(); 313 | Log.d(TAG, "Position AoA Elevation: " + aoaElevation); 314 | } else { 315 | Log.e(TAG, "Unexpected rangingResult value, no Elevation value reported!"); 316 | } 317 | } else if (rangingResult instanceof RangingResult.RangingResultPeerDisconnected) { 318 | Log.d(TAG, "Peer disconnected: address = " + rangingResult.getDevice().getAddress()); 319 | } else { 320 | Log.d(TAG, "Received unknown rangingResult instance"); 321 | } 322 | } 323 | 324 | private void displayRangingError(Throwable error) { 325 | Log.e(TAG, "Ranging error: " + error.getMessage()); 326 | error.printStackTrace(); 327 | } 328 | 329 | public void updateAppState(AppState state) { 330 | switch (state) { 331 | case notStarted: 332 | runOnUiThread(() -> { 333 | bleState.setText(getResources().getString(R.string.ble_not_started)); 334 | uwbState.setText(getResources().getString(R.string.uwb_not_started)); 335 | }); 336 | 337 | break; 338 | 339 | case bleScanning: 340 | runOnUiThread(() -> { 341 | bleState.setText(getResources().getString(R.string.ble_scanning)); 342 | uwbState.setText(getResources().getString(R.string.uwb_not_started)); 343 | }); 344 | 345 | break; 346 | 347 | case bleConnected: 348 | runOnUiThread(() -> { 349 | bleState.setText(getResources().getString(R.string.ble_connected)); 350 | uwbState.setText(getResources().getString(R.string.uwb_not_started)); 351 | }); 352 | 353 | break; 354 | 355 | case uwbConfiguring: 356 | runOnUiThread(() -> { 357 | bleState.setText(getResources().getString(R.string.ble_connected)); 358 | uwbState.setText(getResources().getString(R.string.uwb_configuring)); 359 | }); 360 | 361 | break; 362 | 363 | case uwbStarted: 364 | runOnUiThread(() -> { 365 | bleState.setText(getResources().getString(R.string.ble_connected)); 366 | uwbState.setText(getResources().getString(R.string.uwb_ranging)); 367 | }); 368 | 369 | break; 370 | 371 | case uwbStopped: 372 | runOnUiThread(() -> { 373 | bleState.setText(getResources().getString(R.string.ble_connected)); 374 | uwbState.setText(getResources().getString(R.string.uwb_stopped)); 375 | }); 376 | 377 | break; 378 | 379 | default: 380 | runOnUiThread(() -> { 381 | bleState.setText(getResources().getString(R.string.ble_unknown)); 382 | uwbState.setText(getResources().getString(R.string.uwb_unknown)); 383 | }); 384 | 385 | break; 386 | } 387 | } 388 | 389 | public void updateRangingDistanceInfo(float distance) { 390 | if(distance>1) 391 | runOnUiThread(() -> uwbDistanceInfo.setText(getResources().getString(R.string.uwb_distance_value, distance))); 392 | else 393 | runOnUiThread(() -> uwbDistanceInfo.setText(getResources().getString(R.string.uwb_distance_value_cm, distance*100))); 394 | 395 | } 396 | 397 | public void updateRangingAoaInfo(float aoa) { 398 | runOnUiThread(() -> { 399 | uwbAoaInfo.setText(getResources().getString(R.string.uwb_aoa_value, aoa)); 400 | uwbAoaArrow.setRotation((float) aoa); 401 | }); 402 | } 403 | 404 | 405 | public void updateRangingPartner(String partner) { 406 | runOnUiThread(() -> uwbRangingDevice.setText(getResources().getString(R.string.uwb_ranging_device_value, partner))); 407 | } 408 | 409 | public void resetRangingInfo() { 410 | runOnUiThread(() -> { 411 | uwbDistanceInfo.setText(getResources().getString(R.string.uwb_distance_not_started)); 412 | uwbAoaInfo.setText(getResources().getString(R.string.uwb_aoa_not_started)); 413 | uwbAoaArrow.setRotation(0); 414 | uwbRangingDevice.setText(getResources().getString(R.string.uwb_ranging_device_not_started)); 415 | }); 416 | } 417 | 418 | public void missingRequiredTechnologiesDialog() { 419 | Utils.showDialog(MainActivity.this, 420 | getResources().getString(R.string.app_name), 421 | getResources().getString(R.string.missing_techs), 422 | getResources().getString(R.string.dialog_accept), 423 | (dialog, which) -> { 424 | // Nothing to do 425 | }); 426 | } 427 | 428 | public void enableBluetoothDialog() { 429 | Utils.showDialog(MainActivity.this, 430 | getResources().getString(R.string.app_name), 431 | getResources().getString(R.string.request_enable_bluetooth), 432 | getResources().getString(R.string.dialog_cancel), 433 | (dialog, which) -> { 434 | // Nothing to do 435 | }, 436 | getResources().getString(R.string.dialog_accept), 437 | (dialog, which) -> startActivity(new Intent(Settings.ACTION_BLUETOOTH_SETTINGS))); 438 | } 439 | 440 | public void enableLocationDialog() { 441 | Utils.showDialog(MainActivity.this, 442 | getResources().getString(R.string.app_name), 443 | getResources().getString(R.string.request_enable_location), 444 | getResources().getString(R.string.dialog_cancel), 445 | (dialog, which) -> { 446 | // Nothing to do 447 | }, 448 | getResources().getString(R.string.dialog_accept), 449 | (dialog, which) -> startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))); 450 | } 451 | 452 | public void enableUwbDialog() { 453 | Utils.showDialog(MainActivity.this, 454 | getResources().getString(R.string.app_name), 455 | getResources().getString(R.string.request_enable_uwb), 456 | getResources().getString(R.string.dialog_cancel), 457 | (dialog, which) -> { 458 | // Nothing to do 459 | }, 460 | getResources().getString(R.string.dialog_accept), 461 | (dialog, which) -> startActivity(new Intent(Settings.ACTION_SETTINGS))); 462 | } 463 | 464 | private boolean checkPermissions() { 465 | return (checkSelfPermission(Manifest.permission.BLUETOOTH) == PackageManager.PERMISSION_GRANTED) 466 | && (checkSelfPermission(Manifest.permission.BLUETOOTH_ADMIN) == PackageManager.PERMISSION_GRANTED) 467 | && (checkSelfPermission(Manifest.permission.BLUETOOTH_SCAN) == PackageManager.PERMISSION_GRANTED) 468 | && (checkSelfPermission(Manifest.permission.BLUETOOTH_CONNECT) == PackageManager.PERMISSION_GRANTED) 469 | && (checkSelfPermission(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) 470 | && (checkSelfPermission(Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) 471 | && (checkSelfPermission(Manifest.permission.UWB_RANGING) == PackageManager.PERMISSION_GRANTED); 472 | } 473 | 474 | private void requestPermissions() { 475 | String[] permissionsList = new String[]{ 476 | Manifest.permission.BLUETOOTH, 477 | Manifest.permission.BLUETOOTH_ADMIN, 478 | Manifest.permission.BLUETOOTH_SCAN, 479 | Manifest.permission.BLUETOOTH_CONNECT, 480 | Manifest.permission.ACCESS_COARSE_LOCATION, 481 | Manifest.permission.ACCESS_FINE_LOCATION, 482 | Manifest.permission.UWB_RANGING, 483 | }; 484 | 485 | requestPermissions(permissionsList, PERMISSION_REQUEST_CODE); 486 | } 487 | 488 | @Override 489 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 490 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 491 | 492 | if (requestCode == PERMISSION_REQUEST_CODE) { 493 | if (grantResults.length > 0) { 494 | if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { 495 | boolean showRationale = shouldShowRequestPermissionRationale(permissions[0]); 496 | if (!showRationale) { 497 | // user denied flagging NEVER ASK AGAIN please enable this permission from device setting 498 | // again the permission and directing to the app setting} 499 | Utils.showDialog(MainActivity.this, 500 | getString(R.string.app_name), 501 | getResources().getString(R.string.denied_with_never_ask_again), 502 | getString(R.string.dialog_ok), 503 | (dialog, which) -> { 504 | Intent intent = new Intent(); 505 | intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); 506 | Uri uri = Uri.fromParts("package", getPackageName(), null); 507 | intent.setData(uri); 508 | startActivity(intent); 509 | }); 510 | } else { 511 | Utils.showDialog(MainActivity.this, 512 | getString(R.string.app_name), 513 | getString(R.string.permission_alert), 514 | getString(R.string.dialog_ok), 515 | (dialog, which) -> finish()); 516 | } 517 | } else { 518 | initializeBleUwb(); 519 | } 520 | } 521 | } 522 | } 523 | 524 | @Override 525 | public void onConnect(String remoteDeviceName) { 526 | Toast.makeText(MainActivity.this, "Bluetooth connected!", Toast.LENGTH_LONG).show(); 527 | this.remoteDeviceName = remoteDeviceName; 528 | updateAppState(AppState.bleConnected); 529 | 530 | // Let's proceed with the UWB session configuration 531 | startUwbRangingConfiguration(); 532 | } 533 | 534 | @Override 535 | public void onDisconnect() { 536 | Toast.makeText(MainActivity.this, "Bluetooth disconnected!", Toast.LENGTH_LONG).show(); 537 | this.remoteDeviceName = null; 538 | 539 | // Close UWB Session if this is ongoing 540 | uwbManagerImpl.close(); 541 | 542 | // Let's restart the demo 543 | resetRangingInfo(); 544 | initializeBleUwb(); 545 | } 546 | 547 | @Override 548 | public void onDataReceived(byte[] data) { 549 | 550 | // Process the data received 551 | processUwbRangingConfigurationData(data); 552 | } 553 | } --------------------------------------------------------------------------------