From b165ec6005cb5e9fcc7c1c2bb860344287f3d281 Mon Sep 17 00:00:00 2001 From: Pieter Vander Vennet Date: Thu, 12 Dec 2024 00:44:27 +0100 Subject: [PATCH] Get bridge working, get geolocation working --- app/capacitor.build.gradle | 2 +- .../main/java/org/mapcomplete/Databridge.java | 35 ++-- .../org/mapcomplete/GeolocationBridge.java | 155 ++++++++++++++++++ .../java/org/mapcomplete/MainActivity.java | 85 +++++++++- app/src/main/res/layout/activity_main.xml | 1 - app/src/main/res/xml/file_paths.xml | 4 +- build.gradle | 1 - capacitor.settings.gradle | 3 - 8 files changed, 261 insertions(+), 25 deletions(-) create mode 100644 app/src/main/java/org/mapcomplete/GeolocationBridge.java diff --git a/app/capacitor.build.gradle b/app/capacitor.build.gradle index d82a7ee4..fdb4970c 100644 --- a/app/capacitor.build.gradle +++ b/app/capacitor.build.gradle @@ -9,7 +9,7 @@ android { apply from: "../capacitor-cordova-android-plugins/cordova.variables.gradle" dependencies { - implementation project(':capacitor-geolocation') + } diff --git a/app/src/main/java/org/mapcomplete/Databridge.java b/app/src/main/java/org/mapcomplete/Databridge.java index db12b97b..36c26465 100644 --- a/app/src/main/java/org/mapcomplete/Databridge.java +++ b/app/src/main/java/org/mapcomplete/Databridge.java @@ -11,41 +11,44 @@ import com.getcapacitor.annotation.CapacitorPlugin; import java.util.HashMap; import java.util.Map; -import java.util.Objects; import java.util.function.Consumer; -import java.util.function.Function; -@CapacitorPlugin(name = "Echo") +@CapacitorPlugin(name = "Databridge") public class Databridge extends Plugin { - private final Map> responders; + private static final Map> responders = new HashMap<>(); + static { + responders.put("meta", Databridge.answer("capacitator-shell 0.0.1;")); + } private static Consumer answer(String answer) { + return (PluginCall call) -> Databridge.sendAnswerTo(call, answer); + } + + public static void sendAnswerTo(PluginCall call, String answer) { JSObject ret = new JSObject(); ret.put("value", answer); - Log.i("databridge","Resolving call"); - return (PluginCall call) -> call.resolve(ret); + Log.i("databridge", "Resolving call"); + call.resolve(ret); } /** * A responder will be activated if the native code asks for it. - * Use call.setKeepAlive(true) for multiple responses - * @param responders + * Use call.setKeepAlive(true) if the responder will send multiple responses */ - public Databridge(Map> responders) { - this.responders = responders; - responders.put("meta", Databridge.answer("capacitator-shell 0.0.1;")); + public static void addResponder(String key, Consumer responder) { + responders.put(key, responder); } @PluginMethod() public void request(PluginCall call) { String key = call.getString("key"); - Log.i("databridge","Got a call: "+key); - var c= this.responders.get(key); - if(c != null){ + Log.i("databridge", "Got a call: " + key); + var c = this.responders.get(key); + if (c != null) { c.accept(call); - }else{ - call.reject("ERROR: no responder installed for "+key); + } else { + call.reject("ERROR: no responder installed for " + key); } } } diff --git a/app/src/main/java/org/mapcomplete/GeolocationBridge.java b/app/src/main/java/org/mapcomplete/GeolocationBridge.java new file mode 100644 index 00000000..98aa4830 --- /dev/null +++ b/app/src/main/java/org/mapcomplete/GeolocationBridge.java @@ -0,0 +1,155 @@ +package org.mapcomplete; + +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.util.Log; + +import androidx.core.location.LocationManagerCompat; + +import com.getcapacitor.JSObject; +import com.getcapacitor.PluginCall; + +public class GeolocationBridge { + + public static final int requestCode = 684198; + + private final Context context; + private final MainActivity mainActivity; + + GeolocationBridge(Context context, MainActivity mainActivity) { + this.context = context; + this.mainActivity = mainActivity; + + Databridge.addResponder("location:watch", pluginCall -> { + pluginCall.setKeepAlive(true); + new LocationUpdateListener(pluginCall, context).requestLocationUpdates(true); + }); + } + + +} + +class LocationUpdateListener implements LocationListener { + + private final PluginCall callback; + private final Context context; + + public LocationUpdateListener(PluginCall callback, Context context) { + this.callback = callback; + this.context = context; + } + + private void answer(String answer) { + Databridge.sendAnswerTo(this.callback, answer); + } + + private void error(String msg) { + this.callback.reject(msg); + } + + /** + * Query the location manager to indicate what providers are available. + * Typical providers are `passive`, `network`, `fused` and `gps` + *

+ * This method selects the best appropriate + */ + private String getPreferredProvider(boolean enableHighAccuracy) { + LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + var providers = lm.getProviders(true); + if (enableHighAccuracy) { + if (providers.contains("gps")) { + return "gps"; + } + if (providers.contains("fused")) { + return "fused"; + } + if (providers.contains("network")) { + return "network"; + } + } else { + if (providers.contains("network")) { + return "network"; + } + if (providers.contains("fused")) { + return "fused"; + } + if (providers.contains("gps")) { + return "gps"; + } + } + if (providers.contains("passive")) { + return "passive"; + } + + return null; + } + + + private Boolean isLocationServicesEnabled() { + LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + return LocationManagerCompat.isLocationEnabled(lm); + } + + + @SuppressWarnings("MissingPermission") + public void requestLocationUpdates(boolean enableHighAccuracy) { + + if (!this.isLocationServicesEnabled()) { + this.error("location disabled"); + return; + } + + LocationManager lm = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + + var provider = getPreferredProvider(enableHighAccuracy); + if (provider == null) { + this.error("Location unavailable: no providers defined. Note: this is a Google Play Services free implementation"); + return; + } + lm.requestLocationUpdates(provider, 1000, 10, this); + } + + + @Override + public void onLocationChanged(Location location) { + JSObject loc = new JSObject(); + loc.put("latitude", location.getLatitude()); + loc.put("longitude", location.getLongitude()); + if (location.hasAccuracy()) { + loc.put("accuracy", location.getAccuracy()); + } + if (location.hasAltitude()) { + loc.put("altitude", location.getAltitude()); + } + if (location.hasBearing()) { + // Expected for heading: 0 is north, 90 is east, up till 359° ; see https://developer.mozilla.org/en-US/docs/Web/API/GeolocationCoordinates + // getBearing returns essentially the same + loc.put("heading", location.getBearing()); + } + + JSObject ret = new JSObject(); + ret.put("value", loc); + Log.i("databridge", "Resolving call"); + this.callback.resolve(ret); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + + @Override + public void onProviderEnabled(String provider) { + } + + @Override + public void onProviderDisabled(String provider) { + } + + + + +} + diff --git a/app/src/main/java/org/mapcomplete/MainActivity.java b/app/src/main/java/org/mapcomplete/MainActivity.java index 95433853..6bc968bf 100644 --- a/app/src/main/java/org/mapcomplete/MainActivity.java +++ b/app/src/main/java/org/mapcomplete/MainActivity.java @@ -1,15 +1,98 @@ package org.mapcomplete; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.net.Uri; import android.os.Bundle; +import android.util.Log; + +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; import com.getcapacitor.BridgeActivity; +import com.getcapacitor.JSObject; +import com.getcapacitor.Plugin; +import com.getcapacitor.PluginCall; + +import java.util.Objects; public class MainActivity extends BridgeActivity { + private PluginCall locationRequest = null; + private PluginCall authRequest = null; + @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); registerPlugin(Databridge.class); + new GeolocationBridge(getApplicationContext(), this); + Databridge.addResponder("location:request-permission", pluginCall -> { + this.locationRequest = pluginCall; + this.requestPermission(); + }); + Databridge.addResponder("request:login", pluginCall -> { + this.authRequest = pluginCall; + }); + super.onCreate(savedInstanceState); + } + + private void requestPermission() { + + if (ContextCompat.checkSelfPermission( + getApplicationContext(), + android.Manifest.permission.ACCESS_FINE_LOCATION + ) != PackageManager.PERMISSION_GRANTED) { + // Permission is not granted, request it + ActivityCompat.requestPermissions( + this, + new String[]{android.Manifest.permission.ACCESS_FINE_LOCATION}, + GeolocationBridge.requestCode + ); + + } + } + + @Override + protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + if (!Intent.ACTION_VIEW.equals(intent.getAction())) { + return; + } + Uri url = intent.getData(); + if (url == null) { + return; + } + + if (Objects.equals(url.getPath(), "/land.html")) { + var code = url.getQueryParameter("code"); + var state = url.getQueryParameter("state"); + JSObject obj = new JSObject(); + obj.put("code", code); + obj.put("state", state); + JSObject res = new JSObject(); + res.put("value", obj); + Log.i("main", "Resolving auth call"); + this.authRequest.resolve(res); + return; + } + + System.out.println("Intercepted URL: " + url); + } + + @Override + public void onRequestPermissionsResult( + int requestCode, + String[] permissions, + int[] grantResults + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == GeolocationBridge.requestCode) { + if (this.locationRequest != null) { + // We've only requested "FINE_LOCATION" + var granted = grantResults[0] == PackageManager.PERMISSION_GRANTED; + Log.i("Geolocation", "Got fine location request: " + granted); + Databridge.sendAnswerTo(this.locationRequest, granted ? "granted" : "denied"); + } + } } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index b5ad1387..2522dff0 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,5 @@ - + - \ No newline at end of file + diff --git a/build.gradle b/build.gradle index 85a5dda2..dbd7569e 100644 --- a/build.gradle +++ b/build.gradle @@ -8,7 +8,6 @@ buildscript { } dependencies { classpath 'com.android.tools.build:gradle:8.2.1' - classpath 'com.google.gms:google-services:4.4.0' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/capacitor.settings.gradle b/capacitor.settings.gradle index f0849df5..9a5fa872 100644 --- a/capacitor.settings.gradle +++ b/capacitor.settings.gradle @@ -1,6 +1,3 @@ // DO NOT EDIT THIS FILE! IT IS GENERATED EACH TIME "capacitor update" IS RUN include ':capacitor-android' project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') - -include ':capacitor-geolocation' -project(':capacitor-geolocation').projectDir = new File('../node_modules/@capacitor/geolocation/android')