Skip to content

Commit

Permalink
Android Attempt
Browse files Browse the repository at this point in the history
  • Loading branch information
kdewald committed Oct 19, 2022
1 parent 86642a3 commit 0829f53
Show file tree
Hide file tree
Showing 39 changed files with 1,165 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ The format is based on `Keep a Changelog`_, and this project adheres to
**Added**

- Option to build SimpleBLE plain-flavored (without any BLE code) for testing and debugging purposes.
- Added default constructors for ``Safe::Adapter`` and ``Safe::Peripheral``.

**Fixed**

Expand Down
13 changes: 13 additions & 0 deletions examples/simpleble/android/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.gradle
.idea
**/*.iml
local.properties
build
*~
.externalNativeBuild
libwebp
.DS_Store
**/ndkHelperBin
**/.cxx
display-p3/third_party

1 change: 1 addition & 0 deletions examples/simpleble/android/app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
59 changes: 59 additions & 0 deletions examples/simpleble/android/app/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'

def enableSeparateBuildPerCPUArchitecture = true

android {
compileSdkVersion 33
ndkVersion '25.1.8937393'

defaultConfig {
applicationId 'org.simpleble.android'
minSdkVersion 33
targetSdkVersion 33
versionCode 1
versionName "1.0"
}
buildFeatures {
viewBinding true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}

flavorDimensions 'cpuArch'
productFlavors {
arm8 {
dimension 'cpuArch'
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
}
x86_64 {
dimension 'cpuArch'
ndk {
abiFilters 'x86_64', 'x86'
}
}
universal {
dimension 'cpuArch'
}
}
namespace 'org.simpleble.android'
buildToolsVersion '33.0.0'
}

dependencies {
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}
17 changes: 17 additions & 0 deletions examples/simpleble/android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/gfan/dev/sdk_current/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
19 changes: 19 additions & 0 deletions examples/simpleble/android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".HelloJni" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>
37 changes: 37 additions & 0 deletions examples/simpleble/android/app/src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
cmake_minimum_required(VERSION 3.21)

project(simpleble_android_wrapper)

# To simplify logging, we'll bring in libfmt
include(FetchContent)

# Load default parameters passed in through the command line.
set(LIBFMT_GIT_REPOSITORY "https://github.com/fmtlib/fmt.git")
set(LIBFMT_GIT_TAG "b6f4ceaed0a0a24ccf575fab6c56dd50ccf6f1a9") # v8.1.1
FetchContent_Declare(
fmt
GIT_REPOSITORY ${LIBFMT_GIT_REPOSITORY}
GIT_TAG ${LIBFMT_GIT_TAG}
)
FetchContent_MakeAvailable(fmt)

# Print all CMAKE variables
get_cmake_property(_variableNames VARIABLES)
list (SORT _variableNames)
foreach (_variableName ${_variableNames})
message(STATUS "${_variableName}=${${_variableName}}")
endforeach()

# Load SimpleBLE
set(SIMPLEBLE_PLAIN ON)
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/../../../../../../../simpleble ${CMAKE_BINARY_DIR}/simpleble)

# Build the wrapper
add_library(simpleble_android SHARED simpleble_android.cpp utils/android_utils.cpp)
set_target_properties(
simpleble_android PROPERTIES
CXX_STANDARD 17
CXX_STANDARD_REQUIRED YES
CXX_EXTENSIONS NO)

target_link_libraries(simpleble_android android log simpleble::simpleble fmt::fmt-header-only)
139 changes: 139 additions & 0 deletions examples/simpleble/android/app/src/main/cpp/simpleble_android.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#include <string>
#include <jni.h>

#include "utils/android_utils.h"
#include <simpleble/SimpleBLE.h>
#include <fmt/core.h>
#include <vector>
#include <map>

static std::map<size_t, SimpleBLE::Safe::Adapter> cached_adapters;
static std::map<size_t, std::map<size_t, SimpleBLE::Safe::Peripheral>> cached_peripherals;


// https://docs.oracle.com/javase/9/docs/specs/jni/functions.html
// https://developer.android.com/training/articles/perf-jni
// https://www.developer.com/java/data/manipulating-java-objects-in-native-code/
// jclass Exception = env->FindClass("java/lang/Exception");
// env->ThrowNew(Exception,"Can't divide by zero.");

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
JNIEnv *env;
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
return JNI_ERR;
}

// // Find your class. JNI_OnLoad is called from the correct class loader context for this to work.
// jclass c = env->FindClass("com/example/app/package/MyClass");
// if (c == nullptr) return JNI_ERR;

return JNI_VERSION_1_6;
}

extern "C" JNIEXPORT jlongArray JNICALL Java_org_simpleble_android_Adapter_nativeGetAdapters(JNIEnv *env, jclass clazz) {

auto adapters = SimpleBLE::Safe::Adapter::get_adapters();

// If an error occurred, return an empty list.
if (!adapters.has_value()) return env->NewLongArray(0);

// Go over the results, cache whatever doesn't exist and return the full list.
jsize j_adapter_index = 0;
jlongArray j_adapter_result = env->NewLongArray(static_cast<int>(adapters.value().size()));
for (auto &adapter: adapters.value()) {
size_t adapter_hash = std::hash<std::string>{}(adapter.identifier().value_or("UNKNOWN"));

// Add to the cache if it doesn't exist
if (cached_adapters.count(adapter_hash) == 0) {
cached_adapters.insert({adapter_hash, adapter});
}

// Add to the results
jlong j_adapter_hash = adapter_hash;
env->SetLongArrayRegion(j_adapter_result, j_adapter_index, 1, &j_adapter_hash);
j_adapter_index++;

}

log_info(fmt::format("Found {} adapters. Total cached is {}", adapters.value().size(), cached_adapters.size()));

return j_adapter_result;
}

extern "C" JNIEXPORT jstring JNICALL Java_org_simpleble_android_Adapter_nativeAdapterIdentifier(JNIEnv *env, jobject thiz, jlong adapter_id) {
auto adapter = cached_adapters[adapter_id];
// TODO: Should throw exception in case of failure.
return to_jstring(env, adapter.identifier().value_or("Unknown"));
}

extern "C" JNIEXPORT jstring JNICALL Java_org_simpleble_android_Adapter_nativeAdapterAddress(JNIEnv *env, jobject thiz, jlong adapter_id) {
auto adapter = cached_adapters[adapter_id];
// TODO: Should throw exception in case of failure.
return to_jstring(env, adapter.address().value_or("Unknown"));
}

extern "C" JNIEXPORT void JNICALL Java_org_simpleble_android_Adapter_nativeAdapterScanStart(JNIEnv *env, jobject thiz, jlong adapter_id) {
auto adapter = cached_adapters[adapter_id];
bool success = adapter.scan_start();

if (!success) {
// Throw an exception
jclass Exception = env->FindClass("java/lang/Exception");
env->ThrowNew(Exception, "Failed to start scan");
}
}

extern "C" JNIEXPORT void JNICALL Java_org_simpleble_android_Adapter_nativeAdapterScanStop(JNIEnv *env, jobject thiz, jlong adapter_id) {
auto adapter = cached_adapters[adapter_id];
bool success = adapter.scan_stop();

if (!success) {
// Throw an exception
jclass Exception = env->FindClass("java/lang/Exception");
env->ThrowNew(Exception, "Failed to stop scan");
}
}

extern "C" JNIEXPORT void JNICALL Java_org_simpleble_android_Adapter_nativeAdapterScanFor(JNIEnv *env, jobject thiz, jlong adapter_id, jint timeout) {
auto adapter = cached_adapters[adapter_id];
bool success = adapter.scan_for(timeout);

if (!success) {
// Throw an exception
jclass Exception = env->FindClass("java/lang/Exception");
env->ThrowNew(Exception, "Failed to scan");
}
}

extern "C" JNIEXPORT jboolean JNICALL Java_org_simpleble_android_Adapter_nativeAdapterScanIsActive(JNIEnv *env, jobject thiz, jlong adapter_id) {
auto adapter = cached_adapters[adapter_id];
// TODO: Should throw exception in case of failure.
return adapter.scan_is_active().value_or(false);
}

extern "C" JNIEXPORT jlongArray JNICALL Java_org_simpleble_android_Adapter_nativeAdapterScanGetResults(JNIEnv *env, jobject thiz, jlong adapter_id) {
auto adapter = cached_adapters[adapter_id];

auto peripherals = adapter.scan_get_results();

// If an error occurred, return an empty list.
if (!peripherals.has_value()) return env->NewLongArray(0);

jsize j_peripheral_index = 0;
jlongArray j_peripheral_result = env->NewLongArray(static_cast<int>(peripherals.value().size()));
for (auto &peripheral: peripherals.value()) {
size_t peripheral_hash = std::hash<std::string>{}(peripheral.identifier().value_or("UNKNOWN"));

// Add to the cache if it doesn't exist
if (cached_peripherals[adapter_id].count(peripheral_hash) == 0) {
cached_peripherals[adapter_id].insert({peripheral_hash, peripheral});
}

// Add to the results
jlong j_peripheral_hash = peripheral_hash;
env->SetLongArrayRegion(j_peripheral_result, j_peripheral_index, 1, &j_peripheral_hash);
j_peripheral_index++;
}

return j_peripheral_result;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#include "android_utils.h"

#include <android/log.h>

void log_error(const std::string& msg) {
__android_log_write(ANDROID_LOG_ERROR, "SimpleBLE", msg.c_str());
}
void log_info(const std::string& msg) {
__android_log_write(ANDROID_LOG_INFO, "SimpleBLE", msg.c_str());
}
void log_debug(const std::string& msg) {
__android_log_write(ANDROID_LOG_DEBUG, "SimpleBLE", msg.c_str());
}

jstring to_jstring(JNIEnv* env, const std::string& str){
return env->NewStringUTF(str.c_str());
}
17 changes: 17 additions & 0 deletions examples/simpleble/android/app/src/main/cpp/utils/android_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
//
// Created by kevin on 01-10-2022.
//

#ifndef ANDROID_ANDROID_UTILS_H
#define ANDROID_ANDROID_UTILS_H

#include <string>
#include <jni.h>

void log_error(const std::string& msg);
void log_info(const std::string& msg);
void log_debug(const std::string& msg);

jstring to_jstring(JNIEnv* env, const std::string& str);

#endif //ANDROID_ANDROID_UTILS_H
Loading

0 comments on commit 0829f53

Please sign in to comment.