├── 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 | }
--------------------------------------------------------------------------------