From 85f4f9572ecdc2ab2388a16b30d14ffae42c485a Mon Sep 17 00:00:00 2001 From: TimHoogstrate Date: Mon, 22 Jan 2024 09:54:59 +0100 Subject: [PATCH] [Android] Enhancement-561/satelliteCount and satellitesUsedInFix (#1364) * Added satelliteCount and satellitesUsedInFix to Android * changed the text of the Changelog * Created a AndroidPosition object with Android specific parameters. * fixed test * cleanup * Update CHANGELOG.md * Update pubspec.yaml * Update geolocator_android/android/src/main/java/com/baseflow/geolocator/location/NmeaClient.java Co-authored-by: Maurits van Beusekom * Update geolocator_android/lib/src/types/android_position.dart Co-authored-by: Maurits van Beusekom * Update geolocator_android/lib/src/types/android_position.dart Co-authored-by: Maurits van Beusekom * Update geolocator_android/lib/src/types/android_position.dart Co-authored-by: Maurits van Beusekom * updated formatting * dart format * Update geolocator.dart * Update position.dart * reverted unused changes --------- Co-authored-by: Maurits van Beusekom --- geolocator_android/CHANGELOG.md | 5 + .../geolocator/location/LocationMapper.java | 14 ++- .../geolocator/location/NmeaClient.java | 28 ++++++ .../lib/geolocator_android.dart | 1 + .../lib/src/geolocator_android.dart | 7 +- .../lib/src/types/android_position.dart | 98 +++++++++++++++++++ geolocator_android/pubspec.yaml | 2 +- .../test/geolocator_android_test.dart | 4 +- 8 files changed, 152 insertions(+), 7 deletions(-) create mode 100644 geolocator_android/lib/src/types/android_position.dart diff --git a/geolocator_android/CHANGELOG.md b/geolocator_android/CHANGELOG.md index 643feb901..5377c0e02 100644 --- a/geolocator_android/CHANGELOG.md +++ b/geolocator_android/CHANGELOG.md @@ -1,3 +1,8 @@ +## 4.5.0 + +* Creates `AndroidPosition`, a child class of `Position` with Android specific properties. +* Adds the `satelliteCount` and `satellitesUsedInFix` to `AndroidPosition`. + ## 4.4.1 * Fixes a bug where `getPositionStream` caused an `java.lang.IllegalStateException: passive location requests must have an explicit minimum update interval` because `minUpdateIntervalMillis` property in location request was set to -1 by default diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationMapper.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationMapper.java index 1afaf40da..303e07517 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationMapper.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/LocationMapper.java @@ -21,11 +21,11 @@ public static Map toHashMap(Location location) { if (location.hasAltitude()) position.put("altitude", location.getAltitude()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && location.hasVerticalAccuracy()) - position.put("altitude_accuracy", location.getVerticalAccuracyMeters()); + position.put("altitude_accuracy", location.getVerticalAccuracyMeters()); if (location.hasAccuracy()) position.put("accuracy", (double) location.getAccuracy()); if (location.hasBearing()) position.put("heading", (double) location.getBearing()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && location.hasBearingAccuracy()) - position.put("heading_accuracy", location.getBearingAccuracyDegrees()); + position.put("heading_accuracy", location.getBearingAccuracyDegrees()); if (location.hasSpeed()) position.put("speed", (double) location.getSpeed()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && location.hasSpeedAccuracy()) position.put("speed_accuracy", (double) location.getSpeedAccuracyMetersPerSecond()); @@ -35,6 +35,16 @@ public static Map toHashMap(Location location) { Double mslAltitude = location.getExtras().getDouble(NmeaClient.NMEA_ALTITUDE_EXTRA); position.put("altitude", mslAltitude); } + if (location.getExtras().containsKey(NmeaClient.GNSS_SATELLITE_COUNT_EXTRA)) { + Double mslSatelliteCount = + location.getExtras().getDouble(NmeaClient.GNSS_SATELLITE_COUNT_EXTRA); + position.put("gnss_satellite_count", mslSatelliteCount); + } + if (location.getExtras().containsKey(NmeaClient.GNSS_SATELLITES_USED_IN_FIX_EXTRA)) { + Double mslSatellitesUsedInFix = + location.getExtras().getDouble(NmeaClient.GNSS_SATELLITES_USED_IN_FIX_EXTRA); + position.put("gnss_satellites_used_in_fix", mslSatellitesUsedInFix); + } } return position; } diff --git a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/NmeaClient.java b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/NmeaClient.java index 497f7df3e..427e5c90d 100644 --- a/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/NmeaClient.java +++ b/geolocator_android/android/src/main/java/com/baseflow/geolocator/location/NmeaClient.java @@ -3,6 +3,7 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.content.Context; +import android.location.GnssStatus; import android.location.Location; import android.location.LocationManager; import android.location.OnNmeaMessageListener; @@ -17,6 +18,8 @@ public class NmeaClient { public static final String NMEA_ALTITUDE_EXTRA = "geolocator_mslAltitude"; + public static final String GNSS_SATELLITE_COUNT_EXTRA = "geolocator_mslSatelliteCount"; + public static final String GNSS_SATELLITES_USED_IN_FIX_EXTRA = "geolocator_mslSatellitesUsedInFix"; private final Context context; private final LocationManager locationManager; @@ -24,8 +27,12 @@ public class NmeaClient { @TargetApi(Build.VERSION_CODES.N) private OnNmeaMessageListener nmeaMessageListener; + @TargetApi(Build.VERSION_CODES.N) + private GnssStatus.Callback gnssCallback; private String lastNmeaMessage; + private double gnss_satellite_count; + private double gnss_satellites_used_in_fix; @Nullable private Calendar lastNmeaMessageTime; private boolean listenerAdded = false; @@ -42,6 +49,19 @@ public NmeaClient(@NonNull Context context, @Nullable LocationOptions locationOp lastNmeaMessageTime = Calendar.getInstance(); } }; + + gnssCallback = new GnssStatus.Callback() { + @Override + public void onSatelliteStatusChanged(@NonNull GnssStatus status) { + gnss_satellite_count = status.getSatelliteCount(); + gnss_satellites_used_in_fix = 0; + for (int i = 0; i < gnss_satellite_count; ++i) { + if (status.usedInFix(i)) { + ++gnss_satellites_used_in_fix; + } + } + } + }; } } @@ -54,6 +74,7 @@ public void start() { if (locationOptions != null && locationOptions.isUseMSLAltitude()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && locationManager != null) { locationManager.addNmeaListener(nmeaMessageListener, null); + locationManager.registerGnssStatusCallback(gnssCallback, null); listenerAdded = true; } } @@ -63,6 +84,7 @@ public void stop() { if (locationOptions != null && locationOptions.isUseMSLAltitude()) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && locationManager != null) { locationManager.removeNmeaListener(nmeaMessageListener); + locationManager.unregisterGnssStatusCallback(gnssCallback); listenerAdded = false; } } @@ -74,6 +96,12 @@ public void enrichExtrasWithNmea(@Nullable Location location) { return; } + if (location.getExtras() == null) { + location.setExtras(Bundle.EMPTY); + } + location.getExtras().putDouble(GNSS_SATELLITE_COUNT_EXTRA, gnss_satellite_count); + location.getExtras().putDouble(GNSS_SATELLITES_USED_IN_FIX_EXTRA, gnss_satellites_used_in_fix); + if (lastNmeaMessage != null && locationOptions != null && listenerAdded) { Calendar expiryDate = Calendar.getInstance(); diff --git a/geolocator_android/lib/geolocator_android.dart b/geolocator_android/lib/geolocator_android.dart index 4bdbfcf8e..c15a7c21f 100644 --- a/geolocator_android/lib/geolocator_android.dart +++ b/geolocator_android/lib/geolocator_android.dart @@ -17,5 +17,6 @@ export 'package:geolocator_platform_interface/geolocator_platform_interface.dart export 'src/geolocator_android.dart'; export 'src/types/android_settings.dart' show AndroidSettings; +export 'src/types/android_position.dart' show AndroidPosition; export 'src/types/foreground_settings.dart' show AndroidResource, ForegroundNotificationConfig; diff --git a/geolocator_android/lib/src/geolocator_android.dart b/geolocator_android/lib/src/geolocator_android.dart index 8ebad88b9..04c1ced86 100644 --- a/geolocator_android/lib/src/geolocator_android.dart +++ b/geolocator_android/lib/src/geolocator_android.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/services.dart'; +import 'package:geolocator_android/geolocator_android.dart'; import 'package:geolocator_platform_interface/geolocator_platform_interface.dart'; import 'package:uuid/uuid.dart'; @@ -83,7 +84,7 @@ class GeolocatorAndroid extends GeolocatorPlatform { final positionMap = await _methodChannel.invokeMethod('getLastKnownPosition', parameters); - return positionMap != null ? Position.fromMap(positionMap) : null; + return positionMap != null ? AndroidPosition.fromMap(positionMap) : null; } on PlatformException catch (e) { final error = _handlePlatformException(e); @@ -123,7 +124,7 @@ class GeolocatorAndroid extends GeolocatorPlatform { } final positionMap = await positionFuture; - return Position.fromMap(positionMap); + return AndroidPosition.fromMap(positionMap); } on TimeoutException { final parameters = { 'requestId': requestId, @@ -191,7 +192,7 @@ class GeolocatorAndroid extends GeolocatorPlatform { _positionStream = positionStream .map((dynamic element) => - Position.fromMap(element.cast())) + AndroidPosition.fromMap(element.cast())) .handleError( (error) { if (error is PlatformException) { diff --git a/geolocator_android/lib/src/types/android_position.dart b/geolocator_android/lib/src/types/android_position.dart new file mode 100644 index 000000000..4680130a3 --- /dev/null +++ b/geolocator_android/lib/src/types/android_position.dart @@ -0,0 +1,98 @@ +import 'package:geolocator_platform_interface/geolocator_platform_interface.dart'; +// ignore: depend_on_referenced_packages +import 'package:meta/meta.dart'; + +/// Contains additional location information only available on Android platforms. +@immutable +class AndroidPosition extends Position { + /// Constructs an instance with the given values for testing. [AndroidPosition] + /// instances constructed this way won't actually reflect any real information + /// from the platform, just whatever was passed in at construction time. + const AndroidPosition({ + required this.satelliteCount, + required this.satellitesUsedInFix, + required longitude, + required latitude, + required timestamp, + required accuracy, + required altitude, + required altitudeAccuracy, + required heading, + required headingAccuracy, + required speed, + required speedAccuracy, + int? floor, + isMocked = false, + }) : super( + longitude: longitude, + latitude: latitude, + timestamp: timestamp, + accuracy: accuracy, + altitude: 0.0, + altitudeAccuracy: 0.0, + heading: 0.0, + headingAccuracy: 0.0, + speed: 0.0, + speedAccuracy: 0.0); + + /// If available it returns the number of GNSS satellites. + /// + /// If the number of satellites is not available it returns the default value: 0.0. + final double satelliteCount; + + /// If available it returns the number of GNSS satellites used in fix. + /// + /// If the number of satellites used in fix is not available it returns the default value: 0.0. + final double satellitesUsedInFix; + + @override + bool operator ==(Object other) { + var areEqual = other is AndroidPosition && + other.satelliteCount == satelliteCount && + other.satellitesUsedInFix == satellitesUsedInFix; + return areEqual; + } + + @override + String toString() { + return 'Latitude: $latitude, Longitude: $longitude, Satellite count: $satelliteCount, Satellites used in fix: $satellitesUsedInFix'; + } + + @override + int get hashCode => satelliteCount.hashCode ^ satellitesUsedInFix.hashCode; + + /// Converts the supplied [Map] to an instance of the [AndroidPosition] class. + static AndroidPosition fromMap(dynamic message) { + final Map positionMap = message; + + return AndroidPosition( + satelliteCount: positionMap['gnss_satellite_count'] ?? 0.0, + satellitesUsedInFix: positionMap['gnss_satellites_used_in_fix'] ?? 0.0, + latitude: positionMap['latitude'], + longitude: positionMap['longitude'], + timestamp: DateTime.fromMillisecondsSinceEpoch( + positionMap['timestamp'].toInt(), + isUtc: true), + altitude: positionMap['altitude'] ?? 0.0, + altitudeAccuracy: positionMap['altitude_accuracy'] ?? 0.0, + accuracy: positionMap['accuracy'] ?? 0.0, + heading: positionMap['heading'] ?? 0.0, + headingAccuracy: positionMap['heading_accuracy'] ?? 0.0, + floor: positionMap['floor'], + speed: positionMap['speed'] ?? 0.0, + speedAccuracy: positionMap['speed_accuracy'] ?? 0.0, + isMocked: positionMap['is_mocked'] ?? false, + ); + } + + /// Converts the [AndroidPosition] instance into a [Map] instance that can be + /// serialized to JSON. + @override + Map toJson() { + return super.toJson() + ..addAll({ + 'gnss_satellite_count': satelliteCount, + 'gnss_satellites_used_in_fix': satellitesUsedInFix, + }); + } +} diff --git a/geolocator_android/pubspec.yaml b/geolocator_android/pubspec.yaml index 62d51482f..28386b726 100644 --- a/geolocator_android/pubspec.yaml +++ b/geolocator_android/pubspec.yaml @@ -2,7 +2,7 @@ name: geolocator_android description: Geolocation plugin for Flutter. This plugin provides the Android implementation for the geolocator. repository: https://github.com/baseflow/flutter-geolocator/tree/main/geolocator_android issue_tracker: https://github.com/baseflow/flutter-geolocator/issues?q=is%3Aissue+is%3Aopen -version: 4.4.1 +version: 4.5.0 environment: sdk: ">=2.15.0 <4.0.0" diff --git a/geolocator_android/test/geolocator_android_test.dart b/geolocator_android/test/geolocator_android_test.dart index 447401d19..6611a6ba9 100644 --- a/geolocator_android/test/geolocator_android_test.dart +++ b/geolocator_android/test/geolocator_android_test.dart @@ -9,7 +9,7 @@ import 'package:geolocator_android/geolocator_android.dart'; import 'event_channel_mock.dart'; import 'method_channel_mock.dart'; -Position get mockPosition => Position( +Position get mockPosition => AndroidPosition( latitude: 52.561270, longitude: 5.639382, timestamp: DateTime.fromMillisecondsSinceEpoch( @@ -18,6 +18,8 @@ Position get mockPosition => Position( ), altitude: 3000.0, altitudeAccuracy: 0.0, + satelliteCount: 2.0, + satellitesUsedInFix: 2.0, accuracy: 0.0, heading: 0.0, headingAccuracy: 0.0,