diff --git a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java index bee9fa2b19..ede3b6f8cf 100644 --- a/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java +++ b/OsmAnd-java/src/main/java/net/osmand/GPXUtilities.java @@ -1822,6 +1822,25 @@ public class GPXUtilities { } serializer.endTag(null, "metadata"); + for (WptPt l : file.points) { + serializer.startTag(null, "wpt"); //$NON-NLS-1$ + writeWpt(format, serializer, l); + serializer.endTag(null, "wpt"); //$NON-NLS-1$ + } + + for (Route track : file.routes) { + serializer.startTag(null, "rte"); //$NON-NLS-1$ + writeNotNullText(serializer, "name", track.name); + writeNotNullText(serializer, "desc", track.desc); + + for (WptPt p : track.points) { + serializer.startTag(null, "rtept"); //$NON-NLS-1$ + writeWpt(format, serializer, p); + serializer.endTag(null, "rtept"); //$NON-NLS-1$ + } + writeExtensions(serializer, track); + serializer.endTag(null, "rte"); //$NON-NLS-1$ + } for (Track track : file.tracks) { if (!track.generalTrack) { @@ -1844,26 +1863,6 @@ public class GPXUtilities { } } - for (Route track : file.routes) { - serializer.startTag(null, "rte"); //$NON-NLS-1$ - writeNotNullText(serializer, "name", track.name); - writeNotNullText(serializer, "desc", track.desc); - - for (WptPt p : track.points) { - serializer.startTag(null, "rtept"); //$NON-NLS-1$ - writeWpt(format, serializer, p); - serializer.endTag(null, "rtept"); //$NON-NLS-1$ - } - writeExtensions(serializer, track); - serializer.endTag(null, "rte"); //$NON-NLS-1$ - } - - for (WptPt l : file.points) { - serializer.startTag(null, "wpt"); //$NON-NLS-1$ - writeWpt(format, serializer, l); - serializer.endTag(null, "wpt"); //$NON-NLS-1$ - } - writeExtensions(serializer, file); serializer.endTag(null, "gpx"); //$NON-NLS-1$ diff --git a/OsmAnd-java/src/main/java/net/osmand/binary/StringBundleWriter.java b/OsmAnd-java/src/main/java/net/osmand/binary/StringBundleWriter.java index 2f607aef3a..d3c151c12a 100644 --- a/OsmAnd-java/src/main/java/net/osmand/binary/StringBundleWriter.java +++ b/OsmAnd-java/src/main/java/net/osmand/binary/StringBundleWriter.java @@ -20,7 +20,7 @@ public abstract class StringBundleWriter { public void writeBundle() { for (Entry> entry : bundle.getMap().entrySet()) { - writeItem(entry.getKey(), entry.getValue()); + writeItem("osmand:" + entry.getKey(), entry.getValue()); } } } diff --git a/OsmAnd/build.gradle b/OsmAnd/build.gradle index 7da4480bd7..9f909d50c8 100644 --- a/OsmAnd/build.gradle +++ b/OsmAnd/build.gradle @@ -108,18 +108,25 @@ android { manifest.srcFile "AndroidManifest-debug.xml" } full { - java.srcDirs = ["src-google"] + java.srcDirs = ["src-nogms", "src-google"] + } + fullGms { + java.srcDirs = ["src-gms", "src-google"] } free { - java.srcDirs = ["src-google"] + java.srcDirs = ["src-nogms", "src-google"] + manifest.srcFile "AndroidManifest-free.xml" + } + freeGms { + java.srcDirs = ["src-gms", "src-google"] manifest.srcFile "AndroidManifest-free.xml" } freedev { - java.srcDirs = ["src-google"] + java.srcDirs = ["src-nogms", "src-google"] manifest.srcFile "AndroidManifest-freedev.xml" } freehuawei { - java.srcDirs = ["src-huawei"] + java.srcDirs = ["src-nogms", "src-google"] manifest.srcFile "AndroidManifest-freehuawei.xml" } @@ -172,10 +179,18 @@ android { dimension "version" applicationId "net.osmand" } + freeGms { + dimension "version" + applicationId "net.osmand" + } full { dimension "version" applicationId "net.osmand.plus" } + fullGms { + dimension "version" + applicationId "net.osmand.plus" + } freehuawei { dimension "version" applicationId "net.osmand.huawei" @@ -507,8 +522,10 @@ dependencies { exclude group: "com.fasterxml.jackson.core" } implementation 'com.jaredrummler:colorpicker:1.1.0' + implementation "org.bouncycastle:bcpkix-jdk15on:1.56" freehuaweiImplementation 'com.huawei.hms:iap:5.0.2.300' - implementation "org.bouncycastle:bcpkix-jdk15on:1.56" + freeGmsImplementation 'com.google.android.gms:play-services-location:17.1.0' + fullGmsImplementation 'com.google.android.gms:play-services-location:17.1.0' } diff --git a/OsmAnd/build.gradle.lib b/OsmAnd/build.gradle.lib index 6b0f4da990..f4f9c019ed 100644 --- a/OsmAnd/build.gradle.lib +++ b/OsmAnd/build.gradle.lib @@ -38,7 +38,7 @@ android { jni.srcDirs = [] jniLibs.srcDirs = ["libs"] aidl.srcDirs = ["src"] - java.srcDirs = ["src", "src-google"] + java.srcDirs = ["src", "src-gms", "src-google"] resources.srcDirs = ["src"] renderscript.srcDirs = ["src"] res.srcDirs = ["res"] @@ -409,6 +409,7 @@ dependencies { exclude group: "com.fasterxml.jackson.core" } implementation 'com.jaredrummler:colorpicker:1.1.0' - implementation 'org.bouncycastle:bcpkix-jdk15on:1.56' + + implementation 'com.google.android.gms:play-services-location:17.1.0' } diff --git a/OsmAnd/res/layout/bottom_sheet_item_with_descr_64dp.xml b/OsmAnd/res/layout/bottom_sheet_item_with_descr_64dp.xml index 2ff6a1bf58..95091e1086 100644 --- a/OsmAnd/res/layout/bottom_sheet_item_with_descr_64dp.xml +++ b/OsmAnd/res/layout/bottom_sheet_item_with_descr_64dp.xml @@ -27,6 +27,8 @@ android:layout_weight="1" android:layout_marginLeft="@dimen/content_padding" android:layout_marginStart="@dimen/content_padding" + android:paddingTop="@dimen/content_padding_small" + android:paddingBottom="@dimen/content_padding_small" android:orientation="vertical"> + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/layout/online_routing_preference_segment.xml b/OsmAnd/res/layout/online_routing_preference_segment.xml index 5f095e126c..437ba875e5 100644 --- a/OsmAnd/res/layout/online_routing_preference_segment.xml +++ b/OsmAnd/res/layout/online_routing_preference_segment.xml @@ -129,6 +129,19 @@ tools:visibility="visible" android:visibility="gone" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OsmAnd/res/values-ar/strings.xml b/OsmAnd/res/values-ar/strings.xml index bd3397afc2..4a9fde833f 100644 --- a/OsmAnd/res/values-ar/strings.xml +++ b/OsmAnd/res/values-ar/strings.xml @@ -4023,7 +4023,6 @@ قدم دراجة هوائية سيارة - خطأ، أعد التحقق من المعايير نسخ العنوان محرك التوجيه عبر الإنترنت محركات التوجيه عبر الإنترنت diff --git a/OsmAnd/res/values-cs/strings.xml b/OsmAnd/res/values-cs/strings.xml index 38af3fcceb..6d6b906b06 100644 --- a/OsmAnd/res/values-cs/strings.xml +++ b/OsmAnd/res/values-cs/strings.xml @@ -3960,7 +3960,6 @@ Chůze Kolo Auto - Chyba, zkontrolujte parametry Kopírovat adresu Online navigační služba Online navigační služby diff --git a/OsmAnd/res/values-de/strings.xml b/OsmAnd/res/values-de/strings.xml index e66df6bcd7..b216a47c7c 100644 --- a/OsmAnd/res/values-de/strings.xml +++ b/OsmAnd/res/values-de/strings.xml @@ -3962,7 +3962,6 @@ Zu Fuß Fahrrad Auto - Fehler, Parameter erneut prüfen Subtyp Leer lassen, wenn kein API-Schlüssel vorhanden Adresse kopieren diff --git a/OsmAnd/res/values-eo/strings.xml b/OsmAnd/res/values-eo/strings.xml index 391e96ba42..9671d14e5f 100644 --- a/OsmAnd/res/values-eo/strings.xml +++ b/OsmAnd/res/values-eo/strings.xml @@ -3960,7 +3960,6 @@ Piediranto Biciklo Aŭtomobilo - Eraro, rekontrolu parametrojn Kopii adreson Enreta navigilo Enretaj navigiloj diff --git a/OsmAnd/res/values-es-rAR/strings.xml b/OsmAnd/res/values-es-rAR/strings.xml index fd54cd500f..83700ce7cd 100644 --- a/OsmAnd/res/values-es-rAR/strings.xml +++ b/OsmAnd/res/values-es-rAR/strings.xml @@ -3963,7 +3963,6 @@ Peatón Bicicleta Automóvil - Error, vuelve a comprobar los parámetros Copiar dirección Motor de navegación en línea Motores de navegación en línea diff --git a/OsmAnd/res/values-eu/strings.xml b/OsmAnd/res/values-eu/strings.xml index ee2a825985..dced5cf049 100644 --- a/OsmAnd/res/values-eu/strings.xml +++ b/OsmAnd/res/values-eu/strings.xml @@ -3933,6 +3933,5 @@ Area honi dagokio: %1$s x %2$s Oinez Bizikleta Kotxea - Errorea, egiaztatu parametroak berriro Kopiatu helbidea \ No newline at end of file diff --git a/OsmAnd/res/values-fa/strings.xml b/OsmAnd/res/values-fa/strings.xml index 3cb0760eac..226a00cc5c 100644 --- a/OsmAnd/res/values-fa/strings.xml +++ b/OsmAnd/res/values-fa/strings.xml @@ -3953,5 +3953,4 @@ پا دوچرخه خودرو - خطا، پارامترها را بازبینی کنید \ No newline at end of file diff --git a/OsmAnd/res/values-fr/strings.xml b/OsmAnd/res/values-fr/strings.xml index 0b7dc82b1c..e5f41539c2 100644 --- a/OsmAnd/res/values-fr/strings.xml +++ b/OsmAnd/res/values-fr/strings.xml @@ -3947,7 +3947,6 @@ A pieds Vélo Automobile - Erreur, vérifiez les paramètres Ajouter un moteur de routage en ligne Modifier le moteur de routage en ligne L\'URL avec tous les paramètres sera de la forme : diff --git a/OsmAnd/res/values-gl/strings.xml b/OsmAnd/res/values-gl/strings.xml index 2e0c0053d3..5570577760 100644 --- a/OsmAnd/res/values-gl/strings.xml +++ b/OsmAnd/res/values-gl/strings.xml @@ -3968,7 +3968,6 @@ Lon %2$s Bicicleta Coche - Erro, verifica novamente os parámetros Copiar enderezo Horarios dos avisos por voz Motor de navegación en liña diff --git a/OsmAnd/res/values-hu/strings.xml b/OsmAnd/res/values-hu/strings.xml index d893dcf47e..64d40e287c 100644 --- a/OsmAnd/res/values-hu/strings.xml +++ b/OsmAnd/res/values-hu/strings.xml @@ -3954,7 +3954,6 @@ Hagyja üresen, ha nem Az összes paraméterrel rendelkező URL így néz ki: Útvonaltervezés kipróbálása - Hiba, ellenőrizze újra a paramétereket Cím másolása Online útvonaltervező Online útvonaltervezők diff --git a/OsmAnd/res/values-is/strings.xml b/OsmAnd/res/values-is/strings.xml index 3449458457..0b94e361f7 100644 --- a/OsmAnd/res/values-is/strings.xml +++ b/OsmAnd/res/values-is/strings.xml @@ -3965,6 +3965,5 @@ Gangandi Hjólandi Bíll - Villa, yfirfarðu breytur Afrita heimilisfang \ No newline at end of file diff --git a/OsmAnd/res/values-iw/strings.xml b/OsmAnd/res/values-iw/strings.xml index 44f2c8c034..7ca3bcdb8d 100644 --- a/OsmAnd/res/values-iw/strings.xml +++ b/OsmAnd/res/values-iw/strings.xml @@ -3963,7 +3963,6 @@ ברגל אופנוע מכונית - שגיאה, נא לבדוק את המשתנים מחדש העתקת כתובת מנוע ניווט מקוון מנועי ניווט מקוונים diff --git a/OsmAnd/res/values-ka/strings.xml b/OsmAnd/res/values-ka/strings.xml index 770916e3b3..330d43d2c4 100644 --- a/OsmAnd/res/values-ka/strings.xml +++ b/OsmAnd/res/values-ka/strings.xml @@ -2449,7 +2449,6 @@ OsmAndის გამოწერა დაპაუზებულია OsmAndის გამოწერა შეჩერებულია URL ყველა პარამეტრით აი ასეთი იქნება: - შეცდომა, გადაამოწმეთ პარამეტრები Mapillary-ის გამოსახულება Mapillary-ის ღილაკი ფერთა გამა diff --git a/OsmAnd/res/values-lt/strings.xml b/OsmAnd/res/values-lt/strings.xml index 65c711462f..09ee2a83fd 100644 --- a/OsmAnd/res/values-lt/strings.xml +++ b/OsmAnd/res/values-lt/strings.xml @@ -2770,7 +2770,6 @@ Tai yra puikus būdas paremti OsmAnd ir OSM, jei jie jums patinka. Patikrinti maršruto apskaičiavimą Dviratis Automobilis - Įvyko klaida, patikrinkite paametrus Kopijuoti adresą Folderiai Pasirinkite folderį diff --git a/OsmAnd/res/values-nl/strings.xml b/OsmAnd/res/values-nl/strings.xml index 04da6132ce..4420cd6987 100644 --- a/OsmAnd/res/values-nl/strings.xml +++ b/OsmAnd/res/values-nl/strings.xml @@ -3916,7 +3916,6 @@ Te voet Fiets Auto - Fout, controleer parameters opnieuw Kopieer adres Online routeplanningssysteem Online routeplanningssystemen diff --git a/OsmAnd/res/values-pl/strings.xml b/OsmAnd/res/values-pl/strings.xml index cf4852f488..840fa52189 100644 --- a/OsmAnd/res/values-pl/strings.xml +++ b/OsmAnd/res/values-pl/strings.xml @@ -3956,7 +3956,6 @@ Obliczanie trasy testowej Rower Samochód - Błąd, ponownie sprawdź parametry Skopiuj adres Jazda Stopa diff --git a/OsmAnd/res/values-pt-rBR/strings.xml b/OsmAnd/res/values-pt-rBR/strings.xml index da70b09ce8..52d578c035 100644 --- a/OsmAnd/res/values-pt-rBR/strings.xml +++ b/OsmAnd/res/values-pt-rBR/strings.xml @@ -3955,7 +3955,6 @@ Bicicleta Carro - Erro, verifique novamente os parâmetros Copiar endereço Motor de encaminhamento online Mecanismos de roteamento online diff --git a/OsmAnd/res/values-sc/strings.xml b/OsmAnd/res/values-sc/strings.xml index f5ff13cd8b..aa05e82d25 100644 --- a/OsmAnd/res/values-sc/strings.xml +++ b/OsmAnd/res/values-sc/strings.xml @@ -3957,7 +3957,6 @@ A pee Bitzicleta Màchina - Errore, torra a verificare sos paràmetros Còpia s\'indiritzu Motore de càrculu in lìnia Motores de càrculu in lìnia diff --git a/OsmAnd/res/values-sk/strings.xml b/OsmAnd/res/values-sk/strings.xml index 6d8fb45d93..81a9d73353 100644 --- a/OsmAnd/res/values-sk/strings.xml +++ b/OsmAnd/res/values-sk/strings.xml @@ -3955,7 +3955,6 @@ Chôdza Bicykel Auto - Chyba, skontrolujte parametre Kopírovať adresu Online navigačná služba Online navigačné služby diff --git a/OsmAnd/res/values-tr/strings.xml b/OsmAnd/res/values-tr/strings.xml index ee5e25124a..344183c102 100644 --- a/OsmAnd/res/values-tr/strings.xml +++ b/OsmAnd/res/values-tr/strings.xml @@ -3961,7 +3961,6 @@ Yürüme Bisiklet Araba - Hata, parametreleri tekrar gözden geçirin Adresi kopyala Çevrim içi yönlendirme motoru Çevrim içi yönlendirme motorları diff --git a/OsmAnd/res/values-uk/strings.xml b/OsmAnd/res/values-uk/strings.xml index 2738eda283..6910c9a902 100644 --- a/OsmAnd/res/values-uk/strings.xml +++ b/OsmAnd/res/values-uk/strings.xml @@ -3959,7 +3959,6 @@ Пішки Велосипед Автомобіль - Помилка, повторно перевірте параметри Копіювати адресу Мережний рушій маршрутизації Мережні рушії маршрутизації diff --git a/OsmAnd/res/values-zh-rTW/strings.xml b/OsmAnd/res/values-zh-rTW/strings.xml index 738ede85e2..0f19668c01 100644 --- a/OsmAnd/res/values-zh-rTW/strings.xml +++ b/OsmAnd/res/values-zh-rTW/strings.xml @@ -3953,7 +3953,6 @@ 步行 自行車 汽車 - 錯誤,重新檢查參數 複製地址 線上路線計算引擎 線上路線計算引擎 diff --git a/OsmAnd/res/values/strings.xml b/OsmAnd/res/values/strings.xml index bbb71f001a..8605ea2189 100644 --- a/OsmAnd/res/values/strings.xml +++ b/OsmAnd/res/values/strings.xml @@ -12,6 +12,23 @@ --> + The name is already exists + Server error: %1$s + MTB + Racing bike + Scooter + Truck + Small truck + HGV + Regular cycling + Road cycling + Mountain cycling + Electric cycling + Walking + Hiking + Wheelchair + Show track on map + Start recording Announcement time Announcement time of different voice prompts depends on prompt type, current navigation speed and default navigation speed. Time and distance intervals @@ -35,7 +52,6 @@ Online routing engines Online routing engine Copy address - Error, recheck parameters Car Bike Foot diff --git a/OsmAnd/src-gms/net/osmand/plus/LocationServiceHelperImpl.java b/OsmAnd/src-gms/net/osmand/plus/LocationServiceHelperImpl.java new file mode 100644 index 0000000000..e8182955ec --- /dev/null +++ b/OsmAnd/src-gms/net/osmand/plus/LocationServiceHelperImpl.java @@ -0,0 +1,158 @@ +package net.osmand.plus; + +import android.location.Location; +import android.os.Looper; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.google.android.gms.location.FusedLocationProviderClient; +import com.google.android.gms.location.LocationAvailability; +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.tasks.OnSuccessListener; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; + +import net.osmand.PlatformUtil; +import net.osmand.plus.helpers.DayNightHelper; +import net.osmand.plus.helpers.LocationServiceHelper; + +import org.apache.commons.logging.Log; + +import java.util.Collections; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class LocationServiceHelperImpl extends LocationServiceHelper { + + private static final Log LOG = PlatformUtil.getLog(DayNightHelper.class); + + private final OsmandApplication app; + + // FusedLocationProviderClient - Main class for receiving location updates. + private final FusedLocationProviderClient fusedLocationProviderClient; + + // LocationRequest - Requirements for the location updates, i.e., how often you should receive + // updates, the priority, etc. + private final LocationRequest fusedLocationRequest; + + // LocationCallback - Called when FusedLocationProviderClient has a new Location. + private final com.google.android.gms.location.LocationCallback fusedLocationCallback; + + private LocationCallback locationCallback; + + public LocationServiceHelperImpl(@NonNull OsmandApplication app) { + this.app = app; + + fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(app); + + fusedLocationRequest = new LocationRequest() + // Sets the desired interval for active location updates. This interval is inexact. You + // may not receive updates at all if no location sources are available, or you may + // receive them less frequently than requested. You may also receive updates more + // frequently than requested if other applications are requesting location at a more + // frequent interval. + // + // IMPORTANT NOTE: Apps running on Android 8.0 and higher devices (regardless of + // targetSdkVersion) may receive updates less frequently than this interval when the app + // is no longer in the foreground. + .setInterval(100) + + // Sets the fastest rate for active location updates. This interval is exact, and your + // application will never receive updates more frequently than this value. + .setFastestInterval(50) + + // Sets the maximum time when batched location updates are delivered. Updates may be + // delivered sooner than this interval. + .setMaxWaitTime(200) + + .setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY); + + fusedLocationCallback = new com.google.android.gms.location.LocationCallback() { + @Override + public void onLocationResult(LocationResult locationResult) { + LocationCallback locationCallback = LocationServiceHelperImpl.this.locationCallback; + if (locationCallback != null) { + Location location = locationResult != null ? locationResult.getLastLocation() : null; + net.osmand.Location l = convertLocation(location); + locationCallback.onLocationResult(l == null + ? Collections.emptyList() : Collections.singletonList(l)); + } + + } + + @Override + public void onLocationAvailability(LocationAvailability locationAvailability) { + LocationCallback locationCallback = LocationServiceHelperImpl.this.locationCallback; + if (locationAvailability != null && locationCallback != null) { + locationCallback.onLocationAvailability(locationAvailability.isLocationAvailable()); + } + } + }; + } + + @Override + public void requestLocationUpdates(@NonNull LocationCallback locationCallback) { + this.locationCallback = locationCallback; + // request location updates + try { + fusedLocationProviderClient.requestLocationUpdates( + fusedLocationRequest, fusedLocationCallback, Looper.myLooper()); + } catch (SecurityException e) { + LOG.debug("Location service permission not granted"); + throw e; + } catch (IllegalArgumentException e) { + LOG.debug("GPS location provider not available"); + throw e; + } + } + + @Override + public boolean isNetworkLocationUpdatesSupported() { + return false; + } + + @Override + public void requestNetworkLocationUpdates(@NonNull LocationCallback locationCallback) { + } + + @Override + public void removeLocationUpdates() { + // remove location updates + try { + fusedLocationProviderClient.removeLocationUpdates(fusedLocationCallback); + } catch (SecurityException e) { + LOG.debug("Location service permission not granted", e); + throw e; + } + } + + @Nullable + public net.osmand.Location getFirstTimeRunDefaultLocation() { + final net.osmand.Location[] location = {null}; + /* + try { + Task lastLocation = fusedLocationProviderClient.getLastLocation(); + lastLocation.addOnSuccessListener(new OnSuccessListener() { + @Override + public void onSuccess(Location loc) { + location[0] = convertLocation(loc); + } + }); + } catch (SecurityException e) { + LOG.debug("Location service permission not granted"); + } catch (IllegalArgumentException e) { + LOG.debug("GPS location provider not available"); + } + */ + return location[0]; + } + + @Nullable + private net.osmand.Location convertLocation(@Nullable Location location) { + return location == null ? null : OsmAndLocationProvider.convertLocation(location, app); + } +} diff --git a/OsmAnd/src-nogms/net/osmand/plus/LocationServiceHelperImpl.java b/OsmAnd/src-nogms/net/osmand/plus/LocationServiceHelperImpl.java new file mode 100644 index 0000000000..5f49607954 --- /dev/null +++ b/OsmAnd/src-nogms/net/osmand/plus/LocationServiceHelperImpl.java @@ -0,0 +1,188 @@ +package net.osmand.plus; + +import android.content.Context; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.PlatformUtil; +import net.osmand.plus.helpers.DayNightHelper; +import net.osmand.plus.helpers.LocationServiceHelper; + +import org.apache.commons.logging.Log; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import static android.content.Context.LOCATION_SERVICE; + +public class LocationServiceHelperImpl extends LocationServiceHelper implements LocationListener { + + private static final Log LOG = PlatformUtil.getLog(DayNightHelper.class); + + private final OsmandApplication app; + + private LocationCallback locationCallback; + private LocationCallback networkLocationCallback; + private final LinkedList networkListeners = new LinkedList<>(); + + // Working with location checkListeners + private class NetworkListener implements LocationListener { + + @Override + public void onLocationChanged(Location location) { + LocationCallback locationCallback = LocationServiceHelperImpl.this.networkLocationCallback; + if (locationCallback != null) { + net.osmand.Location l = convertLocation(location); + locationCallback.onLocationResult(l == null + ? Collections.emptyList() : Collections.singletonList(l)); + } + } + + @Override + public void onProviderDisabled(String provider) { + } + + @Override + public void onProviderEnabled(String provider) { + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + } + + public LocationServiceHelperImpl(@NonNull OsmandApplication app) { + this.app = app; + } + + @Override + public void requestLocationUpdates(@NonNull LocationCallback locationCallback) { + this.locationCallback = locationCallback; + // request location updates + LocationManager locationManager = (LocationManager) app.getSystemService(LOCATION_SERVICE); + try { + locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, this); + } catch (SecurityException e) { + LOG.debug("Location service permission not granted"); + throw e; + } catch (IllegalArgumentException e) { + LOG.debug("GPS location provider not available"); + throw e; + } + } + + @Override + public boolean isNetworkLocationUpdatesSupported() { + return true; + } + + @Override + public void requestNetworkLocationUpdates(@NonNull LocationCallback locationCallback) { + this.networkLocationCallback = locationCallback; + // request location updates + LocationManager locationManager = (LocationManager) app.getSystemService(LOCATION_SERVICE); + List providers = locationManager.getProviders(true); + for (String provider : providers) { + if (provider == null || provider.equals(LocationManager.GPS_PROVIDER)) { + continue; + } + try { + NetworkListener networkListener = new NetworkListener(); + locationManager.requestLocationUpdates(provider, 0, 0, networkListener); + networkListeners.add(networkListener); + } catch (SecurityException e) { + LOG.debug(provider + " location service permission not granted"); + } catch (IllegalArgumentException e) { + LOG.debug(provider + " location provider not available"); + } + } + } + + @Override + public void removeLocationUpdates() { + // remove location updates + LocationManager locationManager = (LocationManager) app.getSystemService(LOCATION_SERVICE); + try { + locationManager.removeUpdates(this); + } catch (SecurityException e) { + LOG.debug("Location service permission not granted", e); + throw e; + } finally { + while (!networkListeners.isEmpty()) { + LocationListener listener = networkListeners.poll(); + if (listener != null) { + locationManager.removeUpdates(listener); + } + } + } + } + + @Nullable + public net.osmand.Location getFirstTimeRunDefaultLocation() { + LocationManager locationManager = (LocationManager) app.getSystemService(Context.LOCATION_SERVICE); + List providers = new ArrayList<>(locationManager.getProviders(true)); + // note, passive provider is from API_LEVEL 8 but it is a constant, we can check for it. + // constant should not be changed in future + int passiveFirst = providers.indexOf(LocationManager.PASSIVE_PROVIDER); + // put passive provider to first place + if (passiveFirst > -1) { + providers.add(0, providers.remove(passiveFirst)); + } + // find location + for (String provider : providers) { + try { + net.osmand.Location location = convertLocation(locationManager.getLastKnownLocation(provider)); + if (location != null) { + return location; + } + } catch (SecurityException e) { + // location service permission not granted + } catch (IllegalArgumentException e) { + // location provider not available + } + } + return null; + } + + @Nullable + private net.osmand.Location convertLocation(@Nullable Location location) { + return location == null ? null : OsmAndLocationProvider.convertLocation(location, app); + } + + @Override + public void onLocationChanged(Location location) { + LocationCallback locationCallback = this.locationCallback; + if (locationCallback != null) { + net.osmand.Location l = convertLocation(location); + locationCallback.onLocationResult(l == null + ? Collections.emptyList() : Collections.singletonList(l)); + } + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + } + + @Override + public void onProviderEnabled(String provider) { + LocationCallback locationCallback = this.locationCallback; + if (locationCallback != null) { + locationCallback.onLocationAvailability(true); + } + } + + @Override + public void onProviderDisabled(String provider) { + LocationCallback locationCallback = this.locationCallback; + if (locationCallback != null) { + locationCallback.onLocationAvailability(false); + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/NavigationService.java b/OsmAnd/src/net/osmand/plus/NavigationService.java index 3a824f937e..16f1afddcc 100644 --- a/OsmAnd/src/net/osmand/plus/NavigationService.java +++ b/OsmAnd/src/net/osmand/plus/NavigationService.java @@ -4,20 +4,24 @@ import android.app.Notification; import android.app.Service; import android.content.Context; import android.content.Intent; -import android.location.Location; -import android.location.LocationListener; import android.location.LocationManager; import android.os.Binder; -import android.os.Bundle; import android.os.IBinder; import android.util.Log; import android.widget.Toast; +import androidx.annotation.NonNull; + +import net.osmand.Location; import net.osmand.PlatformUtil; +import net.osmand.plus.helpers.LocationServiceHelper; +import net.osmand.plus.helpers.LocationServiceHelper.LocationCallback; import net.osmand.plus.notifications.OsmandNotification; import net.osmand.plus.settings.backend.OsmandSettings; -public class NavigationService extends Service implements LocationListener { +import java.util.List; + +public class NavigationService extends Service { public static class NavigationServiceBinder extends Binder { } @@ -29,11 +33,11 @@ public class NavigationService extends Service implements LocationListener { private final NavigationServiceBinder binder = new NavigationServiceBinder(); - private String serviceOffProvider; private OsmandSettings settings; protected int usedBy = 0; private OsmAndLocationProvider locationProvider; + private LocationServiceHelper locationServiceHelper; @Override public IBinder onBind(Intent intent) { @@ -72,21 +76,37 @@ public class NavigationService extends Service implements LocationListener { settings = app.getSettings(); usedBy = intent.getIntExtra(USAGE_INTENT, 0); - // use only gps provider - serviceOffProvider = LocationManager.GPS_PROVIDER; locationProvider = app.getLocationProvider(); + locationServiceHelper = app.createLocationServiceHelper(); app.setNavigationService(this); // request location updates - LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); try { - locationManager.requestLocationUpdates(serviceOffProvider, 0, 0, NavigationService.this); + locationServiceHelper.requestLocationUpdates(new LocationCallback() { + @Override + public void onLocationResult(@NonNull List locations) { + if (!locations.isEmpty()) { + Location location = locations.get(locations.size() - 1); + if (!settings.MAP_ACTIVITY_ENABLED.get()) { + locationProvider.setLocationFromService(location); + } + } + } + + @Override + public void onLocationAvailability(boolean locationAvailable) { + if (!locationAvailable) { + OsmandApplication app = (OsmandApplication) getApplication(); + if (app != null) { + app.showToastMessage(getString(R.string.off_router_service_no_gps_available)); + } + } + } + }); } catch (SecurityException e) { Toast.makeText(this, R.string.no_location_permission, Toast.LENGTH_LONG).show(); - Log.d(PlatformUtil.TAG, "Location service permission not granted"); //$NON-NLS-1$ } catch (IllegalArgumentException e) { Toast.makeText(this, R.string.gps_not_available, Toast.LENGTH_LONG).show(); - Log.d(PlatformUtil.TAG, "GPS location provider not available"); //$NON-NLS-1$ } // registering icon at top level @@ -117,11 +137,10 @@ public class NavigationService extends Service implements LocationListener { app.setNavigationService(null); usedBy = 0; // remove updates - LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE); try { - locationManager.removeUpdates(this); + locationServiceHelper.removeLocationUpdates(); } catch (SecurityException e) { - Log.d(PlatformUtil.TAG, "Location service permission not granted"); //$NON-NLS-1$ + // Location service permission not granted } // remove notification stopForeground(Boolean.TRUE); @@ -134,29 +153,6 @@ public class NavigationService extends Service implements LocationListener { }, 500); } - @Override - public void onLocationChanged(Location l) { - if (l != null && !settings.MAP_ACTIVITY_ENABLED.get()) { - net.osmand.Location location = OsmAndLocationProvider.convertLocation(l, (OsmandApplication) getApplication()); - locationProvider.setLocationFromService(location); - } - } - - @Override - public void onProviderDisabled(String provider) { - Toast.makeText(this, getString(R.string.off_router_service_no_gps_available), Toast.LENGTH_LONG).show(); - } - - - @Override - public void onProviderEnabled(String provider) { - } - - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - } - @Override public void onTaskRemoved(Intent rootIntent) { OsmandApplication app = ((OsmandApplication) getApplication()); diff --git a/OsmAnd/src/net/osmand/plus/OsmAndLocationProvider.java b/OsmAnd/src/net/osmand/plus/OsmAndLocationProvider.java index a19ebd3023..ee875fbb17 100644 --- a/OsmAnd/src/net/osmand/plus/OsmAndLocationProvider.java +++ b/OsmAnd/src/net/osmand/plus/OsmAndLocationProvider.java @@ -1,6 +1,7 @@ package net.osmand.plus; import android.Manifest; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.Context; import android.content.DialogInterface; @@ -16,7 +17,6 @@ import android.location.GpsSatellite; import android.location.GpsStatus; import android.location.GpsStatus.Listener; import android.location.Location; -import android.location.LocationListener; import android.location.LocationManager; import android.os.Build; import android.os.Build.VERSION; @@ -25,6 +25,7 @@ import android.os.Bundle; import android.provider.Settings; import android.util.Log; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.app.ActivityCompat; @@ -38,8 +39,9 @@ import net.osmand.binary.RouteDataObject; import net.osmand.data.LatLon; import net.osmand.data.QuadPoint; import net.osmand.plus.TargetPointsHelper.TargetPoint; -import net.osmand.plus.routing.RoutingHelper; +import net.osmand.plus.helpers.LocationServiceHelper; import net.osmand.plus.routing.RouteSegmentSearchResult; +import net.osmand.plus.routing.RoutingHelper; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.router.RouteSegmentResult; @@ -47,8 +49,6 @@ import net.osmand.util.MapUtils; import java.util.ArrayList; import java.util.Arrays; -import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; @@ -75,8 +75,6 @@ public class OsmAndLocationProvider implements SensorEventListener { private static final float ACCURACY_FOR_GPX_AND_ROUTING = 50; - private static final int GPS_TIMEOUT_REQUEST = 0; - private static final int GPS_DIST_REQUEST = 0; private static final int NOT_SWITCH_TO_NETWORK_WHEN_GPS_LOST_MS = 12000; private static final long LOCATION_TIMEOUT_TO_BE_STALE = 1000 * 60 * 2; // 2 minutes @@ -85,9 +83,8 @@ public class OsmAndLocationProvider implements SensorEventListener { private static final long AGPS_TO_REDOWNLOAD = 16 * 60 * 60 * 1000; // 16 hours private static final int REQUESTS_BEFORE_CHECK_LOCATION = 100; - private AtomicInteger locationRequestsCounter = new AtomicInteger(); - private AtomicInteger staleLocationRequestsCounter = new AtomicInteger(); - + private final AtomicInteger locationRequestsCounter = new AtomicInteger(); + private final AtomicInteger staleLocationRequestsCounter = new AtomicInteger(); private long lastTimeGPSLocationFixed = 0; @@ -121,23 +118,23 @@ public class OsmAndLocationProvider implements SensorEventListener { // Current screen orientation private int currentScreenOrientation; - private OsmandApplication app; + private final OsmandApplication app; - private NavigationInfo navigationInfo; - private CurrentPositionHelper currentPositionHelper; - private OsmAndLocationSimulation locationSimulation; + private final NavigationInfo navigationInfo; + private final CurrentPositionHelper currentPositionHelper; + private final OsmAndLocationSimulation locationSimulation; + private final LocationServiceHelper locationServiceHelper; private net.osmand.Location location = null; private GPSInfo gpsInfo = new GPSInfo(); - private List locationListeners = new ArrayList(); - private List compassListeners = new ArrayList(); + private List locationListeners = new ArrayList<>(); + private List compassListeners = new ArrayList<>(); private Object gpsStatusListener; private float[] mRotationM = new float[9]; - - public class SimulationProvider { + public static class SimulationProvider { private int currentRoad; private int currentSegment; private QuadPoint currentPoint; @@ -227,6 +224,7 @@ public class OsmAndLocationProvider implements SensorEventListener { navigationInfo = new NavigationInfo(app); currentPositionHelper = new CurrentPositionHelper(app); locationSimulation = new OsmAndLocationSimulation(app, this); + locationServiceHelper = app.createLocationServiceHelper(); addLocationListener(navigationInfo); addCompassListener(navigationInfo); } @@ -235,7 +233,7 @@ public class OsmAndLocationProvider implements SensorEventListener { final LocationManager service = (LocationManager) app.getSystemService(Context.LOCATION_SERVICE); if (app.getSettings().isInternetConnectionAvailable()) { if (System.currentTimeMillis() - app.getSettings().AGPS_DATA_LAST_TIME_DOWNLOADED.get() > AGPS_TO_REDOWNLOAD) { - //force an updated check for internet connectivity here before destroying A-GPS-data + // force an updated check for internet connectivity here before destroying A-GPS-data if (app.getSettings().isInternetConnectionAvailable(true)) { redownloadAGPS(); } @@ -244,27 +242,34 @@ public class OsmAndLocationProvider implements SensorEventListener { if (isLocationPermissionAvailable(app)) { registerGpsStatusListener(service); try { - service.requestLocationUpdates(LocationManager.GPS_PROVIDER, GPS_TIMEOUT_REQUEST, GPS_DIST_REQUEST, gpsListener); + locationServiceHelper.requestLocationUpdates(new LocationServiceHelper.LocationCallback() { + @Override + public void onLocationResult(@NonNull List locations) { + net.osmand.Location location = null; + if (!locations.isEmpty()) { + location = locations.get(locations.size() - 1); + lastTimeGPSLocationFixed = System.currentTimeMillis(); + } + if (!locationSimulation.isRouteAnimating()) { + setLocation(location); + } + } + }); + } catch (SecurityException e) { + // Location service permission not granted } catch (IllegalArgumentException e) { - Log.d(PlatformUtil.TAG, "GPS location provider not available"); //$NON-NLS-1$ + // GPS location provider not available } // try to always ask for network provide : it is faster way to find location - - List providers = service.getProviders(true); - if (providers == null) { - return; - } - for (String provider : providers) { - if (provider == null || provider.equals(LocationManager.GPS_PROVIDER)) { - continue; - } - try { - NetworkListener networkListener = new NetworkListener(); - service.requestLocationUpdates(provider, GPS_TIMEOUT_REQUEST, GPS_DIST_REQUEST, networkListener); - networkListeners.add(networkListener); - } catch (IllegalArgumentException e) { - Log.d(PlatformUtil.TAG, provider + " location provider not available"); //$NON-NLS-1$ - } + if (locationServiceHelper.isNetworkLocationUpdatesSupported()) { + locationServiceHelper.requestNetworkLocationUpdates(new LocationServiceHelper.LocationCallback() { + @Override + public void onLocationResult(@NonNull List locations) { + if (!locations.isEmpty() && !useOnlyGPS() && !locationSimulation.isRouteAnimating()) { + setLocation(locations.get(locations.size() - 1)); + } + } + }); } } } @@ -284,8 +289,9 @@ public class OsmAndLocationProvider implements SensorEventListener { } } - private void registerGpsStatusListener(final LocationManager service) { - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + @SuppressLint("MissingPermission") + private void registerGpsStatusListener(@NonNull final LocationManager service) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { gpsStatusListener = new GnssStatus.Callback() { @Override @@ -320,7 +326,7 @@ public class OsmAndLocationProvider implements SensorEventListener { updateLocation(location); } }; - service.registerGnssStatusCallback((GnssStatus.Callback) gpsStatusListener); + service.registerGnssStatusCallback((GnssStatus.Callback) gpsStatusListener, null); } else { gpsStatusListener = new Listener() { private GpsStatus gpsStatus; @@ -335,14 +341,12 @@ public class OsmAndLocationProvider implements SensorEventListener { } } - private void updateGPSInfo(GpsStatus s) { + private void updateGPSInfo(@Nullable GpsStatus s) { boolean fixed = false; int n = 0; int u = 0; if (s != null) { - Iterator iterator = s.getSatellites().iterator(); - while (iterator.hasNext()) { - GpsSatellite g = iterator.next(); + for (GpsSatellite g : s.getSatellites()) { n++; if (g.usedInFix()) { u++; @@ -354,7 +358,8 @@ public class OsmAndLocationProvider implements SensorEventListener { gpsInfo.foundSatellites = n; gpsInfo.usedSatellites = u; } - + + @NonNull public GPSInfo getGPSInfo(){ return gpsInfo; } @@ -363,51 +368,29 @@ public class OsmAndLocationProvider implements SensorEventListener { currentScreenOrientation = orientation; } - public void addLocationListener(OsmAndLocationListener listener){ - if(!locationListeners.contains(listener)) { + public void addLocationListener(@NonNull OsmAndLocationListener listener) { + if (!locationListeners.contains(listener)) { locationListeners.add(listener); } } - public void removeLocationListener(OsmAndLocationListener listener){ + public void removeLocationListener(@NonNull OsmAndLocationListener listener) { locationListeners.remove(listener); } - public void addCompassListener(OsmAndCompassListener listener){ - if(!compassListeners.contains(listener)) { + public void addCompassListener(@NonNull OsmAndCompassListener listener) { + if (!compassListeners.contains(listener)) { compassListeners.add(listener); } } - public void removeCompassListener(OsmAndCompassListener listener){ + public void removeCompassListener(@NonNull OsmAndCompassListener listener) { compassListeners.remove(listener); } + @Nullable public net.osmand.Location getFirstTimeRunDefaultLocation() { - if (!isLocationPermissionAvailable(app)) { - return null; - } - LocationManager service = (LocationManager) app.getSystemService(Context.LOCATION_SERVICE); - List ps = service.getProviders(true); - if(ps == null) { - return null; - } - List providers = new ArrayList(ps); - // note, passive provider is from API_LEVEL 8 but it is a constant, we can check for it. - // constant should not be changed in future - int passiveFirst = providers.indexOf("passive"); // LocationManager.PASSIVE_PROVIDER - // put passive provider to first place - if (passiveFirst > -1) { - providers.add(0, providers.remove(passiveFirst)); - } - // find location - for (String provider : providers) { - net.osmand.Location location = convertLocation(service.getLastKnownLocation(provider), app); - if (location != null) { - return location; - } - } - return null; + return isLocationPermissionAvailable(app) ? locationServiceHelper.getFirstTimeRunDefaultLocation() : null; } public synchronized void registerOrUnregisterCompassListener(boolean register) { @@ -466,10 +449,7 @@ public class OsmAndLocationProvider implements SensorEventListener { } private boolean isRunningOnEmulator() { - if (Build.DEVICE.equals("generic")) { //$NON-NLS-1$ - return true; - } - return false; + return Build.DEVICE.equals("generic"); } @Override @@ -599,78 +579,25 @@ public class OsmAndLocationProvider implements SensorEventListener { return MapUtils.unifyRotationTo360((float) (Math.atan2(sinA, cosA) * 180 / Math.PI)); } - private void updateLocation(net.osmand.Location loc) { for (OsmAndLocationListener l : locationListeners) { l.updateLocation(loc); } } - - - private LocationListener gpsListener = new LocationListener() { - @Override - public void onLocationChanged(Location location) { - if (location != null) { - // lastTimeGPSLocationFixed = location.getTime(); - lastTimeGPSLocationFixed = System.currentTimeMillis(); - } - if(!locationSimulation.isRouteAnimating()) { - setLocation(convertLocation(location, app)); - } - } - - @Override - public void onProviderDisabled(String provider) { - } - - @Override - public void onProviderEnabled(String provider) { - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - } - }; - private LinkedList networkListeners = new LinkedList(); - private boolean useOnlyGPS() { - if(app.getRoutingHelper().isFollowingMode()) { + if (app.getRoutingHelper().isFollowingMode()) { return true; } - if((System.currentTimeMillis() - lastTimeGPSLocationFixed) < NOT_SWITCH_TO_NETWORK_WHEN_GPS_LOST_MS) { + if ((System.currentTimeMillis() - lastTimeGPSLocationFixed) < NOT_SWITCH_TO_NETWORK_WHEN_GPS_LOST_MS) { return true; } - if(isRunningOnEmulator()) { + if (isRunningOnEmulator()) { return true; } return false; } - // Working with location checkListeners - private class NetworkListener implements LocationListener { - - @Override - public void onLocationChanged(Location location) { - if (!useOnlyGPS() && !locationSimulation.isRouteAnimating()) { - setLocation(convertLocation(location, app)); - } - } - - @Override - public void onProviderDisabled(String provider) { - } - - @Override - public void onProviderEnabled(String provider) { - } - - @Override - public void onStatusChanged(String provider, int status, Bundle extras) { - } - - }; - private void stopLocationRequests() { LocationManager service = (LocationManager) app.getSystemService(Context.LOCATION_SERVICE); if (gpsStatusListener != null) { @@ -680,9 +607,10 @@ public class OsmAndLocationProvider implements SensorEventListener { service.removeGpsStatusListener((Listener) gpsStatusListener); } } - service.removeUpdates(gpsListener); - while (!networkListeners.isEmpty()) { - service.removeUpdates(networkListeners.poll()); + try { + locationServiceHelper.removeLocationUpdates(); + } catch (SecurityException e) { + // Location service permission not granted } } @@ -963,7 +891,7 @@ public class OsmAndLocationProvider implements SensorEventListener { } public static boolean isNotSimulatedLocation(net.osmand.Location l) { - if(l != null) { + if (l != null) { return !SIMULATED_PROVIDER.equals(l.getProvider()); } return true; @@ -984,7 +912,7 @@ public class OsmAndLocationProvider implements SensorEventListener { networkenabled = lm.isProviderEnabled(LocationManager.NETWORK_PROVIDER); } catch(Exception ex) {} - if(!gpsenabled && !networkenabled) { + if (!gpsenabled && !networkenabled) { // notify user AlertDialog.Builder dialog = new AlertDialog.Builder(context); dialog.setMessage(context.getResources().getString(R.string.gps_network_not_enabled)); @@ -1003,11 +931,8 @@ public class OsmAndLocationProvider implements SensorEventListener { } public static boolean isLocationPermissionAvailable(Context context) { - if (ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) - != PackageManager.PERMISSION_GRANTED) { - return false; - } - return true; + return ActivityCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) + == PackageManager.PERMISSION_GRANTED; } public static void requestFineLocationPermissionIfNeeded(Activity activity) { diff --git a/OsmAnd/src/net/osmand/plus/OsmandApplication.java b/OsmAnd/src/net/osmand/plus/OsmandApplication.java index 8deb9c81a8..842a36237e 100644 --- a/OsmAnd/src/net/osmand/plus/OsmandApplication.java +++ b/OsmAnd/src/net/osmand/plus/OsmandApplication.java @@ -57,6 +57,7 @@ import net.osmand.plus.download.DownloadService; import net.osmand.plus.download.IndexItem; import net.osmand.plus.helpers.AvoidSpecificRoads; import net.osmand.plus.helpers.DayNightHelper; +import net.osmand.plus.helpers.LocationServiceHelper; import net.osmand.plus.helpers.LockHelper; import net.osmand.plus.helpers.WaypointHelper; import net.osmand.plus.helpers.enums.DrivingRegion; @@ -300,7 +301,11 @@ public class OsmandApplication extends MultiDexApplication { public QuickActionRegistry getQuickActionRegistry() { return quickActionRegistry; } - + + public LocationServiceHelper createLocationServiceHelper() { + return new LocationServiceHelperImpl(this); + } + public void setAppCustomization(OsmAndAppCustomization appCustomization) { this.appCustomization = appCustomization; this.appCustomization.setup(this); diff --git a/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java b/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java index 63d82ecc26..472899862b 100644 --- a/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java +++ b/OsmAnd/src/net/osmand/plus/TargetPointsHelper.java @@ -445,7 +445,7 @@ public class TargetPointsHelper { Location lastKnownLocation = ctx.getLocationProvider().getLastKnownLocation(); LatLon latLon = lastKnownLocation != null ? new LatLon(lastKnownLocation.getLatitude(), lastKnownLocation.getLongitude()) : null; - RoutingHelperUtils.checkAndUpdateStartLocation(ctx, latLon); + RoutingHelperUtils.checkAndUpdateStartLocation(ctx, latLon, false); setMyLocationPoint(latLon, false, null); } } diff --git a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java index 10fa1a066b..007280144e 100644 --- a/OsmAnd/src/net/osmand/plus/activities/MapActivity.java +++ b/OsmAnd/src/net/osmand/plus/activities/MapActivity.java @@ -118,6 +118,7 @@ import net.osmand.plus.measurementtool.LoginBottomSheetFragment; import net.osmand.plus.measurementtool.MeasurementEditingContext; import net.osmand.plus.measurementtool.MeasurementToolFragment; import net.osmand.plus.measurementtool.SnapTrackWarningFragment; +import net.osmand.plus.monitoring.TripRecordingBottomSheet; import net.osmand.plus.render.RendererRegistry; import net.osmand.plus.resources.ResourceManager; import net.osmand.plus.routepreparationmenu.ChooseRouteFragment; @@ -1408,6 +1409,7 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven updateMapSettings(); app.getPoiFilters().loadSelectedPoiFilters(); mapViewTrackingUtilities.updateSettings(); + mapViewTrackingUtilities.resetDrivingRegionUpdate(); //app.getRoutingHelper().setAppMode(settings.getApplicationMode()); if (mapLayers.getMapInfoLayer() != null) { mapLayers.getMapInfoLayer().recreateControls(); @@ -2204,6 +2206,10 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven return getFragment(MeasurementToolFragment.TAG); } + public TripRecordingBottomSheet getTripRecordingBottomSheet() { + return getFragment(TripRecordingBottomSheet.TAG); + } + public ChooseRouteFragment getChooseRouteFragment() { return getFragment(ChooseRouteFragment.TAG); } @@ -2220,7 +2226,6 @@ public class MapActivity extends OsmandActionBarActivity implements DownloadEven return getFragment(SnapTrackWarningFragment.TAG); } - @NonNull public TrackMenuFragment getTrackMenuFragment() { return getFragment(TrackMenuFragment.TAG); } diff --git a/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java b/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java index fa66a5c91a..14a6623eae 100644 --- a/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java +++ b/OsmAnd/src/net/osmand/plus/base/MapViewTrackingUtilities.java @@ -13,8 +13,6 @@ import net.osmand.data.LatLon; import net.osmand.data.RotatedTileBox; import net.osmand.map.IMapLocationListener; import net.osmand.map.WorldRegion; -import net.osmand.plus.mapmarkers.MapMarker; -import net.osmand.plus.mapmarkers.MapMarkersHelper.MapMarkerChangedListener; import net.osmand.plus.OsmAndConstants; import net.osmand.plus.OsmAndLocationProvider; import net.osmand.plus.OsmAndLocationProvider.OsmAndCompassListener; @@ -22,10 +20,14 @@ import net.osmand.plus.OsmAndLocationProvider.OsmAndLocationListener; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; import net.osmand.plus.dashboard.DashboardOnMap; +import net.osmand.plus.helpers.enums.DrivingRegion; import net.osmand.plus.mapcontextmenu.MapContextMenu; import net.osmand.plus.mapcontextmenu.other.TrackDetailsMenu; +import net.osmand.plus.mapmarkers.MapMarker; +import net.osmand.plus.mapmarkers.MapMarkersHelper.MapMarkerChangedListener; import net.osmand.plus.routing.RoutingHelper; import net.osmand.plus.routing.RoutingHelperUtils; +import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.views.AnimateDraggingMapThread; import net.osmand.plus.views.OsmandMapTileView; @@ -176,7 +178,7 @@ public class MapViewTrackingUtilities implements OsmAndLocationListener, IMapLoc locationProvider = location.getProvider(); if (settings.DRIVING_REGION_AUTOMATIC.get() && !drivingRegionUpdated && !app.isApplicationInitializing()) { drivingRegionUpdated = true; - RoutingHelperUtils.checkAndUpdateStartLocation(app, location); + RoutingHelperUtils.checkAndUpdateStartLocation(app, location, true); } } if (mapView != null) { @@ -489,7 +491,15 @@ public class MapViewTrackingUtilities implements OsmAndLocationListener, IMapLoc @Override protected void onPostExecute(WorldRegion worldRegion) { if (worldRegion != null) { + DrivingRegion oldRegion = app.getSettings().DRIVING_REGION.get(); + app.setupDrivingRegion(worldRegion); + + DrivingRegion currentRegion = app.getSettings().DRIVING_REGION.get(); + if (oldRegion.leftHandDriving != currentRegion.leftHandDriving) { + ApplicationMode mode = app.getRoutingHelper().getAppMode(); + app.getRoutingHelper().onSettingsChanged(mode, true); + } } } } diff --git a/OsmAnd/src/net/osmand/plus/helpers/DayNightHelper.java b/OsmAnd/src/net/osmand/plus/helpers/DayNightHelper.java index d3ebe9d6ae..20820d379e 100644 --- a/OsmAnd/src/net/osmand/plus/helpers/DayNightHelper.java +++ b/OsmAnd/src/net/osmand/plus/helpers/DayNightHelper.java @@ -43,10 +43,10 @@ public class DayNightHelper implements SensorEventListener { private static final Log log = PlatformUtil.getLog(DayNightHelper.class); - private final OsmandApplication osmandApplication; + private final OsmandApplication app; - public DayNightHelper(OsmandApplication osmandApplication) { - this.osmandApplication = osmandApplication; + public DayNightHelper(OsmandApplication app) { + this.app = app; } private DayNightHelper listener; @@ -56,11 +56,11 @@ public class DayNightHelper implements SensorEventListener { private StateChangedListener sensorStateListener; public boolean isNightModeForMapControls() { - return isNightModeForMapControlsForProfile(osmandApplication.getSettings().APPLICATION_MODE.get()); + return isNightModeForMapControlsForProfile(app.getSettings().APPLICATION_MODE.get()); } public boolean isNightModeForMapControlsForProfile(ApplicationMode mode) { - if (osmandApplication.getSettings().isLightContentForMode(mode)) { + if (app.getSettings().isLightContentForMode(mode)) { return isNightModeForProfile(mode); } else { return true; @@ -72,11 +72,11 @@ public class DayNightHelper implements SensorEventListener { * @return true if day is supposed to be */ public boolean isNightMode() { - return isNightModeForProfile(osmandApplication.getSettings().APPLICATION_MODE.get()); + return isNightModeForProfile(app.getSettings().APPLICATION_MODE.get()); } public boolean isNightModeForProfile(ApplicationMode mode) { - DayNightMode dayNightMode = osmandApplication.getSettings().DAYNIGHT_MODE.getModeValue(mode); + DayNightMode dayNightMode = app.getSettings().DAYNIGHT_MODE.getModeValue(mode); if (dayNightMode.isDay()) { return false; } else if (dayNightMode.isNight()) { @@ -108,24 +108,23 @@ public class DayNightHelper implements SensorEventListener { } public SunriseSunset getSunriseSunset() { - Location lastKnownLocation = osmandApplication.getLocationProvider().getLastKnownLocation(); - if(lastKnownLocation == null) { - lastKnownLocation = osmandApplication.getLocationProvider().getFirstTimeRunDefaultLocation(); + Location lastKnownLocation = app.getLocationProvider().getLastKnownLocation(); + if (lastKnownLocation == null) { + lastKnownLocation = app.getLocationProvider().getFirstTimeRunDefaultLocation(); } if (lastKnownLocation == null) { return null; } double longitude = lastKnownLocation.getLongitude(); Date actualTime = new Date(); - SunriseSunset daynightSwitch = new SunriseSunset(lastKnownLocation.getLatitude(), + return new SunriseSunset(lastKnownLocation.getLatitude(), longitude < 0 ? 360 + longitude : longitude, actualTime, TimeZone.getDefault()); - return daynightSwitch; } public void stopSensorIfNeeded() { if (listener != null) { - SensorManager mSensorManager = (SensorManager) osmandApplication + SensorManager mSensorManager = (SensorManager) app .getSystemService(Context.SENSOR_SERVICE); Sensor mLight = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); mSensorManager.unregisterListener(listener, mLight); @@ -135,9 +134,9 @@ public class DayNightHelper implements SensorEventListener { public void startSensorIfNeeded(StateChangedListener sensorStateListener) { this.sensorStateListener = sensorStateListener; - DayNightMode dayNightMode = osmandApplication.getSettings().DAYNIGHT_MODE.get(); + DayNightMode dayNightMode = app.getSettings().DAYNIGHT_MODE.get(); if (listener == null && dayNightMode.isSensor()) { - SensorManager mSensorManager = (SensorManager) osmandApplication.getSystemService(Context.SENSOR_SERVICE); + SensorManager mSensorManager = (SensorManager) app.getSystemService(Context.SENSOR_SERVICE); Sensor mLight = mSensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); List list = mSensorManager.getSensorList(Sensor.TYPE_LIGHT); log.info("Light sensors:" + list.size()); //$NON-NLS-1$ diff --git a/OsmAnd/src/net/osmand/plus/helpers/LocationServiceHelper.java b/OsmAnd/src/net/osmand/plus/helpers/LocationServiceHelper.java new file mode 100644 index 0000000000..94602e98fc --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/helpers/LocationServiceHelper.java @@ -0,0 +1,29 @@ +package net.osmand.plus.helpers; + +import androidx.annotation.NonNull; + +import net.osmand.Location; + +import java.util.List; + +public abstract class LocationServiceHelper { + + public static abstract class LocationCallback { + + public void onLocationResult(@NonNull List locations) { + } + + public void onLocationAvailability(boolean locationAvailable) { + } + } + + public abstract void requestLocationUpdates(@NonNull LocationCallback locationCallback); + + public abstract boolean isNetworkLocationUpdatesSupported(); + + public abstract void requestNetworkLocationUpdates(@NonNull LocationCallback locationCallback); + + public abstract void removeLocationUpdates(); + + public abstract Location getFirstTimeRunDefaultLocation(); +} diff --git a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java index 16c60911df..42bae2ab62 100644 --- a/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java +++ b/OsmAnd/src/net/osmand/plus/monitoring/OsmandMonitoringPlugin.java @@ -100,12 +100,12 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { public void updateLocation(Location location) { liveMonitoringHelper.updateLocation(location); } - + @Override public int getLogoResourceId() { return R.drawable.ic_action_gps_info; } - + @Override public Drawable getAssetResourceImage() { return app.getUIUtilities().getIcon(R.drawable.trip_recording); @@ -140,7 +140,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { private void registerWidget(MapActivity activity) { MapInfoLayer layer = activity.getMapLayers().getMapInfoLayer(); monitoringControl = createMonitoringControl(activity); - + layer.registerSideWidget(monitoringControl, R.drawable.ic_action_play_dark, R.string.map_widget_monitoring, "monitoring", false, 30); layer.recreateControls(); @@ -161,7 +161,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { } } } - + public static final int[] SECONDS = new int[] {0, 1, 2, 3, 5, 10, 15, 20, 30, 60, 90}; public static final int[] MINUTES = new int[] {2, 3, 5}; public static final int[] MAX_INTERVAL_TO_SEND_MINUTES = new int[] {1, 2, 5, 10, 15, 20, 30, 60, 90, 2 * 60, 3 * 60, 4 * 60, 6 * 60, 12 * 60, 24 * 60}; @@ -287,7 +287,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { controlDialog(map, true); } - + }); return monitoringControl; } @@ -422,7 +422,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { public void saveCurrentTrack() { saveCurrentTrack(null, null); } - + public void saveCurrentTrack(@Nullable final Runnable onComplete) { saveCurrentTrack(onComplete, null); } @@ -464,7 +464,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { SaveGPXBottomSheetFragment.showInstance(((FragmentActivity) a).getSupportFragmentManager(), result.getFilenames()); } } - + if (onComplete != null) { onComplete.run(); } @@ -505,15 +505,9 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { }; if (choice.value || map == null) { runnable.run(); - } else { - showIntervalChooseDialog(map, app.getString(R.string.save_track_interval_globally) + " : %s", - app.getString(R.string.save_track_to_gpx_globally), SECONDS, MINUTES, choice, vs, showTrackSelection, - new OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - runnable.run(); - } - }); + } else if (map instanceof FragmentActivity) { + FragmentActivity activity = (FragmentActivity) map; + TripRecordingBottomSheet.showInstance(activity.getSupportFragmentManager()); } } @@ -588,7 +582,7 @@ public class OsmandMonitoringPlugin extends OsmandPlugin { tv.setText(String.format(patternMsg, s)); } }); - + for (int i = 0; i < secondsLength + minutesLength - 1; i++) { if (i < secondsLength) { if (v.value <= seconds[i] * 1000) { diff --git a/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java b/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java new file mode 100644 index 0000000000..f33f7dfa38 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/monitoring/TripRecordingBottomSheet.java @@ -0,0 +1,273 @@ +package net.osmand.plus.monitoring; + +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.graphics.Typeface; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.text.SpannableString; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.CompoundButton.OnCheckedChangeListener; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.widget.SwitchCompat; +import androidx.fragment.app.FragmentManager; + +import com.google.android.material.slider.RangeSlider; + +import net.osmand.plus.GpxSelectionHelper.SelectedGpxFile; +import net.osmand.plus.NavigationService; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.UiUtilities.DialogButtonType; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.base.MenuBottomSheetDialogFragment; +import net.osmand.plus.base.bottomsheetmenu.BottomSheetItemWithDescription; +import net.osmand.plus.base.bottomsheetmenu.simpleitems.DividerSpaceItem; +import net.osmand.plus.helpers.AndroidUiHelper; +import net.osmand.plus.helpers.FontCache; +import net.osmand.plus.settings.backend.OsmandSettings; +import net.osmand.plus.track.TrackAppearanceFragment; + +import static net.osmand.plus.UiUtilities.CompoundButtonType.PROFILE_DEPENDENT; +import static net.osmand.plus.monitoring.OsmandMonitoringPlugin.MINUTES; +import static net.osmand.plus.monitoring.OsmandMonitoringPlugin.SECONDS; + +public class TripRecordingBottomSheet extends MenuBottomSheetDialogFragment { + + public static final String TAG = TripRecordingBottomSheet.class.getSimpleName(); + + private OsmandApplication app; + private OsmandSettings settings; + + private ImageView upDownBtn; + private SwitchCompat confirmEveryRun; + private TextView intervalValueView; + + private boolean infoExpanded; + + @Override + public void createMenuItems(Bundle savedInstanceState) { + app = requiredMyApplication(); + settings = app.getSettings(); + Context context = requireContext(); + + LayoutInflater inflater = UiUtilities.getInflater(context, nightMode); + View itemView = inflater.inflate(R.layout.trip_recording_fragment, null, false); + items.add(new BottomSheetItemWithDescription.Builder() + .setCustomView(itemView) + .create()); + + int padding = getResources().getDimensionPixelSize(R.dimen.content_padding_small); + final int paddingSmall = getResources().getDimensionPixelSize(R.dimen.content_padding_small); + + items.add(new DividerSpaceItem(context, padding)); + + LinearLayout showTrackOnMapView = itemView.findViewById(R.id.show_track_on_map); + TextView showTrackOnMapTitle = showTrackOnMapView.findViewById(R.id.title); + showTrackOnMapTitle.setText(R.string.show_track_on_map); + + ImageView trackAppearanceIcon = showTrackOnMapView.findViewById(R.id.icon_after_divider); + + int color = settings.CURRENT_TRACK_COLOR.get(); + String width = settings.CURRENT_TRACK_WIDTH.get(); + boolean showArrows = settings.CURRENT_TRACK_SHOW_ARROWS.get(); + Drawable drawable = TrackAppearanceFragment.getTrackIcon(app, width, showArrows, color); + + trackAppearanceIcon.setImageDrawable(drawable); + trackAppearanceIcon.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + hide(); + SelectedGpxFile selectedGpxFile = app.getSavingTrackHelper().getCurrentTrack(); + TrackAppearanceFragment.showInstance(mapActivity, selectedGpxFile); + } + } + }); + + upDownBtn = itemView.findViewById(R.id.up_down_button); + upDownBtn.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + toggleInfoView(); + } + }); + + final int secondsLength = SECONDS.length; + final int minutesLength = MINUTES.length; + + intervalValueView = itemView.findViewById(R.id.interval_value); + updateIntervalLegend(); + + RangeSlider intervalSlider = itemView.findViewById(R.id.interval_slider); + intervalSlider.setValueTo(secondsLength + minutesLength - 1); + intervalSlider.addOnChangeListener(new RangeSlider.OnChangeListener() { + + @Override + public void onValueChange(@NonNull RangeSlider slider, float value, boolean fromUser) { + int progress = (int) value; + if (progress == 0) { + settings.SAVE_GLOBAL_TRACK_INTERVAL.set(0); + } else if (progress < secondsLength) { + settings.SAVE_GLOBAL_TRACK_INTERVAL.set(SECONDS[progress] * 1000); + } else { + settings.SAVE_GLOBAL_TRACK_INTERVAL.set(MINUTES[progress - secondsLength] * 60 * 1000); + } + updateIntervalLegend(); + } + }); + for (int i = 0; i < secondsLength + minutesLength; i++) { + if (i < secondsLength) { + if (settings.SAVE_GLOBAL_TRACK_INTERVAL.get() <= SECONDS[i] * 1000) { + intervalSlider.setValues((float) i); + break; + } + } else { + if (settings.SAVE_GLOBAL_TRACK_INTERVAL.get() <= MINUTES[i - secondsLength] * 1000 * 60) { + intervalSlider.setValues((float) i); + break; + } + } + } + boolean checked = !settings.SAVE_GLOBAL_TRACK_REMEMBER.get(); + confirmEveryRun = itemView.findViewById(R.id.confirm_every_run); + confirmEveryRun.setChecked(checked); + setBackgroundAndPadding(checked, paddingSmall); + confirmEveryRun.setOnCheckedChangeListener(new OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + setBackgroundAndPadding(isChecked, paddingSmall); + settings.SAVE_GLOBAL_TRACK_REMEMBER.set(!isChecked); + } + }); + + SwitchCompat showTrackOnMapButton = showTrackOnMapView.findViewById(R.id.switch_button); + showTrackOnMapButton.setChecked(app.getSelectedGpxHelper().getSelectedCurrentRecordingTrack() != null); + showTrackOnMapButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + app.getSelectedGpxHelper().selectGpxFile(app.getSavingTrackHelper().getCurrentGpx(), isChecked, false); + } + }); + UiUtilities.setupCompoundButton(showTrackOnMapButton, nightMode, PROFILE_DEPENDENT); + + updateUpDownBtn(); + } + + private void updateIntervalLegend() { + String text = getString(R.string.save_track_interval_globally); + String textValue; + int interval = settings.SAVE_GLOBAL_TRACK_INTERVAL.get(); + if (interval == 0) { + textValue = getString(R.string.int_continuosly); + } else { + int seconds = interval / 1000; + if (seconds <= SECONDS[SECONDS.length - 1]) { + textValue = seconds + " " + getString(R.string.int_seconds); + } else { + textValue = (seconds / 60) + " " + getString(R.string.int_min); + } + } + String textAll = getString(R.string.ltr_or_rtl_combine_via_colon, text, textValue); + Typeface typeface = FontCache.getRobotoMedium(app); + SpannableString spannableString = UiUtilities.createCustomFontSpannable(typeface, textAll, textValue); + intervalValueView.setText(spannableString); + } + + public void show() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.show(); + } + } + + public void hide() { + Dialog dialog = getDialog(); + if (dialog != null) { + dialog.hide(); + } + } + + private void setBackgroundAndPadding(boolean isChecked, int paddingSmall) { + if (nightMode) { + confirmEveryRun.setBackgroundResource( + isChecked ? R.drawable.layout_bg_dark_solid : R.drawable.layout_bg_dark); + } else { + confirmEveryRun.setBackgroundResource( + isChecked ? R.drawable.layout_bg_solid : R.drawable.layout_bg); + } + confirmEveryRun.setPadding(paddingSmall, 0, paddingSmall, 0); + } + + private void updateUpDownBtn() { + int iconId = infoExpanded ? R.drawable.ic_action_arrow_down : R.drawable.ic_action_arrow_up; + upDownBtn.setImageDrawable(getContentIcon(iconId)); + } + + private void toggleInfoView() { + infoExpanded = !infoExpanded; + AndroidUiHelper.updateVisibility(confirmEveryRun, infoExpanded); + updateUpDownBtn(); + } + + @Override + protected boolean useVerticalButtons() { + return true; + } + + @Override + protected int getRightBottomButtonTextId() { + return R.string.start_recording; + } + + @Override + protected int getDismissButtonTextId() { + return R.string.shared_string_cancel; + } + + @Override + protected DialogButtonType getRightBottomButtonType() { + return DialogButtonType.PRIMARY; + } + + @Override + public int getSecondDividerHeight() { + return getResources().getDimensionPixelSize(R.dimen.bottom_sheet_icon_margin); + } + + @Override + protected void onRightBottomButtonClick() { + app.getSavingTrackHelper().startNewSegment(); + settings.SAVE_GLOBAL_TRACK_TO_GPX.set(true); + app.startNavigationService(NavigationService.USED_BY_GPX); + dismiss(); + } + + @Nullable + public MapActivity getMapActivity() { + Activity activity = getActivity(); + if (activity instanceof MapActivity) { + return (MapActivity) activity; + } + return null; + } + + public static void showInstance(@NonNull FragmentManager fragmentManager) { + if (!fragmentManager.isStateSaved()) { + TripRecordingBottomSheet fragment = new TripRecordingBottomSheet(); + fragment.show(fragmentManager, TAG); + } + } +} \ No newline at end of file diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/EngineParameter.java b/OsmAnd/src/net/osmand/plus/onlinerouting/EngineParameter.java new file mode 100644 index 0000000000..fa3ccd81c7 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/EngineParameter.java @@ -0,0 +1,10 @@ +package net.osmand.plus.onlinerouting; + +public enum EngineParameter { + KEY, + VEHICLE_KEY, + CUSTOM_NAME, + NAME_INDEX, + CUSTOM_URL, + API_KEY +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/EngineType.java b/OsmAnd/src/net/osmand/plus/onlinerouting/EngineType.java deleted file mode 100644 index fe5080f14c..0000000000 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/EngineType.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.osmand.plus.onlinerouting; - -public enum EngineType { - - GRAPHHOPPER("Graphhopper", "https://graphhopper.com/api/1/route"), - OSRM("OSRM", "https://router.project-osrm.org/route/v1/"), - ORS("Openroute Service", "https://api.openrouteservice.org/v2/directions/"); - - private String title; - private String standardUrl; - - EngineType(String title, String standardUrl) { - this.title = title; - this.standardUrl = standardUrl; - } - - public String getTitle() { - return title; - } - - public String getStandardUrl() { - return standardUrl; - } -} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngine.java deleted file mode 100644 index 3193d86d09..0000000000 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngine.java +++ /dev/null @@ -1,109 +0,0 @@ -package net.osmand.plus.onlinerouting; - -import android.content.Context; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import net.osmand.plus.R; -import net.osmand.util.Algorithms; - -import java.util.HashMap; -import java.util.Map; - -public class OnlineRoutingEngine { - - public final static String ONLINE_ROUTING_ENGINE_PREFIX = "online_routing_engine_"; - - public enum EngineParameter { - CUSTOM_NAME, - CUSTOM_URL, - API_KEY - } - - private String stringKey; - private EngineType type; - private String vehicleKey; - private Map params = new HashMap<>(); - - public OnlineRoutingEngine(@NonNull String stringKey, - @NonNull EngineType type, - @NonNull String vehicleKey, - @Nullable Map params) { - this(stringKey, type, vehicleKey); - if (!Algorithms.isEmpty(params)) { - this.params.putAll(params); - } - } - - public OnlineRoutingEngine(@NonNull String stringKey, - @NonNull EngineType type, - @NonNull String vehicleKey) { - this.stringKey = stringKey; - this.type = type; - this.vehicleKey = vehicleKey; - } - - public String getStringKey() { - return stringKey; - } - - public EngineType getType() { - return type; - } - - public String getBaseUrl() { - String customUrl = getParameter(EngineParameter.CUSTOM_URL); - if (Algorithms.isEmpty(customUrl)) { - return type.getStandardUrl(); - } - return customUrl; - } - - public String getVehicleKey() { - return vehicleKey; - } - - public Map getParams() { - return params; - } - - public String getParameter(EngineParameter paramKey) { - return params.get(paramKey.name()); - } - - public void putParameter(EngineParameter paramKey, String paramValue) { - params.put(paramKey.name(), paramValue); - } - - public String getName(@NonNull Context ctx) { - String customName = getParameter(EngineParameter.CUSTOM_NAME); - if (customName != null) { - return customName; - } else { - return getStandardName(ctx); - } - } - - private String getStandardName(@NonNull Context ctx) { - return getStandardName(ctx, type, vehicleKey); - } - - public static String getStandardName(@NonNull Context ctx, - @NonNull EngineType type, - @NonNull String vehicleKey) { - String vehicleTitle = VehicleType.toHumanString(ctx, vehicleKey); - String pattern = ctx.getString(R.string.ltr_or_rtl_combine_via_dash); - return String.format(pattern, type.getTitle(), vehicleTitle); - } - - public static OnlineRoutingEngine createNewEngine(@NonNull EngineType type, - @NonNull String vehicleKey, - @Nullable Map params) { - return new OnlineRoutingEngine(generateKey(), type, vehicleKey, params); - } - - private static String generateKey() { - return ONLINE_ROUTING_ENGINE_PREFIX + System.currentTimeMillis(); - } -} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngineFragment.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngineFragment.java deleted file mode 100644 index 59df4ef698..0000000000 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingEngineFragment.java +++ /dev/null @@ -1,609 +0,0 @@ -package net.osmand.plus.onlinerouting; - -import android.content.Context; -import android.os.Build; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.widget.FrameLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.widget.Toolbar; -import androidx.fragment.app.FragmentActivity; -import androidx.fragment.app.FragmentManager; - -import net.osmand.AndroidNetworkUtils; -import net.osmand.AndroidNetworkUtils.OnRequestResultListener; -import net.osmand.AndroidUtils; -import net.osmand.CallbackWithObject; -import net.osmand.data.LatLon; -import net.osmand.plus.OsmandApplication; -import net.osmand.plus.R; -import net.osmand.plus.UiUtilities; -import net.osmand.plus.UiUtilities.DialogButtonType; -import net.osmand.plus.activities.MapActivity; -import net.osmand.plus.base.BaseOsmAndFragment; -import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem; -import net.osmand.plus.onlinerouting.OnlineRoutingCard.OnTextChangedListener; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameter; -import net.osmand.plus.routepreparationmenu.cards.BaseCard; -import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.util.Algorithms; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.util.ArrayList; -import java.util.List; - -public class OnlineRoutingEngineFragment extends BaseOsmAndFragment { - - public static final String TAG = OnlineRoutingEngineFragment.class.getSimpleName(); - - private static final String ENGINE_NAME_KEY = "engine_name"; - private static final String ENGINE_SERVER_KEY = "engine_server"; - private static final String ENGINE_SERVER_URL_KEY = "engine_server_url"; - private static final String ENGINE_VEHICLE_TYPE_KEY = "engine_vehicle_type"; - private static final String ENGINE_CUSTOM_VEHICLE_KEY = "engine_custom_vehicle"; - private static final String ENGINE_API_KEY_KEY = "engine_api_key"; - private static final String EXAMPLE_LOCATION_KEY = "example_location"; - private static final String APP_MODE_KEY = "app_mode"; - private static final String EDITED_ENGINE_KEY = "edited_engine_key"; - - private OsmandApplication app; - private MapActivity mapActivity; - private OnlineRoutingHelper helper; - - private View view; - private ViewGroup segmentsContainer; - private OnlineRoutingCard nameCard; - private OnlineRoutingCard typeCard; - private OnlineRoutingCard vehicleCard; - private OnlineRoutingCard apiKeyCard; - private OnlineRoutingCard exampleCard; - private View testResultsContainer; - - private ApplicationMode appMode; - - private OnlineRoutingEngineObject engine; - private ExampleLocation selectedLocation; - private String editedEngineKey; - - private enum ExampleLocation { - - AMSTERDAM("Amsterdam", - new LatLon(52.379189, 4.899431), - new LatLon(52.308056, 4.764167)), - - BERLIN("Berlin", - new LatLon(52.520008, 13.404954), - new LatLon(52.3666652, 13.501997992)), - - NEW_YORK("New York", - new LatLon(43.000000, -75.000000), - new LatLon(40.641766, -73.780968)), - - PARIS("Paris", - new LatLon(48.864716, 2.349014), - new LatLon(48.948437, 2.434931)); - - ExampleLocation(String name, LatLon cityCenterLatLon, LatLon cityAirportLatLon) { - this.name = name; - this.cityCenterLatLon = cityCenterLatLon; - this.cityAirportLatLon = cityAirportLatLon; - } - - private String name; - private LatLon cityCenterLatLon; - private LatLon cityAirportLatLon; - - public String getName() { - return name; - } - - public LatLon getCityCenterLatLon() { - return cityCenterLatLon; - } - - public LatLon getCityAirportLatLon() { - return cityAirportLatLon; - } - } - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - app = requireMyApplication(); - mapActivity = getMapActivity(); - helper = app.getOnlineRoutingHelper(); - engine = new OnlineRoutingEngineObject(); - if (savedInstanceState != null) { - restoreState(savedInstanceState); - } else { - initState(); - } - } - - @Nullable - @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { - view = getInflater().inflate( - R.layout.online_routing_engine_fragment, container, false); - segmentsContainer = (ViewGroup) view.findViewById(R.id.segments_container); - if (Build.VERSION.SDK_INT >= 21) { - AndroidUtils.addStatusBarPadding21v(getContext(), view); - } - setupToolbar((Toolbar) view.findViewById(R.id.toolbar)); - - setupNameCard(); - setupTypeCard(); - setupVehicleCard(); - setupApiKeyCard(); - setupExampleCard(); - setupResultsContainer(); - addSpaceSegment(); - - setupButtons(); - - updateCardViews(nameCard, typeCard, vehicleCard, exampleCard); - return view; - } - - private void setupNameCard() { - nameCard = new OnlineRoutingCard(mapActivity, isNightMode()); - nameCard.build(mapActivity); - nameCard.setDescription(getString(R.string.select_nav_profile_dialog_message)); - nameCard.setEditedText(engine.getName(app)); - nameCard.setFieldBoxLabelText(getString(R.string.shared_string_name)); - nameCard.setOnTextChangedListener(new OnTextChangedListener() { - @Override - public void onTextChanged(boolean changedByUser, String text) { - if (changedByUser) { - engine.customName = text; - } - } - }); - nameCard.showDivider(); - segmentsContainer.addView(nameCard.getView()); - } - - private void setupTypeCard() { - typeCard = new OnlineRoutingCard(mapActivity, isNightMode()); - typeCard.build(mapActivity); - typeCard.setHeaderTitle(getString(R.string.shared_string_type)); - List serverItems = new ArrayList<>(); - for (EngineType server : EngineType.values()) { - serverItems.add(new HorizontalSelectionItem(server.getTitle(), server)); - } - typeCard.setSelectionMenu(serverItems, engine.type.getTitle(), - new CallbackWithObject() { - @Override - public boolean processResult(HorizontalSelectionItem result) { - EngineType type = (EngineType) result.getObject(); - if (engine.type != type) { - engine.type = type; - updateCardViews(nameCard, typeCard, exampleCard); - return true; - } - return false; - } - }); - typeCard.setOnTextChangedListener(new OnTextChangedListener() { - @Override - public void onTextChanged(boolean editedByUser, String text) { - if (editedByUser) { - engine.customServerUrl = text; - updateCardViews(exampleCard); - } - } - }); - typeCard.setFieldBoxLabelText(getString(R.string.shared_string_server_url)); - typeCard.showDivider(); - segmentsContainer.addView(typeCard.getView()); - } - - private void setupVehicleCard() { - vehicleCard = new OnlineRoutingCard(mapActivity, isNightMode()); - vehicleCard.build(mapActivity); - vehicleCard.setHeaderTitle(getString(R.string.shared_string_vehicle)); - List vehicleItems = new ArrayList<>(); - for (VehicleType vehicle : VehicleType.values()) { - vehicleItems.add(new HorizontalSelectionItem(vehicle.getTitle(app), vehicle)); - } - vehicleCard.setSelectionMenu(vehicleItems, engine.vehicleType.getTitle(app), - new CallbackWithObject() { - @Override - public boolean processResult(HorizontalSelectionItem result) { - VehicleType vehicle = (VehicleType) result.getObject(); - if (engine.vehicleType != vehicle) { - engine.vehicleType = vehicle; - updateCardViews(nameCard, vehicleCard, exampleCard); - return true; - } - return false; - } - }); - vehicleCard.setFieldBoxLabelText(getString(R.string.shared_string_custom)); - vehicleCard.setOnTextChangedListener(new OnTextChangedListener() { - @Override - public void onTextChanged(boolean editedByUser, String text) { - if (editedByUser) { - engine.customVehicleKey = text; - updateCardViews(nameCard, exampleCard); - } - } - }); - vehicleCard.setEditedText(engine.customVehicleKey); - vehicleCard.setFieldBoxHelperText(getString(R.string.shared_string_enter_param)); - vehicleCard.showDivider(); - segmentsContainer.addView(vehicleCard.getView()); - } - - private void setupApiKeyCard() { - apiKeyCard = new OnlineRoutingCard(mapActivity, isNightMode()); - apiKeyCard.build(mapActivity); - apiKeyCard.setHeaderTitle(getString(R.string.shared_string_api_key)); - apiKeyCard.setFieldBoxLabelText(getString(R.string.keep_it_empty_if_not)); - apiKeyCard.setEditedText(engine.apiKey); - apiKeyCard.showDivider(); - apiKeyCard.setOnTextChangedListener(new OnTextChangedListener() { - @Override - public void onTextChanged(boolean editedByUser, String text) { - engine.apiKey = text; - updateCardViews(exampleCard); - } - }); - segmentsContainer.addView(apiKeyCard.getView()); - } - - private void setupExampleCard() { - exampleCard = new OnlineRoutingCard(mapActivity, isNightMode()); - exampleCard.build(mapActivity); - exampleCard.setHeaderTitle(getString(R.string.shared_string_example)); - List locationItems = new ArrayList<>(); - for (ExampleLocation location : ExampleLocation.values()) { - locationItems.add(new HorizontalSelectionItem(location.getName(), location)); - } - exampleCard.setSelectionMenu(locationItems, selectedLocation.getName(), - new CallbackWithObject() { - @Override - public boolean processResult(HorizontalSelectionItem result) { - ExampleLocation location = (ExampleLocation) result.getObject(); - if (selectedLocation != location) { - selectedLocation = location; - updateCardViews(exampleCard); - return true; - } - return false; - } - }); - exampleCard.setFieldBoxHelperText(getString(R.string.online_routing_example_hint)); - exampleCard.setButton(getString(R.string.test_route_calculation), new View.OnClickListener() { - @Override - public void onClick(View v) { - testEngineWork(); - } - }); - segmentsContainer.addView(exampleCard.getView()); - } - - private void setupResultsContainer() { - testResultsContainer = getInflater().inflate( - R.layout.bottom_sheet_item_with_descr_64dp, segmentsContainer, false); - testResultsContainer.setVisibility(View.GONE); - segmentsContainer.addView(testResultsContainer); - } - - private void addSpaceSegment() { - int space = (int) getResources().getDimension(R.dimen.empty_state_text_button_padding_top); - View bottomSpaceView = new View(app); - bottomSpaceView.setLayoutParams( - new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, space)); - segmentsContainer.addView(bottomSpaceView); - } - - private void setupToolbar(Toolbar toolbar) { - ImageView navigationIcon = toolbar.findViewById(R.id.close_button); - navigationIcon.setImageResource(R.drawable.ic_action_close); - navigationIcon.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - dismiss(); - } - }); - TextView title = toolbar.findViewById(R.id.toolbar_title); - toolbar.findViewById(R.id.toolbar_subtitle).setVisibility(View.GONE); - View actionBtn = toolbar.findViewById(R.id.action_button); - if (isEditingMode()) { - title.setText(getString(R.string.edit_online_routing_engine)); - ImageView ivBtn = toolbar.findViewById(R.id.action_button_icon); - ivBtn.setImageDrawable( - getIcon(R.drawable.ic_action_delete_dark, R.color.color_osm_edit_delete)); - actionBtn.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - deleteEngine(); - dismiss(); - } - }); - } else { - title.setText(getString(R.string.add_online_routing_engine)); - actionBtn.setVisibility(View.GONE); - } - } - - private void updateCardViews(BaseCard... cardsToUpdate) { - for (BaseCard card : cardsToUpdate) { - if (nameCard.equals(card)) { - if (Algorithms.isEmpty(engine.customName)) { - String name; - if (Algorithms.isEmpty(engine.getVehicleKey())) { - name = engine.type.getTitle(); - } else { - name = OnlineRoutingEngine.getStandardName(app, engine.type, engine.getVehicleKey()); - } - nameCard.setEditedText(name); - } - - } else if (typeCard.equals(card)) { - typeCard.setHeaderSubtitle(engine.type.getTitle()); - typeCard.setEditedText(engine.getBaseUrl()); - if (engine.type == EngineType.GRAPHHOPPER || engine.type == EngineType.ORS) { - apiKeyCard.show(); - } else { - apiKeyCard.hide(); - } - - } else if (vehicleCard.equals(card)) { - VehicleType vt = VehicleType.getVehicleByKey(engine.getVehicleKey()); - vehicleCard.setHeaderSubtitle(vt.getTitle(app)); - if (vt == VehicleType.CUSTOM) { - vehicleCard.showFieldBox(); - vehicleCard.setEditedText(engine.getVehicleKey()); - } else { - vehicleCard.hideFieldBox(); - } - - } else if (exampleCard.equals(card)) { - exampleCard.setEditedText(getTestUrl()); - } - } - } - - private void setupButtons() { - boolean nightMode = isNightMode(); - View cancelButton = view.findViewById(R.id.dismiss_button); - UiUtilities.setupDialogButton(nightMode, cancelButton, - DialogButtonType.SECONDARY, R.string.shared_string_cancel); - cancelButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - dismiss(); - } - }); - - view.findViewById(R.id.buttons_divider).setVisibility(View.VISIBLE); - - View saveButton = view.findViewById(R.id.right_bottom_button); - UiUtilities.setupDialogButton(nightMode, saveButton, - UiUtilities.DialogButtonType.PRIMARY, R.string.shared_string_save); - saveButton.setVisibility(View.VISIBLE); - saveButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - saveChanges(); - dismiss(); - } - }); - } - - private void saveChanges() { - OnlineRoutingEngine engineToSave; - if (isEditingMode()) { - engineToSave = new OnlineRoutingEngine(editedEngineKey, engine.type, engine.getVehicleKey()); - } else { - engineToSave = OnlineRoutingEngine.createNewEngine(engine.type, engine.getVehicleKey(), null); - } - - engineToSave.putParameter(EngineParameter.CUSTOM_NAME, engine.customName); - engineToSave.putParameter(EngineParameter.CUSTOM_URL, engine.customServerUrl); - if (engine.type == EngineType.GRAPHHOPPER || engine.type == EngineType.ORS) { - engineToSave.putParameter(EngineParameter.API_KEY, engine.apiKey); - } - - helper.saveEngine(engineToSave); - } - - private void deleteEngine() { - helper.deleteEngine(editedEngineKey); - } - - private String getTestUrl() { - List path = new ArrayList<>(); - path.add(selectedLocation.getCityCenterLatLon()); - path.add(selectedLocation.getCityAirportLatLon()); - OnlineRoutingEngine tmpEngine = - OnlineRoutingEngine.createNewEngine(engine.type, engine.getVehicleKey(), null); - tmpEngine.putParameter(EngineParameter.CUSTOM_URL, engine.customServerUrl); - tmpEngine.putParameter(EngineParameter.API_KEY, engine.apiKey); - return helper.createFullUrl(tmpEngine, path); - } - - private void testEngineWork() { - final EngineType type = engine.type; - final ExampleLocation location = selectedLocation; - AndroidNetworkUtils.sendRequestAsync(app, exampleCard.getEditedText(), null, - null, false, false, new OnRequestResultListener() { - @Override - public void onResult(String response) { - boolean resultOk = false; - if (response != null) { - try { - JSONObject obj = new JSONObject(response); - - if (type == EngineType.GRAPHHOPPER) { - resultOk = obj.has("paths"); - } else if (type == EngineType.OSRM) { - resultOk = obj.has("routes"); - } else if (type == EngineType.ORS) { - resultOk = obj.has("features"); - } - } catch (JSONException e) { - - } - } - showTestResults(resultOk, location); - } - }); - } - - private void showTestResults(boolean resultOk, ExampleLocation location) { - testResultsContainer.setVisibility(View.VISIBLE); - ImageView ivImage = testResultsContainer.findViewById(R.id.icon); - TextView tvTitle = testResultsContainer.findViewById(R.id.title); - TextView tvDescription = testResultsContainer.findViewById(R.id.description); - if (resultOk) { - ivImage.setImageDrawable(getContentIcon(R.drawable.ic_action_gdirections_dark)); - tvTitle.setText(getString(R.string.shared_string_ok)); - } else { - ivImage.setImageDrawable(getContentIcon(R.drawable.ic_action_alert)); - tvTitle.setText(getString(R.string.message_error_recheck_parameters)); - } - tvDescription.setText(location.getName()); - } - - private boolean isEditingMode() { - return editedEngineKey != null; - } - - @Override - public void onSaveInstanceState(@NonNull Bundle outState) { - super.onSaveInstanceState(outState); - saveState(outState); - } - - private void saveState(Bundle outState) { - outState.putString(ENGINE_NAME_KEY, engine.customName); - outState.putString(ENGINE_SERVER_KEY, engine.type.name()); - outState.putString(ENGINE_SERVER_URL_KEY, engine.customServerUrl); - outState.putString(ENGINE_VEHICLE_TYPE_KEY, engine.vehicleType.name()); - outState.putString(ENGINE_CUSTOM_VEHICLE_KEY, engine.customVehicleKey); - outState.putString(ENGINE_API_KEY_KEY, engine.apiKey); - outState.putString(EXAMPLE_LOCATION_KEY, selectedLocation.name()); - if (appMode != null) { - outState.putString(APP_MODE_KEY, appMode.getStringKey()); - } - outState.putString(EDITED_ENGINE_KEY, editedEngineKey); - } - - private void restoreState(Bundle savedState) { - engine.customName = savedState.getString(ENGINE_NAME_KEY); - engine.type = EngineType.valueOf(savedState.getString(ENGINE_SERVER_KEY)); - engine.customServerUrl = savedState.getString(ENGINE_SERVER_URL_KEY); - engine.vehicleType = VehicleType.valueOf(savedState.getString(ENGINE_VEHICLE_TYPE_KEY)); - engine.customVehicleKey = savedState.getString(ENGINE_CUSTOM_VEHICLE_KEY); - engine.apiKey = savedState.getString(ENGINE_API_KEY_KEY); - selectedLocation = ExampleLocation.valueOf(savedState.getString(EXAMPLE_LOCATION_KEY)); - appMode = ApplicationMode.valueOfStringKey(savedState.getString(APP_MODE_KEY), null); - editedEngineKey = savedState.getString(EDITED_ENGINE_KEY); - } - - private void initState() { - engine.type = EngineType.values()[0]; - engine.vehicleType = VehicleType.values()[0]; - selectedLocation = ExampleLocation.values()[0]; - - if (isEditingMode()) { - OnlineRoutingEngine editedEngine = helper.getEngineByKey(editedEngineKey); - if (editedEngine != null) { - engine.customName = editedEngine.getParameter(EngineParameter.CUSTOM_NAME); - engine.type = editedEngine.getType(); - String vehicleKey = editedEngine.getVehicleKey(); - if (vehicleKey != null) { - VehicleType vehicleType = VehicleType.getVehicleByKey(vehicleKey); - if (vehicleType == VehicleType.CUSTOM) { - engine.customVehicleKey = vehicleKey; - } - engine.vehicleType = vehicleType; - } - engine.apiKey = editedEngine.getParameter(EngineParameter.API_KEY); - } - } - } - - private void dismiss() { - FragmentActivity activity = getActivity(); - if (activity != null) { - activity.onBackPressed(); - } - } - - private boolean isNightMode() { - return !app.getSettings().isLightContentForMode(appMode); - } - - @Nullable - private MapActivity getMapActivity() { - FragmentActivity activity = getActivity(); - if (activity instanceof MapActivity) { - return (MapActivity) activity; - } else { - return null; - } - } - - private LayoutInflater getInflater() { - return UiUtilities.getInflater(mapActivity, isNightMode()); - } - - public static void showInstance(@NonNull FragmentActivity activity, - @NonNull ApplicationMode appMode, - String editedEngineKey) { - FragmentManager fm = activity.getSupportFragmentManager(); - if (!fm.isStateSaved() && fm.findFragmentByTag(OnlineRoutingEngineFragment.TAG) == null) { - OnlineRoutingEngineFragment fragment = new OnlineRoutingEngineFragment(); - fragment.appMode = appMode; - fragment.editedEngineKey = editedEngineKey; - fm.beginTransaction() - .add(R.id.fragmentContainer, fragment, TAG) - .addToBackStack(TAG).commitAllowingStateLoss(); - } - } - - private static class OnlineRoutingEngineObject { - private String customName; - private EngineType type; - private String customServerUrl; - private VehicleType vehicleType; - private String customVehicleKey; - private String apiKey; - - public String getVehicleKey() { - if (vehicleType == VehicleType.CUSTOM) { - return customVehicleKey; - } - return vehicleType.getKey(); - } - - public String getName(Context ctx) { - if (customName != null) { - return customName; - } - return OnlineRoutingEngine.getStandardName(ctx, type, getVehicleKey()); - } - - public String getBaseUrl() { - if (Algorithms.isEmpty(customServerUrl)) { - return type.getStandardUrl(); - } - return customServerUrl; - } - } -} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingFactory.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingFactory.java new file mode 100644 index 0000000000..4b92000847 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingFactory.java @@ -0,0 +1,36 @@ +package net.osmand.plus.onlinerouting; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.plus.onlinerouting.engine.EngineType; +import net.osmand.plus.onlinerouting.engine.GraphhopperEngine; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; +import net.osmand.plus.onlinerouting.engine.OrsEngine; +import net.osmand.plus.onlinerouting.engine.OsrmEngine; + +import java.util.Map; + +public class OnlineRoutingFactory { + + public static OnlineRoutingEngine createEngine(@NonNull EngineType type) { + return createEngine(type, null); + } + + @NonNull + public static OnlineRoutingEngine createEngine(@NonNull EngineType type, + @Nullable Map params) { + switch (type) { + case GRAPHHOPPER: + return new GraphhopperEngine(params); + case OSRM: + return new OsrmEngine(params); + case ORS: + return new OrsEngine(params); + default: + throw new IllegalArgumentException( + "Online routing type {" + type.name() + "} not supported"); + } + } + +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java index b54746ddd6..b28d98d55f 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingHelper.java @@ -1,6 +1,7 @@ package net.osmand.plus.onlinerouting; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; @@ -10,10 +11,10 @@ import net.osmand.data.LatLon; import net.osmand.osm.io.NetworkUtils; import net.osmand.plus.OsmandApplication; import net.osmand.plus.Version; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameter; +import net.osmand.plus.onlinerouting.engine.EngineType; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.util.Algorithms; -import net.osmand.util.GeoPolylineParserUtil; import org.apache.commons.logging.Log; import org.json.JSONArray; @@ -24,7 +25,7 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.lang.reflect.Type; -import java.net.URLConnection; +import java.net.HttpURLConnection; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; @@ -35,14 +36,18 @@ public class OnlineRoutingHelper { private static final Log LOG = PlatformUtil.getLog(OnlineRoutingHelper.class); + private static final String ITEMS = "items"; + private static final String TYPE = "type"; + private static final String PARAMS = "params"; + private OsmandApplication app; private OsmandSettings settings; private Map cachedEngines; - public OnlineRoutingHelper(OsmandApplication app) { + public OnlineRoutingHelper(@NonNull OsmandApplication app) { this.app = app; this.settings = app.getSettings(); - loadFromSettings(); + this.cachedEngines = loadSavedEngines(); } @NonNull @@ -50,104 +55,42 @@ public class OnlineRoutingHelper { return new ArrayList<>(cachedEngines.values()); } - public OnlineRoutingEngine getEngineByKey(String stringKey) { + @NonNull + public List getEnginesExceptMentioned(@Nullable String ... excludeKeys) { + List engines = getEngines(); + if (excludeKeys != null) { + for (String key : excludeKeys) { + OnlineRoutingEngine engine = getEngineByKey(key); + engines.remove(engine); + } + } + return engines; + } + + @Nullable + public OnlineRoutingEngine getEngineByKey(@Nullable String stringKey) { return cachedEngines.get(stringKey); } + @NonNull public List calculateRouteOnline(@NonNull OnlineRoutingEngine engine, @NonNull List path) throws IOException, JSONException { - String fullUrl = createFullUrl(engine, path); - String content = makeRequest(fullUrl); - return parseResponse(engine, content); + String url = engine.getFullUrl(path); + String content = makeRequest(url); + return engine.parseServerResponse(content); } - public String createFullUrl(OnlineRoutingEngine engine, List path) { - StringBuilder sb = new StringBuilder(engine.getBaseUrl()); - String vehicle = engine.getVehicleKey(); - String apiKey = engine.getParameter(EngineParameter.API_KEY); - switch (engine.getType()) { - - case GRAPHHOPPER: - sb.append("?"); - for (LatLon point : path) { - sb.append("point=") - .append(point.getLatitude()) - .append(',') - .append(point.getLongitude()) - .append('&'); - } - sb.append("vehicle=").append(vehicle); - - if (!Algorithms.isEmpty(apiKey)) { - sb.append('&').append("key=").append(apiKey); - } - break; - - case OSRM: - sb.append(vehicle).append('/'); - for (int i = 0; i < path.size(); i++) { - LatLon point = path.get(i); - sb.append(point.getLongitude()).append(',').append(point.getLatitude()); - if (i < path.size() - 1) { - sb.append(';'); - } - } - break; - - case ORS: - if (path.size() > 1) { - sb.append("driving-car").append('?'); // todo only for testing - if (!Algorithms.isEmpty(apiKey)) { - sb.append("api_key=").append(apiKey); - } - LatLon start = path.get(0); - LatLon end = path.get(path.size() - 1); - sb.append('&').append("start=") - .append(start.getLatitude()).append(',').append(start.getLongitude()); - sb.append('&').append("end=") - .append(end.getLatitude()).append(',').append(end.getLongitude()); - } - break; - - } - return sb.toString(); - } - - private List parseResponse(OnlineRoutingEngine engine, String content) throws JSONException { - JSONObject obj = new JSONObject(content); - - switch (engine.getType()) { - - case GRAPHHOPPER: - return GeoPolylineParserUtil.parse( - obj.getJSONArray("paths").getJSONObject(0).getString("points"), - GeoPolylineParserUtil.PRECISION_5); - - case OSRM: - return GeoPolylineParserUtil.parse( - obj.getJSONArray("routes").getJSONObject(0).getString("geometry"), - GeoPolylineParserUtil.PRECISION_5); - - case ORS: - JSONArray array = obj.getJSONArray("features").getJSONObject(0) - .getJSONObject("geometry").getJSONArray("coordinates"); - List track = new ArrayList<>(); - for (int i = 0; i < array.length(); i++) { - JSONArray point = array.getJSONArray(i); - double lat = Double.parseDouble(point.getString(0)); - double lon = Double.parseDouble(point.getString(1)); - track.add(new LatLon(lat, lon)); - } - return track; - } - return new ArrayList<>(); - } - - private String makeRequest(String url) throws IOException { - URLConnection connection = NetworkUtils.getHttpURLConnection(url); + @NonNull + public String makeRequest(@NonNull String url) throws IOException { + HttpURLConnection connection = NetworkUtils.getHttpURLConnection(url); connection.setRequestProperty("User-Agent", Version.getFullVersion(app)); StringBuilder content = new StringBuilder(); - BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + BufferedReader reader; + if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) { + reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); + } else { + reader = new BufferedReader(new InputStreamReader(connection.getErrorStream())); + } String s; while ((s = reader.readLine()) != null) { content.append(s); @@ -160,32 +103,49 @@ public class OnlineRoutingHelper { } public void saveEngine(@NonNull OnlineRoutingEngine engine) { - String stringKey = engine.getStringKey(); - cachedEngines.put(stringKey, engine); - saveToSettings(); - } - - public void deleteEngine(@NonNull String stringKey) { - OnlineRoutingEngine engine = getEngineByKey(stringKey); - if (engine != null) { - deleteEngine(engine); - } + deleteInaccessibleParameters(engine); + String key = createEngineKeyIfNeeded(engine); + cachedEngines.put(key, engine); + saveCacheToSettings(); } public void deleteEngine(@NonNull OnlineRoutingEngine engine) { String stringKey = engine.getStringKey(); - if (cachedEngines.containsKey(stringKey)) { + deleteEngine(stringKey); + } + + public void deleteEngine(@Nullable String stringKey) { + if (stringKey != null) { cachedEngines.remove(stringKey); - saveToSettings(); + saveCacheToSettings(); } } - private void loadFromSettings() { + private void deleteInaccessibleParameters(@NonNull OnlineRoutingEngine engine) { + for (EngineParameter key : EngineParameter.values()) { + if (!engine.isParameterAllowed(key)) { + engine.remove(key); + } + } + } + + @NonNull + private String createEngineKeyIfNeeded(@NonNull OnlineRoutingEngine engine) { + String key = engine.get(EngineParameter.KEY); + if (Algorithms.isEmpty(key)) { + key = OnlineRoutingEngine.generateKey(); + engine.put(EngineParameter.KEY, key); + } + return key; + } + + @NonNull + private Map loadSavedEngines() { Map cachedEngines = new LinkedHashMap<>(); for (OnlineRoutingEngine engine : readFromSettings()) { cachedEngines.put(engine.getStringKey(), engine); } - this.cachedEngines = cachedEngines; + return cachedEngines; } @NonNull @@ -196,14 +156,14 @@ public class OnlineRoutingHelper { try { JSONObject json = new JSONObject(jsonString); readFromJson(json, engines); - } catch (JSONException e) { + } catch (JSONException | IllegalArgumentException e) { LOG.debug("Error when reading engines from JSON ", e); } } return engines; } - private void saveToSettings() { + private void saveCacheToSettings() { if (!Algorithms.isEmpty(cachedEngines)) { try { JSONObject json = new JSONObject(); @@ -217,38 +177,44 @@ public class OnlineRoutingHelper { } } - public static void readFromJson(JSONObject json, List engines) throws JSONException { + public static void readFromJson(@NonNull JSONObject json, + @NonNull List engines) throws JSONException { if (!json.has("items")) { return; } Gson gson = new Gson(); - Type type = new TypeToken>() { + Type typeToken = new TypeToken>() { }.getType(); - JSONArray itemsJson = json.getJSONArray("items"); + JSONArray itemsJson = json.getJSONArray(ITEMS); for (int i = 0; i < itemsJson.length(); i++) { JSONObject object = itemsJson.getJSONObject(i); - String key = object.getString("key"); - String vehicleKey = object.getString("vehicle"); - EngineType engineType = EngineType.valueOf(object.getString("type")); - String paramsString = object.getString("params"); - HashMap params = gson.fromJson(paramsString, type); - engines.add(new OnlineRoutingEngine(key, engineType, vehicleKey, params)); + if (object.has(TYPE) && object.has(PARAMS)) { + EngineType type = EngineType.getTypeByName(object.getString(TYPE)); + String paramsString = object.getString(PARAMS); + HashMap params = gson.fromJson(paramsString, typeToken); + OnlineRoutingEngine engine = OnlineRoutingFactory.createEngine(type, params); + if (!Algorithms.isEmpty(engine.getStringKey())) { + engines.add(engine); + } + } } } - public static void writeToJson(JSONObject json, List engines) throws JSONException { + public static void writeToJson(@NonNull JSONObject json, + @NonNull List engines) throws JSONException { JSONArray jsonArray = new JSONArray(); Gson gson = new Gson(); Type type = new TypeToken>() { }.getType(); for (OnlineRoutingEngine engine : engines) { + if (Algorithms.isEmpty(engine.getStringKey())) { + continue; + } JSONObject jsonObject = new JSONObject(); - jsonObject.put("key", engine.getStringKey()); - jsonObject.put("type", engine.getType().name()); - jsonObject.put("vehicle", engine.getVehicleKey()); - jsonObject.put("params", gson.toJson(engine.getParams(), type)); + jsonObject.put(TYPE, engine.getType().name()); + jsonObject.put(PARAMS, gson.toJson(engine.getParams(), type)); jsonArray.put(jsonObject); } - json.put("items", jsonArray); + json.put(ITEMS, jsonArray); } } diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/VehicleType.java b/OsmAnd/src/net/osmand/plus/onlinerouting/VehicleType.java index 3e3fea6660..9936f8efbf 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/VehicleType.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/VehicleType.java @@ -3,48 +3,26 @@ package net.osmand.plus.onlinerouting; import android.content.Context; import androidx.annotation.NonNull; +import androidx.annotation.StringRes; -import net.osmand.plus.R; -import net.osmand.util.Algorithms; +public class VehicleType { + private final String key; + @StringRes + private final int titleId; -public enum VehicleType { - CAR("car", R.string.routing_engine_vehicle_type_car), - BIKE("bike", R.string.routing_engine_vehicle_type_bike), - FOOT("foot", R.string.routing_engine_vehicle_type_foot), - DRIVING("driving", R.string.routing_engine_vehicle_type_driving), - CUSTOM("", R.string.shared_string_custom); - - VehicleType(String key, int titleId) { + public VehicleType(@NonNull String key, + @StringRes int titleId) { this.key = key; this.titleId = titleId; } - private String key; - private int titleId; - + @NonNull public String getKey() { return key; } - public String getTitle(Context ctx) { + @NonNull + public String getTitle(@NonNull Context ctx) { return ctx.getString(titleId); } - - public static String toHumanString(@NonNull Context ctx, - @NonNull String key) { - VehicleType vehicleType = getVehicleByKey(key); - if (vehicleType == CUSTOM) { - return Algorithms.capitalizeFirstLetter(key); - } - return vehicleType.getTitle(ctx); - } - - public static VehicleType getVehicleByKey(String key) { - for (VehicleType v : values()) { - if (Algorithms.objectEquals(v.getKey(), key)) { - return v; - } - } - return CUSTOM; - } -} \ No newline at end of file +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/EngineType.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/EngineType.java new file mode 100644 index 0000000000..431ad33a3d --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/EngineType.java @@ -0,0 +1,34 @@ +package net.osmand.plus.onlinerouting.engine; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.util.Algorithms; + +public enum EngineType { + GRAPHHOPPER("Graphhopper"), + OSRM("OSRM"), + ORS("Openroute Service"); + + private final String title; + + EngineType(String title) { + this.title = title; + } + + public String getTitle() { + return title; + } + + @NonNull + public static EngineType getTypeByName(@Nullable String name) { + if (!Algorithms.isEmpty(name)) { + for (EngineType type : values()) { + if (type.name().equals(name)) { + return type; + } + } + } + return values()[0]; + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java new file mode 100644 index 0000000000..d676e1e154 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/GraphhopperEngine.java @@ -0,0 +1,95 @@ +package net.osmand.plus.onlinerouting.engine; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.data.LatLon; +import net.osmand.plus.R; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.util.GeoPolylineParserUtil; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.List; +import java.util.Map; + +import static net.osmand.util.Algorithms.isEmpty; + +public class GraphhopperEngine extends OnlineRoutingEngine { + + public GraphhopperEngine(@Nullable Map params) { + super(params); + } + + @Override + public @NonNull EngineType getType() { + return EngineType.GRAPHHOPPER; + } + + @NonNull + @Override + public String getStandardUrl() { + return "https://graphhopper.com/api/1/route"; + } + + @Override + protected void collectAllowedParameters() { + allowParameters(EngineParameter.API_KEY); + } + + @Override + protected void collectAllowedVehicles(@NonNull List vehicles) { + vehicles.add(new VehicleType("car", R.string.routing_engine_vehicle_type_car)); + vehicles.add(new VehicleType("bike", R.string.routing_engine_vehicle_type_bike)); + vehicles.add(new VehicleType("foot", R.string.routing_engine_vehicle_type_foot)); + vehicles.add(new VehicleType("hike", R.string.routing_engine_vehicle_type_hiking)); + vehicles.add(new VehicleType("mtb", R.string.routing_engine_vehicle_type_mtb)); + vehicles.add(new VehicleType("racingbike", R.string.routing_engine_vehicle_type_racingbike)); + vehicles.add(new VehicleType("scooter", R.string.routing_engine_vehicle_type_scooter)); + vehicles.add(new VehicleType("truck", R.string.routing_engine_vehicle_type_truck)); + vehicles.add(new VehicleType("small_truck", R.string.routing_engine_vehicle_type_small_truck)); + } + + @Override + protected void makeFullUrl(@NonNull StringBuilder sb, + @NonNull List path) { + sb.append("?"); + for (LatLon point : path) { + sb.append("point=") + .append(point.getLatitude()) + .append(',') + .append(point.getLongitude()) + .append('&'); + } + String vehicle = get(EngineParameter.VEHICLE_KEY); + if (isEmpty(vehicle)) { + sb.append("vehicle=").append(vehicle); + } + String apiKey = get(EngineParameter.API_KEY); + if (isEmpty(apiKey)) { + sb.append('&').append("key=").append(apiKey); + } + } + + @NonNull + @Override + public List parseServerResponse(@NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + return GeoPolylineParserUtil.parse( + obj.getJSONArray("paths").getJSONObject(0).getString("points"), + GeoPolylineParserUtil.PRECISION_5); + } + + @Override + public boolean parseServerMessage(@NonNull StringBuilder sb, + @NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + if (obj.has("message")) { + String message = obj.getString("message"); + sb.append(message); + } + return obj.has("paths"); + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java new file mode 100644 index 0000000000..e3f2d416ce --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OnlineRoutingEngine.java @@ -0,0 +1,205 @@ +package net.osmand.plus.onlinerouting.engine; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.data.LatLon; +import net.osmand.plus.R; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.OnlineRoutingFactory; +import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.util.Algorithms; + +import org.json.JSONException; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static net.osmand.util.Algorithms.isEmpty; + +public abstract class OnlineRoutingEngine implements Cloneable { + + public final static String ONLINE_ROUTING_ENGINE_PREFIX = "online_routing_engine_"; + public static final VehicleType CUSTOM_VEHICLE = new VehicleType("", R.string.shared_string_custom); + + private final Map params = new HashMap<>(); + private final List allowedVehicles = new ArrayList<>(); + private final Set allowedParameters = new HashSet<>(); + + public OnlineRoutingEngine(@Nullable Map params) { + if (!isEmpty(params)) { + this.params.putAll(params); + } + collectAllowedVehiclesInternal(); + collectAllowedParametersInternal(); + } + + @NonNull + public abstract EngineType getType(); + + @Nullable + public String getStringKey() { + return get(EngineParameter.KEY); + } + + @NonNull + public String getName(@NonNull Context ctx) { + String customName = get(EngineParameter.CUSTOM_NAME); + if (customName != null) { + return customName; + } else { + return getStandardName(ctx); + } + } + + @NonNull + private String getStandardName(@NonNull Context ctx) { + String base = getBaseName(ctx); + String index = get(EngineParameter.NAME_INDEX); + return !isEmpty(index) ? base + " " + index : base; + } + + @NonNull + public String getBaseName(@NonNull Context ctx) { + String vehicleTitle = getSelectedVehicleName(ctx); + if (isEmpty(vehicleTitle)) { + return getType().getTitle(); + } else { + String pattern = ctx.getString(R.string.ltr_or_rtl_combine_via_dash); + return String.format(pattern, getType().getTitle(), vehicleTitle); + } + } + + @NonNull + public String getBaseUrl() { + String customUrl = get(EngineParameter.CUSTOM_URL); + if (isEmpty(customUrl)) { + return getStandardUrl(); + } + return customUrl; + } + + @NonNull + public String getFullUrl(@NonNull List path) { + StringBuilder sb = new StringBuilder(getBaseUrl()); + makeFullUrl(sb, path); + return sb.toString(); + } + + protected abstract void makeFullUrl(@NonNull StringBuilder sb, + @NonNull List path); + + @NonNull + public abstract List parseServerResponse(@NonNull String content) throws JSONException; + + @NonNull + public abstract String getStandardUrl(); + + @NonNull + public Map getParams() { + return params; + } + + @Nullable + public String get(@NonNull EngineParameter key) { + return params.get(key.name()); + } + + public void put(@NonNull EngineParameter key, @NonNull String value) { + params.put(key.name(), value); + } + + public void remove(@NonNull EngineParameter key) { + params.remove(key.name()); + } + + private void collectAllowedVehiclesInternal() { + allowedVehicles.clear(); + collectAllowedVehicles(allowedVehicles); + allowedVehicles.add(CUSTOM_VEHICLE); + } + + protected abstract void collectAllowedVehicles(@NonNull List vehicles); + + @NonNull + public List getAllowedVehicles() { + return Collections.unmodifiableList(allowedVehicles); + } + + private void collectAllowedParametersInternal() { + allowedParameters.clear(); + allowParameters(EngineParameter.KEY, EngineParameter.VEHICLE_KEY, + EngineParameter.CUSTOM_NAME, EngineParameter.NAME_INDEX, EngineParameter.CUSTOM_URL); + collectAllowedParameters(); + } + + protected abstract void collectAllowedParameters(); + + public boolean isParameterAllowed(EngineParameter key) { + return allowedParameters.contains(key); + } + + protected void allowParameters(@NonNull EngineParameter ... allowedParams) { + allowedParameters.addAll(Arrays.asList(allowedParams)); + } + + @Nullable + private String getSelectedVehicleName(@NonNull Context ctx) { + String key = get(EngineParameter.VEHICLE_KEY); + VehicleType vt = getVehicleTypeByKey(key); + if (!vt.equals(CUSTOM_VEHICLE)) { + return vt.getTitle(ctx); + } + return key != null ? Algorithms.capitalizeFirstLetter(key) : null; + } + + @NonNull + public VehicleType getSelectedVehicleType() { + String key = get(EngineParameter.VEHICLE_KEY); + return getVehicleTypeByKey(key); + } + + @NonNull + public VehicleType getVehicleTypeByKey(@Nullable String vehicleKey) { + if (!isEmpty(vehicleKey)) { + for (VehicleType vt : allowedVehicles) { + if (Algorithms.objectEquals(vt.getKey(), vehicleKey)) { + return vt; + } + } + } + return CUSTOM_VEHICLE; + } + + public abstract boolean parseServerMessage(@NonNull StringBuilder sb, + @NonNull String content) throws JSONException; + + @NonNull + @Override + public Object clone() { + return OnlineRoutingFactory.createEngine(getType(), getParams()); + } + + @NonNull + public static String generateKey() { + return ONLINE_ROUTING_ENGINE_PREFIX + System.currentTimeMillis(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof OnlineRoutingEngine)) return false; + + OnlineRoutingEngine engine = (OnlineRoutingEngine) o; + if (getType() != engine.getType()) return false; + return Algorithms.objectEquals(getParams(), engine.getParams()); + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java new file mode 100644 index 0000000000..7c57737d46 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OrsEngine.java @@ -0,0 +1,104 @@ +package net.osmand.plus.onlinerouting.engine; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.data.LatLon; +import net.osmand.plus.R; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.VehicleType; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static net.osmand.util.Algorithms.isEmpty; + +public class OrsEngine extends OnlineRoutingEngine { + + public OrsEngine(@Nullable Map params) { + super(params); + } + + @Override + public @NonNull EngineType getType() { + return EngineType.ORS; + } + + @NonNull + @Override + public String getStandardUrl() { + return "https://api.openrouteservice.org/v2/directions/"; + } + + @Override + protected void collectAllowedParameters() { + allowParameters(EngineParameter.API_KEY); + } + + @Override + protected void collectAllowedVehicles(@NonNull List vehicles) { + vehicles.add(new VehicleType("driving-car", R.string.routing_engine_vehicle_type_car)); + vehicles.add(new VehicleType("driving-hgv", R.string.routing_engine_vehicle_type_hgv)); + vehicles.add(new VehicleType("cycling-regular", R.string.routing_engine_vehicle_type_cycling_regular)); + vehicles.add(new VehicleType("cycling-road", R.string.routing_engine_vehicle_type_cycling_road)); + vehicles.add(new VehicleType("cycling-mountain", R.string.routing_engine_vehicle_type_cycling_mountain)); + vehicles.add(new VehicleType("cycling-electric", R.string.routing_engine_vehicle_type_cycling_electric)); + vehicles.add(new VehicleType("foot-walking", R.string.routing_engine_vehicle_type_walking)); + vehicles.add(new VehicleType("foot-hiking", R.string.routing_engine_vehicle_type_hiking)); + vehicles.add(new VehicleType("wheelchair", R.string.routing_engine_vehicle_type_wheelchair)); + } + + @Override + protected void makeFullUrl(@NonNull StringBuilder sb, + @NonNull List path) { + if (path.size() > 1) { + String vehicleKey = get(EngineParameter.VEHICLE_KEY); + if (!isEmpty(vehicleKey)) { + sb.append(vehicleKey); + } + sb.append('?'); + String apiKey = get(EngineParameter.API_KEY); + if (!isEmpty(apiKey)) { + sb.append("api_key=").append(apiKey); + } + LatLon start = path.get(0); + LatLon end = path.get(path.size() - 1); + sb.append('&').append("start=") + .append(start.getLongitude()).append(',').append(start.getLatitude()); + sb.append('&').append("end=") + .append(end.getLongitude()).append(',').append(end.getLatitude()); + } + } + + @NonNull + @Override + public List parseServerResponse(@NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + JSONArray array = obj.getJSONArray("features").getJSONObject(0) + .getJSONObject("geometry").getJSONArray("coordinates"); + List track = new ArrayList<>(); + for (int i = 0; i < array.length(); i++) { + JSONArray point = array.getJSONArray(i); + double lon = Double.parseDouble(point.getString(0)); + double lat = Double.parseDouble(point.getString(1)); + track.add(new LatLon(lat, lon)); + } + return track; + } + + @Override + public boolean parseServerMessage(@NonNull StringBuilder sb, + @NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + if (obj.has("error")) { + String message = obj.getString("error"); + sb.append(message); + } + return obj.has("features"); + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java new file mode 100644 index 0000000000..1ef9c1a622 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/engine/OsrmEngine.java @@ -0,0 +1,84 @@ +package net.osmand.plus.onlinerouting.engine; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import net.osmand.data.LatLon; +import net.osmand.plus.R; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.util.GeoPolylineParserUtil; + +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.List; +import java.util.Map; + +import static net.osmand.util.Algorithms.isEmpty; + +public class OsrmEngine extends OnlineRoutingEngine { + + public OsrmEngine(@Nullable Map params) { + super(params); + } + + @Override + public @NonNull EngineType getType() { + return EngineType.OSRM; + } + + @NonNull + @Override + public String getStandardUrl() { + return "https://router.project-osrm.org/route/v1/"; + } + + @Override + protected void collectAllowedParameters() { } + + @Override + protected void collectAllowedVehicles(@NonNull List vehicles) { + vehicles.add(new VehicleType("car", R.string.routing_engine_vehicle_type_car)); + vehicles.add(new VehicleType("bike", R.string.routing_engine_vehicle_type_bike)); + vehicles.add(new VehicleType("foot", R.string.routing_engine_vehicle_type_foot)); + } + + @Override + protected void makeFullUrl(@NonNull StringBuilder sb, + @NonNull List path) { + String vehicleKey = get(EngineParameter.VEHICLE_KEY); + if (!isEmpty(vehicleKey)) { + sb.append(vehicleKey).append('/'); + } + for (int i = 0; i < path.size(); i++) { + LatLon point = path.get(i); + sb.append(point.getLongitude()).append(',').append(point.getLatitude()); + if (i < path.size() - 1) { + sb.append(';'); + } + } + sb.append('?'); + sb.append("overview=full"); + } + + @NonNull + @Override + public List parseServerResponse(@NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + return GeoPolylineParserUtil.parse( + obj.getJSONArray("routes").getJSONObject(0).getString("geometry"), + GeoPolylineParserUtil.PRECISION_5); + } + + @Override + public boolean parseServerMessage(@NonNull StringBuilder sb, + @NonNull String content) throws JSONException { + JSONObject obj = new JSONObject(content); + if (obj.has("message")) { + String message = obj.getString("message"); + sb.append(message); + } + return obj.has("routes"); + } +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/ui/ExampleLocation.java b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/ExampleLocation.java new file mode 100644 index 0000000000..0a384002f7 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/ExampleLocation.java @@ -0,0 +1,52 @@ +package net.osmand.plus.onlinerouting.ui; + +import androidx.annotation.NonNull; + +import net.osmand.data.LatLon; + +public enum ExampleLocation { + + AMSTERDAM("Amsterdam", + new LatLon(52.379189, 4.899431), + new LatLon(52.308056, 4.764167)), + + BERLIN("Berlin", + new LatLon(52.520008, 13.404954), + new LatLon(52.3666652, 13.501997992)), + + NEW_YORK("New York", + new LatLon(43.000000, -75.000000), + new LatLon(40.641766, -73.780968)), + + PARIS("Paris", + new LatLon(48.864716, 2.349014), + new LatLon(48.948437, 2.434931)); + + ExampleLocation(@NonNull String name, + @NonNull LatLon cityCenterLatLon, + @NonNull LatLon cityAirportLatLon) { + this.name = name; + this.cityCenterLatLon = cityCenterLatLon; + this.cityAirportLatLon = cityAirportLatLon; + } + + private String name; + private LatLon cityCenterLatLon; + private LatLon cityAirportLatLon; + + @NonNull + public String getName() { + return name; + } + + @NonNull + public LatLon getCityCenterLatLon() { + return cityCenterLatLon; + } + + @NonNull + public LatLon getCityAirportLatLon() { + return cityAirportLatLon; + } + +} diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingCard.java b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingCard.java similarity index 69% rename from OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingCard.java rename to OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingCard.java index 1b9e7f3b29..4403d512f4 100644 --- a/OsmAnd/src/net/osmand/plus/onlinerouting/OnlineRoutingCard.java +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingCard.java @@ -1,14 +1,17 @@ -package net.osmand.plus.onlinerouting; +package net.osmand.plus.onlinerouting.ui; import android.text.Editable; import android.text.TextWatcher; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnFocusChangeListener; +import android.view.ViewGroup; import android.widget.EditText; import android.widget.TextView; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -22,7 +25,10 @@ import net.osmand.plus.helpers.AndroidUiHelper; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionAdapterListener; import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem; +import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; import net.osmand.plus.routepreparationmenu.cards.BaseCard; +import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.widgets.OsmandTextFieldBoxes; import java.util.List; @@ -39,13 +45,18 @@ public class OnlineRoutingCard extends BaseCard { private OsmandTextFieldBoxes textFieldBoxes; private EditText editText; private TextView tvHelperText; + private TextView tvErrorText; private View bottomDivider; private View button; private OnTextChangedListener onTextChangedListener; + private boolean fieldBoxHelperTextShowed; - public OnlineRoutingCard(@NonNull MapActivity mapActivity, boolean nightMode) { + private ApplicationMode appMode; + + public OnlineRoutingCard(@NonNull MapActivity mapActivity, boolean nightMode, ApplicationMode appMode) { super(mapActivity); this.nightMode = nightMode; + this.appMode = appMode; } @Override @@ -64,9 +75,13 @@ public class OnlineRoutingCard extends BaseCard { textFieldBoxes = view.findViewById(R.id.field_box); editText = view.findViewById(R.id.edit_text); tvHelperText = view.findViewById(R.id.helper_text); + tvErrorText = view.findViewById(R.id.error_text); bottomDivider = view.findViewById(R.id.bottom_divider); button = view.findViewById(R.id.button); + int activeColor = ContextCompat.getColor(app, appMode.getIconColorInfo().getColor(nightMode)); + textFieldBoxes.setPrimaryColor(activeColor); + editText.addTextChangedListener(new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { @@ -80,7 +95,7 @@ public class OnlineRoutingCard extends BaseCard { public void afterTextChanged(Editable s) { if (onTextChangedListener != null) { boolean editedByUser = editText.getTag() == null; - String text = editText.getText().toString(); + String text = editText.getText().toString().trim(); onTextChangedListener.onTextChanged(editedByUser, text); } } @@ -107,9 +122,9 @@ public class OnlineRoutingCard extends BaseCard { tvHeaderSubtitle.setText(subtitle); } - public void setSelectionMenu(List items, - String selectedItemTitle, - final CallbackWithObject callback) { + public void setSelectionMenu(@NonNull List items, + @NonNull String selectedItemTitle, + @NonNull final CallbackWithObject callback) { showElements(rvSelectionMenu); rvSelectionMenu.setLayoutManager( new LinearLayoutManager(app, RecyclerView.HORIZONTAL, false)); @@ -122,11 +137,25 @@ public class OnlineRoutingCard extends BaseCard { if (callback.processResult(item)) { adapter.setSelectedItem(item); } + Object obj = item.getObject(); + updateBottomMarginSelectionMenu(obj); } }); + Object item = adapter.getItemByTitle(selectedItemTitle).getObject(); + updateBottomMarginSelectionMenu(item); rvSelectionMenu.setAdapter(adapter); } + private void updateBottomMarginSelectionMenu(Object item) { + if (item instanceof VehicleType) { + VehicleType vt = (VehicleType) item; + boolean hasPadding = vt.equals(OnlineRoutingEngine.CUSTOM_VEHICLE); + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) rvSelectionMenu.getLayoutParams(); + int contentPadding = app.getResources().getDimensionPixelSize(R.dimen.content_padding); + params.bottomMargin = hasPadding ? contentPadding : 0; + } + } + public void setDescription(@NonNull String description) { showElements(tvDescription); tvDescription.setText(description); @@ -139,15 +168,31 @@ public class OnlineRoutingCard extends BaseCard { public void setFieldBoxHelperText(@NonNull String helperText) { showElements(fieldBoxContainer, tvHelperText); + fieldBoxHelperTextShowed = true; tvHelperText.setText(helperText); } + public void showFieldBoxError(@NonNull String errorText) { + showElements(fieldBoxContainer, tvErrorText); + hideElements(tvHelperText); + tvErrorText.setText(errorText); + } + + public void hideFieldBoxError() { + hideElements(tvErrorText); + if (fieldBoxHelperTextShowed) { + showElements(tvHelperText); + } + } + public void setEditedText(@NonNull String text) { - editText.setTag(""); // needed to indicate that the text was edited programmatically + showElements(fieldBoxContainer); + editText.setTag(""); // indicate that the text was edited programmatically editText.setText(text); editText.setTag(null); } + @NonNull public String getEditedText() { return editText.getText().toString(); } @@ -156,7 +201,8 @@ public class OnlineRoutingCard extends BaseCard { showElements(bottomDivider); } - public void setButton(String title, OnClickListener listener) { + public void setButton(@NonNull String title, + @NonNull OnClickListener listener) { showElements(button); button.setOnClickListener(listener); UiUtilities.setupDialogButton(nightMode, button, DialogButtonType.PRIMARY, title); @@ -186,11 +232,11 @@ public class OnlineRoutingCard extends BaseCard { AndroidUiHelper.setVisibility(View.GONE, views); } - public void setOnTextChangedListener(OnTextChangedListener onTextChangedListener) { + public void setOnTextChangedListener(@Nullable OnTextChangedListener onTextChangedListener) { this.onTextChangedListener = onTextChangedListener; } public interface OnTextChangedListener { - void onTextChanged(boolean editedByUser, String text); + void onTextChanged(boolean editedByUser, @NonNull String text); } } diff --git a/OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingEngineFragment.java b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingEngineFragment.java new file mode 100644 index 0000000000..4c0bca61a6 --- /dev/null +++ b/OsmAnd/src/net/osmand/plus/onlinerouting/ui/OnlineRoutingEngineFragment.java @@ -0,0 +1,721 @@ +package net.osmand.plus.onlinerouting.ui; + +import android.annotation.SuppressLint; +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.view.ViewTreeObserver; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.ScrollView; +import android.widget.TextView; + +import androidx.activity.OnBackPressedCallback; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.Toolbar; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; + +import net.osmand.AndroidUtils; +import net.osmand.CallbackWithObject; +import net.osmand.data.LatLon; +import net.osmand.plus.OsmandApplication; +import net.osmand.plus.R; +import net.osmand.plus.UiUtilities; +import net.osmand.plus.UiUtilities.DialogButtonType; +import net.osmand.plus.activities.MapActivity; +import net.osmand.plus.base.BaseOsmAndFragment; +import net.osmand.plus.mapcontextmenu.other.HorizontalSelectionAdapter.HorizontalSelectionItem; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.OnlineRoutingFactory; +import net.osmand.plus.onlinerouting.OnlineRoutingHelper; +import net.osmand.plus.onlinerouting.VehicleType; +import net.osmand.plus.onlinerouting.ui.OnlineRoutingCard.OnTextChangedListener; +import net.osmand.plus.onlinerouting.engine.EngineType; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; +import net.osmand.plus.routepreparationmenu.cards.BaseCard; +import net.osmand.plus.settings.backend.ApplicationMode; +import net.osmand.util.Algorithms; + +import org.json.JSONException; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine.CUSTOM_VEHICLE; + +public class OnlineRoutingEngineFragment extends BaseOsmAndFragment { + + public static final String TAG = OnlineRoutingEngineFragment.class.getSimpleName(); + + private static final String ENGINE_TYPE_KEY = "engine_type"; + private static final String ENGINE_CUSTOM_VEHICLE_KEY = "engine_custom_vehicle"; + private static final String EXAMPLE_LOCATION_KEY = "example_location"; + private static final String APP_MODE_KEY = "app_mode"; + private static final String EDITED_ENGINE_KEY = "edited_engine_key"; + + private OsmandApplication app; + private ApplicationMode appMode; + private MapActivity mapActivity; + private OnlineRoutingHelper helper; + + private View view; + private ViewGroup segmentsContainer; + private OnlineRoutingCard nameCard; + private OnlineRoutingCard typeCard; + private OnlineRoutingCard vehicleCard; + private OnlineRoutingCard apiKeyCard; + private OnlineRoutingCard exampleCard; + private View testResultsContainer; + private View saveButton; + private ScrollView scrollView; + private OnGlobalLayoutListener onGlobalLayout; + private boolean isKeyboardShown = false; + + private OnlineRoutingEngine engine; + private OnlineRoutingEngine initEngine; + private String customVehicleKey; + private ExampleLocation selectedLocation; + private String editedEngineKey; + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + app = requireMyApplication(); + mapActivity = getMapActivity(); + helper = app.getOnlineRoutingHelper(); + if (savedInstanceState != null) { + restoreState(savedInstanceState); + } else { + initState(); + } + requireMyActivity().getOnBackPressedDispatcher().addCallback(this, new OnBackPressedCallback(true) { + public void handleOnBackPressed() { + MapActivity mapActivity = getMapActivity(); + if (mapActivity != null) { + showExitDialog(); + } + } + }); + } + + @SuppressLint("ClickableViewAccessibility") + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + view = getInflater().inflate( + R.layout.online_routing_engine_fragment, container, false); + segmentsContainer = (ViewGroup) view.findViewById(R.id.segments_container); + scrollView = (ScrollView) segmentsContainer.getParent(); + if (Build.VERSION.SDK_INT >= 21) { + AndroidUtils.addStatusBarPadding21v(getContext(), view); + } + setupToolbar((Toolbar) view.findViewById(R.id.toolbar)); + + setupNameCard(); + setupTypeCard(); + setupVehicleCard(); + setupApiKeyCard(); + setupExampleCard(); + setupResultsContainer(); + setupButtons(); + + generateUniqueNameIfNeeded(); + updateCardViews(nameCard, typeCard, vehicleCard, exampleCard); + + scrollView.setOnTouchListener(new View.OnTouchListener() { + int scrollViewY = 0; + + @Override + public boolean onTouch(View v, MotionEvent event) { + int y = scrollView.getScrollY(); + if (isKeyboardShown && scrollViewY != y) { + scrollViewY = y; + View focus = mapActivity.getCurrentFocus(); + if (focus != null) { + AndroidUtils.hideSoftKeyboard(mapActivity, focus); + focus.clearFocus(); + } + } + return false; + } + }); + + onGlobalLayout = new ViewTreeObserver.OnGlobalLayoutListener() { + private int layoutHeightPrevious; + private int layoutHeightMin; + + @Override + public void onGlobalLayout() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.getViewTreeObserver().removeOnGlobalLayoutListener(this); + } else { + view.getViewTreeObserver().removeGlobalOnLayoutListener(this); + } + + Rect visibleDisplayFrame = new Rect(); + view.getWindowVisibleDisplayFrame(visibleDisplayFrame); + int layoutHeight = visibleDisplayFrame.bottom; + + if (layoutHeight < layoutHeightPrevious) { + isKeyboardShown = true; + layoutHeightMin = layoutHeight; + } else { + isKeyboardShown = layoutHeight == layoutHeightMin; + } + + if (layoutHeight != layoutHeightPrevious) { + FrameLayout.LayoutParams rootViewLayout = (FrameLayout.LayoutParams) view.getLayoutParams(); + rootViewLayout.height = layoutHeight; + view.requestLayout(); + layoutHeightPrevious = layoutHeight; + } + + view.post(new Runnable() { + @Override + public void run() { + view.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayout); + } + }); + + } + }; + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.getViewTreeObserver().addOnGlobalLayoutListener(onGlobalLayout); + } + + return view; + } + + private void setupToolbar(Toolbar toolbar) { + ImageView navigationIcon = toolbar.findViewById(R.id.close_button); + navigationIcon.setImageResource(R.drawable.ic_action_close); + navigationIcon.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + showExitDialog(); + } + }); + TextView title = toolbar.findViewById(R.id.toolbar_title); + toolbar.findViewById(R.id.toolbar_subtitle).setVisibility(View.GONE); + View actionBtn = toolbar.findViewById(R.id.action_button); + if (isEditingMode()) { + title.setText(getString(R.string.edit_online_routing_engine)); + ImageView ivBtn = toolbar.findViewById(R.id.action_button_icon); + ivBtn.setImageDrawable( + getIcon(R.drawable.ic_action_delete_dark, R.color.color_osm_edit_delete)); + actionBtn.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onDeleteEngine(); + dismiss(); + } + }); + } else { + title.setText(getString(R.string.add_online_routing_engine)); + actionBtn.setVisibility(View.GONE); + } + } + + private void setupNameCard() { + nameCard = new OnlineRoutingCard(mapActivity, isNightMode(), appMode); + nameCard.build(mapActivity); + nameCard.setDescription(getString(R.string.select_nav_profile_dialog_message)); + nameCard.setEditedText(engine.getName(app)); + nameCard.setFieldBoxLabelText(getString(R.string.shared_string_name)); + nameCard.setOnTextChangedListener(new OnTextChangedListener() { + @Override + public void onTextChanged(boolean changedByUser, @NonNull String text) { + if (changedByUser) { + engine.put(EngineParameter.CUSTOM_NAME, text); + checkCustomNameUnique(engine); + } + } + }); + nameCard.showDivider(); + segmentsContainer.addView(nameCard.getView()); + } + + private void setupTypeCard() { + typeCard = new OnlineRoutingCard(mapActivity, isNightMode(), appMode); + typeCard.build(mapActivity); + typeCard.setHeaderTitle(getString(R.string.shared_string_type)); + List serverItems = new ArrayList<>(); + for (EngineType server : EngineType.values()) { + serverItems.add(new HorizontalSelectionItem(server.getTitle(), server)); + } + typeCard.setSelectionMenu(serverItems, engine.getType().getTitle(), + new CallbackWithObject() { + @Override + public boolean processResult(HorizontalSelectionItem result) { + EngineType type = (EngineType) result.getObject(); + if (engine.getType() != type) { + changeEngineType(type); + return true; + } + return false; + } + }); + typeCard.setOnTextChangedListener(new OnTextChangedListener() { + @Override + public void onTextChanged(boolean editedByUser, @NonNull String text) { + if (editedByUser) { + engine.put(EngineParameter.CUSTOM_URL, text); + updateCardViews(exampleCard); + } + } + }); + typeCard.setFieldBoxLabelText(getString(R.string.shared_string_server_url)); + typeCard.showDivider(); + segmentsContainer.addView(typeCard.getView()); + } + + private void setupVehicleCard() { + vehicleCard = new OnlineRoutingCard(mapActivity, isNightMode(), appMode); + vehicleCard.build(mapActivity); + vehicleCard.setHeaderTitle(getString(R.string.shared_string_vehicle)); + vehicleCard.setFieldBoxLabelText(getString(R.string.shared_string_custom)); + vehicleCard.setOnTextChangedListener(new OnTextChangedListener() { + @Override + public void onTextChanged(boolean editedByUser, @NonNull String text) { + if (editedByUser) { + customVehicleKey = text; + engine.put(EngineParameter.VEHICLE_KEY, customVehicleKey); + updateCardViews(nameCard, exampleCard); + } + } + }); + vehicleCard.setEditedText(customVehicleKey); + vehicleCard.setFieldBoxHelperText(getString(R.string.shared_string_enter_param)); + vehicleCard.showDivider(); + segmentsContainer.addView(vehicleCard.getView()); + setupVehicleTypes(); + } + + private void setupVehicleTypes() { + List vehicleItems = new ArrayList<>(); + for (VehicleType vehicle : engine.getAllowedVehicles()) { + vehicleItems.add(new HorizontalSelectionItem(vehicle.getTitle(app), vehicle)); + } + vehicleCard.setSelectionMenu(vehicleItems, engine.getSelectedVehicleType().getTitle(app), + new CallbackWithObject() { + @Override + public boolean processResult(HorizontalSelectionItem result) { + VehicleType vehicle = (VehicleType) result.getObject(); + if (!Algorithms.objectEquals(engine.getSelectedVehicleType(), vehicle)) { + String vehicleKey = vehicle.equals(CUSTOM_VEHICLE) ? customVehicleKey : vehicle.getKey(); + engine.put(EngineParameter.VEHICLE_KEY, vehicleKey); + generateUniqueNameIfNeeded(); + updateCardViews(nameCard, vehicleCard, exampleCard); + return true; + } + return false; + } + }); + } + + private void setupApiKeyCard() { + apiKeyCard = new OnlineRoutingCard(mapActivity, isNightMode(), appMode); + apiKeyCard.build(mapActivity); + apiKeyCard.setHeaderTitle(getString(R.string.shared_string_api_key)); + apiKeyCard.setFieldBoxLabelText(getString(R.string.keep_it_empty_if_not)); + String apiKey = engine.get(EngineParameter.API_KEY); + if (apiKey != null) { + apiKeyCard.setEditedText(apiKey); + } + apiKeyCard.showDivider(); + apiKeyCard.setOnTextChangedListener(new OnTextChangedListener() { + @Override + public void onTextChanged(boolean editedByUser, @NonNull String text) { + if (Algorithms.isBlank(text)) { + engine.remove(EngineParameter.API_KEY); + } else { + engine.put(EngineParameter.API_KEY, text); + } + updateCardViews(exampleCard); + } + }); + segmentsContainer.addView(apiKeyCard.getView()); + } + + private void setupExampleCard() { + exampleCard = new OnlineRoutingCard(mapActivity, isNightMode(), appMode); + exampleCard.build(mapActivity); + exampleCard.setHeaderTitle(getString(R.string.shared_string_example)); + List locationItems = new ArrayList<>(); + for (ExampleLocation location : ExampleLocation.values()) { + locationItems.add(new HorizontalSelectionItem(location.getName(), location)); + } + exampleCard.setSelectionMenu(locationItems, selectedLocation.getName(), + new CallbackWithObject() { + @Override + public boolean processResult(HorizontalSelectionItem result) { + ExampleLocation location = (ExampleLocation) result.getObject(); + if (selectedLocation != location) { + selectedLocation = location; + updateCardViews(exampleCard); + return true; + } + return false; + } + }); + exampleCard.setDescription(getString(R.string.online_routing_example_hint)); + exampleCard.showFieldBox(); + exampleCard.setButton(getString(R.string.test_route_calculation), new View.OnClickListener() { + @Override + public void onClick(View v) { + testEngineWork(); + } + }); + segmentsContainer.addView(exampleCard.getView()); + } + + private void setupResultsContainer() { + testResultsContainer = getInflater().inflate( + R.layout.bottom_sheet_item_with_descr_64dp, segmentsContainer, false); + testResultsContainer.setVisibility(View.INVISIBLE); + segmentsContainer.addView(testResultsContainer); + } + + private void setupButtons() { + boolean nightMode = isNightMode(); + View cancelButton = view.findViewById(R.id.dismiss_button); + UiUtilities.setupDialogButton(nightMode, cancelButton, + DialogButtonType.SECONDARY, R.string.shared_string_cancel); + cancelButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + showExitDialog(); + } + }); + + view.findViewById(R.id.buttons_divider).setVisibility(View.VISIBLE); + + saveButton = view.findViewById(R.id.right_bottom_button); + UiUtilities.setupDialogButton(nightMode, saveButton, + UiUtilities.DialogButtonType.PRIMARY, R.string.shared_string_save); + saveButton.setVisibility(View.VISIBLE); + saveButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + onSaveEngine(); + dismiss(); + } + }); + } + + private void changeEngineType(EngineType type) { + OnlineRoutingEngine tmp = (OnlineRoutingEngine) engine.clone(); + engine = OnlineRoutingFactory.createEngine(type, tmp.getParams()); + + // after changing the type, select the vehicle + // with the same name that was selected before + VehicleType previous = tmp.getSelectedVehicleType(); + VehicleType next = null; + for (VehicleType vt : engine.getAllowedVehicles()) { + if (Algorithms.objectEquals(previous.getTitle(app), vt.getTitle(app))) { + next = vt; + break; + } + } + String vehicleKey; + if (next != null) { + vehicleKey = next.equals(CUSTOM_VEHICLE) ? customVehicleKey : next.getKey(); + } else { + vehicleKey = engine.getAllowedVehicles().get(0).getKey(); + } + engine.put(EngineParameter.VEHICLE_KEY, vehicleKey); + + setupVehicleTypes(); + generateUniqueNameIfNeeded(); + updateCardViews(nameCard, typeCard, vehicleCard, exampleCard); + } + + private void generateUniqueNameIfNeeded() { + if (engine.get(EngineParameter.CUSTOM_NAME) == null) { + engine.remove(EngineParameter.NAME_INDEX); + if (hasNameDuplicate(engine.getName(app))) { + int index = 0; + do { + engine.put(EngineParameter.NAME_INDEX, String.valueOf(++index)); + } while (hasNameDuplicate(engine.getName(app))); + } + } + } + + private void checkCustomNameUnique(@NonNull OnlineRoutingEngine engine) { + if (hasNameDuplicate(engine.getName(app))) { + nameCard.showFieldBoxError(getString(R.string.message_name_is_already_exists)); + saveButton.setEnabled(false); + } else { + nameCard.hideFieldBoxError(); + saveButton.setEnabled(true); + } + } + + private boolean hasNameDuplicate(@NonNull String name) { + for (OnlineRoutingEngine engine : helper.getEnginesExceptMentioned(editedEngineKey)) { + if (Algorithms.objectEquals(engine.getName(app), name)) { + return true; + } + } + return false; + } + + private void onSaveEngine() { + if (engine != null) { + helper.saveEngine(engine); + } + } + + private void onDeleteEngine() { + helper.deleteEngine(engine); + } + + private boolean isEditingMode() { + return editedEngineKey != null; + } + + private String getTestUrl() { + List path = new ArrayList<>(); + path.add(selectedLocation.getCityCenterLatLon()); + path.add(selectedLocation.getCityAirportLatLon()); + return engine.getFullUrl(path); + } + + private void testEngineWork() { + final OnlineRoutingEngine requestedEngine = (OnlineRoutingEngine) engine.clone(); + final ExampleLocation location = selectedLocation; + new Thread(new Runnable() { + @Override + public void run() { + StringBuilder message = new StringBuilder(); + boolean resultOk = false; + try { + String response = helper.makeRequest(exampleCard.getEditedText()); + resultOk = requestedEngine.parseServerMessage(message, response); + } catch (IOException | JSONException e) { + message.append(e.toString()); + } + showTestResults(resultOk, message.toString(), location); + } + }).start(); + } + + private void showTestResults(final boolean resultOk, + final @NonNull String message, + final @NonNull ExampleLocation location) { + app.runInUIThread(new Runnable() { + @Override + public void run() { + testResultsContainer.setVisibility(View.VISIBLE); + ImageView ivImage = testResultsContainer.findViewById(R.id.icon); + TextView tvTitle = testResultsContainer.findViewById(R.id.title); + TextView tvDescription = testResultsContainer.findViewById(R.id.description); + if (resultOk) { + ivImage.setImageDrawable(getContentIcon(R.drawable.ic_action_gdirections_dark)); + tvTitle.setText(getString(R.string.shared_string_ok)); + } else { + ivImage.setImageDrawable(getContentIcon(R.drawable.ic_action_alert)); + tvTitle.setText(String.format(getString(R.string.message_server_error), message)); + } + tvDescription.setText(location.getName()); + } + }); + } + + private void updateCardViews(@NonNull BaseCard... cardsToUpdate) { + for (BaseCard card : cardsToUpdate) { + if (nameCard.equals(card)) { + if (Algorithms.isEmpty(engine.get(EngineParameter.CUSTOM_NAME))) { + nameCard.setEditedText(engine.getName(app)); + } + + } else if (typeCard.equals(card)) { + typeCard.setHeaderSubtitle(engine.getType().getTitle()); + typeCard.setEditedText(engine.getBaseUrl()); + if (engine.isParameterAllowed(EngineParameter.API_KEY)) { + apiKeyCard.show(); + } else { + apiKeyCard.hide(); + } + + } else if (vehicleCard.equals(card)) { + VehicleType vt = engine.getSelectedVehicleType(); + vehicleCard.setHeaderSubtitle(vt.getTitle(app)); + if (vt.equals(CUSTOM_VEHICLE)) { + vehicleCard.showFieldBox(); + vehicleCard.setEditedText(customVehicleKey); + } else { + vehicleCard.hideFieldBox(); + } + + } else if (exampleCard.equals(card)) { + exampleCard.setEditedText(getTestUrl()); + } + } + } + + public void showExitDialog() { + View focus = view.findFocus(); + AndroidUtils.hideSoftKeyboard(mapActivity, focus); + if (!engine.equals(initEngine)) { + AlertDialog.Builder dismissDialog = createWarningDialog(mapActivity, + R.string.shared_string_dismiss, R.string.exit_without_saving, R.string.shared_string_cancel); + dismissDialog.setPositiveButton(R.string.shared_string_exit, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dismiss(); + } + }); + dismissDialog.show(); + } else { + dismiss(); + } + } + + private AlertDialog.Builder createWarningDialog(Activity activity, int title, int message, int negButton) { + Context themedContext = UiUtilities.getThemedContext(activity, isNightMode()); + AlertDialog.Builder warningDialog = new AlertDialog.Builder(themedContext); + warningDialog.setTitle(getString(title)); + warningDialog.setMessage(getString(message)); + warningDialog.setNegativeButton(negButton, null); + return warningDialog; + } + + private void dismiss() { + FragmentActivity activity = getActivity(); + if (activity != null) { + FragmentManager fragmentManager = activity.getSupportFragmentManager(); + if (!fragmentManager.isStateSaved()) { + fragmentManager.popBackStack(); + } + } + } + + private boolean isNightMode() { + return !app.getSettings().isLightContentForMode(getAppMode()); + } + + @NonNull + private ApplicationMode getAppMode() { + return appMode != null ? appMode : app.getSettings().getApplicationMode(); + } + + @Nullable + private MapActivity getMapActivity() { + FragmentActivity activity = getActivity(); + if (activity instanceof MapActivity) { + return (MapActivity) activity; + } else { + return null; + } + } + + private LayoutInflater getInflater() { + return UiUtilities.getInflater(mapActivity, isNightMode()); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { + view.getViewTreeObserver().removeOnGlobalLayoutListener(onGlobalLayout); + } else { + view.getViewTreeObserver().removeGlobalOnLayoutListener(onGlobalLayout); + } + } + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + super.onSaveInstanceState(outState); + saveState(outState); + } + + private void saveState(@NonNull Bundle outState) { + outState.putString(ENGINE_TYPE_KEY, engine.getType().name()); + for (EngineParameter key : EngineParameter.values()) { + String value = engine.get(key); + if (value != null) { + outState.putString(key.name(), value); + } + } + outState.putString(ENGINE_CUSTOM_VEHICLE_KEY, customVehicleKey); + outState.putString(EXAMPLE_LOCATION_KEY, selectedLocation.name()); + outState.putString(APP_MODE_KEY, getAppMode().getStringKey()); + outState.putString(EDITED_ENGINE_KEY, editedEngineKey); + } + + private void restoreState(@NonNull Bundle savedState) { + editedEngineKey = savedState.getString(EngineParameter.KEY.name()); + initEngine = createInitStateEngine(); + String typeKey = savedState.getString(ENGINE_TYPE_KEY); + EngineType type = EngineType.getTypeByName(typeKey); + engine = OnlineRoutingFactory.createEngine(type); + for (EngineParameter key : EngineParameter.values()) { + String value = savedState.getString(key.name()); + if (value != null) { + engine.put(key, value); + } + } + customVehicleKey = savedState.getString(ENGINE_CUSTOM_VEHICLE_KEY); + selectedLocation = ExampleLocation.valueOf(savedState.getString(EXAMPLE_LOCATION_KEY)); + appMode = ApplicationMode.valueOfStringKey(savedState.getString(APP_MODE_KEY), null); + } + + private void initState() { + initEngine = createInitStateEngine(); + selectedLocation = ExampleLocation.values()[0]; + engine = (OnlineRoutingEngine) initEngine.clone(); + if (Algorithms.objectEquals(engine.getSelectedVehicleType(), CUSTOM_VEHICLE)) { + customVehicleKey = engine.get(EngineParameter.VEHICLE_KEY); + } else { + customVehicleKey = ""; + } + } + + private OnlineRoutingEngine createInitStateEngine() { + OnlineRoutingEngine engine; + OnlineRoutingEngine editedEngine = helper.getEngineByKey(editedEngineKey); + if (editedEngine != null) { + engine = (OnlineRoutingEngine) editedEngine.clone(); + } else { + engine = OnlineRoutingFactory.createEngine(EngineType.values()[0]); + String vehicle = engine.getAllowedVehicles().get(0).getKey(); + engine.put(EngineParameter.VEHICLE_KEY, vehicle); + if (editedEngineKey != null) { + engine.put(EngineParameter.KEY, editedEngineKey); + } + } + return engine; + } + + public static void showInstance(@NonNull FragmentActivity activity, + @NonNull ApplicationMode appMode, + @Nullable String editedEngineKey) { + FragmentManager fm = activity.getSupportFragmentManager(); + if (!fm.isStateSaved() && fm.findFragmentByTag(OnlineRoutingEngineFragment.TAG) == null) { + OnlineRoutingEngineFragment fragment = new OnlineRoutingEngineFragment(); + fragment.appMode = appMode; + fragment.editedEngineKey = editedEngineKey; + fm.beginTransaction() + .add(R.id.fragmentContainer, fragment, TAG) + .addToBackStack(TAG).commitAllowingStateLoss(); + } + } +} diff --git a/OsmAnd/src/net/osmand/plus/profiles/OnlineRoutingEngineDataObject.java b/OsmAnd/src/net/osmand/plus/profiles/OnlineRoutingEngineDataObject.java index 46a35ea67e..fd0f593462 100644 --- a/OsmAnd/src/net/osmand/plus/profiles/OnlineRoutingEngineDataObject.java +++ b/OsmAnd/src/net/osmand/plus/profiles/OnlineRoutingEngineDataObject.java @@ -1,12 +1,27 @@ package net.osmand.plus.profiles; +import androidx.annotation.NonNull; + import net.osmand.plus.R; public class OnlineRoutingEngineDataObject extends ProfileDataObject { + private int order; + public OnlineRoutingEngineDataObject(String name, String description, - String stringKey) { + String stringKey, + int order) { super(name, description, stringKey, R.drawable.ic_world_globe_dark, false, null); + this.order = order; + } + + @Override + public int compareTo(@NonNull ProfileDataObject profileDataObject) { + if (profileDataObject instanceof OnlineRoutingEngineDataObject) { + OnlineRoutingEngineDataObject another = (OnlineRoutingEngineDataObject) profileDataObject; + return (this.order < another.order) ? -1 : ((this.order == another.order) ? 0 : 1); + } + return 0; } } diff --git a/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java b/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java index 28967ba2f3..6306dee9cc 100644 --- a/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java +++ b/OsmAnd/src/net/osmand/plus/profiles/ProfileDataUtils.java @@ -5,7 +5,7 @@ import android.content.Context; import net.osmand.plus.OsmandApplication; import net.osmand.plus.OsmandPlugin; import net.osmand.plus.R; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; import net.osmand.plus.profiles.RoutingProfileDataObject.RoutingProfilesResources; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.router.GeneralRouter; @@ -56,7 +56,22 @@ public class ProfileDataUtils { Collections.sort(fileNames, new Comparator() { @Override public int compare(String s, String t1) { - return s.equals(OSMAND_NAVIGATION) ? -1 : t1.equals(OSMAND_NAVIGATION) ? 1 : s.compareToIgnoreCase(t1); + // OsmAnd navigation should be at the top of the list + if (s.equals(OSMAND_NAVIGATION)) { + return -1; + } else if (t1.equals(OSMAND_NAVIGATION)) { + return 1; + + // Online navigation should be at the bottom of the list + } else if (s.equals(ONLINE_NAVIGATION)) { + return 1; + } else if (t1.equals(ONLINE_NAVIGATION)) { + return -1; + + // Other sorted by file names + } else { + return s.compareToIgnoreCase(t1); + } } }); for (String fileName : fileNames) { @@ -71,9 +86,11 @@ public class ProfileDataUtils { public static List getOnlineRoutingProfiles(OsmandApplication app) { List objects = new ArrayList<>(); - for (OnlineRoutingEngine engine : app.getOnlineRoutingHelper().getEngines()) { + List engines = app.getOnlineRoutingHelper().getEngines(); + for (int i = 0; i < engines.size(); i++) { + OnlineRoutingEngine engine = engines.get(i); objects.add(new OnlineRoutingEngineDataObject( - engine.getName(app), engine.getBaseUrl(), engine.getStringKey())); + engine.getName(app), engine.getBaseUrl(), engine.getStringKey(), i)); } return objects; } diff --git a/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java b/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java index 98ca4771cc..eb97a8ae85 100644 --- a/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java +++ b/OsmAnd/src/net/osmand/plus/profiles/SelectProfileBottomSheet.java @@ -39,8 +39,9 @@ import net.osmand.plus.base.bottomsheetmenu.simpleitems.TitleItem; import net.osmand.plus.helpers.FontCache; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.plus.settings.bottomsheets.BasePreferenceBottomSheet; -import net.osmand.plus.onlinerouting.OnlineRoutingEngineFragment; +import net.osmand.plus.onlinerouting.ui.OnlineRoutingEngineFragment; import net.osmand.router.RoutingConfiguration; +import net.osmand.util.Algorithms; import org.apache.commons.logging.Log; @@ -250,7 +251,7 @@ public class SelectProfileBottomSheet extends BasePreferenceBottomSheet { tvTitle.setText(profile.getName()); tvDescription.setText(profile.getDescription()); - boolean isSelected = setupSelected && profile.getStringKey().equals(selectedItemKey); + boolean isSelected = setupSelected && Algorithms.objectEquals(profile.getStringKey(), selectedItemKey); int iconColor; if (dialogMode == DialogMode.NAVIGATION_PROFILE) { iconColor = isSelected ? activeColorResId : iconDefaultColorResId; diff --git a/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java b/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java index 89c17f1c2b..96017bc96c 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java +++ b/OsmAnd/src/net/osmand/plus/routing/RouteProvider.java @@ -2,7 +2,6 @@ package net.osmand.plus.routing; import android.content.Context; -import android.os.Build; import android.os.Bundle; import android.util.Base64; @@ -20,16 +19,14 @@ import net.osmand.binary.BinaryMapIndexReader; import net.osmand.data.LatLon; import net.osmand.data.LocationPoint; import net.osmand.data.WptLocationPoint; -import net.osmand.osm.io.NetworkUtils; import net.osmand.plus.OsmandApplication; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine; import net.osmand.plus.onlinerouting.OnlineRoutingHelper; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.plus.settings.backend.CommonPreference; import net.osmand.plus.R; import net.osmand.plus.TargetPointsHelper; import net.osmand.plus.TargetPointsHelper.TargetPoint; -import net.osmand.plus.Version; import net.osmand.plus.render.NativeOsmandLibrary; import net.osmand.plus.settings.backend.ApplicationMode; import net.osmand.router.GeneralRouter; @@ -48,21 +45,16 @@ import net.osmand.router.RoutingConfiguration.Builder; import net.osmand.router.RoutingContext; import net.osmand.router.TurnType; import net.osmand.util.Algorithms; -import net.osmand.util.GeoPolylineParserUtil; import net.osmand.util.MapUtils; import org.json.JSONException; -import org.json.JSONObject; import org.xml.sax.SAXException; -import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; import java.net.MalformedURLException; -import java.net.URLConnection; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -1210,8 +1202,12 @@ public class RouteProvider { private RouteCalculationResult findOnlineRoute(RouteCalculationParams params) throws IOException, JSONException { OnlineRoutingHelper helper = params.ctx.getOnlineRoutingHelper(); String stringKey = params.mode.getRoutingProfile(); - List route = helper.calculateRouteOnline(helper.getEngineByKey(stringKey), getFullPathFromParams(params)); - if (!route.isEmpty()) { + OnlineRoutingEngine engine = helper.getEngineByKey(stringKey); + List route = null; + if (engine != null) { + route = helper.calculateRouteOnline(engine, getFullPathFromParams(params)); + } + if (!Algorithms.isEmpty(route)) { List res = new ArrayList<>(); for (LatLon pt : route) { WptPt wpt = new WptPt(); diff --git a/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java b/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java index 03af123cc8..1addb2f50a 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java +++ b/OsmAnd/src/net/osmand/plus/routing/RoutingHelper.java @@ -194,7 +194,7 @@ public class RoutingHelper { } public synchronized void setFinalAndCurrentLocation(LatLon finalLocation, List intermediatePoints, Location currentLocation) { - RoutingHelperUtils.checkAndUpdateStartLocation(app, currentLocation); + RoutingHelperUtils.checkAndUpdateStartLocation(app, currentLocation, false); RouteCalculationResult previousRoute = route; clearCurrentRoute(finalLocation, intermediatePoints); // to update route diff --git a/OsmAnd/src/net/osmand/plus/routing/RoutingHelperUtils.java b/OsmAnd/src/net/osmand/plus/routing/RoutingHelperUtils.java index b756238eca..6803a2920f 100644 --- a/OsmAnd/src/net/osmand/plus/routing/RoutingHelperUtils.java +++ b/OsmAnd/src/net/osmand/plus/routing/RoutingHelperUtils.java @@ -8,9 +8,6 @@ import net.osmand.data.LatLon; import net.osmand.data.QuadRect; import net.osmand.plus.OsmandApplication; import net.osmand.plus.TargetPointsHelper; -import net.osmand.plus.helpers.enums.MetricsConstants; -import net.osmand.plus.settings.backend.ApplicationMode; -import net.osmand.plus.settings.backend.OsmandSettings; import net.osmand.util.MapUtils; import java.util.List; @@ -164,19 +161,20 @@ public class RoutingHelperUtils { } - public static void checkAndUpdateStartLocation(@NonNull OsmandApplication app, LatLon newStartLocation) { + public static void checkAndUpdateStartLocation(@NonNull OsmandApplication app, LatLon newStartLocation, boolean force) { if (newStartLocation != null) { LatLon lastStartLocation = app.getSettings().getLastStartPoint(); - if (lastStartLocation == null || MapUtils.getDistance(newStartLocation, lastStartLocation) > CACHE_RADIUS) { + if (lastStartLocation == null || MapUtils.getDistance(newStartLocation, lastStartLocation) > CACHE_RADIUS || force) { app.getMapViewTrackingUtilities().detectDrivingRegion(newStartLocation); app.getSettings().setLastStartPoint(newStartLocation); } } } - public static void checkAndUpdateStartLocation(@NonNull OsmandApplication app, Location nextStartLocation) { + public static void checkAndUpdateStartLocation(@NonNull OsmandApplication app, Location nextStartLocation, boolean force) { if (nextStartLocation != null) { - checkAndUpdateStartLocation(app, new LatLon(nextStartLocation.getLatitude(), nextStartLocation.getLongitude())); + LatLon newStartLocation = new LatLon(nextStartLocation.getLatitude(), nextStartLocation.getLongitude()); + checkAndUpdateStartLocation(app, newStartLocation, force); } } } diff --git a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OnlineRoutingSettingsItem.java b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OnlineRoutingSettingsItem.java index 779a97d1df..cf08142393 100644 --- a/OsmAnd/src/net/osmand/plus/settings/backend/backup/OnlineRoutingSettingsItem.java +++ b/OsmAnd/src/net/osmand/plus/settings/backend/backup/OnlineRoutingSettingsItem.java @@ -7,8 +7,9 @@ import androidx.annotation.Nullable; import net.osmand.plus.OsmandApplication; import net.osmand.plus.R; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine; -import net.osmand.plus.onlinerouting.OnlineRoutingEngine.EngineParameter; +import net.osmand.plus.onlinerouting.EngineParameter; +import net.osmand.plus.onlinerouting.OnlineRoutingFactory; +import net.osmand.plus.onlinerouting.engine.OnlineRoutingEngine; import net.osmand.plus.onlinerouting.OnlineRoutingHelper; import org.json.JSONException; @@ -93,8 +94,8 @@ public class OnlineRoutingSettingsItem extends CollectionSettingsItem points = article.getGpxFile().getPoints(); WptPt gpxPoint = null; String coordinates = url.replace(PREFIX_GEO, ""); double lat; double lon; try { - lat = Double.valueOf(coordinates.substring(0, coordinates.indexOf(","))); - lon = Double.valueOf(coordinates.substring(coordinates.indexOf(",") + 1, - coordinates.length())); + lat = Double.parseDouble(coordinates.substring(0, coordinates.indexOf(","))); + lon = Double.parseDouble(coordinates.substring(coordinates.indexOf(",") + 1)); } catch (NumberFormatException e) { Log.w(TAG, e.getMessage(), e); return true; } for (WptPt point : points) { - if (point.getLatitude() == lat && point.getLongitude() == lon) { + if (MapUtils.getDistance(point.getLatitude(), point.getLongitude(), lat, lon) < ROUNDING_ERROR) { gpxPoint = point; break; } } if (gpxPoint != null) { final OsmandSettings settings = app.getSettings(); - settings.setMapLocationToShow(lat, lon, settings.getLastKnownMapZoom(), + settings.setMapLocationToShow(gpxPoint.getLatitude(), gpxPoint.getLongitude(), + settings.getLastKnownMapZoom(), new PointDescription(PointDescription.POINT_TYPE_WPT, gpxPoint.name), false, gpxPoint);