snapshot of https://github.com/tauri-apps/plugins-workspace.git (v2, 2026-09-23; blobs > 131072B omitted)
Some checks failed
integration tests / run-integration-tests (windows-latest) (push) Has been cancelled
Test Rust / test (push) Has been cancelled
test plugins e2e (mobile) / ios (push) Has been cancelled
test plugins e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test plugins e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
integration tests / run-integration-tests (macos-latest) (push) Has been cancelled
version or publish / version-or-publish (push) Failing after 0s
integration tests / run-integration-tests (ubuntu-22.04) (push) Failing after 0s
Lint JavaScript / eslint (push) Failing after 0s
Sync / sync-to-mirrors (push) Failing after 0s
test plugins e2e (mobile) / android (push) Failing after 0s
test plugins e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
Test Rust / changes (push) Failing after 0s
Lint Rust / changes (push) Failing after 0s
Lint Rust / clippy (push) Has been skipped
Audit Rust / audit-rust (push) Failing after 1s
Audit JavaScript / audit-js (push) Failing after 1s
Some checks failed
integration tests / run-integration-tests (windows-latest) (push) Has been cancelled
Test Rust / test (push) Has been cancelled
test plugins e2e (mobile) / ios (push) Has been cancelled
test plugins e2e / test (map[name:Windows os:windows-latest]) (push) Has been cancelled
test plugins e2e / test (map[name:macOS os:macos-latest]) (push) Has been cancelled
integration tests / run-integration-tests (macos-latest) (push) Has been cancelled
version or publish / version-or-publish (push) Failing after 0s
integration tests / run-integration-tests (ubuntu-22.04) (push) Failing after 0s
Lint JavaScript / eslint (push) Failing after 0s
Sync / sync-to-mirrors (push) Failing after 0s
test plugins e2e (mobile) / android (push) Failing after 0s
test plugins e2e / test (map[name:Linux os:ubuntu-latest]) (push) Failing after 0s
Test Rust / changes (push) Failing after 0s
Lint Rust / changes (push) Failing after 0s
Lint Rust / clippy (push) Has been skipped
Audit Rust / audit-rust (push) Failing after 1s
Audit JavaScript / audit-js (push) Failing after 1s
This commit is contained in:
2
plugins/geolocation/android/.gitignore
vendored
Normal file
2
plugins/geolocation/android/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/build
|
||||
/.tauri
|
||||
36
plugins/geolocation/android/build.gradle.kts
Normal file
36
plugins/geolocation/android/build.gradle.kts
Normal file
@@ -0,0 +1,36 @@
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
id("org.jetbrains.kotlin.android")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "app.tauri.geolocation"
|
||||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
|
||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles("consumer-rules.pro")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
targetCompatibility = JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = "1.8"
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation("androidx.core:core-ktx:1.9.0")
|
||||
implementation("androidx.appcompat:appcompat:1.6.0")
|
||||
implementation("com.google.android.material:material:1.7.0")
|
||||
implementation("com.google.android.gms:play-services-location:21.3.0")
|
||||
testImplementation("junit:junit:4.13.2")
|
||||
androidTestImplementation("androidx.test.ext:junit:1.1.5")
|
||||
androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")
|
||||
implementation(project(":tauri-android"))
|
||||
}
|
||||
21
plugins/geolocation/android/consumer-rules.pro
Normal file
21
plugins/geolocation/android/consumer-rules.pro
Normal file
@@ -0,0 +1,21 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# 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 *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
2
plugins/geolocation/android/settings.gradle
Normal file
2
plugins/geolocation/android/settings.gradle
Normal file
@@ -0,0 +1,2 @@
|
||||
include ':tauri-android'
|
||||
project(':tauri-android').projectDir = new File('./.tauri/tauri-api')
|
||||
@@ -0,0 +1,28 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package app.tauri.geolocation
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("app.tauri.geolocation", appContext.packageName)
|
||||
}
|
||||
}
|
||||
6
plugins/geolocation/android/src/main/AndroidManifest.xml
Normal file
6
plugins/geolocation/android/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||
<!-- <uses-feature android:name="android.hardware.gps" android:required="true" /> -->
|
||||
</manifest>
|
||||
144
plugins/geolocation/android/src/main/java/Geolocation.kt
Normal file
144
plugins/geolocation/android/src/main/java/Geolocation.kt
Normal file
@@ -0,0 +1,144 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package app.tauri.geolocation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.location.Location
|
||||
import android.location.LocationManager
|
||||
import android.os.SystemClock
|
||||
import androidx.core.location.LocationManagerCompat
|
||||
import app.tauri.Logger
|
||||
import com.google.android.gms.common.ConnectionResult
|
||||
import com.google.android.gms.common.GoogleApiAvailability
|
||||
import com.google.android.gms.location.FusedLocationProviderClient
|
||||
import com.google.android.gms.location.LocationCallback
|
||||
import com.google.android.gms.location.LocationRequest
|
||||
import com.google.android.gms.location.LocationResult
|
||||
import com.google.android.gms.location.LocationServices
|
||||
import com.google.android.gms.location.Priority
|
||||
|
||||
|
||||
public class Geolocation(private val context: Context) {
|
||||
private var fusedLocationClient: FusedLocationProviderClient? = null
|
||||
private var locationCallback: LocationCallback? = null
|
||||
|
||||
|
||||
fun isLocationServicesEnabled(): Boolean {
|
||||
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
return LocationManagerCompat.isLocationEnabled(lm)
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingPermission")
|
||||
fun sendLocation(enableHighAccuracy: Boolean, successCallback: (location: Location) -> Unit, errorCallback: (error: String) -> Unit) {
|
||||
val resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
|
||||
if (resultCode == ConnectionResult.SUCCESS) {
|
||||
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
|
||||
if (this.isLocationServicesEnabled()) {
|
||||
var networkEnabled = false
|
||||
|
||||
try {
|
||||
networkEnabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||
} catch (_: Exception) {
|
||||
Logger.error("isProviderEnabled failed")
|
||||
}
|
||||
|
||||
val lowPrio = if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER
|
||||
val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio
|
||||
|
||||
LocationServices
|
||||
.getFusedLocationProviderClient(context)
|
||||
.getCurrentLocation(prio, null)
|
||||
.addOnFailureListener { e -> e.message?.let { errorCallback(it) } }
|
||||
.addOnSuccessListener { location ->
|
||||
if (location == null) {
|
||||
errorCallback("Location unavailable.")
|
||||
} else {
|
||||
successCallback(location)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
errorCallback("Location disabled.")
|
||||
}
|
||||
} else {
|
||||
errorCallback("Google Play Services unavailable.")
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun requestLocationUpdates(enableHighAccuracy: Boolean, timeout: Long, successCallback: (location: Location) -> Unit, errorCallback: (error: String) -> Unit) {
|
||||
val resultCode = GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context);
|
||||
if (resultCode == ConnectionResult.SUCCESS) {
|
||||
clearLocationUpdates()
|
||||
fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
|
||||
|
||||
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
|
||||
if (this.isLocationServicesEnabled()) {
|
||||
var networkEnabled = false
|
||||
|
||||
try {
|
||||
networkEnabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER)
|
||||
} catch (_: Exception) {
|
||||
Logger.error("isProviderEnabled failed")
|
||||
}
|
||||
|
||||
val lowPrio = if (networkEnabled) Priority.PRIORITY_BALANCED_POWER_ACCURACY else Priority.PRIORITY_LOW_POWER
|
||||
val prio = if (enableHighAccuracy) Priority.PRIORITY_HIGH_ACCURACY else lowPrio
|
||||
|
||||
val locationRequest = LocationRequest.Builder(timeout)
|
||||
.setMaxUpdateDelayMillis(timeout)
|
||||
.setMinUpdateIntervalMillis(timeout)
|
||||
.setPriority(prio)
|
||||
.build()
|
||||
|
||||
locationCallback =
|
||||
object : LocationCallback() {
|
||||
override fun onLocationResult(locationResult: LocationResult) {
|
||||
val lastLocation = locationResult.lastLocation
|
||||
if (lastLocation == null) {
|
||||
errorCallback("Location unavailable.")
|
||||
} else {
|
||||
successCallback(lastLocation)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fusedLocationClient?.requestLocationUpdates(locationRequest, locationCallback!!, null)
|
||||
} else {
|
||||
errorCallback("Location disabled.")
|
||||
}
|
||||
} else {
|
||||
errorCallback("Google Play Services not available.")
|
||||
}
|
||||
}
|
||||
|
||||
fun clearLocationUpdates() {
|
||||
if (locationCallback != null) {
|
||||
fusedLocationClient?.removeLocationUpdates(locationCallback!!)
|
||||
locationCallback = null
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("MissingPermission")
|
||||
fun getLastLocation(maximumAge: Long): Location? {
|
||||
var lastLoc: Location? = null
|
||||
val lm = context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
|
||||
|
||||
for (provider in lm.allProviders) {
|
||||
val tmpLoc = lm.getLastKnownLocation(provider)
|
||||
if (tmpLoc != null) {
|
||||
val locationAge = SystemClock.elapsedRealtimeNanos() - tmpLoc.elapsedRealtimeNanos
|
||||
val maxAgeNano = maximumAge * 1000000L
|
||||
if (locationAge <= maxAgeNano && (lastLoc == null || lastLoc.elapsedRealtimeNanos > tmpLoc.elapsedRealtimeNanos)) {
|
||||
lastLoc = tmpLoc
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return lastLoc
|
||||
}
|
||||
}
|
||||
172
plugins/geolocation/android/src/main/java/GeolocationPlugin.kt
Normal file
172
plugins/geolocation/android/src/main/java/GeolocationPlugin.kt
Normal file
@@ -0,0 +1,172 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package app.tauri.geolocation
|
||||
|
||||
import android.Manifest
|
||||
import android.app.Activity
|
||||
import android.location.Location
|
||||
import android.os.Build
|
||||
import android.webkit.WebView
|
||||
import app.tauri.Logger
|
||||
import app.tauri.PermissionState
|
||||
import app.tauri.annotation.Command
|
||||
import app.tauri.annotation.InvokeArg
|
||||
import app.tauri.annotation.Permission
|
||||
import app.tauri.annotation.PermissionCallback
|
||||
import app.tauri.annotation.TauriPlugin
|
||||
import app.tauri.plugin.Channel
|
||||
import app.tauri.plugin.Invoke
|
||||
import app.tauri.plugin.JSObject
|
||||
import app.tauri.plugin.Plugin
|
||||
|
||||
@InvokeArg
|
||||
class PositionOptions {
|
||||
var enableHighAccuracy: Boolean = false
|
||||
var maximumAge: Long = 0
|
||||
var timeout: Long = 10000
|
||||
}
|
||||
|
||||
@InvokeArg
|
||||
class WatchArgs {
|
||||
var options: PositionOptions = PositionOptions()
|
||||
lateinit var channel: Channel
|
||||
}
|
||||
|
||||
@InvokeArg
|
||||
class ClearWatchArgs {
|
||||
var channelId: Long = 0
|
||||
}
|
||||
|
||||
// TODO: Plugin does not ask user to enable google location services (like gmaps does)
|
||||
|
||||
private const val ALIAS_LOCATION: String = "location"
|
||||
private const val ALIAS_COARSE_LOCATION: String = "coarseLocation"
|
||||
|
||||
@TauriPlugin(
|
||||
permissions = [
|
||||
Permission(strings = [
|
||||
Manifest.permission.ACCESS_FINE_LOCATION,
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
],
|
||||
alias = ALIAS_LOCATION
|
||||
),
|
||||
Permission(strings = [
|
||||
Manifest.permission.ACCESS_COARSE_LOCATION
|
||||
],
|
||||
alias = ALIAS_COARSE_LOCATION
|
||||
)
|
||||
]
|
||||
)
|
||||
class GeolocationPlugin(private val activity: Activity): Plugin(activity) {
|
||||
private lateinit var implementation: Geolocation
|
||||
private var watchers = hashMapOf<Long, Pair<Invoke, WatchArgs>>()
|
||||
|
||||
override fun load(webView: WebView) {
|
||||
super.load(webView)
|
||||
implementation = Geolocation(activity.applicationContext)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
// Clear all location updates on pause to avoid possible background location calls
|
||||
implementation.clearLocationUpdates()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
// resume watchers
|
||||
for ((watcher, args) in watchers.values) {
|
||||
startWatch(watcher, args)
|
||||
}
|
||||
}
|
||||
|
||||
@Command
|
||||
override fun checkPermissions(invoke: Invoke) {
|
||||
if (implementation.isLocationServicesEnabled()) {
|
||||
super.checkPermissions(invoke)
|
||||
} else {
|
||||
invoke.reject("Location services are disabled.")
|
||||
}
|
||||
}
|
||||
|
||||
@Command
|
||||
override fun requestPermissions(invoke: Invoke) {
|
||||
if (implementation.isLocationServicesEnabled()) {
|
||||
super.requestPermissions(invoke)
|
||||
} else {
|
||||
invoke.reject("Location services are disabled.")
|
||||
}
|
||||
}
|
||||
|
||||
@Command
|
||||
fun getCurrentPosition(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(PositionOptions::class.java)
|
||||
|
||||
val location = implementation.getLastLocation(args.maximumAge)
|
||||
if (location != null) {
|
||||
invoke.resolve(convertLocation(location))
|
||||
} else {
|
||||
implementation.sendLocation(args.enableHighAccuracy,
|
||||
{ loc -> invoke.resolve(convertLocation(loc)) },
|
||||
{ error -> invoke.reject(error) })
|
||||
}
|
||||
}
|
||||
|
||||
@PermissionCallback
|
||||
private fun positionPermissionCallback(invoke: Invoke) {
|
||||
val permissionsResultJSON = JSObject()
|
||||
permissionsResultJSON.put("location", getPermissionState(ALIAS_LOCATION))
|
||||
permissionsResultJSON.put("coarseLocation", getPermissionState(ALIAS_COARSE_LOCATION))
|
||||
invoke.resolve(permissionsResultJSON)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun watchPosition(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(WatchArgs::class.java)
|
||||
startWatch(invoke, args)
|
||||
}
|
||||
|
||||
private fun startWatch(invoke: Invoke, args: WatchArgs) {
|
||||
implementation.requestLocationUpdates(
|
||||
args.options.enableHighAccuracy,
|
||||
args.options.timeout,
|
||||
{ location -> args.channel.send(convertLocation(location)) },
|
||||
{ error -> args.channel.sendObject(error) })
|
||||
|
||||
watchers[args.channel.id] = Pair(invoke, args)
|
||||
}
|
||||
|
||||
@Command
|
||||
fun clearWatch(invoke: Invoke) {
|
||||
val args = invoke.parseArgs(ClearWatchArgs::class.java)
|
||||
|
||||
watchers.remove(args.channelId)
|
||||
|
||||
if (watchers.isEmpty()) {
|
||||
implementation.clearLocationUpdates()
|
||||
}
|
||||
|
||||
invoke.resolve()
|
||||
}
|
||||
|
||||
private fun convertLocation(location: Location): JSObject {
|
||||
val ret = JSObject()
|
||||
val coords = JSObject()
|
||||
|
||||
coords.put("latitude", location.latitude)
|
||||
coords.put("longitude", location.longitude)
|
||||
coords.put("accuracy", location.accuracy)
|
||||
coords.put("altitude", location.altitude)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
coords.put("altitudeAccuracy", location.verticalAccuracyMeters)
|
||||
}
|
||||
coords.put("speed", location.speed)
|
||||
coords.put("heading", location.bearing)
|
||||
ret.put("timestamp", location.time)
|
||||
ret.put("coords", coords)
|
||||
|
||||
return ret
|
||||
}
|
||||
}
|
||||
21
plugins/geolocation/android/src/test/java/ExampleUnitTest.kt
Normal file
21
plugins/geolocation/android/src/test/java/ExampleUnitTest.kt
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2019-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
package app.tauri.geolocation
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user