From d88ee93605d0ced62c550572cad906d56834a170 Mon Sep 17 00:00:00 2001 From: Felipe Martinez Date: Sat, 23 Mar 2024 13:52:46 +0100 Subject: [PATCH] Add location service --- api/api.d.ts | 32 +++++++++++------ app/build.gradle.kts | 4 +++ .../pinepartner/scripting/Permission.kt | 1 + .../pipe01/pinepartner/scripting/Runner.kt | 9 ++++- .../pinepartner/scripting/api/Location.kt | 35 +++++++++++++++++++ .../pinepartner/scripting/api/Require.kt | 8 +++++ .../scripting/api/adapters/LocationAdapter.kt | 29 +++++++++++++++ .../pinepartner/service/BackgroundService.kt | 6 +++- 8 files changed, 112 insertions(+), 12 deletions(-) create mode 100644 app/src/main/java/net/pipe01/pinepartner/scripting/api/Location.kt create mode 100644 app/src/main/java/net/pipe01/pinepartner/scripting/api/adapters/LocationAdapter.kt diff --git a/api/api.d.ts b/api/api.d.ts index c47635f..63692f6 100644 --- a/api/api.d.ts +++ b/api/api.d.ts @@ -47,7 +47,14 @@ declare interface PlaybackState { get album(): string | null; } -declare interface Watches { +declare interface Location { + get latitude(): number; + get longitude(): number; + get altitude(): number | null; + get accuracy(): number | null; +} + +declare interface WatchesService { get all(): Watch[]; addEventListener(event: "connected", cb: (watch: Watch) => void): void; @@ -57,7 +64,7 @@ declare interface Watches { removeEventListener(event: "disconnected", cb: (watchAddress: string) => void): void; } -declare interface Notifications { +declare interface NotificationsService { addEventListener(event: "received", cb: (notif: Notification) => void): void; removeEventListener(event: "received", cb: (notif: Notification) => void): void; } @@ -68,12 +75,12 @@ declare type HTTPOptions = { headers?: Record; } -declare interface HTTP { +declare interface HTTPService { request(method: HTTPMethod, url: string, cb: (response: string) => void): void; request(method: HTTPMethod, url: string, options: HTTPOptions, cb: (response: string) => void): void; } -declare interface Volume { +declare interface VolumeService { get voiceCallStream(): VolumeStream; get systemStream(): VolumeStream; get ringStream(): VolumeStream; @@ -83,7 +90,7 @@ declare interface Volume { get accessibilityStream(): VolumeStream; } -declare interface Media { +declare interface MediaService { get state(): PlaybackState; play(): void; @@ -92,8 +99,13 @@ declare interface Media { previous(): void; } -declare function require(module: "watches"): Watches; -declare function require(module: "notifications"): Notifications; -declare function require(module: "http"): HTTP; -declare function require(module: "volume"): Volume; -declare function require(module: "media"): Media; +declare interface LocationService { + get location(): Location +} + +declare function require(module: "watches"): WatchesService; +declare function require(module: "notifications"): NotificationsService; +declare function require(module: "http"): HTTPService; +declare function require(module: "volume"): VolumeService; +declare function require(module: "media"): MediaService; +declare function require(module: "location"): LocationService; diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 09ca360..797f1f0 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -83,6 +83,8 @@ dependencies { debugImplementation("androidx.compose.ui:ui-tooling") debugImplementation("androidx.compose.ui:ui-test-manifest") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.8.1-Beta") + implementation("com.google.accompanist:accompanist-drawablepainter:0.34.0") val nav_version = "2.7.7" @@ -104,4 +106,6 @@ dependencies { implementation(files("libs/rhino-1.7.15-SNAPSHOT.jar")) implementation("com.github.qawaz:compose-code-editor:2.0.3") + + implementation("com.google.android.gms:play-services-location:21.2.0") } \ No newline at end of file diff --git a/app/src/main/java/net/pipe01/pinepartner/scripting/Permission.kt b/app/src/main/java/net/pipe01/pinepartner/scripting/Permission.kt index 1a83259..8bc4771 100644 --- a/app/src/main/java/net/pipe01/pinepartner/scripting/Permission.kt +++ b/app/src/main/java/net/pipe01/pinepartner/scripting/Permission.kt @@ -6,4 +6,5 @@ enum class Permission(val title: String) { HTTP("Send HTTP requests"), VOLUME_CONTROL("Control the phone's volume"), MEDIA_CONTROL("Control media playback"), + LOCATION("Access location services"), } diff --git a/app/src/main/java/net/pipe01/pinepartner/scripting/Runner.kt b/app/src/main/java/net/pipe01/pinepartner/scripting/Runner.kt index 4af24b3..bcaaa4a 100644 --- a/app/src/main/java/net/pipe01/pinepartner/scripting/Runner.kt +++ b/app/src/main/java/net/pipe01/pinepartner/scripting/Runner.kt @@ -4,14 +4,17 @@ package net.pipe01.pinepartner.scripting import android.media.AudioManager import android.media.session.MediaSessionManager import android.util.Log +import com.google.android.gms.location.FusedLocationProviderClient import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.launch import kotlinx.coroutines.newSingleThreadContext import net.pipe01.pinepartner.data.AppDatabase import net.pipe01.pinepartner.data.Plugin import net.pipe01.pinepartner.scripting.api.Finalizeable import net.pipe01.pinepartner.scripting.api.HTTP +import net.pipe01.pinepartner.scripting.api.Location import net.pipe01.pinepartner.scripting.api.Media import net.pipe01.pinepartner.scripting.api.Notifications import net.pipe01.pinepartner.scripting.api.Require @@ -19,6 +22,7 @@ import net.pipe01.pinepartner.scripting.api.Volume import net.pipe01.pinepartner.scripting.api.Watches import net.pipe01.pinepartner.scripting.api.adapters.BLECharacteristicAdapter import net.pipe01.pinepartner.scripting.api.adapters.BLEServiceAdapter +import net.pipe01.pinepartner.scripting.api.adapters.LocationAdapter import net.pipe01.pinepartner.scripting.api.adapters.NotificationAdapter import net.pipe01.pinepartner.scripting.api.adapters.PlaybackStateAdapter import net.pipe01.pinepartner.scripting.api.adapters.VolumeStreamAdapter @@ -41,6 +45,7 @@ data class ScriptDependencies( val deviceManager: DeviceManager, val audioManager: AudioManager, val mediaSessionManager: MediaSessionManager, + val fusedLocationProviderClient: FusedLocationProviderClient, ) class Runner(val plugin: Plugin, deps: ScriptDependencies) { @@ -97,6 +102,7 @@ class Runner(val plugin: Plugin, deps: ScriptDependencies) { ScriptableObject.defineClass(scope, HTTP::class.java) ScriptableObject.defineClass(scope, Volume::class.java) ScriptableObject.defineClass(scope, Media::class.java) + ScriptableObject.defineClass(scope, Location::class.java) ScriptableObject.defineClass(scope, WatchAdapter::class.java) ScriptableObject.defineClass(scope, NotificationAdapter::class.java) @@ -104,6 +110,7 @@ class Runner(val plugin: Plugin, deps: ScriptDependencies) { ScriptableObject.defineClass(scope, BLECharacteristicAdapter::class.java) ScriptableObject.defineClass(scope, VolumeStreamAdapter::class.java) ScriptableObject.defineClass(scope, PlaybackStateAdapter::class.java) + ScriptableObject.defineClass(scope, LocationAdapter::class.java) ScriptableObject.putProperty(scope, "require", Require(deps, plugin.permissions, contextFactory, dispatcher, ::addEvent)) @@ -129,7 +136,7 @@ class Runner(val plugin: Plugin, deps: ScriptDependencies) { fun start() { if (!_hasStarted.getAndSet(true)) { - CoroutineScope(dispatcher).run { + CoroutineScope(dispatcher).launch { contextFactory.call { try { script.exec(it, scope) diff --git a/app/src/main/java/net/pipe01/pinepartner/scripting/api/Location.kt b/app/src/main/java/net/pipe01/pinepartner/scripting/api/Location.kt new file mode 100644 index 0000000..6464a9d --- /dev/null +++ b/app/src/main/java/net/pipe01/pinepartner/scripting/api/Location.kt @@ -0,0 +1,35 @@ +package net.pipe01.pinepartner.scripting.api + +import android.annotation.SuppressLint +import com.google.android.gms.location.FusedLocationProviderClient +import com.google.android.gms.location.Priority +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.tasks.await +import net.pipe01.pinepartner.scripting.api.adapters.LocationAdapter +import org.mozilla.javascript.annotations.JSGetter + +class Location : ApiScriptableObject(CLASS_NAME) { + companion object { + const val CLASS_NAME = "Location" + } + + private lateinit var fusedLocationClient: FusedLocationProviderClient + + fun init(fusedLocationClient: FusedLocationProviderClient) { + this.fusedLocationClient = fusedLocationClient + } + + @SuppressLint("MissingPermission") + @JSGetter + fun getCurrent(): LocationAdapter { + val task = fusedLocationClient.getCurrentLocation(Priority.PRIORITY_HIGH_ACCURACY, null) + + val location = runBlocking { + task.await() + } + + return newObject(LocationAdapter.CLASS_NAME).apply { + init(location) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/net/pipe01/pinepartner/scripting/api/Require.kt b/app/src/main/java/net/pipe01/pinepartner/scripting/api/Require.kt index ad35e9b..ace209a 100644 --- a/app/src/main/java/net/pipe01/pinepartner/scripting/api/Require.kt +++ b/app/src/main/java/net/pipe01/pinepartner/scripting/api/Require.kt @@ -73,6 +73,14 @@ class Require( } } + "location" -> { + checkPermission(cx, Permission.LOCATION) + + createInstance(cx, scope) { + init(deps.fusedLocationProviderClient) + } + } + else -> throw ScriptRuntime.throwError(cx, scope, "Unknown module $className") } } diff --git a/app/src/main/java/net/pipe01/pinepartner/scripting/api/adapters/LocationAdapter.kt b/app/src/main/java/net/pipe01/pinepartner/scripting/api/adapters/LocationAdapter.kt new file mode 100644 index 0000000..67af6d6 --- /dev/null +++ b/app/src/main/java/net/pipe01/pinepartner/scripting/api/adapters/LocationAdapter.kt @@ -0,0 +1,29 @@ +package net.pipe01.pinepartner.scripting.api.adapters + +import android.location.Location +import net.pipe01.pinepartner.scripting.api.ApiScriptableObject +import org.mozilla.javascript.annotations.JSGetter + +class LocationAdapter : ApiScriptableObject(CLASS_NAME) { + companion object { + const val CLASS_NAME = "LocationAdapter" + } + + private lateinit var location: Location + + fun init(location: Location) { + this.location = location + } + + @JSGetter + fun getLatitude() = location.latitude + + @JSGetter + fun getLongitude() = location.longitude + + @JSGetter + fun getAltitude() = if (location.hasAltitude()) location.altitude else null + + @JSGetter + fun getAccuracy() = if (location.hasAccuracy()) location.accuracy else null +} \ No newline at end of file diff --git a/app/src/main/java/net/pipe01/pinepartner/service/BackgroundService.kt b/app/src/main/java/net/pipe01/pinepartner/service/BackgroundService.kt index 50b0024..fcaac76 100644 --- a/app/src/main/java/net/pipe01/pinepartner/service/BackgroundService.kt +++ b/app/src/main/java/net/pipe01/pinepartner/service/BackgroundService.kt @@ -18,6 +18,7 @@ import android.os.IBinder import android.util.Log import androidx.annotation.RequiresApi import androidx.core.app.NotificationCompat +import com.google.android.gms.location.LocationServices import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -98,6 +99,8 @@ class BackgroundService : Service() { val mediaSessionManager = getSystemService(Context.MEDIA_SESSION_SERVICE) as MediaSessionManager + val fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(applicationContext) + BuiltInPlugins.init(assets) pluginManager = PluginManager( @@ -107,7 +110,8 @@ class BackgroundService : Service() { notifManager, deviceManager, audioManager, - mediaSessionManager + mediaSessionManager, + fusedLocationProviderClient, ), )