diff --git a/app/CMakeLists.txt b/app/CMakeLists.txt index 3590c862a..b96c77c27 100644 --- a/app/CMakeLists.txt +++ b/app/CMakeLists.txt @@ -252,12 +252,18 @@ if (IOS) endif () if (ANDROID) - set(MM_HDRS ${MM_HDRS} position/tracking/androidtrackingbackend.h - position/tracking/androidtrackingbroadcast.h + set(MM_HDRS + ${MM_HDRS} + position/tracking/androidtrackingbackend.h + position/tracking/androidtrackingbroadcast.h + position/providers/androidpositionprovider.h ) - set(MM_SRCS ${MM_SRCS} position/tracking/androidtrackingbackend.cpp - position/tracking/androidtrackingbroadcast.cpp + set(MM_SRCS + ${MM_SRCS} + position/tracking/androidtrackingbackend.cpp + position/tracking/androidtrackingbroadcast.cpp + position/providers/androidpositionprovider.cpp ) endif () diff --git a/app/android/src/uk/co/lutraconsulting/MMAndroidPosition.java b/app/android/src/uk/co/lutraconsulting/MMAndroidPosition.java new file mode 100644 index 000000000..f2731f7aa --- /dev/null +++ b/app/android/src/uk/co/lutraconsulting/MMAndroidPosition.java @@ -0,0 +1,196 @@ +package uk.co.lutraconsulting; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.location.GnssStatus; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Looper; +import android.os.Handler; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.core.app.ActivityCompat; + +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 MMAndroidPosition { + + static public abstract class Callback { + public void onPositionChanged(@NonNull Location location, GnssStatus gnssStatus) { + } + } + + private static native void jniOnPositionUpdated(int instanceId, Location location, GnssStatus gnssStatus); + + // find out whether fused provider could be actually used + static public boolean isFusedLocationProviderAvailable(Context context) { + GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance(); + return googleApiAvailability.isGooglePlayServicesAvailable(context) == ConnectionResult.SUCCESS; + } + + // get more details why FLP is not available (e.g. play services missing, disabled, updating...) + static public String fusedLocationProviderErrorString(Context context) { + GoogleApiAvailability googleApiAvailability = GoogleApiAvailability.getInstance(); + return googleApiAvailability.getErrorString(googleApiAvailability.isGooglePlayServicesAvailable(context)); + } + + // called from C++ code + static public MMAndroidPosition createWithJniCallback(Context context, boolean useFused, int instanceId) { + Log.i("CPP", "[java] createWithJniCallback"); + + MMAndroidPosition.Callback callback = new MMAndroidPosition.Callback() { + @Override + public void onPositionChanged(@NonNull Location location, GnssStatus gnssStatus) { + jniOnPositionUpdated(instanceId, location, gnssStatus); + } + }; + + return new MMAndroidPosition(context, callback, useFused); + } + + private final Context mContext; + private final LocationManager mLocationManager; + private final boolean mUseFused; + private FusedLocationProviderClient mFusedLocationClient = null; + private final LocationCallback mLocationCallback; + private final LocationListener mLocationManagerCallback; + private final GnssStatus.Callback mGnssStatusCallback; + private final MMAndroidPosition.Callback mClientCallback; + private boolean mFusedAvailable = false; + private boolean mGpsProviderAvailable = false; + private boolean mIsStarted = false; + private String mErrorMessage; + private GnssStatus mLastGnssStatus; + + public MMAndroidPosition(Context context, MMAndroidPosition.Callback clientCallback, boolean useFused) { + mContext = context; + mClientCallback = clientCallback; + mUseFused = useFused; + + Log.i("CPP", "[java] constructor!"); + + mLocationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + + if (mUseFused) { + mFusedAvailable = isFusedLocationProviderAvailable(context); + Log.i("CPP", "[java] fused available: " + mFusedAvailable); + if (mFusedAvailable) { + mFusedLocationClient = LocationServices.getFusedLocationProviderClient(context); + } + } else { + mGpsProviderAvailable = mLocationManager.isProviderEnabled(LocationManager.GPS_PROVIDER); + Log.i("CPP", "[java] gps provider available: " + mGpsProviderAvailable); + } + + mLocationCallback = new LocationCallback() { + @Override + public void onLocationResult(@NonNull LocationResult locationResult) { + for (Location location : locationResult.getLocations()) { + Log.i("CPP", "[java] FLP " + location.getLatitude() + " " + location.getLongitude()); + + // call the native function! + mClientCallback.onPositionChanged(location, mLastGnssStatus); + } + } + }; + + mGnssStatusCallback = new GnssStatus.Callback() { + @Override + public void onSatelliteStatusChanged(@NonNull GnssStatus status) { + + // store the satellite info + mLastGnssStatus = status; + } + }; + + mLocationManagerCallback = new LocationListener() { + @Override + public void onLocationChanged(@NonNull Location location) { + Log.i("CPP", "[java] GPS " + location.getLatitude() + " " + location.getLongitude()); + + mClientCallback.onPositionChanged(location, mLastGnssStatus); + } + }; + + Log.i("CPP", "[java] constructor end"); + + } + + public String errorMessage() { + return mErrorMessage; + } + + public boolean start() { + Log.i("CPP", "[java] start()"); + + if (mIsStarted) + return false; + + if (mUseFused && !mFusedAvailable) { + mErrorMessage = "FUSED_NOT_AVAILABLE"; + Log.e("CPP", "[java] FUSED_NOT_AVAILABLE"); + return false; + } + + if (!mUseFused && !mGpsProviderAvailable) { + mErrorMessage = "GPS_NOT_AVAILABLE"; + Log.e("CPP", "[java] GPS_NOT_AVAILABLE"); + return false; + } + + if (ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && + ActivityCompat.checkSelfPermission(mContext, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) { + mErrorMessage = "MISSING_PERMISSIONS"; + Log.e("CPP", "[java] MISSING_PERMISSIONS"); + return false; + } + + if (mUseFused) { + LocationRequest locationRequest = new LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 1000).build(); + + mFusedLocationClient.requestLocationUpdates(locationRequest, mLocationCallback, Looper.getMainLooper()); + } + else { + mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 1000L, 0.F, mLocationManagerCallback, Looper.getMainLooper()); + } + + mLocationManager.registerGnssStatusCallback(mGnssStatusCallback, new Handler(Looper.getMainLooper())); + + Log.i("CPP", "[java] started!"); + + mIsStarted = true; + return true; + } + + public boolean stop() { + Log.i("CPP", "[java] stop()"); + + if (!mIsStarted) + return false; + + if (mUseFused) { + mFusedLocationClient.removeLocationUpdates(mLocationCallback); + } else { + mLocationManager.removeUpdates(mLocationManagerCallback); + } + + mLocationManager.unregisterGnssStatusCallback(mGnssStatusCallback); + + Log.i("CPP", "[java] stopped!"); + + mIsStarted = false; + return true; + } +} diff --git a/app/position/positionkit.cpp b/app/position/positionkit.cpp index 4a26fb2de..6510cb464 100644 --- a/app/position/positionkit.cpp +++ b/app/position/positionkit.cpp @@ -18,6 +18,10 @@ #include "position/providers/internalpositionprovider.h" #include "position/providers/simulatedpositionprovider.h" +#ifdef ANDROID +#include "position/providers/androidpositionprovider.h" +#include +#endif #include "appsettings.h" #include "inpututils.h" @@ -105,6 +109,25 @@ AbstractPositionProvider *PositionKit::constructProvider( const QString &type, c QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership ); return provider; } +#ifdef ANDROID + else if ( id == QStringLiteral( "android_fused" ) || id == QStringLiteral( "android_gps" ) ) + { + bool fused = ( id == QStringLiteral( "android_fused" ) ); + if ( fused && !AndroidPositionProvider::isFusedAvailable() ) + { + // TODO: inform user + use AndroidPositionProvider::fusedErrorString() output? + + // fallback to the default - at this point the Qt Positioning implementation + AbstractPositionProvider *provider = new InternalPositionProvider(); + QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership ); + return provider; + } + __android_log_print( ANDROID_LOG_INFO, "CPP", "MAKE PROVIDER %d", fused ); + AbstractPositionProvider *provider = new AndroidPositionProvider( fused ); + QQmlEngine::setObjectOwnership( provider, QQmlEngine::CppOwnership ); + return provider; + } +#endif else // id == devicegps { AbstractPositionProvider *provider = new InternalPositionProvider(); @@ -132,13 +155,10 @@ AbstractPositionProvider *PositionKit::constructActiveProvider( AppSettings *app return constructProvider( QStringLiteral( "internal" ), QStringLiteral( "simulated" ) ); } } - else if ( providerId == QStringLiteral( "devicegps" ) ) - { - return constructProvider( QStringLiteral( "internal" ), QStringLiteral( "devicegps" ) ); - } - else if ( providerId == QStringLiteral( "simulated" ) ) + else if ( providerId == QStringLiteral( "devicegps" ) || providerId == QStringLiteral( "simulated" ) || + providerId == QStringLiteral( "android_fused" ) || providerId == QStringLiteral( "android_gps" ) ) { - return constructProvider( QStringLiteral( "internal" ), QStringLiteral( "simulated" ) ); + return constructProvider( QStringLiteral( "internal" ), providerId ); } else { diff --git a/app/position/providers/androidpositionprovider.cpp b/app/position/providers/androidpositionprovider.cpp new file mode 100644 index 000000000..277776d14 --- /dev/null +++ b/app/position/providers/androidpositionprovider.cpp @@ -0,0 +1,254 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#include "androidpositionprovider.h" +#include "coreutils.h" + +#include "qgis.h" + +#include +#include + +#include + +#include + + +int AndroidPositionProvider::sLastInstanceId = 0; +QMap AndroidPositionProvider::sInstances; + + +void jniOnPositionUpdated( JNIEnv *env, jclass clazz, jint instanceId, jobject locationObj, jobject gnssStatusObj ) +{ + AndroidPositionProvider *inst = AndroidPositionProvider::sInstances[instanceId]; + if ( !inst ) + { + __android_log_print( ANDROID_LOG_ERROR, "CPP", "[c++] unknown instance! %d", instanceId ); + return; + } + + QJniObject location( locationObj ); + if ( !location.isValid() ) + { + __android_log_print( ANDROID_LOG_ERROR, "CPP", "[c++] invalid location obj" ); + return; + } + + const jdouble latitude = location.callMethod( "getLatitude" ); + const jdouble longitude = location.callMethod( "getLongitude" ); + const jlong timestamp = location.callMethod( "getTime" ); + + GeoPosition pos; + pos.latitude = latitude; + pos.longitude = longitude; + pos.utcDateTime = QDateTime::fromMSecsSinceEpoch( timestamp, QTimeZone::UTC ); + + if ( location.callMethod( "hasAltitude" ) ) + { + const jdouble value = location.callMethod( "getAltitude" ); + if ( !qFuzzyIsNull( value ) ) + pos.elevation = value; + } + + // TODO: we are getting ellipsoid elevation here. From API level 34 (Android 14), + // there is AltitudeConverter() class in Java that can be used to add MSL altitude + // to Location object. How to deal with this correctly? (we could also convert + // to MSL (orthometric) altitude ourselves if we add geoid model to our APK + + // horizontal accuracy + if ( location.callMethod( "hasAccuracy" ) ) + { + const jfloat accuracy = location.callMethod( "getAccuracy" ); + if ( !qFuzzyIsNull( accuracy ) ) + pos.hacc = accuracy; + } + + // vertical accuracy (available since API Level 26 (Android 8.0)) + if ( QNativeInterface::QAndroidApplication::sdkVersion() >= 26 ) + { + if ( location.callMethod( "hasVerticalAccuracy" ) ) + { + const jfloat accuracy = location.callMethod( "getVerticalAccuracyMeters" ); + if ( !qFuzzyIsNull( accuracy ) ) + pos.vacc = accuracy; + } + } + + // ground speed + if ( location.callMethod( "hasSpeed" ) ) + { + const jfloat speed = location.callMethod( "getSpeed" ); + if ( !qFuzzyIsNull( speed ) ) + pos.speed = speed * 3.6; // convert from m/s to km/h + + // could also use getSpeedAccuracyMetersPerSecond() since API level 26 (Android 8.0) + } + + // bearing + if ( location.callMethod( "hasBearing" ) ) + { + const jfloat bearing = location.callMethod( "getBearing" ); + if ( !qFuzzyIsNull( bearing ) ) + pos.direction = bearing; + + // could also use getBearingAccuracyDegrees() since API level 26 (Android 8.0) + } + + // could also use isMock() to detect if location is mocked + // (may useful to check if 3rd party app is setting it for external GNSS receiver) + + // could also use getExtras() to get further details from mocked location + // (the key/value pairs are vendor-specific, and could include things like DOP, + // info about corrections, geoid undulation, receiver model) + + __android_log_print( ANDROID_LOG_INFO, "CPP", "[c++] pos %f %f", latitude, longitude ); + + QJniObject gnssStatus( gnssStatusObj ); + if ( gnssStatus.isValid() ) + { + int satellitesUsed = 0; + const int satellitesCount = gnssStatus.callMethod( "getSatelliteCount" ); + for ( int i = 0; i < satellitesCount; ++i ) + { + if ( gnssStatus.callMethod( "usedInFix", i ) ) + ++satellitesUsed; + + // we could get more info here (ID, azimuth, elevation, signal strength, ...) + // but we are not using that anywhere + } + + pos.satellitesVisible = satellitesCount; + pos.satellitesUsed = satellitesUsed; + } + + QMetaObject::invokeMethod( inst, "positionChanged", + Qt::AutoConnection, Q_ARG( GeoPosition, pos ) ); + +} + + +AndroidPositionProvider::AndroidPositionProvider( bool fused, QObject *parent ) + : AbstractPositionProvider( fused ? QStringLiteral( "android_fused" ) : QStringLiteral( "android_gps" ), + QStringLiteral( "internal" ), + fused ? tr( "Internal (fused)" ) : tr( "Internal (gps)" ), parent ) + , mFused( fused ) + , mInstanceId( ++sLastInstanceId ) +{ + __android_log_print( ANDROID_LOG_INFO, "CPP", "[c++] CONSTRUCT" ); + + Q_ASSERT( !sInstances.contains( mInstanceId ) ); + sInstances[mInstanceId] = this; + + // register the native methods + + JNINativeMethod methods[] + { + { + "jniOnPositionUpdated", + "(ILandroid/location/Location;Landroid/location/GnssStatus;)V", + reinterpret_cast( jniOnPositionUpdated ) + } + }; + + QJniEnvironment javaenv; + + javaenv.registerNativeMethods( "uk/co/lutraconsulting/MMAndroidPosition", methods, 1 ); + + // create the Java object + + __android_log_print( ANDROID_LOG_INFO, "CPP", "[c++] create Java object" ); + + jobject context = QNativeInterface::QAndroidApplication::context(); + + mAndroidPos = QJniObject::callStaticObjectMethod( "uk/co/lutraconsulting/MMAndroidPosition", "createWithJniCallback", + "(Landroid/content/Context;ZI)Luk/co/lutraconsulting/MMAndroidPosition;", context, mFused, mInstanceId ); + + AndroidPositionProvider::startUpdates(); +} + +AndroidPositionProvider::~AndroidPositionProvider() +{ + __android_log_print( ANDROID_LOG_INFO, "CPP", "DESTRUCT" ); + + Q_ASSERT( sInstances[mInstanceId] == this ); + sInstances.remove( mInstanceId ); + +} + +bool AndroidPositionProvider::isFusedAvailable() +{ + jobject context = QNativeInterface::QAndroidApplication::context(); + + return QJniObject::callStaticMethod( "uk/co/lutraconsulting/MMAndroidPosition", "isFusedLocationProviderAvailable", + "(Landroid/content/Context;)Z", context ); +} + +QString AndroidPositionProvider::fusedErrorString() +{ + jobject context = QNativeInterface::QAndroidApplication::context(); + + QJniObject str = QJniObject::callStaticObjectMethod( "uk/co/lutraconsulting/MMAndroidPosition", "fusedLocationProviderErrorString", + "(Landroid/content/Context;)Ljava/lang/String;", context ); + + return str.toString(); +} + + +void AndroidPositionProvider::startUpdates() +{ + __android_log_print( ANDROID_LOG_INFO, "CPP", "[c++] start updates" ); + + // permissions are currently being requested in main.qml, so here + // we only check that we have the permissions we need. + QLocationPermission perm; + perm.setAccuracy( QLocationPermission::Precise ); + if ( qApp->checkPermission( perm ) != Qt::PermissionStatus::Granted ) + { + __android_log_print( ANDROID_LOG_ERROR, "CPP", "[c++] no location permissions - not starting!" ); + setState( tr( "No location permissions" ), State::NoConnection ); + return; + } + + jboolean res = mAndroidPos.callMethod( "start", "()Z" ); + + __android_log_print( ANDROID_LOG_INFO, "CPP", "[c++] start updates res: %d", res ); + + if ( !res ) + { + QJniObject errMsgJni = mAndroidPos.callObjectMethod( "errorMessage", "()Ljava/lang/String;" ); + QString errMsg = errMsgJni.toString(); + __android_log_print( ANDROID_LOG_INFO, "CPP", "[c++] error: %s", errMsg.toUtf8().constData() ); + if ( errMsg == "MISSING_PERMISSIONS" ) + setState( tr( "No location permissions" ), State::NoConnection ); + else if ( errMsg == "FUSED_NOT_AVAILABLE" ) + setState( tr( "Fused location not available" ), State::NoConnection ); + else + setState( errMsg, State::NoConnection ); + return; + } + + setState( tr( "Connected" ), State::Connected ); +} + +void AndroidPositionProvider::stopUpdates() +{ + __android_log_print( ANDROID_LOG_INFO, "CPP", "[c++] stop updates" ); + + jboolean res = mAndroidPos.callMethod( "stop", "()Z" ); + + __android_log_print( ANDROID_LOG_INFO, "CPP", "[c++] stop updates res: %d", res ); + +} + +void AndroidPositionProvider::closeProvider() +{ + stopUpdates(); + + mAndroidPos = QJniObject(); +} diff --git a/app/position/providers/androidpositionprovider.h b/app/position/providers/androidpositionprovider.h new file mode 100644 index 000000000..fa1772334 --- /dev/null +++ b/app/position/providers/androidpositionprovider.h @@ -0,0 +1,59 @@ +/*************************************************************************** + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + ***************************************************************************/ + +#ifndef ANDROIDPOSITIONPROVIDER_H +#define ANDROIDPOSITIONPROVIDER_H + +#include "inputconfig.h" +#include "abstractpositionprovider.h" + +#include + +/** + * AndroidPositionProvider uses Android's LocationManager API (when fused=false) + * or Fused Location Provider from Google Play Services (when fused=true). + * + * Compared to Qt Positioning, it can use Fused Location Provider and it is + * potentially more flexible becuase we are not going through a generic + * positioning API. + */ +class AndroidPositionProvider : public AbstractPositionProvider +{ + Q_OBJECT + + public: + explicit AndroidPositionProvider( bool fused, QObject *parent = nullptr ); + virtual ~AndroidPositionProvider() override; + + virtual void startUpdates() override; + virtual void stopUpdates() override; + virtual void closeProvider() override; + + //! Checks whether the fused location provider can be used (i.e. Google Play services are present) + static bool isFusedAvailable(); + //! If fused provider is not available, returns error string that could be presented to users. + //! It is not very human friendly, but at least something (e.g. "SERVICE_DISABLED") + static QString fusedErrorString(); + + public slots: + + private: + bool mFused; + int mInstanceId; + QJniObject mAndroidPos; + + public: + // Multiple PositionProvider instances may exist at a time (because a new provider + // gets created before the old one gets deleted), and our JNI callback method needs + // to know to which instance to deliver a location update. + static QMap sInstances; + static int sLastInstanceId; +}; + +#endif // ANDROIDPOSITIONPROVIDER_H diff --git a/app/position/providers/internalpositionprovider.cpp b/app/position/providers/internalpositionprovider.cpp index 0fb68dc33..fc8a02670 100644 --- a/app/position/providers/internalpositionprovider.cpp +++ b/app/position/providers/internalpositionprovider.cpp @@ -13,7 +13,7 @@ #include "qgis.h" InternalPositionProvider::InternalPositionProvider( QObject *parent ) - : AbstractPositionProvider( QStringLiteral( "devicegps" ), QStringLiteral( "internal" ), QStringLiteral( "Internal" ), parent ) + : AbstractPositionProvider( QStringLiteral( "devicegps" ), QStringLiteral( "internal" ), tr( "Internal" ), parent ) { mGpsPositionSource = std::unique_ptr( QGeoPositionInfoSource::createDefaultSource( nullptr ) ); diff --git a/app/position/providers/positionprovidersmodel.cpp b/app/position/providers/positionprovidersmodel.cpp index 9d9baa424..e070e4576 100644 --- a/app/position/providers/positionprovidersmodel.cpp +++ b/app/position/providers/positionprovidersmodel.cpp @@ -11,6 +11,10 @@ #include "inpututils.h" #include "coreutils.h" +#ifdef ANDROID +#include "position/providers/androidpositionprovider.h" +#endif + PositionProvidersModel::PositionProvidersModel( QObject *parent ) : QAbstractListModel( parent ) { if ( !InputUtils::isMobilePlatform() ) @@ -20,6 +24,9 @@ PositionProvidersModel::PositionProvidersModel( QObject *parent ) : QAbstractLis mProviders.push_front( simulated ); } + // Keep the names of position providers in sync with names + // used in the constructors of the providers... + PositionProvider internal; internal.name = tr( "Internal" ); internal.description = tr( "GPS receiver of this device" ); @@ -27,6 +34,30 @@ PositionProvidersModel::PositionProvidersModel( QObject *parent ) : QAbstractLis internal.providerId = "devicegps"; mProviders.push_front( internal ); + +#ifdef ANDROID + PositionProvider internalFused; + internalFused.name = tr( "Internal (fused)" ); + if ( AndroidPositionProvider::isFusedAvailable() ) + internalFused.description = tr( "Using GPS, Wifi and sensors" ); + else + internalFused.description = tr( "Not available (%1)" ).arg( AndroidPositionProvider::fusedErrorString() ); + internalFused.providerType = "internal"; + internalFused.providerId = "android_fused"; + mProviders.push_front( internalFused ); + + // This one should have pretty much the same behavior as the provider + // implemented using Qt Positioning, so let's skip it. When we ditch + // Qt's implementation on Android, this can be used as a fallback. +#if 0 + PositionProvider internalGps; + internalGps.name = tr( "Internal (gps)" ); + internalGps.description = tr( "Using GPS only" ); + internalGps.providerType = "internal"; + internalGps.providerId = "android_gps"; + mProviders.push_front( internalGps ); +#endif +#endif } PositionProvidersModel::~PositionProvidersModel() = default; diff --git a/app/qml/main.qml b/app/qml/main.qml index 2051c4439..6232857e1 100644 --- a/app/qml/main.qml +++ b/app/qml/main.qml @@ -244,20 +244,6 @@ ApplicationWindow { LocationPermission { id: locationPermission accuracy: LocationPermission.Precise - - function requestPermissionAsync() { - if ( locationPermission.status === Qt.Granted ) { - return true; - } - else if ( locationPermission.status === Qt.Undetermined ) { - locationPermission.request() - } - else if ( locationPermission.status === Qt.Denied ) { - __inputUtils.log("Permissions", "Location permission is denied") - __notificationModel.addInfo( qsTr( "Location permission is required to show your location on map. Please enable it in system settings." ) ); - } - return false; - } } MMToolbar { @@ -948,6 +934,10 @@ ApplicationWindow { // check location permission if ( locationPermission.status === Qt.Undetermined ) { + // This is the place where we actually request permissions. + // When the system's request permissions dialog get closed, + // we get a notification that our application is active again, + // and PositionKit::appStateChanged() will try to start updates. locationPermission.request(); } else if ( locationPermission.status === Qt.Denied ) { diff --git a/app/qml/settings/MMSettingsPage.qml b/app/qml/settings/MMSettingsPage.qml index a1b29e942..4de37f510 100644 --- a/app/qml/settings/MMSettingsPage.qml +++ b/app/qml/settings/MMSettingsPage.qml @@ -76,7 +76,7 @@ MMPage { MMSettingsComponents.MMSettingsItem { width: parent.width title: qsTr("Manage GPS receivers") - value: "Internal" + value: __positionKit.positionProvider.name() onClicked: root.manageGpsClicked() } diff --git a/cmake_templates/build.gradle.in b/cmake_templates/build.gradle.in index 0b4e7adc8..c83f1abab 100644 --- a/cmake_templates/build.gradle.in +++ b/cmake_templates/build.gradle.in @@ -33,6 +33,7 @@ dependencies { implementation 'androidx.core:core-splashscreen:1.0.0-beta02' implementation "androidx.exifinterface:exifinterface:1.3.3" implementation 'com.android.support:appcompat-v7:28.0.0' + implementation 'com.google.android.gms:play-services-location:21.2.0' } android {