Skip to content

Commit

Permalink
Merge branch 'kmp-pkjs' into kmp
Browse files Browse the repository at this point in the history
  • Loading branch information
crc-32 committed Oct 14, 2024
2 parents c3fbb7f + 451ef11 commit 7557956
Show file tree
Hide file tree
Showing 176 changed files with 4,731 additions and 1,883 deletions.
5 changes: 3 additions & 2 deletions android/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ android {
java.srcDirs("src/main/kotlin")
}
getByName("androidTest") {
assets.srcDirs("src/androidTest/assets")
java.srcDirs("src/androidTest/kotlin")
}
}
Expand Down Expand Up @@ -73,7 +74,7 @@ android {
buildTypes {
getByName("release") {
isMinifyEnabled = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro", rootProject.file("shared/androidMain/proguard-rules.pro"))
signingConfig = signingConfigs.getByName("release")
}
getByName("debug") {
Expand Down Expand Up @@ -137,7 +138,7 @@ dependencies {
implementation("androidx.lifecycle:lifecycle-service:$lifecycleVersion")
implementation("com.jakewharton.timber:timber:$timberVersion")
implementation("androidx.core:core-ktx:$androidxCoreVersion")
implementation("androidx.work:work-runtime-ktx:$workManagerVersion")
implementation(libs.androidx.work.runtime.ktx)
implementation("com.squareup.okio:okio:$okioVersion")
implementation(libs.androidx.room.runtime)
implementation(libs.kotlinx.datetime)
Expand Down
4 changes: 4 additions & 0 deletions android/app/src/androidTest/assets/print_test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Pebble.addEventListener('ready', function() {
// PebbleKit JS is ready!
console.log('PebbleKit JS ready!');
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package io.rebble.cobble.shared.js

import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import io.rebble.cobble.shared.domain.common.PebbleDevice
import io.rebble.libpebblecommon.metadata.pbw.appinfo.PbwAppInfo
import io.rebble.libpebblecommon.util.runBlocking
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.serialization.json.Json
import org.junit.Before
import org.junit.Test
import java.io.File

class WebViewJsRunnerTest {

private lateinit var context: Context
private val json = Json {ignoreUnknownKeys = true}
private val coroutineScope = CoroutineScope(Dispatchers.Default)
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().targetContext
}

// Copies from the android_assets of the test to the sdcard so the WebView can access it
private fun assetsToSdcard(file: String): String {
val sdcardPath = context.getExternalFilesDir(null)!!.absolutePath
val testPath = "$sdcardPath/test"
File(testPath).mkdir()
val testFile = "$testPath/$file"
val assetManager = InstrumentationRegistry.getInstrumentation().getContext().getAssets()
val inputStream = assetManager.open(file)
val outputStream = File(testFile).outputStream()
inputStream.copyTo(outputStream)
inputStream.close()
outputStream.close()
return testFile
}

@Test
fun test() = runBlocking {
val appInfo: PbwAppInfo = json.decodeFromString(
"""
{
"targetPlatforms": [
"aplite",
"basalt",
"chalk",
"diorite"
],
"projectType": "native",
"messageKeys": {},
"companyName": "ttmm",
"enableMultiJS": true,
"versionLabel": "2.12",
"longName": "ttmmbrn",
"shortName": "ttmmbrn",
"name": "ttmmbrn",
"sdkVersion": "3",
"displayName": "ttmmbrn",
"uuid": "c4c60c62-2c22-4ad7-aef4-cad9481da58b",
"appKeys": {},
"capabilities": [
"health",
"location",
"configurable"
],
"watchapp": {
"watchface": true
},
"resources": {
"media": []
}
}
""".trimIndent()
)
val printTestPath = assetsToSdcard("print_test.js")
val webViewJsRunner = WebViewJsRunner(context, PebbleDevice(null, "dummy"), coroutineScope, appInfo, printTestPath)
webViewJsRunner.start()
delay(1000)
webViewJsRunner.stop()
}
}
4 changes: 2 additions & 2 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@
android:foregroundServiceType="connectedDevice"
android:permission="android.permission.FOREGROUND_SERVICE" />
<service
android:name=".notifications.NotificationListener"
android:name=".shared.domain.notifications.NotificationListener"
android:exported="true"
android:foregroundServiceType="specialUse"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
Expand Down Expand Up @@ -262,7 +262,7 @@
</provider>

<provider
android:name=".providers.PebbleKitProvider"
android:name=".shared.providers.PebbleKitProvider"
android:authorities="${pebbleKitProviderAuthority}"
android:exported="true"
tools:ignore="ExportedContentProvider" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import io.rebble.cobble.di.AppComponent
import io.rebble.cobble.di.DaggerAppComponent
import io.rebble.cobble.log.AppTaggedDebugTree
import io.rebble.cobble.log.FileLoggingTree
import io.rebble.cobble.shared.database.closeDatabase
import io.rebble.cobble.shared.di.initKoin
import timber.log.Timber
import kotlin.system.exitProcess
Expand All @@ -27,7 +26,6 @@ class CobbleApplication : FlutterApplication() {
initKoin(applicationContext)

component.initNotificationChannels()
component.initLibPebbleCommonServices()

beginConnectingToDefaultWatch()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,10 @@ import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.rebble.cobble.bridges.FlutterBridge
import io.rebble.cobble.bridges.ui.REQUEST_CODE_NOTIFICATIONS_POST
import io.rebble.cobble.datasources.PermissionChangeBus
import io.rebble.cobble.service.CompanionDeviceService
import io.rebble.cobble.service.InCallService
import io.rebble.cobble.shared.database.closeDatabase
import io.rebble.cobble.util.hasNotificationPostingPermission
import io.rebble.cobble.shared.domain.PermissionChangeBus
import io.rebble.cobble.shared.util.hasNotificationPostingPermission
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.plus
import java.net.URI
Expand Down Expand Up @@ -128,7 +127,7 @@ class FlutterMainActivity : FlutterActivity() {
}

override fun onDestroy() {
closeDatabase()
//closeDatabase()
super.onDestroy()
}

Expand Down
3 changes: 0 additions & 3 deletions android/app/src/main/kotlin/io/rebble/cobble/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,11 @@ import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.WindowCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import io.rebble.cobble.shared.ui.view.MainView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.plus

class MainActivity : AppCompatActivity() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import android.content.Context
import android.os.Build
import androidx.annotation.RequiresPermission
import io.rebble.cobble.bluetooth.classic.ReconnectionSocketServer
import io.rebble.cobble.shared.domain.common.PebbleDevice
import io.rebble.cobble.shared.domain.state.ConnectionState
import io.rebble.cobble.shared.domain.state.ConnectionStateManager
import io.rebble.cobble.shared.domain.state.watchOrNull
Expand All @@ -15,8 +14,6 @@ import kotlinx.coroutines.flow.*
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.EmptyCoroutineContext
import kotlin.math.min

@OptIn(ExperimentalCoroutinesApi::class)
Expand All @@ -39,22 +36,6 @@ class ConnectionLooper @Inject constructor(
private var lastConnectedWatch: String? = null
private var delayJob: Job? = null

fun negotiationsComplete(watch: PebbleDevice) {
if (connectionState.value is ConnectionState.Negotiating) {
_connectionState.value = ConnectionState.Connected(watch)
} else {
Timber.w("negotiationsComplete state mismatch!")
}
}

fun recoveryMode(watch: PebbleDevice) {
if (connectionState.value is ConnectionState.Connected || connectionState.value is ConnectionState.Negotiating) {
_connectionState.value = ConnectionState.RecoveryMode(watch)
} else {
Timber.w("recoveryMode state mismatch!")
}
}

fun signalWatchPresence(macAddress: String) {
_watchPresenceState.value = macAddress
if (lastConnectedWatch == macAddress) {
Expand Down Expand Up @@ -110,10 +91,10 @@ class ConnectionLooper @Inject constructor(

getBluetoothStatus(context).first { bluetoothOn -> bluetoothOn }
}

val connectionScope = CoroutineScope(SupervisorJob() + errorHandler + Dispatchers.IO) + CoroutineName("ConnectionScope-$macAddress")
try {
blueCommon.startSingleWatchConnection(macAddress).collect {
if (it is SingleConnectionStatus.Connected && connectionState.value !is ConnectionState.Connected && connectionState.value !is ConnectionState.RecoveryMode) {
if (it is SingleConnectionStatus.Connected /*&& connectionState.value !is ConnectionState.Connected && connectionState.value !is ConnectionState.RecoveryMode*/) {
// initial connection, wait on negotiation
_connectionState.value = ConnectionState.Negotiating(it.watch)
} else {
Expand All @@ -123,12 +104,15 @@ class ConnectionLooper @Inject constructor(
if (it is SingleConnectionStatus.Connected) {
retryTime = HALF_OF_INITAL_RETRY_TIME
retries = 0
_connectionState.value.watchOrNull?.connectionScope?.value = connectionScope
}
}
} catch (_: CancellationException) {
// Do nothing. Cancellation is OK
} catch (e: Exception) {
Timber.e(e, "Watch connection error")
} finally {
connectionScope.cancel("Connection ended")
}

if (isActive) {
Expand Down Expand Up @@ -184,28 +168,6 @@ class ConnectionLooper @Inject constructor(
}
currentConnection?.cancel()
}

/**
* Get [CoroutineScope] that is active while watch is connected and cancelled if watch
* disconnects.
*/
fun getWatchConnectedScope(
context: CoroutineContext = EmptyCoroutineContext
): CoroutineScope {
val scope = CoroutineScope(SupervisorJob() + errorHandler + context)

scope.launch(Dispatchers.Unconfined) {
connectionState.collect {
if (it !is ConnectionState.Connected &&
it !is ConnectionState.Negotiating &&
it !is ConnectionState.RecoveryMode) {
scope.cancel()
}
}
}

return scope
}
}

private fun SingleConnectionStatus.toConnectionStatus(): ConnectionState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import io.rebble.cobble.bluetooth.classic.BlueSerialDriver
import io.rebble.cobble.bluetooth.classic.SocketSerialDriver
import io.rebble.cobble.bluetooth.scan.BleScanner
import io.rebble.cobble.bluetooth.scan.ClassicScanner
import io.rebble.cobble.datasources.FlutterPreferences
import io.rebble.cobble.datasources.IncomingPacketsListener
import io.rebble.cobble.shared.datastore.FlutterPreferences
import io.rebble.cobble.shared.domain.common.PebbleDevice
import io.rebble.libpebblecommon.ProtocolHandler
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.onCompletion
import timber.log.Timber
import javax.inject.Inject
import javax.inject.Singleton
Expand All @@ -28,7 +28,6 @@ class DeviceTransport @Inject constructor(
private val context: Context,
private val bleScanner: BleScanner,
private val classicScanner: ClassicScanner,
private val protocolHandler: ProtocolHandler,
private val flutterPreferences: FlutterPreferences,
private val incomingPacketsListener: IncomingPacketsListener
) {
Expand Down Expand Up @@ -56,23 +55,25 @@ class DeviceTransport @Inject constructor(
lastMacAddress = macAddress

val bluetoothDevice = if (BuildConfig.DEBUG && !macAddress.contains(":")) {
EmulatedPebbleDevice(macAddress, protocolHandler)
EmulatedPebbleDevice(macAddress)
} else {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
BluetoothPebbleDevice(bluetoothAdapter.getRemoteDevice(macAddress), protocolHandler, macAddress)
BluetoothPebbleDevice(bluetoothAdapter.getRemoteDevice(macAddress), macAddress)
}

val driver = getTargetTransport(bluetoothDevice)
this@DeviceTransport.driver = driver
return driver.startSingleWatchConnection(bluetoothDevice)
return driver.startSingleWatchConnection(bluetoothDevice).onCompletion {
bluetoothDevice.close()
}
}

@Throws(SecurityException::class)
private fun getTargetTransport(pebbleDevice: PebbleDevice): BlueIO {
return when (pebbleDevice) {
is EmulatedPebbleDevice -> {
SocketSerialDriver(
protocolHandler,
pebbleDevice,
incomingPacketsListener.receivedPackets
)
}
Expand All @@ -87,7 +88,7 @@ class DeviceTransport @Inject constructor(
}
BlueLEDriver(
context = context,
protocolHandler = protocolHandler,
pebbleDevice = pebbleDevice,
gattServerManager = gattServerManager,
incomingPacketsListener = incomingPacketsListener.receivedPackets,
) {
Expand All @@ -97,7 +98,7 @@ class DeviceTransport @Inject constructor(

BluetoothDevice.DEVICE_TYPE_CLASSIC, BluetoothDevice.DEVICE_TYPE_DUAL -> { // Serial only device or serial/LE
BlueSerialDriver(
protocolHandler,
pebbleDevice,
incomingPacketsListener.receivedPackets
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import android.content.Context
import android.content.IntentFilter
import androidx.annotation.RequiresPermission
import io.rebble.cobble.bluetooth.ScannedPebbleDevice
import io.rebble.cobble.util.coroutines.asFlow
import io.rebble.cobble.shared.util.coroutines.asFlow
import kotlinx.coroutines.CompletableDeferred
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.FlowPreview
Expand Down
Loading

0 comments on commit 7557956

Please sign in to comment.