Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion android/EdgeAI/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ android {
compileSdk = 35

defaultConfig {
minSdk = 34
minSdk = 23
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,13 @@ object EdgeAI {
* - Subsequent calls: Returns immediately with success
* - Cancellable: Can be cancelled via coroutine cancellation
*
* ## Package Name Detection
*
* The SDK attempts to find the BreezeApp Engine Service in the following order:
* 1. **Explicit Target:** If [targetPackageName] is provided, it is used directly.
* 2. **Embedded (Host):** Checks if the service exists in the current application's package.
* 3. **Standalone:** Falls back to the standard standalone app package (`com.mtkresearch.breezeapp.engine`).
*
* ## Example Usage
*
* ```kotlin
Expand All @@ -194,47 +201,70 @@ object EdgeAI {
* }.onFailure { error ->
* println("Initialization failed: ${error.message}")
* }
*
* // Connect to a specific host (e.g. Signal)
* EdgeAI.initialize(context, targetPackageName = "org.thoughtcrime.securesms")
* }
* ```
*
* @param context Android application context. Will be converted to application context internally.
* @param targetPackageName Optional package name to bind to. If null, auto-detection logic is used.
* @return [Result]<Unit> indicating success or failure
*
* @see initializeAndWait
* @see shutdown
*/
suspend fun initialize(context: Context): Result<Unit> = suspendCancellableCoroutine { continuation ->
suspend fun initialize(context: Context, targetPackageName: String? = null): Result<Unit> = suspendCancellableCoroutine { continuation ->
if (isInitialized) {
continuation.resume(Result.success(Unit))
return@suspendCancellableCoroutine
}

this.context = context.applicationContext
val appContext = context.applicationContext
this.context = appContext

// Set up completion callback
initializationCompletion = { result ->
isInitialized = result.isSuccess
continuation.resume(result)
}

// Determine which package to bind to
val resolvedPackageName = targetPackageName ?: run {
// Auto-detect strategy:
// 1. Check if the service exists in the current app (Embedded/Library mode)
val localIntent = Intent(AI_ROUTER_SERVICE_ACTION).setPackage(appContext.packageName)
val pm = appContext.packageManager
val localResolve = pm.resolveService(localIntent, 0)

if (localResolve != null) {
Log.d(TAG, "Found embedded BreezeApp Engine in local package: ${appContext.packageName}")
appContext.packageName
} else {
// 2. Fallback to standalone app
Log.d(TAG, "Embedded engine not found, falling back to standalone package: $AI_ROUTER_SERVICE_PACKAGE")
AI_ROUTER_SERVICE_PACKAGE
}
}

// Bind to service
val intent = Intent(AI_ROUTER_SERVICE_ACTION).apply {
setPackage(AI_ROUTER_SERVICE_PACKAGE)
setPackage(resolvedPackageName)
}

val bound = try {
context.applicationContext.bindService(
appContext.bindService(
intent,
serviceConnection,
Context.BIND_AUTO_CREATE
)
} catch (e: Exception) {
Log.e(TAG, "Failed to bind to service", e)
Log.e(TAG, "Failed to bind to service in package: $resolvedPackageName", e)
false
}

if (!bound) {
val error = ServiceConnectionException("Failed to bind to BreezeApp Engine Service")
val error = ServiceConnectionException("Failed to bind to BreezeApp Engine Service in package: $resolvedPackageName")
initializationCompletion?.invoke(Result.failure(error))
initializationCompletion = null
}
Expand All @@ -244,7 +274,7 @@ object EdgeAI {
initializationCompletion = null
if (isBound) {
try {
context.applicationContext.unbindService(serviceConnection)
appContext.unbindService(serviceConnection)
} catch (e: Exception) {
Log.w(TAG, "Error unbinding service during cancellation: ${e.message}")
}
Expand Down Expand Up @@ -297,18 +327,22 @@ object EdgeAI {
* } catch (e: ServiceConnectionException) {
* println("BreezeApp Engine not available: ${e.message}")
* }
*
* // Connect to Signal's engine instance
* EdgeAI.initializeAndWait(context, targetPackageName = "org.thoughtcrime.securesms")
* }
* ```
*
* @param context Android application context
* @param targetPackageName Optional package name to bind to
* @param timeoutMs Timeout in milliseconds (currently unused, kept for API compatibility)
* @throws ServiceConnectionException if BreezeApp Engine is not available or initialization fails
*
* @see initialize
* @see shutdown
*/
suspend fun initializeAndWait(context: Context, timeoutMs: Long = 10000) {
val result = initialize(context)
suspend fun initializeAndWait(context: Context, targetPackageName: String? = null, timeoutMs: Long = 10000) {
val result = initialize(context, targetPackageName)
result.getOrElse { error ->
throw error as? ServiceConnectionException
?: ServiceConnectionException("Initialization failed: ${error.message}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.mtkresearch.breezeapp.edgeai.*
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.launch
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
import org.junit.Test
import org.junit.Assert.*
import org.junit.runner.RunWith
Expand Down Expand Up @@ -62,7 +63,7 @@ class SDKLifecycleExamples : EdgeAITestBase() {
@Test
fun `01 - initialize with Result`() = runTest {
val result = EdgeAI.initialize(
context = mock()
context = mockContext()
)

result.onSuccess {
Expand Down Expand Up @@ -96,7 +97,7 @@ class SDKLifecycleExamples : EdgeAITestBase() {
fun `02 - initialize and wait`() = runTest {
try {
EdgeAI.initializeAndWait(
context = mock()
context = mockContext()
)

println("✓ SDK initialized successfully")
Expand Down Expand Up @@ -217,7 +218,7 @@ class SDKLifecycleExamples : EdgeAITestBase() {
kotlinx.coroutines.Dispatchers.Main
).launch {
try {
EdgeAI.initializeAndWait(mock())
EdgeAI.initializeAndWait(mockContext())
_isReady.value = true
} catch (e: EdgeAIException) {
_isReady.value = false
Expand Down Expand Up @@ -284,6 +285,18 @@ class SDKLifecycleExamples : EdgeAITestBase() {
// === HELPER FUNCTIONS ===

private fun mockContext(): android.content.Context {
return mock()
val mockContext: android.content.Context = mock()
val mockPackageManager: android.content.pm.PackageManager = mock()

// Return self as application context
whenever(mockContext.applicationContext).thenReturn(mockContext)

// Return fake package name
whenever(mockContext.packageName).thenReturn("com.mtkresearch.breezeapp.edgeai.test")

// Return mock package manager
whenever(mockContext.packageManager).thenReturn(mockPackageManager)

return mockContext
}
}
Loading