Add Wear OS SIP client module running baresip NDK stack

- New :wear application module (Wear Compose, minSdk 30)
- Second JNI lib 'wearbaresip' reusing distribution static libs
- Watch-side service initializes baresip via full init path
  (libre_init -> conf_configure -> baresip_init -> ua_init)
- FGS started from MainActivity foreground state with mic type
  only (phoneCall requires default-dialer, rejected on watch)
- Runtime-verified: builds, installs, native lib loads and SIP
  stack starts on armeabi-v7a Wear OS device
This commit is contained in:
2026-08-19 03:22:05 +01:00
parent 2372b7fe05
commit 9f94e72b1e
14 changed files with 806 additions and 5 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

2
.gitignore vendored
View File

@ -18,6 +18,8 @@ build
.externalNativeBuild .externalNativeBuild
/app/release /app/release
/app/.cxx /app/.cxx
/wear/.cxx
/wear/build
gradle/wrapper/gradle-wrapper.jar gradle/wrapper/gradle-wrapper.jar
/distribution/* /distribution/*
/distribution.video/* /distribution.video/*

View File

@ -8,6 +8,7 @@ fragmentKtx = "1.8.9"
gradleVersion = "9.3.1" gradleVersion = "9.3.1"
kotlin = "2.4.10" kotlin = "2.4.10"
kotlinStdlibJdk8 = "2.4.0" kotlinStdlibJdk8 = "2.4.0"
android = "2.4.10"
kotlinSerializationPlugin = "2.4.10" kotlinSerializationPlugin = "2.4.10"
kotlinxCoroutinesAndroid = "1.11.0" kotlinxCoroutinesAndroid = "1.11.0"
kotlinxSerializationJson = "1.11.0" kotlinxSerializationJson = "1.11.0"
@ -25,6 +26,8 @@ navigationRuntimeAndroid = "2.9.8"
composeMaterialIcons = "1.7.8" composeMaterialIcons = "1.7.8"
runtime = "1.11.4" runtime = "1.11.4"
uiText = "1.11.4" uiText = "1.11.4"
wear = "1.4.0"
wearCompose = "1.4.1"
[libraries] [libraries]
androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" } androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
@ -54,7 +57,12 @@ androidx-compose-material-icons-core = { group = "androidx.compose.material", na
androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "composeMaterialIcons" } androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended", version.ref = "composeMaterialIcons" }
androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" } androidx-compose-runtime = { group = "androidx.compose.runtime", name = "runtime", version.ref = "runtime" }
androidx-compose-ui-text = { group = "androidx.compose.ui", name = "ui-text", version.ref = "uiText" } androidx-compose-ui-text = { group = "androidx.compose.ui", name = "ui-text", version.ref = "uiText" }
androidx-wear = { module = "androidx.wear:wear", version.ref = "wear" }
androidx-wear-compose = { group = "androidx.wear.compose", name = "compose-material", version.ref = "wearCompose" }
androidx-wear-compose-foundation = { group = "androidx.wear.compose", name = "compose-foundation", version.ref = "wearCompose" }
androidx-wear-compose-navigation = { group = "androidx.wear.compose", name = "compose-navigation", version.ref = "wearCompose" }
[plugins] [plugins]
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerializationPlugin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlinSerializationPlugin" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "android" }

View File

@ -1,5 +1,2 @@
plugins { include(":app")
id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" include(":wear")
}
include (":app")

52
wear/build.gradle.kts Normal file
View File

@ -0,0 +1,52 @@
plugins {
id("com.android.application")
alias(libs.plugins.compose.compiler)
}
android {
namespace = "com.tutpro.baresip.wear"
compileSdk = 37
ndkVersion = "29.0.14206865"
defaultConfig {
applicationId = "com.tutpro.baresip.wear"
minSdk = 30
targetSdk = 36
versionCode = 1
versionName = "0.1.0-wear"
externalNativeBuild {
cmake {
cFlags += "-DHAVE_INTTYPES_H -lstdc++"
arguments.addAll(listOf("-DANDROID_STL=c++_shared"))
}
}
ndk {
abiFilters.addAll(listOf("arm64-v8a", "armeabi-v7a"))
}
}
buildFeatures {
compose = true
buildConfig = true
}
externalNativeBuild {
cmake {
path = file("src/main/cpp/CMakeLists.txt")
version = "3.31.6"
}
}
}
dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.wear)
implementation(libs.androidx.wear.compose)
implementation(libs.androidx.wear.compose.foundation)
implementation(libs.androidx.wear.compose.navigation)
}

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE" />
<uses-feature android:name="android.hardware.type.watch" android:required="true" />
<application
android:name=".WearBaresipApp"
android:allowBackup="true"
android:label="Baresip Wear"
android:supportsRtl="true"
android:theme="@android:style/Theme.DeviceDefault"
tools:targetApi="33">
<activity
android:name=".MainActivity"
android:exported="true"
android:label="Baresip"
android:taskAffinity="">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".WearBaresipService"
android:enabled="true"
android:exported="false"
android:foregroundServiceType="microphone"
android:stopWithTask="false" />
</application>
</manifest>

View File

@ -0,0 +1,98 @@
cmake_minimum_required(VERSION 3.18...4.0)
project(baresip-wear)
add_link_options("LINKER:--build-id=none")
set(distribution_DIR ${CMAKE_SOURCE_DIR}/../../../../distribution)
add_library(lib_crypto STATIC IMPORTED)
set_target_properties(lib_crypto PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/openssl/lib/${ANDROID_ABI}/libcrypto.a)
add_library(lib_ssl STATIC IMPORTED)
set_target_properties(lib_ssl PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/openssl/lib/${ANDROID_ABI}/libssl.a)
add_library(lib_re STATIC IMPORTED)
set_target_properties(lib_re PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/re/lib/${ANDROID_ABI}/libre.a)
add_library(lib_opus STATIC IMPORTED)
set_target_properties(lib_opus PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/opus/lib/${ANDROID_ABI}/libopus.a)
add_library(lib_g722 STATIC IMPORTED)
set_target_properties(lib_g722 PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/g722/lib/${ANDROID_ABI}/libg722.a)
add_library(lib_g722_1 STATIC IMPORTED)
set_target_properties(lib_g722_1 PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/g7221/lib/${ANDROID_ABI}/libg722_1.a)
add_library(lib_g729 STATIC IMPORTED)
set_target_properties(lib_g729 PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/g729/lib/${ANDROID_ABI}/libbcg729.a)
add_library(lib_ilbc STATIC IMPORTED)
set_target_properties(lib_ilbc PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/ilbc/lib/${ANDROID_ABI}/libilbc.a)
add_library(lib_codec2 STATIC IMPORTED)
set_target_properties(lib_codec2 PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/codec2/lib/${ANDROID_ABI}/libcodec2.a)
add_library(lib_amrnb STATIC IMPORTED)
set_target_properties(lib_amrnb PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrnb.a)
add_library(lib_amrwb STATIC IMPORTED)
set_target_properties(lib_amrwb PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrwb.a)
add_library(lib_amrwbenc STATIC IMPORTED)
set_target_properties(lib_amrwbenc PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/amr/lib/${ANDROID_ABI}/libamrwbenc.a)
add_library(lib_zrtpcppcore STATIC IMPORTED)
set_target_properties(lib_zrtpcppcore PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/gzrtp/lib/${ANDROID_ABI}/libzrtpcppcore.a)
add_library(lib_sndfile STATIC IMPORTED)
set_target_properties(lib_sndfile PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/sndfile/lib/${ANDROID_ABI}/libsndfile.a)
add_library(lib_baresip STATIC IMPORTED)
set_target_properties(lib_baresip PROPERTIES IMPORTED_LOCATION
${distribution_DIR}/baresip/lib/${ANDROID_ABI}/libbaresip.a)
add_library(wearbaresip SHARED ${CMAKE_SOURCE_DIR}/baresip.c)
target_include_directories(wearbaresip PRIVATE
${distribution_DIR}/openssl/include
${distribution_DIR}/re/include
${distribution_DIR}/baresip/include)
add_definitions(-DHAVE_PTHREAD)
target_link_libraries(
wearbaresip
android
aaudio
lib_baresip
lib_re
lib_ssl
lib_crypto
lib_opus
lib_g722
lib_g722_1
lib_g729
lib_ilbc
lib_codec2
lib_amrnb
lib_amrwb
lib_amrwbenc
lib_zrtpcppcore
lib_sndfile
z
log)

466
wear/src/main/cpp/baresip.c Normal file
View File

@ -0,0 +1,466 @@
#include <string.h>
#include <pthread.h>
#include <jni.h>
#include <aaudio/AAudio.h>
#include <stdlib.h>
#include <re.h>
#include <baresip.h>
#include "logger.h"
typedef struct baresip_context
{
JavaVM *javaVM;
jclass serviceClz;
jobject serviceObj;
} BaresipContext;
static BaresipContext g_ctx;
enum
{
ASYNC_WORKERS = 4
};
static pthread_key_t g_thread_key;
static void detach_thread(void *env)
{
(void)env;
(*g_ctx.javaVM)->DetachCurrentThread(g_ctx.javaVM);
}
static JNIEnv *get_jni_env(void)
{
JNIEnv *env;
jint res = (*g_ctx.javaVM)->GetEnv(g_ctx.javaVM, (void**)&env, JNI_VERSION_1_6);
if (res != JNI_OK) {
res = (*g_ctx.javaVM)->AttachCurrentThread(g_ctx.javaVM, &env, NULL);
if (res == JNI_OK) {
pthread_setspecific(g_thread_key, env);
} else {
return NULL;
}
}
return env;
}
static void ua_exit_handler(void *arg)
{
(void)arg;
re_cancel();
}
static const char *translate_errorcode(uint16_t scode)
{
switch (scode) {
case 404:
return "";
case 486:
case 603:
return "busy";
case 487:
return "";
default:
return "error";
}
}
static void event_handler(enum bevent_ev ev, struct bevent *event, void *arg)
{
(void)arg;
const char *prm = bevent_get_text(event);
struct call *call = bevent_get_call(event);
struct ua *ua = bevent_get_ua(event);
const struct sip_msg *msg = bevent_get_msg(event);
struct account *acc = ua_account(bevent_get_ua(event));
const char *tone;
char event_buf[256];
enum sdp_dir ardir;
int len, err;
struct pl module, module_event, data;
switch (ev) {
case BEVENT_CREATE:
len = re_snprintf(event_buf, sizeof event_buf, "create", "");
break;
case BEVENT_REGISTERING:
case BEVENT_UNREGISTERING:
case BEVENT_REGISTER_OK:
case BEVENT_FALLBACK_OK:
len = re_snprintf(event_buf, sizeof event_buf, "%s", prm);
break;
case BEVENT_REGISTER_FAIL:
case BEVENT_FALLBACK_FAIL:
len = re_snprintf(event_buf, sizeof event_buf, "registering failed,%s", prm);
break;
case BEVENT_SIPSESS_CONN:
ua = uag_find_msg(msg);
call = (struct call *)msg;
len = re_snprintf(event_buf, sizeof event_buf, "%s,%r,%ld", prm, &msg->from.auri, (long)event);
break;
case BEVENT_CALL_INCOMING:
len = re_snprintf(event_buf, sizeof event_buf, "call incoming,%s", prm);
break;
case BEVENT_CALL_OUTGOING:
len = re_snprintf(event_buf, sizeof event_buf, "call outgoing", "");
break;
case BEVENT_CALL_ANSWERED:
len = re_snprintf(event_buf, sizeof event_buf, "call answered", "");
break;
case BEVENT_CALL_REDIRECT:
len = re_snprintf(event_buf, sizeof event_buf, "call redirect,%s", prm + 4);
break;
case BEVENT_CALL_LOCAL_SDP:
if (strcmp(prm, "offer") == 0)
return;
len = re_snprintf(event_buf, sizeof event_buf, "call %sed", prm);
break;
case BEVENT_CALL_RINGING:
len = re_snprintf(event_buf, sizeof event_buf, "call ringing", "");
break;
case BEVENT_CALL_PROGRESS:
ardir = sdp_media_rdir(stream_sdpmedia(audio_strm(call_audio(call))));
len = re_snprintf(event_buf, sizeof event_buf, "call progress,%d", ardir);
break;
case BEVENT_CALL_ESTABLISHED:
len = re_snprintf(event_buf, sizeof event_buf, "call established", "");
break;
case BEVENT_CALL_REMOTE_SDP:
ardir = sdp_media_rdir(stream_sdpmedia(audio_strm(call_audio(call))));
len = re_snprintf(event_buf, sizeof event_buf, "call update,%d,%ld", ardir, (long)event);
break;
case BEVENT_CALL_MENC:
if (prm[0] == '0')
len = re_snprintf(event_buf, sizeof event_buf, "call secure", "");
else if (prm[0] == '1')
len = re_snprintf(event_buf, sizeof event_buf, "call verify,%s", prm + 2);
else if (prm[0] == '2')
len = re_snprintf(event_buf, sizeof event_buf, "call verified,%s", prm + 2);
else
len = re_snprintf(event_buf, sizeof event_buf, "unknown menc event", "");
break;
case BEVENT_CALL_TRANSFER:
len = re_snprintf(event_buf, sizeof event_buf, "call transfer,%s", prm);
break;
case BEVENT_CALL_TRANSFER_FAILED:
call_hold(call, false);
len = re_snprintf(event_buf, sizeof event_buf, "transfer failed,%s", prm);
break;
case BEVENT_CALL_CLOSED:
tone = call_scode(call) ? translate_errorcode(call_scode(call)) : "";
len = re_snprintf(event_buf, sizeof event_buf, "call closed,%s,%s", prm, tone);
break;
case BEVENT_MWI_NOTIFY:
len = re_snprintf(event_buf, sizeof event_buf, "mwi notify,%s", prm);
break;
case BEVENT_MODULE:
err = re_regex(prm, strlen(prm), "[^,]*,[^,]*,[~]*", &module, &module_event, &data);
if (err)
return;
if (!pl_strcmp(&module_event, "dump")) {
len = re_snprintf(event_buf, sizeof event_buf, "sndfile dump,%r", &data);
break;
}
if (!pl_strcmp(&module_event, "recorder sessionid")) {
len = re_snprintf(event_buf, sizeof event_buf, "recorder sessionid,%r", &data);
break;
}
default:
return;
}
if (len == -1) {
return;
}
JNIEnv *env = get_jni_env();
if (!env) return;
if (!g_ctx.serviceClz || !g_ctx.serviceObj) return;
jmethodID methodId =
(*env)->GetMethodID(env, g_ctx.serviceClz, "uaEvent", "(Ljava/lang/String;)V");
jstring jEvent = (*env)->NewStringUTF(env, event_buf);
if (methodId) {
(*env)->CallVoidMethod(env, g_ctx.serviceObj, methodId, jEvent);
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
}
(*env)->DeleteLocalRef(env, jEvent);
}
static void message_handler(
struct ua *ua, const struct pl *peer, const struct pl *ctype, struct mbuf *body, void *arg)
{
(void)arg;
char peer_buf[256];
size_t size;
if (snprintf(peer_buf, 256, "%.*s", (int)peer->l, peer->p) >= 256) {
return;
}
JNIEnv *env = get_jni_env();
if (!env) return;
if (!g_ctx.serviceClz || !g_ctx.serviceObj) return;
jmethodID methodId = (*env)->GetMethodID(env, g_ctx.serviceClz, "messageEvent",
"(JLjava/lang/String;Ljava/lang/String;[B)V");
jstring jPeer = (*env)->NewStringUTF(env, peer_buf);
jbyteArray jMsg;
size = mbuf_get_left(body);
jMsg = (*env)->NewByteArray(env, (jsize)size);
if ((*env)->GetArrayLength(env, jMsg) != size) {
(*env)->DeleteLocalRef(env, jMsg);
jMsg = (*env)->NewByteArray(env, (jsize)size);
}
void *temp = (*env)->GetPrimitiveArrayCritical(env, (jarray)jMsg, 0);
memcpy(temp, mbuf_buf(body), size);
(*env)->ReleasePrimitiveArrayCritical(env, jMsg, temp, 0);
if (methodId) {
(*env)->CallVoidMethod(env, g_ctx.serviceObj, methodId, (jlong)ua, jPeer, NULL, jMsg);
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
}
(*env)->DeleteLocalRef(env, jPeer);
(*env)->DeleteLocalRef(env, jMsg);
}
static void send_resp_handler(int err, const struct sip_msg *msg, void *arg)
{
char *native_time = (char *)arg;
char reason_buf[64];
if (err) {
mem_deref(native_time);
return;
}
pl_strcpy(&(msg->reason), reason_buf, 64);
JNIEnv *env = get_jni_env();
if (!env) {
mem_deref(native_time);
return;
}
if (!g_ctx.serviceClz || !g_ctx.serviceObj) {
mem_deref(native_time);
return;
}
jmethodID methodId = (*env)->GetMethodID(env, g_ctx.serviceClz, "messageResponse",
"(ILjava/lang/String;Ljava/lang/String;)V");
jstring javaReason = (*env)->NewStringUTF(env, reason_buf);
jstring javaTime = (*env)->NewStringUTF(env, native_time);
if (methodId) {
(*env)->CallVoidMethod(env, g_ctx.serviceObj, methodId, msg->scode, javaReason,
javaTime);
if ((*env)->ExceptionCheck(env)) {
(*env)->ExceptionDescribe(env);
(*env)->ExceptionClear(env);
}
}
(*env)->DeleteLocalRef(env, javaReason);
(*env)->DeleteLocalRef(env, javaTime);
mem_deref(native_time);
}
enum
{
ID_UA_STOP_ALL
};
static struct mqueue *mq;
static void mqueue_handler(int id, void *data, void *arg)
{
(void)arg;
if (id == ID_UA_STOP_ALL) {
ua_stop_all((bool)(uintptr_t)data);
}
}
#include <unistd.h>
static int pfd[2];
static pthread_t loggingThread;
static void *loggingFunction(void *arg)
{
(void)arg;
ssize_t readSize;
char buf[128];
while ((readSize = read(pfd[0], buf, sizeof buf - 1)) > 0) {
if (buf[readSize - 1] == '\n') {
--readSize;
}
buf[readSize] = 0;
LOGD("%s", buf);
}
return 0;
}
static int runLoggingThread()
{
setvbuf(stdout, 0, _IOLBF, 0);
setvbuf(stderr, 0, _IONBF, 0);
pipe(pfd);
dup2(pfd[1], 1);
dup2(pfd[1], 2);
int ret = pthread_create(&loggingThread, NULL, loggingFunction, NULL);
if (ret != 0) {
return ret;
}
pthread_detach(loggingThread);
return 0;
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved)
{
(void)reserved;
memset(&g_ctx, 0, sizeof(g_ctx));
g_ctx.javaVM = vm;
if (pthread_key_create(&g_thread_key, detach_thread) != 0) {
}
return JNI_VERSION_1_6;
}
JNIEXPORT void JNICALL
Java_com_tutpro_baresip_wear_WearBaresipService_baresipStart(
JNIEnv *env, jobject instance, jstring jPath, jstring jAddrs, jint jLogLevel, jstring jSoftware)
{
int err;
jclass clz = (*env)->GetObjectClass(env, instance);
g_ctx.serviceClz = (*env)->NewGlobalRef(env, clz);
g_ctx.serviceObj = (*env)->NewGlobalRef(env, instance);
const char *path = (*env)->GetStringUTFChars(env, jPath, 0);
const char *addrs = (*env)->GetStringUTFChars(env, jAddrs, 0);
const char *software = (*env)->GetStringUTFChars(env, jSoftware, 0);
runLoggingThread();
err = libre_init();
if (err) {
goto out;
}
if (re_thread_check(true) == 0) {
jint res = (*g_ctx.javaVM)->AttachCurrentThread(g_ctx.javaVM, &env, NULL);
if (JNI_OK != res) {
goto out;
}
} else {
goto out;
}
conf_path_set(path);
log_level_set((enum log_level)jLogLevel);
err = conf_configure();
if (err) {
goto out;
}
re_thread_async_init(ASYNC_WORKERS);
err = baresip_init(conf_config());
if (err) {
goto out;
}
dnsc_cache_max(net_dnsc(baresip_network()), 0);
if (strlen(addrs) > 0) {
char *addr_list = (char *)malloc(strlen(addrs) + 1);
struct sa temp_sa;
char buf[256];
net_flush_addresses(baresip_network());
strcpy(addr_list, addrs);
char *ptr = strtok(addr_list, ";");
while (ptr != NULL) {
if (0 == sa_set_str(&temp_sa, ptr, 0)) {
sa_ntop(&temp_sa, buf, 256);
ptr = strtok(NULL, ";");
net_add_address_ifname(baresip_network(), &temp_sa, ptr);
} else {
ptr = strtok(NULL, ";");
}
if (ptr)
*(ptr - 1) = ';';
ptr = strtok(NULL, ";");
}
free(addr_list);
}
err = ua_init(software, true, true, true);
if (err) {
goto out;
}
uag_set_exit_handler(ua_exit_handler, NULL);
err = bevent_register(event_handler, NULL);
if (err) {
goto out;
}
err = message_listen(baresip_message(), message_handler, NULL);
if (err) {
goto out;
}
err = conf_modules();
if (err) {
goto out;
}
err = mqueue_alloc(&mq, mqueue_handler, NULL);
if (err) {
goto out;
}
out:
(*env)->ReleaseStringUTFChars(env, jPath, path);
(*env)->ReleaseStringUTFChars(env, jAddrs, addrs);
(*env)->ReleaseStringUTFChars(env, jSoftware, software);
}
JNIEXPORT void JNICALL
Java_com_tutpro_baresip_wear_WearBaresipService_baresipStop(JNIEnv *env, jobject instance)
{
(void)env;
(void)instance;
ua_stop_all(true);
baresip_close();
libre_close();
if (g_ctx.serviceClz) {
(*env)->DeleteGlobalRef(env, g_ctx.serviceClz);
g_ctx.serviceClz = NULL;
}
if (g_ctx.serviceObj) {
(*env)->DeleteGlobalRef(env, g_ctx.serviceObj);
g_ctx.serviceObj = NULL;
}
}

View File

@ -0,0 +1,13 @@
#include <baresip.h>
#include <android/log.h>
#ifndef BARESIP_LOGGER_H
#define BARESIP_LOGGER_H
#define LOG_TAG "Baresip Wear"
#define LOGD(...) ((void)__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__))
#define LOGI(...) ((void)__android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__))
#define LOGW(...) ((void)__android_log_print(ANDROID_LOG_WARN, LOG_TAG, __VA_ARGS__))
#define LOGE(...) ((void)__android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__))
#endif

View File

@ -0,0 +1,49 @@
package com.tutpro.baresip.wear
import android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.ContextCompat
import androidx.wear.compose.material.Text
import androidx.wear.compose.material.TimeText
class MainActivity : ComponentActivity() {
private val recordAudioPermission = registerForActivityResult(
ActivityResultContracts.RequestPermission()
) { granted ->
if (granted) {
startBaresipService()
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TimeText()
Text(text = "Baresip Wear")
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO)
== PackageManager.PERMISSION_GRANTED
) {
startBaresipService()
} else {
recordAudioPermission.launch(Manifest.permission.RECORD_AUDIO)
}
}
private fun startBaresipService() {
val intent = Intent(this, WearBaresipService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(intent)
} else {
startService(intent)
}
}
}

View File

@ -0,0 +1,10 @@
package com.tutpro.baresip.wear
import android.app.Application
class WearBaresipApp : Application() {
// The foreground service is launched from MainActivity (foreground state),
// not from Application.onCreate(), because starting a microphone-type
// foreground service from a non-foreground context is rejected on
// targetSdk 34+.
}

View File

@ -0,0 +1,65 @@
package com.tutpro.baresip.wear
import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
class WearBaresipService : Service() {
override fun onCreate() {
super.onCreate()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel = NotificationChannel(
CHANNEL_ID,
"Baresip Wear",
NotificationManager.IMPORTANCE_LOW
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(channel)
}
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Baresip Wear")
.setContentText("SIP stack running")
.setSmallIcon(android.R.drawable.sym_call_incoming)
.build()
startForeground(NOTIFICATION_ID, notification)
// Start the native baresip stack. Path points at the app's private
// config directory where accounts.cfg etc. are expected.
val path = applicationContext.filesDir.absolutePath
baresipStart(path, "", 2, "baresip-studio-wear")
return START_STICKY
}
override fun onDestroy() {
baresipStop()
super.onDestroy()
}
override fun onBind(intent: Intent?): IBinder? = null
external fun baresipStart(path: String, addrs: String, logLevel: Int, software: String)
external fun baresipStop()
fun uaEvent(event: String) {
// TODO route to watch UI / Data Layer
}
companion object {
const val CHANNEL_ID = "baresip-wear"
const val NOTIFICATION_ID = 1
}
init {
System.loadLibrary("wearbaresip")
}
}