Skip to content

Commit

Permalink
feat: allow using google location engine on Android (#586)
Browse files Browse the repository at this point in the history
  • Loading branch information
KiwiKilian authored Jan 3, 2025
1 parent 3a4c038 commit 92ffdb7
Show file tree
Hide file tree
Showing 10 changed files with 273 additions and 27 deletions.
35 changes: 29 additions & 6 deletions android/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
buildscript {
// Buildscript is evaluated before everything else so we can't use getExtOrDefault
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["MapLibreReactNative_kotlinVersion"]
def kotlin_version = rootProject.ext.has("kotlinVersion") ? rootProject.ext.get("kotlinVersion") : project.properties["org.maplibre.reactnative.kotlinVersion"]

repositories {
google()
Expand Down Expand Up @@ -32,11 +32,15 @@ apply plugin: "kotlin-android"
// }

def getExtOrDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["MapLibreReactNative_" + name]
return rootProject.ext.has(name) ? rootProject.ext.get(name) : project.properties["org.maplibre.reactnative." + name]
}

def getConfigurableExtOrDefault(name) {
return rootProject.ext.has("org.maplibre.reactnative." + name) ? rootProject.ext.get("org.maplibre.reactnative." + name) : project.properties["org.maplibre.reactnative." + name]
}

def getExtOrIntegerDefault(name) {
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["MapLibreReactNative_" + name]).toInteger()
return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["org.maplibre.reactnative." + name]).toInteger()
}

def supportsNamespace() {
Expand Down Expand Up @@ -68,6 +72,20 @@ android {
buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
}

sourceSets {
main {
java.srcDirs = ['src/main/java']

if (getConfigurableExtOrDefault("locationEngine") == "default") {
java.srcDirs += 'src/main/location-engine-default'
} else if (getConfigurableExtOrDefault("locationEngine") == "google") {
java.srcDirs += 'src/main/location-engine-google'
} else {
throw new GradleException("org.maplibre.reactnative.locationEngine.locationEngine should be one of [\"default\", \"google\"]`. \"${getConfigurableExtOrDefault("locationEngine")}\" was provided.")
}
}
}

buildTypes {
release {
minifyEnabled false
Expand Down Expand Up @@ -106,11 +124,16 @@ dependencies {
implementation "androidx.vectordrawable:vectordrawable:1.1.0"
implementation "androidx.annotation:annotation:1.7.0"
implementation "androidx.appcompat:appcompat:1.6.1"
implementation "com.squareup.okhttp3:okhttp:${getExtOrDefault('okhttpVersion')}"
implementation "com.squareup.okhttp3:okhttp-urlconnection:${getExtOrDefault('okhttpVersion')}"
implementation "com.squareup.okhttp3:okhttp:${getConfigurableExtOrDefault('okhttpVersion')}"
implementation "com.squareup.okhttp3:okhttp-urlconnection:${getConfigurableExtOrDefault('okhttpVersion')}"

// MapLibre plugins
// MapLibre Plugins
implementation ("org.maplibre.gl:android-plugin-localization-v9:3.0.1")
implementation ("org.maplibre.gl:android-plugin-annotation-v9:3.0.1")
implementation ("org.maplibre.gl:android-plugin-markerview-v9:3.0.1")

// Dependencies for Google Location Engine
if (getConfigurableExtOrDefault("locationEngine") == "google") {
implementation "com.google.android.gms:play-services-location:21.3.0"
}
}
17 changes: 11 additions & 6 deletions android/gradle.properties
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
MapLibreReactNative_kotlinVersion=1.7.0
MapLibreReactNative_minSdkVersion=21
MapLibreReactNative_targetSdkVersion=31
MapLibreReactNative_compileSdkVersion=31
MapLibreReactNative_ndkversion=21.4.7075529
org.maplibre.reactnative.kotlinVersion=1.7.0
org.maplibre.reactnative.minSdkVersion=21
org.maplibre.reactnative.targetSdkVersion=31
org.maplibre.reactnative.compileSdkVersion=31
org.maplibre.reactnative.ndkVersion=21.4.7075529

MapLibreReactNative_okhttpVersion=4.9.0
# MapLibre React Native Customizations

org.maplibre.reactnative.okhttpVersion=4.9.0

# Available values: default, google
org.maplibre.reactnative.locationEngine=default
Original file line number Diff line number Diff line change
Expand Up @@ -7,21 +7,14 @@

import org.maplibre.android.location.engine.LocationEngine;
import org.maplibre.android.location.engine.LocationEngineCallback;

/*
import com.mapbox.android.core.location.LocationEngineListener;
import com.mapbox.android.core.location.LocationEnginePriority;
*/

import org.maplibre.android.location.engine.LocationEngineDefault;
import org.maplibre.android.location.engine.LocationEngineRequest;
import org.maplibre.android.location.engine.LocationEngineResult;
import org.maplibre.android.location.permissions.PermissionsManager;
import org.maplibre.reactnative.location.engine.LocationEngineProvider;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

@SuppressWarnings({"MissingPermission"})
public class LocationManager implements LocationEngineCallback<LocationEngineResult> {
Expand Down Expand Up @@ -58,8 +51,10 @@ private LocationManager(Context context) {
this.buildEngineRequest();

}

private void buildEngineRequest() {
locationEngine = LocationEngineDefault.INSTANCE.getDefaultLocationEngine(this.context.getApplicationContext());
locationEngine = new LocationEngineProvider().getLocationEngine(context);

locationEngineRequest = new LocationEngineRequest.Builder(DEFAULT_INTERVAL_MILLIS)
.setFastestInterval(DEFAULT_FASTEST_INTERVAL_MILLIS)
.setPriority(LocationEngineRequest.PRIORITY_HIGH_ACCURACY)
Expand All @@ -78,9 +73,11 @@ public void removeLocationListener(OnUserLocationChange listener) {
listeners.remove(listener);
}
}

public void setMinDisplacement(float minDisplacement) {
mMinDisplacement = minDisplacement;
}

public void enable() {
if (!PermissionsManager.areLocationPermissionsGranted(context)) {
return;
Expand Down Expand Up @@ -134,8 +131,7 @@ public void getLastKnownLocation(LocationEngineCallback<LocationEngineResult> ca

try {
locationEngine.getLastLocation(callback);
}
catch(Exception exception) {
} catch (Exception exception) {
Log.w(LOG_TAG, exception);
callback.onFailure(exception);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package org.maplibre.reactnative.location.engine;

import android.content.Context;
import android.util.Log;

import org.maplibre.android.location.engine.LocationEngine;
import org.maplibre.android.location.engine.LocationEngineDefault;

public class DefaultLocationEngineProvider implements LocationEngineProvidable {
private static final String LOG_TAG = "DefaultLocationEngineProvider";

@Override
public LocationEngine getLocationEngine(Context context) {
LocationEngine locationEngine = LocationEngineDefault.INSTANCE.getDefaultLocationEngine(context.getApplicationContext());
Log.d(LOG_TAG, "DefaultLocationEngine will be used.");
return locationEngine;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package org.maplibre.reactnative.location.engine;

import android.content.Context;

import org.maplibre.android.location.engine.LocationEngine;

public interface LocationEngineProvidable {
LocationEngine getLocationEngine(Context context);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.maplibre.reactnative.location.engine;

import android.content.Context;

import org.maplibre.android.location.engine.LocationEngine;

public class LocationEngineProvider implements LocationEngineProvidable {
@Override
public LocationEngine getLocationEngine(Context context) {
return new DefaultLocationEngineProvider().getLocationEngine(context);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
package org.maplibre.reactnative.location.engine;

import android.annotation.SuppressLint;
import android.app.PendingIntent;
import android.content.Context;
import android.location.Location;
import android.os.Looper;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;

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;
import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;

import org.maplibre.android.location.engine.LocationEngineCallback;
import org.maplibre.android.location.engine.LocationEngineImpl;
import org.maplibre.android.location.engine.LocationEngineRequest;
import org.maplibre.android.location.engine.LocationEngineResult;

import java.util.Collections;
import java.util.List;

/**
* Wraps implementation of Fused Location Provider
*/
public class GoogleLocationEngineImpl implements LocationEngineImpl<LocationCallback> {
private final FusedLocationProviderClient fusedLocationProviderClient;

@VisibleForTesting
GoogleLocationEngineImpl(FusedLocationProviderClient fusedLocationProviderClient) {
this.fusedLocationProviderClient = fusedLocationProviderClient;
}

public GoogleLocationEngineImpl(@NonNull Context context) {
this.fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(context);
}

@NonNull
@Override
public LocationCallback createListener(LocationEngineCallback<LocationEngineResult> callback) {
return new GoogleLocationEngineCallbackTransport(callback);
}

@SuppressLint("MissingPermission")
@Override
public void getLastLocation(@NonNull LocationEngineCallback<LocationEngineResult> callback)
throws SecurityException {
GoogleLastLocationEngineCallbackTransport transport =
new GoogleLastLocationEngineCallbackTransport(callback);
fusedLocationProviderClient.getLastLocation().addOnSuccessListener(transport).addOnFailureListener(transport);
}

@SuppressLint("MissingPermission")
@Override
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull LocationCallback listener,
@Nullable Looper looper) throws SecurityException {
fusedLocationProviderClient.requestLocationUpdates(toGMSLocationRequest(request), listener, looper);
}

@SuppressLint("MissingPermission")
@Override
public void requestLocationUpdates(@NonNull LocationEngineRequest request,
@NonNull PendingIntent pendingIntent) throws SecurityException {
fusedLocationProviderClient.requestLocationUpdates(toGMSLocationRequest(request), pendingIntent);
}

@Override
public void removeLocationUpdates(@NonNull LocationCallback listener) {
if (listener != null) {
fusedLocationProviderClient.removeLocationUpdates(listener);
}
}

@Override
public void removeLocationUpdates(PendingIntent pendingIntent) {
if (pendingIntent != null) {
fusedLocationProviderClient.removeLocationUpdates(pendingIntent);
}
}

private static LocationRequest toGMSLocationRequest(LocationEngineRequest request) {
LocationRequest.Builder builder = new LocationRequest.Builder(request.getInterval());
builder.setMinUpdateIntervalMillis(request.getFastestInterval());
builder.setMinUpdateDistanceMeters(request.getDisplacement());
builder.setMaxUpdateDelayMillis(request.getMaxWaitTime());
builder.setPriority(toGMSLocationPriority(request.getPriority()));
return builder.build();
}

private static int toGMSLocationPriority(int enginePriority) {
switch (enginePriority) {
case LocationEngineRequest.PRIORITY_HIGH_ACCURACY:
return Priority.PRIORITY_HIGH_ACCURACY;
case LocationEngineRequest.PRIORITY_BALANCED_POWER_ACCURACY:
return Priority.PRIORITY_BALANCED_POWER_ACCURACY;
case LocationEngineRequest.PRIORITY_LOW_POWER:
return Priority.PRIORITY_LOW_POWER;
case LocationEngineRequest.PRIORITY_NO_POWER:
default:
return Priority.PRIORITY_PASSIVE;
}
}

private static final class GoogleLocationEngineCallbackTransport extends LocationCallback {
private final LocationEngineCallback<LocationEngineResult> callback;

GoogleLocationEngineCallbackTransport(LocationEngineCallback<LocationEngineResult> callback) {
this.callback = callback;
}

@Override
public void onLocationResult(LocationResult locationResult) {
super.onLocationResult(locationResult);
List<Location> locations = locationResult.getLocations();
if (!locations.isEmpty()) {
callback.onSuccess(LocationEngineResult.create(locations));
} else {
callback.onFailure(new Exception("Unavailable location"));
}
}
}

@VisibleForTesting
static final class GoogleLastLocationEngineCallbackTransport
implements OnSuccessListener<Location>, OnFailureListener {
private final LocationEngineCallback<LocationEngineResult> callback;

GoogleLastLocationEngineCallbackTransport(LocationEngineCallback<LocationEngineResult> callback) {
this.callback = callback;
}

@Override
public void onSuccess(Location location) {
callback.onSuccess(location != null ? LocationEngineResult.create(location) :
LocationEngineResult.create(Collections.<Location>emptyList()));
}

@Override
public void onFailure(@NonNull Exception e) {
callback.onFailure(e);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.maplibre.reactnative.location.engine;

import android.content.Context;
import android.util.Log;

import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.GoogleApiAvailability;

import org.maplibre.android.location.engine.LocationEngine;
import org.maplibre.android.location.engine.LocationEngineProxy;

public class GoogleLocationEngineProvider implements LocationEngineProvidable {
private static final String LOG_TAG = "GoogleLocationEngineProvider";

public LocationEngine getLocationEngine(Context context) {
if (GoogleApiAvailability.getInstance().isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS) {
LocationEngine locationEngine = new LocationEngineProxy<>(new GoogleLocationEngineImpl(context.getApplicationContext()));
Log.d(LOG_TAG, "GoogleLocationEngine will be used.");
return locationEngine;
} else {
return new DefaultLocationEngineProvider().getLocationEngine(context);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.maplibre.reactnative.location.engine;

import android.content.Context;

import org.maplibre.android.location.engine.LocationEngine;

public class LocationEngineProvider implements LocationEngineProvidable {
@Override
public LocationEngine getLocationEngine(Context context) {
return new GoogleLocationEngineProvider().getLocationEngine(context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,6 @@ export function UserLocationForNavigation() {
followPitch={60}
pitch={0}
onUserTrackingModeChange={(event) => {
console.log("js userTrackingModeChange");
console.log("js", event.type);
console.log("js", JSON.stringify(event.nativeEvent));

if (
navigationActive &&
!event.nativeEvent.payload.followUserLocation
Expand Down

0 comments on commit 92ffdb7

Please sign in to comment.